添加了NEON硬件编码器选项,并增加了刷新列表、清空下载缓存和清空转码缓存的按钮。
This commit is contained in:
@@ -268,8 +268,36 @@ header p {
|
|||||||
|
|
||||||
.section-actions {
|
.section-actions {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
align-items: center;
|
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 {
|
.section-header h2 {
|
||||||
|
|||||||
@@ -41,12 +41,9 @@
|
|||||||
<div class="section-header">
|
<div class="section-header">
|
||||||
<h2>Available Videos</h2>
|
<h2>Available Videos</h2>
|
||||||
<div class="section-actions">
|
<div class="section-actions">
|
||||||
<button id="refresh-btn" class="icon-btn" title="Refresh List">
|
<button id="refresh-btn" class="action-btn" title="刷新列表">刷新列表</button>
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M3 12a9 9 0 1 0 9-9 9.75 9.75 0 0 0-6.74 2.74L3 8"/><path d="M3 3v5h5"/></svg>
|
<button id="clear-download-cache-btn" class="action-btn" title="清空下载缓存">清空下载缓存</button>
|
||||||
</button>
|
<button id="clear-transcode-cache-btn" class="action-btn danger" title="清空转码缓存">清空转码缓存</button>
|
||||||
<button id="reset-cache-btn" class="icon-btn" title="Reset Download Cache" aria-label="Reset Download Cache">
|
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M3 4v6h6"/><path d="M21 20v-6h-6"/><path d="M21 10a9 9 0 0 0-15.2-6.8L3 8"/><path d="M3 14a9 9 0 0 0 15.2 6.8L21 16"/></svg>
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="bucket-panel">
|
<div class="bucket-panel">
|
||||||
|
|||||||
@@ -2,7 +2,8 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
const videoListEl = document.getElementById('video-list');
|
const videoListEl = document.getElementById('video-list');
|
||||||
const loadingSpinner = document.getElementById('loading-spinner');
|
const loadingSpinner = document.getElementById('loading-spinner');
|
||||||
const refreshBtn = document.getElementById('refresh-btn');
|
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 bucketSelect = document.getElementById('bucket-select');
|
||||||
const loginScreen = document.getElementById('login-screen');
|
const loginScreen = document.getElementById('login-screen');
|
||||||
const appContainer = document.getElementById('app-container');
|
const appContainer = document.getElementById('app-container');
|
||||||
@@ -196,16 +197,38 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const resetCache = async () => {
|
const clearDownloadCache = async () => {
|
||||||
if (!resetCacheBtn) return;
|
if (!clearDownloadCacheBtn) return;
|
||||||
resetCacheBtn.disabled = true;
|
clearDownloadCacheBtn.disabled = true;
|
||||||
resetCacheBtn.title = 'Resetting cache...';
|
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();
|
stopPolling();
|
||||||
selectedKey = null;
|
selectedKey = null;
|
||||||
currentVideoKey = null;
|
currentVideoKey = null;
|
||||||
subscribedKey = null;
|
subscribedKey = null;
|
||||||
if (transcodeBtn) {
|
if (transcodeBtn) {
|
||||||
|
transcodeBtn.disabled = true;
|
||||||
transcodeBtn.classList.add('hidden');
|
transcodeBtn.classList.add('hidden');
|
||||||
}
|
}
|
||||||
if (playBtn) {
|
if (playBtn) {
|
||||||
@@ -220,18 +243,19 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
resetProgress();
|
resetProgress();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const res = await fetch('/api/reset-cache', { method: 'POST' });
|
const res = await fetch('/api/clear-transcode-cache', { method: 'POST' });
|
||||||
if (!res.ok) {
|
if (!res.ok) {
|
||||||
const data = await res.json().catch(() => ({}));
|
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) {
|
} catch (err) {
|
||||||
console.error('Reset cache failed:', err);
|
console.error('Clear transcode cache failed:', err);
|
||||||
alert(`Reset cache failed: ${err.message}`);
|
alert(`清空转码缓存失败: ${err.message}`);
|
||||||
} finally {
|
} finally {
|
||||||
resetCacheBtn.disabled = false;
|
clearTranscodeCacheBtn.disabled = false;
|
||||||
resetCacheBtn.title = 'Reset Download Cache';
|
clearTranscodeCacheBtn.textContent = '清空转码缓存';
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -481,6 +505,12 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
|
|
||||||
// Bind events
|
// Bind events
|
||||||
refreshBtn.addEventListener('click', () => fetchVideos(selectedBucket));
|
refreshBtn.addEventListener('click', () => fetchVideos(selectedBucket));
|
||||||
|
if (clearDownloadCacheBtn) {
|
||||||
|
clearDownloadCacheBtn.addEventListener('click', clearDownloadCache);
|
||||||
|
}
|
||||||
|
if (clearTranscodeCacheBtn) {
|
||||||
|
clearTranscodeCacheBtn.addEventListener('click', clearTranscodeCache);
|
||||||
|
}
|
||||||
if (loginBtn) {
|
if (loginBtn) {
|
||||||
loginBtn.addEventListener('click', login);
|
loginBtn.addEventListener('click', login);
|
||||||
}
|
}
|
||||||
|
|||||||
64
server.js
64
server.js
@@ -148,20 +148,51 @@ wss.on('connection', (ws) => {
|
|||||||
ws.on('close', () => removeWsClient(ws));
|
ws.on('close', () => removeWsClient(ws));
|
||||||
});
|
});
|
||||||
|
|
||||||
const clearMp4Cache = () => {
|
const mp4BaseDir = path.join(__dirname, 'public', 'mp4');
|
||||||
const mp4Dir = path.join(__dirname, 'public', 'mp4');
|
|
||||||
if (!fs.existsSync(mp4Dir)) return;
|
const ensureDirectoryExists = (dirPath) => {
|
||||||
|
if (!fs.existsSync(dirPath)) {
|
||||||
|
fs.mkdirSync(dirPath, { recursive: true });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const clearDownloadCache = () => {
|
||||||
|
const tmpDir = os.tmpdir();
|
||||||
try {
|
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) {
|
} catch (err) {
|
||||||
if (typeof fs.rmdirSync === 'function') {
|
if (typeof fs.rmdirSync === 'function') {
|
||||||
fs.rmdirSync(mp4Dir, { recursive: true });
|
fs.rmdirSync(mp4BaseDir, { recursive: true });
|
||||||
} else {
|
} else {
|
||||||
throw err;
|
throw err;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const clearTranscodeCache = () => {
|
||||||
|
clearMp4Cache();
|
||||||
|
Object.keys(progressMap).forEach((key) => delete progressMap[key]);
|
||||||
|
};
|
||||||
|
|
||||||
|
ensureDirectoryExists(mp4BaseDir);
|
||||||
|
|
||||||
// Endpoint to list available buckets
|
// Endpoint to list available buckets
|
||||||
app.get('/api/buckets', async (req, res) => {
|
app.get('/api/buckets', async (req, res) => {
|
||||||
try {
|
try {
|
||||||
@@ -228,8 +259,7 @@ app.get('/api/config', (req, res) => {
|
|||||||
|
|
||||||
app.post('/api/reset-cache', (req, res) => {
|
app.post('/api/reset-cache', (req, res) => {
|
||||||
try {
|
try {
|
||||||
clearMp4Cache();
|
clearTranscodeCache();
|
||||||
Object.keys(progressMap).forEach((key) => delete progressMap[key]);
|
|
||||||
res.json({ message: 'Cache reset' });
|
res.json({ message: 'Cache reset' });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error resetting cache:', 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
|
// Endpoint to transcode S3 video streaming to MP4
|
||||||
app.post('/api/transcode', async (req, res) => {
|
app.post('/api/transcode', async (req, res) => {
|
||||||
const { bucket, key, codec, encoder } = req.body;
|
const { bucket, key, codec, encoder } = req.body;
|
||||||
|
|||||||
Reference in New Issue
Block a user