diff --git a/Jellyfin.Plugin.Enigma2/BroadcastSlateProgram.cs b/Jellyfin.Plugin.Enigma2/BroadcastSlateProgram.cs index 4a67819..ed57aad 100644 --- a/Jellyfin.Plugin.Enigma2/BroadcastSlateProgram.cs +++ b/Jellyfin.Plugin.Enigma2/BroadcastSlateProgram.cs @@ -27,9 +27,10 @@ public static class BroadcastSlateProgram var p = start + 4 + ((clip[start + 3] & 32) != 0 ? clip[start + 4] + 1 : 0); if (p + 14 <= start + 188 && clip[p] == 0 && clip[p + 1] == 0 && clip[p + 2] == 1 && (clip[p + 7] & 128) != 0) { - long pts = ((long)(clip[p + 9] & 14) << 29) | ((long)clip[p + 10] << 22) | ((long)(clip[p + 11] & 254) << 14) | ((long)clip[p + 12] << 7) | (clip[p + 13] >> 1); + long pts = ((long)(clip[p + 9] & 14) << 29) | ((long)clip[p + 10] << 22) | ((long)(clip[p + 11] & 254) << 14) | ((long)clip[p + 12] << 7) | ((long)clip[p + 13] >> 1); for (var i = 0; i < tracks.Output.Length; i++) - if (tracks.Output[i].Codec == "DVBSUB") output.Write(ClearSubtitle(tracks.Input[i], 256 + i, pts)); + if (tracks.Output[i].Codec == "DVBSUB") output.Write(ClearSubtitle(tracks.Input[i], 256 + i, pts, + tracks.Input[0].Width ?? ContinuityStreams.Width, tracks.Input[0].Height ?? ContinuityStreams.Height)); cleared = true; } } @@ -50,12 +51,12 @@ public static class BroadcastSlateProgram if (!written) throw new InvalidDataException("Status clip has no expected program map"); return output.ToArray(); } - private static byte[] ClearSubtitle(MediaStream stream, int pid, long pts) + private static byte[] ClearSubtitle(MediaStream stream, int pid, long pts, int width, int height) { var extra = (stream as BroadcastMediaStream)?.SubtitleExtraData; var page = extra is { Length: >= 2 } ? (extra[0] << 8) | extra[1] : 1; var payload = new byte[] { 32, 0, - 15, 20, (byte)(page >> 8), (byte)page, 0, 5, 0, 7, 127, 4, 55, + 15, 20, (byte)(page >> 8), (byte)page, 0, 5, 0, (byte)((width - 1) >> 8), (byte)(width - 1), (byte)((height - 1) >> 8), (byte)(height - 1), 15, 16, (byte)(page >> 8), (byte)page, 0, 2, 1, 3, 15, 128, (byte)(page >> 8), (byte)page, 0, 0, 255 }; var pes = new List { 0, 0, 1, 189, 0, (byte)(payload.Length + 8), 128, 128, 5, diff --git a/Jellyfin.Plugin.Enigma2/BroadcastTracks.cs b/Jellyfin.Plugin.Enigma2/BroadcastTracks.cs index 4549dc2..ad2c8c8 100644 --- a/Jellyfin.Plugin.Enigma2/BroadcastTracks.cs +++ b/Jellyfin.Plugin.Enigma2/BroadcastTracks.cs @@ -13,6 +13,8 @@ namespace Jellyfin.Plugin.Enigma2; // Probe-only transport details; the public Jellyfin contract remains MediaStream. public sealed class BroadcastMediaStream : MediaStream { + public string SampleAspectRatio { get; set; } + public string FrameRateRational { get; set; } public int? TransportId { get; set; } public bool IsVisualImpaired { get; set; } public byte[] SubtitleExtraData { get; set; } @@ -27,9 +29,9 @@ public sealed class BroadcastTracks public string Key { get; } public int AudioCount => Input.Count(s => s.Type == MediaStreamType.Audio); public bool HasSubtitles => Input.Any(s => s.Type == MediaStreamType.Subtitle); - public BroadcastTracks(IEnumerable streams, bool normalize = true, bool preserveResolution = true) + public BroadcastTracks(IEnumerable streams, bool normalize = false, bool preserveResolution = true) { - Normalize = normalize; + Normalize = false; // Legacy argument retained for compatibility; channel AV is never normalized. var all = streams.ToArray(); Input = all.Where(s => s.Type == MediaStreamType.Video).Take(1) .Concat(all.Where(s => s.Type == MediaStreamType.Audio)) @@ -41,21 +43,13 @@ public sealed class BroadcastTracks Output = Input.Select((s, i) => { var copy = LiveStreamProbe.ParseLiveMetadata(s); copy.Index = i; if (s is BroadcastMediaStream { IsVisualImpaired: true } && string.IsNullOrWhiteSpace(copy.Title)) copy.Title = "Audio description"; - if (normalize && copy.Type == MediaStreamType.Video) - { - copy.Codec = "h264"; copy.Width = preserveResolution ? s.Width : ContinuityStreams.Width; - copy.Height = preserveResolution ? s.Height : ContinuityStreams.Height; copy.Level = null; - copy.Profile = "Constrained Baseline"; copy.PixelFormat = "yuv420p"; copy.IsInterlaced = false; - copy.AverageFrameRate = copy.RealFrameRate = ContinuityStreams.FrameRate; - copy.ColorRange = copy.ColorSpace = copy.ColorTransfer = copy.ColorPrimaries = null; copy.BitRate = null; - } - if (normalize && copy.Type == MediaStreamType.Audio) - { copy.Codec = "aac"; copy.Channels = 2; copy.SampleRate = 48000; copy.BitRate = 128000; copy.Profile = "LC"; copy.ChannelLayout = "stereo"; } return copy; }).ToArray(); // Stable per-channel track cache; no URLs, credentials or user identities in its key. Key = Convert.ToHexString(SHA256.HashData(Encoding.UTF8.GetBytes(JsonSerializer.Serialize(Input.Select(s => new { s.Type, s.Index, s.Codec, s.Language, s.Title, s.IsDefault, s.IsForced, s.IsHearingImpaired, s.Channels, s.Width, s.Height, + s.Profile, s.PixelFormat, s.AverageFrameRate, s.RealFrameRate, s.IsInterlaced, s.ColorRange, s.ColorSpace, s.ColorTransfer, s.ColorPrimaries, s.SampleRate, s.ChannelLayout, + Sar = (s as BroadcastMediaStream)?.SampleAspectRatio, Rate = (s as BroadcastMediaStream)?.FrameRateRational, Pid = (s as BroadcastMediaStream)?.TransportId, Extra = (s as BroadcastMediaStream)?.SubtitleExtraData }))))).ToLowerInvariant(); } diff --git a/Jellyfin.Plugin.Enigma2/Configuration/PluginConfiguration.cs b/Jellyfin.Plugin.Enigma2/Configuration/PluginConfiguration.cs index dbe0e52..d1a119a 100644 --- a/Jellyfin.Plugin.Enigma2/Configuration/PluginConfiguration.cs +++ b/Jellyfin.Plugin.Enigma2/Configuration/PluginConfiguration.cs @@ -34,6 +34,8 @@ namespace Jellyfin.Plugin.Enigma2.Configuration public bool ShowNativeStartupMessages { get; set; } = true; public bool EnableContinuitySlates { get; set; } = false; public bool PreserveSourceResolution { get; set; } = true; + public int StreamStartupWaitSeconds { get; set; } = 90; + public int StreamStallWaitSeconds { get; set; } = 30; public PluginConfiguration() diff --git a/Jellyfin.Plugin.Enigma2/Configuration/configPage.html b/Jellyfin.Plugin.Enigma2/Configuration/configPage.html index 41c6d03..9f229aa 100644 --- a/Jellyfin.Plugin.Enigma2/Configuration/configPage.html +++ b/Jellyfin.Plugin.Enigma2/Configuration/configPage.html @@ -157,7 +157,17 @@ Podtrzymuj stream planszą FFmpeg podczas oczekiwania i przerw -
Ponawia odbiór aż do zatrzymania przez widza. Jeden proces odbioru na kanał; język planszy według interfejsu użytkownika, następnie profilu i ustawień Jellyfina. Zachowuje rozdzielczość obrazu źródła, również 4K, przy kodowaniu H264 50 kl./s i AAC stereo. Wymaga CPU; nie zachowuje HDR, dźwięku przestrzennego ani dodatkowych ścieżek. Nie dotyczy nagrań.
+
Obraz i dźwięk kanału są kopiowane bez przekodowania: bez limitu 50 kl./s, zmiany rozdzielczości, HDR i dźwięku przestrzennego. Plansza jest dostępna dla rozpoznanego progresywnego H264 8-bit z AAC LC stereo 48 kHz; ma rozmiar i klatkaż źródła oraz wymaga zgodnych granic klatek obrazu i dźwięku. Pozostałe formaty oraz źródła bez metadanych używają zwykłego oczekiwania, bez obniżania jakości. Język planszy według interfejsu, profilu i ustawień Jellyfina. Nie dotyczy nagrań.
+
+ + +
Domyślnie 90, zakres 25–180. Ponawia analizę świeżej próbki przy jednym połączeniu dekodera. Anulowanie ostatniego widza kończy próbę. Android TV może mieć własny, krótszy limit.
+
+
+ + +
Domyślnie 30, zakres 8–60. Dotyczy braku danych; nie potwierdza poprawnego obrazu w napływających pakietach.
+