Record coordinated reception results and prepare scoped web H264 option

This commit is contained in:
2026-10-12 23:57:10 +02:00
parent 5c10d8b9b8
commit a7e44efdf7
15 changed files with 12390 additions and 17 deletions
+31 -1
View File
@@ -1,9 +1,39 @@
// Jellyfin Web's supported window plugin API supplies the native playback manager.
window.DecoderChannelPlayback = async () => class DecoderChannelPlayback {
constructor({ playbackManager, toast, appRouter, loading, events }) {
constructor({ playbackManager, toast, appRouter, loading, events, appHost }) {
this.id = 'decoder-channel-playback';
this.name = 'Decoder channel playback';
this.type = 'integration';
// Limit only this provider's web live TV negotiation, preserving the shared client profile.
if (appHost?.getDeviceProfile && !appHost.getDeviceProfile.decoderH264Policy) {
const originalProfile = appHost.getDeviceProfile;
const getDeviceProfile = async function (item, options) {
const profile = await originalProfile.call(this, item, options);
const api = window.ApiClient;
if (!profile || item?.Type !== 'TvChannel' || window.NativeShell ||
playbackManager.getCurrentPlayer()?.isLocalPlayer === false || !api?.getCurrentUserId() ||
(item.ServerId && item.ServerId !== api.serverId())) return profile;
try {
// Fresh policy per negotiation; an unavailable endpoint preserves native behavior.
let timeout;
const request = api.ajax({ type: 'GET', dataType: 'json',
url: api.getUrl('DecoderIntegration/WebPlaybackPolicy', { channelId: item.Id }) });
const policy = await Promise.race([request, new Promise(resolve => { timeout = setTimeout(() => resolve(null), 2000); })])
.finally(() => clearTimeout(timeout));
if (!policy?.IsEnigmaChannel || !policy.PreferH264) return profile;
const video = p => p.Type === 'Video' && (!p.Context || p.Context === 'Streaming');
const h264 = p => (p.VideoCodec || '').split(',').some(c => c.trim().toLowerCase() === 'h264');
if (!profile.TranscodingProfiles?.some(p => video(p) && h264(p))) return profile;
const copy = JSON.parse(JSON.stringify(profile));
copy.TranscodingProfiles = copy.TranscodingProfiles.filter(p => !video(p) || h264(p))
.map(p => video(p) ? { ...p, VideoCodec: 'h264' } : p);
return copy;
} catch { return profile; }
};
getDeviceProfile.decoderH264Policy = true;
try { appHost.getDeviceProfile = getDeviceProfile; }
catch { /* A client exposing a read-only host keeps its native negotiation. */ }
}
let generation = 0, starting = null, cancelButton = null, watcher = null;
const clearControls = () => { clearInterval(watcher); watcher = null; cancelButton?.remove(); cancelButton = null; };
events.on(playbackManager, 'playbackstop', () => { if (!starting) clearControls(); });