diff --git a/CHANGELOG.md b/CHANGELOG.md index 6f86a36c6..a2319be3a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,12 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). ## [Unreleased] +### Added +- Add experimental `HLS Hybrid` channel mode + - Media items are transcoded using the channel's ffmpeg profile and served using HLS + +### Fixed +- Fix serving channels.m3u with missing content ratings ## [0.0.44-prealpha] - 2021-06-09 ### Added diff --git a/ErsatzTV.Application/Streaming/Queries/GetHlsPlaylistByChannelNumber.cs b/ErsatzTV.Application/Streaming/Queries/GetHlsPlaylistByChannelNumber.cs index e2f6363d8..e836ddbe9 100644 --- a/ErsatzTV.Application/Streaming/Queries/GetHlsPlaylistByChannelNumber.cs +++ b/ErsatzTV.Application/Streaming/Queries/GetHlsPlaylistByChannelNumber.cs @@ -5,5 +5,5 @@ using MediatR; namespace ErsatzTV.Application.Streaming.Queries { public record GetHlsPlaylistByChannelNumber - (string Scheme, string Host, string ChannelNumber) : IRequest>; + (string Scheme, string Host, string ChannelNumber, string Mode) : IRequest>; } diff --git a/ErsatzTV.Application/Streaming/Queries/GetHlsPlaylistByChannelNumberHandler.cs b/ErsatzTV.Application/Streaming/Queries/GetHlsPlaylistByChannelNumberHandler.cs index 5d2fd407d..60818c9e2 100644 --- a/ErsatzTV.Application/Streaming/Queries/GetHlsPlaylistByChannelNumberHandler.cs +++ b/ErsatzTV.Application/Streaming/Queries/GetHlsPlaylistByChannelNumberHandler.cs @@ -39,6 +39,10 @@ namespace ErsatzTV.Application.Streaming.Queries GetHlsPlaylistByChannelNumber request, Channel channel) { + string mode = string.IsNullOrWhiteSpace(request.Mode) + ? string.Empty + : $"&mode={request.Mode}"; + DateTimeOffset now = DateTimeOffset.Now; Option maybePlayoutItem = await _playoutRepository.GetPlayoutItem(channel.Id, now); return maybePlayoutItem.Match>( @@ -48,11 +52,11 @@ namespace ErsatzTV.Application.Streaming.Queries double timeRemaining = Math.Abs((playoutItem.FinishOffset - now).TotalSeconds); return $@"#EXTM3U #EXT-X-VERSION:3 -#EXT-X-TARGETDURATION:6 +#EXT-X-TARGETDURATION:10 #EXT-X-MEDIA-SEQUENCE:{index} #EXT-X-DISCONTINUITY #EXTINF:{timeRemaining:F2}, -{request.Scheme}://{request.Host}/ffmpeg/stream/{request.ChannelNumber}?index={index}&mode=hls-direct +{request.Scheme}://{request.Host}/ffmpeg/stream/{request.ChannelNumber}?index={index}{mode} "; }, () => diff --git a/ErsatzTV.Core/Domain/StreamingMode.cs b/ErsatzTV.Core/Domain/StreamingMode.cs index 727208c11..db27ac2f8 100644 --- a/ErsatzTV.Core/Domain/StreamingMode.cs +++ b/ErsatzTV.Core/Domain/StreamingMode.cs @@ -3,6 +3,7 @@ public enum StreamingMode { TransportStream = 1, - HttpLiveStreamingDirect = 2 + HttpLiveStreamingDirect = 2, + HttpLiveStreamingHybrid = 3 } } diff --git a/ErsatzTV.Core/FFmpeg/FFmpegPlaybackSettingsCalculator.cs b/ErsatzTV.Core/FFmpeg/FFmpegPlaybackSettingsCalculator.cs index ce171c9d6..3b941cf1f 100644 --- a/ErsatzTV.Core/FFmpeg/FFmpegPlaybackSettingsCalculator.cs +++ b/ErsatzTV.Core/FFmpeg/FFmpegPlaybackSettingsCalculator.cs @@ -69,6 +69,7 @@ namespace ErsatzTV.Core.FFmpeg result.VideoCodec = "copy"; result.Deinterlace = false; break; + case StreamingMode.HttpLiveStreamingHybrid: case StreamingMode.TransportStream: result.HardwareAcceleration = ffmpegProfile.HardwareAcceleration; diff --git a/ErsatzTV.Core/Hdhr/LineupItem.cs b/ErsatzTV.Core/Hdhr/LineupItem.cs index 853108931..f054e0fb0 100644 --- a/ErsatzTV.Core/Hdhr/LineupItem.cs +++ b/ErsatzTV.Core/Hdhr/LineupItem.cs @@ -22,7 +22,8 @@ namespace ErsatzTV.Core.Hdhr public string URL => _channel.StreamingMode switch { - StreamingMode.HttpLiveStreamingDirect => $"{_scheme}://{_host}/iptv/channel/{_channel.Number}.m3u8", + StreamingMode.HttpLiveStreamingDirect or StreamingMode.HttpLiveStreamingHybrid => + $"{_scheme}://{_host}/iptv/channel/{_channel.Number}.m3u8", _ => $"{_scheme}://{_host}/iptv/channel/{_channel.Number}.ts" }; } diff --git a/ErsatzTV.Core/Iptv/ChannelGuide.cs b/ErsatzTV.Core/Iptv/ChannelGuide.cs index f0a737a1f..48e1da3de 100644 --- a/ErsatzTV.Core/Iptv/ChannelGuide.cs +++ b/ErsatzTV.Core/Iptv/ChannelGuide.cs @@ -349,7 +349,7 @@ namespace ErsatzTV.Core.Iptv private static Option ParseContentRating(string contentRating, string system) { - Option maybeFirst = contentRating.Split('/').HeadOrNone(); + Option maybeFirst = (contentRating ?? string.Empty).Split('/').HeadOrNone(); return maybeFirst.Map>( first => { diff --git a/ErsatzTV.Core/Iptv/ChannelPlaylist.cs b/ErsatzTV.Core/Iptv/ChannelPlaylist.cs index ee15f0278..c212c8084 100644 --- a/ErsatzTV.Core/Iptv/ChannelPlaylist.cs +++ b/ErsatzTV.Core/Iptv/ChannelPlaylist.cs @@ -42,7 +42,8 @@ namespace ErsatzTV.Core.Iptv string format = channel.StreamingMode switch { - StreamingMode.HttpLiveStreamingDirect => "m3u8", + StreamingMode.HttpLiveStreamingDirect => "m3u8?mode=hls-direct", + StreamingMode.HttpLiveStreamingHybrid => "m3u8", _ => "ts" }; diff --git a/ErsatzTV/Controllers/IptvController.cs b/ErsatzTV/Controllers/IptvController.cs index c3d7fdf20..52813d947 100644 --- a/ErsatzTV/Controllers/IptvController.cs +++ b/ErsatzTV/Controllers/IptvController.cs @@ -55,12 +55,16 @@ namespace ErsatzTV.Controllers error => BadRequest(error.Value))); [HttpGet("iptv/channel/{channelNumber}.m3u8")] - public Task GetHttpLiveStreamingVideo(string channelNumber) => + public Task GetHttpLiveStreamingVideo( + string channelNumber, + [FromQuery] + string mode = "mixed") => _mediator.Send( new GetHlsPlaylistByChannelNumber( Request.Scheme, Request.Host.ToString(), - channelNumber)) + channelNumber, + mode)) .Map( result => result.Match( playlist => Content(playlist, "application/x-mpegurl"), diff --git a/ErsatzTV/Pages/ChannelEditor.razor b/ErsatzTV/Pages/ChannelEditor.razor index c41a0fdcc..07f1e0197 100644 --- a/ErsatzTV/Pages/ChannelEditor.razor +++ b/ErsatzTV/Pages/ChannelEditor.razor @@ -26,9 +26,10 @@ MPEG-TS HLS Direct + HLS Hybrid + Disabled="@(_model.StreamingMode == StreamingMode.HttpLiveStreamingDirect)"> @foreach (FFmpegProfileViewModel profile in _ffmpegProfiles) { @profile.Name diff --git a/ErsatzTV/Pages/Channels.razor b/ErsatzTV/Pages/Channels.razor index 583ffb323..3197fd75a 100644 --- a/ErsatzTV/Pages/Channels.razor +++ b/ErsatzTV/Pages/Channels.razor @@ -50,9 +50,9 @@ @context.Name @context.PreferredLanguageCode - @(context.StreamingMode == StreamingMode.TransportStream ? "MPEG-TS" : "HLS Direct") + @GetStreamingMode(context.StreamingMode) - @if (context.StreamingMode == StreamingMode.TransportStream) + @if (context.StreamingMode != StreamingMode.HttpLiveStreamingDirect) { @_ffmpegProfiles.Find(p => p.Id == context.FFmpegProfileId)?.Name } @@ -138,4 +138,10 @@ }; } + private static string GetStreamingMode(StreamingMode streamingMode) => streamingMode switch { + StreamingMode.HttpLiveStreamingDirect => "HLS Direct", + StreamingMode.HttpLiveStreamingHybrid => "HLS Hybrid", + _ => "MPEG-TS" + }; + } \ No newline at end of file