feat: added smart auto-transcoding with probe-based routing and MKV/remux support
This commit is contained in:
@@ -451,6 +451,17 @@
|
||||
|
||||
<div class="settings-section">
|
||||
<h3>Streaming</h3>
|
||||
<div class="setting-item">
|
||||
<label class="setting-toggle">
|
||||
<input type="checkbox" id="setting-auto-transcode">
|
||||
<span class="toggle-slider"></span>
|
||||
</label>
|
||||
<div class="setting-info">
|
||||
<span class="setting-label">Auto Transcode (Smart)</span>
|
||||
<span class="setting-hint">Automatically detect stream codecs and only transcode/remux when needed.
|
||||
Adds ~1-3s probe delay on first play.</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="setting-item">
|
||||
<label class="setting-toggle">
|
||||
<input type="checkbox" id="setting-force-proxy">
|
||||
|
||||
@@ -45,6 +45,7 @@ class VideoPlayer {
|
||||
forceProxy: false,
|
||||
forceTranscode: false,
|
||||
forceRemux: false,
|
||||
autoTranscode: true,
|
||||
streamFormat: 'm3u8',
|
||||
epgRefreshInterval: '24'
|
||||
};
|
||||
@@ -667,10 +668,55 @@ class VideoPlayer {
|
||||
// Determine if HLS or direct stream
|
||||
this.currentUrl = streamUrl;
|
||||
|
||||
// CHECK: Auto Transcode (Smart) - probe first, then decide
|
||||
if (this.settings.autoTranscode) {
|
||||
console.log('[Player] Auto Transcode enabled. Probing stream...');
|
||||
try {
|
||||
const probeRes = await fetch(`/api/probe?url=${encodeURIComponent(streamUrl)}`);
|
||||
const info = await probeRes.json();
|
||||
console.log(`[Player] Probe result: video=${info.video}, audio=${info.audio}, compatible=${info.compatible}`);
|
||||
|
||||
if (info.needsTranscode) {
|
||||
// Incompatible audio (AC3/EAC3/DTS) - use transcode
|
||||
console.log('[Player] Auto: Using transcode (incompatible audio)');
|
||||
const transcodeUrl = `/api/transcode?url=${encodeURIComponent(streamUrl)}`;
|
||||
this.currentUrl = transcodeUrl;
|
||||
this.video.src = transcodeUrl;
|
||||
this.video.play().catch(e => {
|
||||
if (e.name !== 'AbortError') console.log('[Player] Autoplay prevented:', e);
|
||||
});
|
||||
this.updateNowPlaying(channel);
|
||||
this.showNowPlayingOverlay();
|
||||
this.fetchEpgData(channel);
|
||||
window.dispatchEvent(new CustomEvent('channelChanged', { detail: channel }));
|
||||
return;
|
||||
} else if (info.needsRemux) {
|
||||
// Raw .ts container - use remux
|
||||
console.log('[Player] Auto: Using remux (.ts container)');
|
||||
const remuxUrl = `/api/remux?url=${encodeURIComponent(streamUrl)}`;
|
||||
this.currentUrl = remuxUrl;
|
||||
this.video.src = remuxUrl;
|
||||
this.video.play().catch(e => {
|
||||
if (e.name !== 'AbortError') console.log('[Player] Autoplay prevented:', e);
|
||||
});
|
||||
this.updateNowPlaying(channel);
|
||||
this.showNowPlayingOverlay();
|
||||
this.fetchEpgData(channel);
|
||||
window.dispatchEvent(new CustomEvent('channelChanged', { detail: channel }));
|
||||
return;
|
||||
}
|
||||
// Compatible - fall through to normal HLS.js path
|
||||
console.log('[Player] Auto: Using HLS.js (compatible)');
|
||||
} catch (err) {
|
||||
console.warn('[Player] Probe failed, using normal playback:', err.message);
|
||||
// Continue with normal playback on probe failure
|
||||
}
|
||||
}
|
||||
|
||||
// CHECK: Force Transcode Priority - transcoded streams bypass HLS.js
|
||||
if (this.settings.forceTranscode) {
|
||||
console.log('[Player] Force Transcode enabled. Routing through ffmpeg...');
|
||||
const transcodeUrl = this.getTranscodeUrl(streamUrl);
|
||||
const transcodeUrl = `/api/transcode?url=${encodeURIComponent(streamUrl)}`;
|
||||
this.currentUrl = transcodeUrl;
|
||||
|
||||
// Transcoded streams are fragmented MP4 - play directly with <video> element
|
||||
|
||||
@@ -34,6 +34,7 @@ class SettingsPage {
|
||||
const forceProxyToggle = document.getElementById('setting-force-proxy');
|
||||
const forceTranscodeToggle = document.getElementById('setting-force-transcode');
|
||||
const forceRemuxToggle = document.getElementById('setting-force-remux');
|
||||
const autoTranscodeToggle = document.getElementById('setting-auto-transcode');
|
||||
|
||||
// Load current settings
|
||||
if (this.app.player?.settings) {
|
||||
@@ -52,6 +53,9 @@ class SettingsPage {
|
||||
if (forceRemuxToggle) {
|
||||
forceRemuxToggle.checked = this.app.player.settings.forceRemux || false;
|
||||
}
|
||||
if (autoTranscodeToggle) {
|
||||
autoTranscodeToggle.checked = this.app.player.settings.autoTranscode || false;
|
||||
}
|
||||
}
|
||||
|
||||
// Arrow keys toggle
|
||||
@@ -106,6 +110,12 @@ class SettingsPage {
|
||||
this.app.player.saveSettings();
|
||||
});
|
||||
|
||||
// Auto transcode (smart) toggle
|
||||
autoTranscodeToggle?.addEventListener('change', () => {
|
||||
this.app.player.settings.autoTranscode = autoTranscodeToggle.checked;
|
||||
this.app.player.saveSettings();
|
||||
});
|
||||
|
||||
// EPG refresh interval
|
||||
const epgRefreshSelect = document.getElementById('epg-refresh-interval');
|
||||
if (epgRefreshSelect && this.app.player?.settings) {
|
||||
@@ -271,6 +281,7 @@ class SettingsPage {
|
||||
const forceProxyToggle = document.getElementById('setting-force-proxy');
|
||||
const forceTranscodeToggle = document.getElementById('setting-force-transcode');
|
||||
const forceRemuxToggle = document.getElementById('setting-force-remux');
|
||||
const autoTranscodeToggle = document.getElementById('setting-auto-transcode');
|
||||
const epgRefreshSelect = document.getElementById('epg-refresh-interval');
|
||||
const streamFormatSelect = document.getElementById('setting-stream-format');
|
||||
|
||||
@@ -283,6 +294,7 @@ class SettingsPage {
|
||||
if (forceProxyToggle) forceProxyToggle.checked = s.forceProxy || false;
|
||||
if (forceTranscodeToggle) forceTranscodeToggle.checked = s.forceTranscode || false;
|
||||
if (forceRemuxToggle) forceRemuxToggle.checked = s.forceRemux || false;
|
||||
if (autoTranscodeToggle) autoTranscodeToggle.checked = s.autoTranscode || false;
|
||||
if (epgRefreshSelect) epgRefreshSelect.value = s.epgRefreshInterval || '24';
|
||||
if (streamFormatSelect) streamFormatSelect.value = s.streamFormat || 'm3u8';
|
||||
}
|
||||
|
||||
@@ -229,6 +229,41 @@ class WatchPage {
|
||||
const isRawTs = url.includes('.ts') && !url.includes('.m3u8');
|
||||
const isDirectVideo = url.includes('.mp4') || url.includes('.mkv') || url.includes('.avi');
|
||||
|
||||
// Priority 0: Auto Transcode (Smart) - probe first, then decide
|
||||
if (settings.autoTranscode) {
|
||||
console.log('[WatchPage] Auto Transcode enabled. Probing stream...');
|
||||
try {
|
||||
const probeRes = await fetch(`/api/probe?url=${encodeURIComponent(url)}`);
|
||||
const info = await probeRes.json();
|
||||
console.log(`[WatchPage] Probe result: video=${info.video}, audio=${info.audio}, compatible=${info.compatible}`);
|
||||
|
||||
if (info.needsTranscode) {
|
||||
console.log('[WatchPage] Auto: Using transcode (incompatible audio)');
|
||||
const finalUrl = `/api/transcode?url=${encodeURIComponent(url)}`;
|
||||
this.video.src = finalUrl;
|
||||
this.video.play().catch(e => {
|
||||
if (e.name !== 'AbortError') console.error('[WatchPage] Autoplay error:', e);
|
||||
});
|
||||
this.setVolumeFromStorage();
|
||||
return;
|
||||
} else if (info.needsRemux) {
|
||||
console.log('[WatchPage] Auto: Using remux (.ts container)');
|
||||
const finalUrl = `/api/remux?url=${encodeURIComponent(url)}`;
|
||||
this.video.src = finalUrl;
|
||||
this.video.play().catch(e => {
|
||||
if (e.name !== 'AbortError') console.error('[WatchPage] Autoplay error:', e);
|
||||
});
|
||||
this.setVolumeFromStorage();
|
||||
return;
|
||||
}
|
||||
// Compatible - fall through to normal playback
|
||||
console.log('[WatchPage] Auto: Using normal playback (compatible)');
|
||||
} catch (err) {
|
||||
console.warn('[WatchPage] Probe failed, using normal playback:', err.message);
|
||||
// Continue with normal playback on probe failure
|
||||
}
|
||||
}
|
||||
|
||||
// Priority 1: Force Transcode - route through FFmpeg
|
||||
if (settings.forceTranscode) {
|
||||
console.log('[WatchPage] Force Transcode enabled');
|
||||
|
||||
Reference in New Issue
Block a user