Compare commits

...

2 Commits

Author SHA1 Message Date
CN-JS-HuiBai
df5999c338 优化布局Again 2026-04-04 14:03:17 +08:00
CN-JS-HuiBai
3b65c306de 优化布局 2026-04-04 14:02:49 +08:00
3 changed files with 95 additions and 39 deletions

View File

@@ -139,8 +139,12 @@
<div id="now-playing" class="now-playing hidden"> <div id="now-playing" class="now-playing hidden">
<h3>Now Playing</h3> <h3>Now Playing</h3>
<p id="current-video-title">video.mp4</p> <p id="current-video-title">video.mp4</p>
<button id="transcode-btn" class="play-btn hidden">Start Transcode</button> <div style="display: flex; gap: 0.5rem; margin-top: 1rem; align-items: center; flex-wrap: wrap;">
<button id="stop-transcode-btn" class="play-btn stop-btn hidden">Stop Transcode</button> <button id="transcode-btn" class="play-btn hidden">Start Transcode</button>
<button id="stop-transcode-btn" class="play-btn stop-btn hidden">Stop Transcode</button>
<button id="clear-playing-download-cache-btn" class="action-btn danger hidden">清空下载缓存</button>
<button id="clear-playing-transcode-cache-btn" class="action-btn danger hidden">清空转码缓存</button>
</div>
</div> </div>
</section> </section>
</main> </main>

View File

@@ -18,6 +18,8 @@ document.addEventListener('DOMContentLoaded', () => {
const currentVideoTitle = document.getElementById('current-video-title'); const currentVideoTitle = document.getElementById('current-video-title');
const transcodeBtn = document.getElementById('transcode-btn'); const transcodeBtn = document.getElementById('transcode-btn');
const stopTranscodeBtn = document.getElementById('stop-transcode-btn'); const stopTranscodeBtn = document.getElementById('stop-transcode-btn');
const clearPlayingDownloadBtn = document.getElementById('clear-playing-download-cache-btn');
const clearPlayingTranscodeBtn = document.getElementById('clear-playing-transcode-cache-btn');
const themeSelector = document.getElementById('theme-selector'); const themeSelector = document.getElementById('theme-selector');
const videoListHeader = document.getElementById('video-list-header'); const videoListHeader = document.getElementById('video-list-header');
const logoutBtn = document.getElementById('logout-btn'); const logoutBtn = document.getElementById('logout-btn');
@@ -724,20 +726,16 @@ document.addEventListener('DOMContentLoaded', () => {
if (index === parts.length - 1) { if (index === parts.length - 1) {
current[part].__file = key; current[part].__file = key;
current[part].__hasTranscodeCache = vid.hasTranscodeCache; current[part].__hasTranscodeCache = vid.hasTranscodeCache;
current[part].__hasDownloadCache = vid.hasDownloadCache;
} }
current = current[part]; current = current[part];
}); });
}); });
const createFileItem = (name, key, hasTranscodeCache) => { const createFileItem = (name, key, hasTranscodeCache, hasDownloadCache) => {
const li = document.createElement('li'); const li = document.createElement('li');
li.className = 'video-item file-item'; li.className = 'video-item file-item';
const ext = name.split('.').pop().toUpperCase(); const ext = name.split('.').pop().toUpperCase();
let cacheButtonHtml = '';
if (hasTranscodeCache) {
cacheButtonHtml = `<button class="action-btn danger clear-video-cache-btn" data-key="${key}" style="padding: 0.3rem 0.6rem; font-size: 0.75rem; margin-left: auto;" title="清空转码缓存">清空转码缓存</button>`;
}
li.innerHTML = ` li.innerHTML = `
<div class="video-icon"> <div class="video-icon">
@@ -747,44 +745,20 @@ document.addEventListener('DOMContentLoaded', () => {
<div class="video-title" title="${key}">${name}</div> <div class="video-title" title="${key}">${name}</div>
<div class="video-meta">Video / ${ext}</div> <div class="video-meta">Video / ${ext}</div>
</div> </div>
${cacheButtonHtml}
`; `;
li.addEventListener('click', (e) => { li.addEventListener('click', (e) => {
e.stopPropagation(); e.stopPropagation();
selectVideo(key, li); selectVideo(key, li, hasTranscodeCache, hasDownloadCache);
}); });
const clearBtn = li.querySelector('.clear-video-cache-btn');
if (clearBtn) {
clearBtn.addEventListener('click', async (e) => {
e.stopPropagation();
clearBtn.disabled = true;
clearBtn.textContent = '清空中...';
try {
const res = await fetch('/api/clear-video-transcode-cache', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ bucket: selectedBucket, key: key })
});
if (!res.ok) throw new Error('Failed to clear cache');
fetchVideos(selectedBucket);
} catch (err) {
console.error(err);
alert('清空转码缓存失败: ' + err.message);
clearBtn.disabled = false;
clearBtn.textContent = '清空转码缓存';
}
});
}
return li; return li;
}; };
const renderTree = (node, container) => { const renderTree = (node, container) => {
for (const [name, value] of Object.entries(node)) { for (const [name, value] of Object.entries(node)) {
if (name === '__file' || name === '__hasTranscodeCache') continue; if (name === '__file' || name === '__hasTranscodeCache' || name === '__hasDownloadCache') continue;
const childKeys = Object.keys(value).filter(k => k !== '__file' && k !== '__hasTranscodeCache'); const childKeys = Object.keys(value).filter(k => k !== '__file' && k !== '__hasTranscodeCache' && k !== '__hasDownloadCache');
const hasChildren = childKeys.length > 0; const hasChildren = childKeys.length > 0;
const isFile = typeof value.__file === 'string'; const isFile = typeof value.__file === 'string';
@@ -815,13 +789,13 @@ document.addEventListener('DOMContentLoaded', () => {
li.appendChild(folderHeader); li.appendChild(folderHeader);
if (isFile) { if (isFile) {
subListContainer.appendChild(createFileItem(name, value.__file, value.__hasTranscodeCache)); subListContainer.appendChild(createFileItem(name, value.__file, value.__hasTranscodeCache, value.__hasDownloadCache));
} }
renderTree(value, subListContainer); renderTree(value, subListContainer);
li.appendChild(subListContainer); li.appendChild(subListContainer);
container.appendChild(li); container.appendChild(li);
} else if (isFile) { } else if (isFile) {
container.appendChild(createFileItem(name, value.__file, value.__hasTranscodeCache)); container.appendChild(createFileItem(name, value.__file, value.__hasTranscodeCache, value.__hasDownloadCache));
} }
} }
}; };
@@ -834,7 +808,7 @@ document.addEventListener('DOMContentLoaded', () => {
}; };
// Handle video selection // Handle video selection
const selectVideo = (key, listItemNode) => { const selectVideo = (key, listItemNode, hasTranscodeCache = false, hasDownloadCache = false) => {
document.querySelectorAll('.video-item').forEach(n => n.classList.remove('active')); document.querySelectorAll('.video-item').forEach(n => n.classList.remove('active'));
listItemNode.classList.add('active'); listItemNode.classList.add('active');
@@ -868,6 +842,16 @@ document.addEventListener('DOMContentLoaded', () => {
stopTranscodeBtn.disabled = false; stopTranscodeBtn.disabled = false;
stopTranscodeBtn.textContent = 'Stop Transcode'; stopTranscodeBtn.textContent = 'Stop Transcode';
} }
if (clearPlayingDownloadBtn) {
if (hasDownloadCache) clearPlayingDownloadBtn.classList.remove('hidden');
else clearPlayingDownloadBtn.classList.add('hidden');
}
if (clearPlayingTranscodeBtn) {
if (hasTranscodeCache) clearPlayingTranscodeBtn.classList.remove('hidden');
else clearPlayingTranscodeBtn.classList.add('hidden');
}
if (playBtn) { if (playBtn) {
playBtn.classList.add('hidden'); playBtn.classList.add('hidden');
} }
@@ -1116,6 +1100,52 @@ document.addEventListener('DOMContentLoaded', () => {
if (stopTranscodeBtn) { if (stopTranscodeBtn) {
stopTranscodeBtn.addEventListener('click', stopTranscode); stopTranscodeBtn.addEventListener('click', stopTranscode);
} }
if (clearPlayingDownloadBtn) {
clearPlayingDownloadBtn.addEventListener('click', async () => {
if (!currentVideoKey) return;
clearPlayingDownloadBtn.disabled = true;
clearPlayingDownloadBtn.textContent = '清空中...';
try {
const res = await fetch('/api/clear-video-download-cache', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ bucket: selectedBucket, key: currentVideoKey })
});
if (!res.ok) throw new Error('清空失败');
clearPlayingDownloadBtn.classList.add('hidden');
fetchVideos(selectedBucket);
} catch (err) {
alert('清空下载缓存失败: ' + err.message);
} finally {
clearPlayingDownloadBtn.disabled = false;
clearPlayingDownloadBtn.textContent = '清空下载缓存';
}
});
}
if (clearPlayingTranscodeBtn) {
clearPlayingTranscodeBtn.addEventListener('click', async () => {
if (!currentVideoKey) return;
clearPlayingTranscodeBtn.disabled = true;
clearPlayingTranscodeBtn.textContent = '清空中...';
try {
const res = await fetch('/api/clear-video-transcode-cache', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ bucket: selectedBucket, key: currentVideoKey })
});
if (!res.ok) throw new Error('清空失败');
clearPlayingTranscodeBtn.classList.add('hidden');
fetchVideos(selectedBucket);
} catch (err) {
alert('清空转码缓存失败: ' + err.message);
} finally {
clearPlayingTranscodeBtn.disabled = false;
clearPlayingTranscodeBtn.textContent = '清空转码缓存';
}
});
}
if (loginBtn) { if (loginBtn) {
loginBtn.addEventListener('click', login); loginBtn.addEventListener('click', login);
} }

View File

@@ -566,9 +566,11 @@ app.get('/api/videos', async (req, res) => {
const safeBucket = bucket.replace(/[^a-z0-9]/gi, '_'); const safeBucket = bucket.replace(/[^a-z0-9]/gi, '_');
const safeKeySegments = key.split('/').map(segment => segment.replace(/[^a-z0-9]/gi, '_')); const safeKeySegments = key.split('/').map(segment => segment.replace(/[^a-z0-9]/gi, '_'));
const hlsDir = path.join(CACHE_DIR, `hls-${safeBucket}-${safeKeySegments.join('-')}`); const hlsDir = path.join(CACHE_DIR, `hls-${safeBucket}-${safeKeySegments.join('-')}`);
const tmpInputPath = path.join(CACHE_DIR, `s3-input-${safeBucket}-${safeKeySegments.join('-')}.tmp`);
return { return {
key: key, key: key,
hasTranscodeCache: fs.existsSync(hlsDir) hasTranscodeCache: fs.existsSync(hlsDir),
hasDownloadCache: fs.existsSync(tmpInputPath)
}; };
}); });
@@ -639,6 +641,26 @@ app.post('/api/clear-video-transcode-cache', async (req, res) => {
} }
}); });
app.post('/api/clear-video-download-cache', async (req, res) => {
try {
const { bucket, key } = req.body;
if (!bucket || !key) {
return res.status(400).json({ error: 'Bucket and key are required' });
}
const safeBucket = bucket.replace(/[^a-z0-9]/gi, '_');
const safeKeySegments = key.split('/').map(segment => segment.replace(/[^a-z0-9]/gi, '_'));
const tmpInputPath = path.join(CACHE_DIR, `s3-input-${safeBucket}-${safeKeySegments.join('-')}.tmp`);
if (fs.existsSync(tmpInputPath)) {
fs.rmSync(tmpInputPath, { force: true });
}
res.json({ message: 'Download cache cleared for video' });
} catch (error) {
console.error('Error clearing video download cache:', error);
res.status(500).json({ error: 'Failed to clear download cache', detail: error.message });
}
});
app.post('/api/stop-transcode', (req, res) => { app.post('/api/stop-transcode', (req, res) => {
try { try {
const { key } = req.body; const { key } = req.body;