diff --git a/public/css/style.css b/public/css/style.css
index 267db5e..286b098 100644
--- a/public/css/style.css
+++ b/public/css/style.css
@@ -268,8 +268,36 @@ header p {
.section-actions {
display: flex;
+ flex-wrap: wrap;
align-items: center;
- gap: 0.5rem;
+ gap: 0.75rem;
+}
+
+.action-btn {
+ border: none;
+ background: #2563eb;
+ color: #ffffff;
+ font-size: 0.95rem;
+ font-weight: 600;
+ padding: 0.75rem 1rem;
+ border-radius: 14px;
+ min-width: 140px;
+ cursor: pointer;
+ transition: background 0.2s ease, transform 0.2s ease, box-shadow 0.2s ease;
+}
+
+.action-btn:hover {
+ background: #1d4ed8;
+ transform: translateY(-1px);
+ box-shadow: 0 12px 24px rgba(37, 99, 235, 0.14);
+}
+
+.action-btn.danger {
+ background: #dc2626;
+}
+
+.action-btn.danger:hover {
+ background: #b91c1c;
}
.section-header h2 {
diff --git a/public/index.html b/public/index.html
index 9b82324..f525953 100644
--- a/public/index.html
+++ b/public/index.html
@@ -41,12 +41,9 @@
diff --git a/public/js/main.js b/public/js/main.js
index 3501b87..549f997 100644
--- a/public/js/main.js
+++ b/public/js/main.js
@@ -2,7 +2,8 @@ document.addEventListener('DOMContentLoaded', () => {
const videoListEl = document.getElementById('video-list');
const loadingSpinner = document.getElementById('loading-spinner');
const refreshBtn = document.getElementById('refresh-btn');
- const resetCacheBtn = document.getElementById('reset-cache-btn');
+ const clearDownloadCacheBtn = document.getElementById('clear-download-cache-btn');
+ const clearTranscodeCacheBtn = document.getElementById('clear-transcode-cache-btn');
const bucketSelect = document.getElementById('bucket-select');
const loginScreen = document.getElementById('login-screen');
const appContainer = document.getElementById('app-container');
@@ -196,16 +197,38 @@ document.addEventListener('DOMContentLoaded', () => {
}
};
- const resetCache = async () => {
- if (!resetCacheBtn) return;
- resetCacheBtn.disabled = true;
- resetCacheBtn.title = 'Resetting cache...';
+ const clearDownloadCache = async () => {
+ if (!clearDownloadCacheBtn) return;
+ clearDownloadCacheBtn.disabled = true;
+ clearDownloadCacheBtn.textContent = '清空中...';
+
+ try {
+ const res = await fetch('/api/clear-download-cache', { method: 'POST' });
+ if (!res.ok) {
+ const data = await res.json().catch(() => ({}));
+ throw new Error(data.error || '清空下载缓存失败');
+ }
+ alert('下载缓存已清空');
+ } catch (err) {
+ console.error('Clear download cache failed:', err);
+ alert(`清空下载缓存失败: ${err.message}`);
+ } finally {
+ clearDownloadCacheBtn.disabled = false;
+ clearDownloadCacheBtn.textContent = '清空下载缓存';
+ }
+ };
+
+ const clearTranscodeCache = async () => {
+ if (!clearTranscodeCacheBtn) return;
+ clearTranscodeCacheBtn.disabled = true;
+ clearTranscodeCacheBtn.textContent = '清空中...';
stopPolling();
selectedKey = null;
currentVideoKey = null;
subscribedKey = null;
if (transcodeBtn) {
+ transcodeBtn.disabled = true;
transcodeBtn.classList.add('hidden');
}
if (playBtn) {
@@ -220,18 +243,19 @@ document.addEventListener('DOMContentLoaded', () => {
resetProgress();
try {
- const res = await fetch('/api/reset-cache', { method: 'POST' });
+ const res = await fetch('/api/clear-transcode-cache', { method: 'POST' });
if (!res.ok) {
const data = await res.json().catch(() => ({}));
- throw new Error(data.error || 'Reset failed');
+ throw new Error(data.error || '清空转码缓存失败');
}
- await fetchVideos();
+ await fetchVideos(selectedBucket);
+ alert('转码缓存已清空');
} catch (err) {
- console.error('Reset cache failed:', err);
- alert(`Reset cache failed: ${err.message}`);
+ console.error('Clear transcode cache failed:', err);
+ alert(`清空转码缓存失败: ${err.message}`);
} finally {
- resetCacheBtn.disabled = false;
- resetCacheBtn.title = 'Reset Download Cache';
+ clearTranscodeCacheBtn.disabled = false;
+ clearTranscodeCacheBtn.textContent = '清空转码缓存';
}
};
@@ -481,6 +505,12 @@ document.addEventListener('DOMContentLoaded', () => {
// Bind events
refreshBtn.addEventListener('click', () => fetchVideos(selectedBucket));
+ if (clearDownloadCacheBtn) {
+ clearDownloadCacheBtn.addEventListener('click', clearDownloadCache);
+ }
+ if (clearTranscodeCacheBtn) {
+ clearTranscodeCacheBtn.addEventListener('click', clearTranscodeCache);
+ }
if (loginBtn) {
loginBtn.addEventListener('click', login);
}
diff --git a/server.js b/server.js
index ea8af31..670813d 100644
--- a/server.js
+++ b/server.js
@@ -148,20 +148,51 @@ wss.on('connection', (ws) => {
ws.on('close', () => removeWsClient(ws));
});
-const clearMp4Cache = () => {
- const mp4Dir = path.join(__dirname, 'public', 'mp4');
- if (!fs.existsSync(mp4Dir)) return;
+const mp4BaseDir = path.join(__dirname, 'public', 'mp4');
+
+const ensureDirectoryExists = (dirPath) => {
+ if (!fs.existsSync(dirPath)) {
+ fs.mkdirSync(dirPath, { recursive: true });
+ }
+};
+
+const clearDownloadCache = () => {
+ const tmpDir = os.tmpdir();
try {
- fs.rmSync(mp4Dir, { recursive: true, force: true });
+ if (!fs.existsSync(tmpDir)) return;
+ const files = fs.readdirSync(tmpDir);
+ for (const file of files) {
+ if (file.startsWith('s3-input-') && file.endsWith('.tmp')) {
+ const filePath = path.join(tmpDir, file);
+ fs.rmSync(filePath, { force: true });
+ }
+ }
+ } catch (err) {
+ console.error('Failed to clear download cache:', err);
+ throw err;
+ }
+};
+
+const clearMp4Cache = () => {
+ if (!fs.existsSync(mp4BaseDir)) return;
+ try {
+ fs.rmSync(mp4BaseDir, { recursive: true, force: true });
} catch (err) {
if (typeof fs.rmdirSync === 'function') {
- fs.rmdirSync(mp4Dir, { recursive: true });
+ fs.rmdirSync(mp4BaseDir, { recursive: true });
} else {
throw err;
}
}
};
+const clearTranscodeCache = () => {
+ clearMp4Cache();
+ Object.keys(progressMap).forEach((key) => delete progressMap[key]);
+};
+
+ensureDirectoryExists(mp4BaseDir);
+
// Endpoint to list available buckets
app.get('/api/buckets', async (req, res) => {
try {
@@ -228,8 +259,7 @@ app.get('/api/config', (req, res) => {
app.post('/api/reset-cache', (req, res) => {
try {
- clearMp4Cache();
- Object.keys(progressMap).forEach((key) => delete progressMap[key]);
+ clearTranscodeCache();
res.json({ message: 'Cache reset' });
} catch (error) {
console.error('Error resetting cache:', error);
@@ -237,6 +267,26 @@ app.post('/api/reset-cache', (req, res) => {
}
});
+app.post('/api/clear-download-cache', (req, res) => {
+ try {
+ clearDownloadCache();
+ res.json({ message: 'Download cache cleared' });
+ } catch (error) {
+ console.error('Error clearing download cache:', error);
+ res.status(500).json({ error: 'Failed to clear download cache', detail: error.message });
+ }
+});
+
+app.post('/api/clear-transcode-cache', (req, res) => {
+ try {
+ clearTranscodeCache();
+ res.json({ message: 'Transcode cache cleared' });
+ } catch (error) {
+ console.error('Error clearing transcode cache:', error);
+ res.status(500).json({ error: 'Failed to clear transcode cache', detail: error.message });
+ }
+});
+
// Endpoint to transcode S3 video streaming to MP4
app.post('/api/transcode', async (req, res) => {
const { bucket, key, codec, encoder } = req.body;