新增编码器选择
This commit is contained in:
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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 });
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
10
server.js
10
server.js
@@ -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, '_'));
|
||||||
|
|||||||
Reference in New Issue
Block a user