Browse Source

add hls hybrid mode (#252)

* fix serving channels.m3u with missing content ratings

* add hls hybrid mode
pull/253/head
Jason Dove 5 years ago committed by GitHub
parent
commit
7d852bc960
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 6
      CHANGELOG.md
  2. 2
      ErsatzTV.Application/Streaming/Queries/GetHlsPlaylistByChannelNumber.cs
  3. 8
      ErsatzTV.Application/Streaming/Queries/GetHlsPlaylistByChannelNumberHandler.cs
  4. 3
      ErsatzTV.Core/Domain/StreamingMode.cs
  5. 1
      ErsatzTV.Core/FFmpeg/FFmpegPlaybackSettingsCalculator.cs
  6. 3
      ErsatzTV.Core/Hdhr/LineupItem.cs
  7. 2
      ErsatzTV.Core/Iptv/ChannelGuide.cs
  8. 3
      ErsatzTV.Core/Iptv/ChannelPlaylist.cs
  9. 8
      ErsatzTV/Controllers/IptvController.cs
  10. 3
      ErsatzTV/Pages/ChannelEditor.razor
  11. 10
      ErsatzTV/Pages/Channels.razor

6
CHANGELOG.md

@ -4,6 +4,12 @@ All notable changes to this project will be documented in this file. @@ -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

2
ErsatzTV.Application/Streaming/Queries/GetHlsPlaylistByChannelNumber.cs

@ -5,5 +5,5 @@ using MediatR; @@ -5,5 +5,5 @@ using MediatR;
namespace ErsatzTV.Application.Streaming.Queries
{
public record GetHlsPlaylistByChannelNumber
(string Scheme, string Host, string ChannelNumber) : IRequest<Either<BaseError, string>>;
(string Scheme, string Host, string ChannelNumber, string Mode) : IRequest<Either<BaseError, string>>;
}

8
ErsatzTV.Application/Streaming/Queries/GetHlsPlaylistByChannelNumberHandler.cs

@ -39,6 +39,10 @@ namespace ErsatzTV.Application.Streaming.Queries @@ -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<PlayoutItem> maybePlayoutItem = await _playoutRepository.GetPlayoutItem(channel.Id, now);
return maybePlayoutItem.Match<Either<BaseError, string>>(
@ -48,11 +52,11 @@ namespace ErsatzTV.Application.Streaming.Queries @@ -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}
";
},
() =>

3
ErsatzTV.Core/Domain/StreamingMode.cs

@ -3,6 +3,7 @@ @@ -3,6 +3,7 @@
public enum StreamingMode
{
TransportStream = 1,
HttpLiveStreamingDirect = 2
HttpLiveStreamingDirect = 2,
HttpLiveStreamingHybrid = 3
}
}

1
ErsatzTV.Core/FFmpeg/FFmpegPlaybackSettingsCalculator.cs

@ -69,6 +69,7 @@ namespace ErsatzTV.Core.FFmpeg @@ -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;

3
ErsatzTV.Core/Hdhr/LineupItem.cs

@ -22,7 +22,8 @@ namespace ErsatzTV.Core.Hdhr @@ -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"
};
}

2
ErsatzTV.Core/Iptv/ChannelGuide.cs

@ -349,7 +349,7 @@ namespace ErsatzTV.Core.Iptv @@ -349,7 +349,7 @@ namespace ErsatzTV.Core.Iptv
private static Option<ContentRating> ParseContentRating(string contentRating, string system)
{
Option<string> maybeFirst = contentRating.Split('/').HeadOrNone();
Option<string> maybeFirst = (contentRating ?? string.Empty).Split('/').HeadOrNone();
return maybeFirst.Map<Option<ContentRating>>(
first =>
{

3
ErsatzTV.Core/Iptv/ChannelPlaylist.cs

@ -42,7 +42,8 @@ namespace ErsatzTV.Core.Iptv @@ -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"
};

8
ErsatzTV/Controllers/IptvController.cs

@ -55,12 +55,16 @@ namespace ErsatzTV.Controllers @@ -55,12 +55,16 @@ namespace ErsatzTV.Controllers
error => BadRequest(error.Value)));
[HttpGet("iptv/channel/{channelNumber}.m3u8")]
public Task<IActionResult> GetHttpLiveStreamingVideo(string channelNumber) =>
public Task<IActionResult> GetHttpLiveStreamingVideo(
string channelNumber,
[FromQuery]
string mode = "mixed") =>
_mediator.Send(
new GetHlsPlaylistByChannelNumber(
Request.Scheme,
Request.Host.ToString(),
channelNumber))
channelNumber,
mode))
.Map(
result => result.Match<IActionResult>(
playlist => Content(playlist, "application/x-mpegurl"),

3
ErsatzTV/Pages/ChannelEditor.razor

@ -26,9 +26,10 @@ @@ -26,9 +26,10 @@
<MudSelect Class="mt-3" Label="Streaming Mode" @bind-Value="_model.StreamingMode" For="@(() => _model.StreamingMode)">
<MudSelectItem Value="@(StreamingMode.TransportStream)">MPEG-TS</MudSelectItem>
<MudSelectItem Value="@(StreamingMode.HttpLiveStreamingDirect)">HLS Direct</MudSelectItem>
<MudSelectItem Value="@(StreamingMode.HttpLiveStreamingHybrid)">HLS Hybrid</MudSelectItem>
</MudSelect>
<MudSelect Class="mt-3" Label="FFmpeg Profile" @bind-Value="_model.FFmpegProfileId" For="@(() => _model.FFmpegProfileId)"
Disabled="@(_model.StreamingMode != StreamingMode.TransportStream)">
Disabled="@(_model.StreamingMode == StreamingMode.HttpLiveStreamingDirect)">
@foreach (FFmpegProfileViewModel profile in _ffmpegProfiles)
{
<MudSelectItem Value="@profile.Id">@profile.Name</MudSelectItem>

10
ErsatzTV/Pages/Channels.razor

@ -50,9 +50,9 @@ @@ -50,9 +50,9 @@
</MudTd>
<MudTd DataLabel="Name">@context.Name</MudTd>
<MudTd DataLabel="Language">@context.PreferredLanguageCode</MudTd>
<MudTd DataLabel="Mode">@(context.StreamingMode == StreamingMode.TransportStream ? "MPEG-TS" : "HLS Direct")</MudTd>
<MudTd DataLabel="Mode">@GetStreamingMode(context.StreamingMode)</MudTd>
<MudTd DataLabel="FFmpeg Profile">
@if (context.StreamingMode == StreamingMode.TransportStream)
@if (context.StreamingMode != StreamingMode.HttpLiveStreamingDirect)
{
@_ffmpegProfiles.Find(p => p.Id == context.FFmpegProfileId)?.Name
}
@ -138,4 +138,10 @@ @@ -138,4 +138,10 @@
};
}
private static string GetStreamingMode(StreamingMode streamingMode) => streamingMode switch {
StreamingMode.HttpLiveStreamingDirect => "HLS Direct",
StreamingMode.HttpLiveStreamingHybrid => "HLS Hybrid",
_ => "MPEG-TS"
};
}
Loading…
Cancel
Save