新增编码器选择

This commit is contained in:
CN-JS-HuiBai
2026-04-02 17:40:06 +08:00
parent 47de5ac65b
commit eb6068cbb8
4 changed files with 55 additions and 7 deletions

View File

@@ -401,6 +401,27 @@ header p {
font-weight: 600; font-weight: 600;
} }
.play-btn {
margin-top: 1rem;
padding: 0.85rem 1.25rem;
border: none;
border-radius: 999px;
background: var(--accent);
color: #fff;
font-weight: 700;
cursor: pointer;
transition: transform 0.2s ease, background 0.2s ease;
}
.play-btn:hover {
background: var(--accent-hover);
transform: translateY(-1px);
}
.play-btn.hidden {
display: none !important;
}
.hidden { .hidden {
display: none !important; display: none !important;
} }

View File

@@ -39,6 +39,14 @@
<option value="h265">H265</option> <option value="h265">H265</option>
</select> </select>
</div> </div>
<div class="codec-panel">
<label for="encoder-select">硬件编码器:</label>
<select id="encoder-select">
<option value="software">Software</option>
<option value="nvidia">NVIDIA</option>
<option value="intel">Intel</option>
</select>
</div>
<div id="loading-spinner" class="spinner-container"> <div id="loading-spinner" class="spinner-container">
<div class="spinner"></div> <div class="spinner"></div>
<p>Fetching S3 Objects...</p> <p>Fetching S3 Objects...</p>
@@ -63,6 +71,7 @@
<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="play-btn" class="play-btn hidden">Play</button>
</div> </div>
</section> </section>
</main> </main>

View File

@@ -3,14 +3,23 @@ document.addEventListener('DOMContentLoaded', () => {
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 codecSelect = document.getElementById('codec-select'); const codecSelect = document.getElementById('codec-select');
const encoderSelect = document.getElementById('encoder-select');
const playerOverlay = document.getElementById('player-overlay'); const playerOverlay = document.getElementById('player-overlay');
const transcodingOverlay = document.getElementById('transcoding-overlay'); const transcodingOverlay = document.getElementById('transcoding-overlay');
const videoPlayer = document.getElementById('video-player'); const videoPlayer = document.getElementById('video-player');
const nowPlaying = document.getElementById('now-playing'); const nowPlaying = document.getElementById('now-playing');
const currentVideoTitle = document.getElementById('current-video-title'); const currentVideoTitle = document.getElementById('current-video-title');
const playBtn = document.getElementById('play-btn');
let currentPollInterval = null; let currentPollInterval = null;
if (playBtn) {
playBtn.addEventListener('click', () => {
videoPlayer.play().catch(e => console.log('Play blocked:', e));
playBtn.classList.add('hidden');
});
}
// Fetch list of videos from the backend // Fetch list of videos from the backend
const fetchVideos = async () => { const fetchVideos = async () => {
videoListEl.classList.add('hidden'); videoListEl.classList.add('hidden');
@@ -134,6 +143,7 @@ document.addEventListener('DOMContentLoaded', () => {
playerOverlay.classList.add('hidden'); playerOverlay.classList.add('hidden');
videoPlayer.classList.add('hidden'); videoPlayer.classList.add('hidden');
videoPlayer.pause(); videoPlayer.pause();
if (playBtn) playBtn.classList.add('hidden');
nowPlaying.classList.remove('hidden'); nowPlaying.classList.remove('hidden');
currentVideoTitle.textContent = key.split('/').pop(); currentVideoTitle.textContent = key.split('/').pop();
@@ -142,10 +152,11 @@ document.addEventListener('DOMContentLoaded', () => {
try { try {
const codec = codecSelect?.value || 'h264'; const codec = codecSelect?.value || 'h264';
const encoder = encoderSelect?.value || 'software';
const res = await fetch('/api/transcode', { const res = await fetch('/api/transcode', {
method: 'POST', method: 'POST',
headers: { 'Content-Type': 'application/json' }, headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ key, codec }) body: JSON.stringify({ key, codec, encoder })
}); });
const data = await res.json(); const data = await res.json();
@@ -170,7 +181,7 @@ document.addEventListener('DOMContentLoaded', () => {
const res = await fetch(`/api/status?key=${encodeURIComponent(key)}`); const res = await fetch(`/api/status?key=${encodeURIComponent(key)}`);
const data = await res.json(); const data = await res.json();
if (data.ready) { if (data.ready) {
stopPolling(); stopPolling();
playHlsStream(data.hlsUrl); playHlsStream(data.hlsUrl);
} else if (attempts >= maxAttempts) { } else if (attempts >= maxAttempts) {
@@ -194,20 +205,21 @@ document.addEventListener('DOMContentLoaded', () => {
const playHlsStream = (url) => { const playHlsStream = (url) => {
transcodingOverlay.classList.add('hidden'); transcodingOverlay.classList.add('hidden');
videoPlayer.classList.remove('hidden'); videoPlayer.classList.remove('hidden');
playBtn.classList.add('hidden');
if (Hls.isSupported()) { if (Hls.isSupported()) {
const hls = new Hls(); const hls = new Hls();
hls.loadSource(url); hls.loadSource(url);
hls.attachMedia(videoPlayer); hls.attachMedia(videoPlayer);
hls.on(Hls.Events.MANIFEST_PARSED, () => { hls.on(Hls.Events.MANIFEST_PARSED, () => {
videoPlayer.play().catch(e => console.log('Auto-play blocked')); playBtn.classList.remove('hidden');
}); });
} else if (videoPlayer.canPlayType('application/vnd.apple.mpegurl')) { } else if (videoPlayer.canPlayType('application/vnd.apple.mpegurl')) {
// Safari uses native HLS // Safari uses native HLS
videoPlayer.src = url; videoPlayer.src = url;
videoPlayer.addEventListener('loadedmetadata', () => { videoPlayer.addEventListener('loadedmetadata', () => {
videoPlayer.play().catch(e => console.log('Auto-play blocked')); playBtn.classList.remove('hidden');
}); }, { once: true });
} }
}; };

View File

@@ -65,14 +65,20 @@ app.get('/api/videos', async (req, res) => {
// Endpoint to transcode S3 video streaming to HLS // Endpoint to transcode S3 video streaming to HLS
app.post('/api/transcode', async (req, res) => { app.post('/api/transcode', async (req, res) => {
const { key, codec } = req.body; const { key, codec, encoder } = req.body;
if (!key) { if (!key) {
return res.status(400).json({ error: 'Video key is required' }); return res.status(400).json({ error: 'Video key is required' });
} }
const safeCodec = codec === 'h265' ? 'h265' : 'h264'; const safeCodec = codec === 'h265' ? 'h265' : 'h264';
const videoCodec = safeCodec === 'h265' ? 'libx265' : 'libx264'; const safeEncoder = ['nvidia', 'intel'].includes(encoder) ? encoder : 'software';
const codecMap = {
software: { h264: 'libx264', h265: 'libx265' },
nvidia: { h264: 'h264_nvenc', h265: 'hevc_nvenc' },
intel: { h264: 'h264_qsv', h265: 'hevc_qsv' }
};
const videoCodec = codecMap[safeEncoder][safeCodec];
try { try {
const safeKeySegments = key.split('/').map(segment => segment.replace(/[^a-zA-Z0-9_\-]/g, '_')); const safeKeySegments = key.split('/').map(segment => segment.replace(/[^a-zA-Z0-9_\-]/g, '_'));