use base path in channel playlist and channel guide (#1028)
This commit is contained in:
@@ -7,6 +7,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
|||||||
### Fixed
|
### Fixed
|
||||||
- Fix removing Jellyfin and Emby libraries that have been deleted from the source media server
|
- Fix removing Jellyfin and Emby libraries that have been deleted from the source media server
|
||||||
- Fix `Work-Ahead HLS Segmenter Limit` setting to properly limit number of channels that can work-ahead at once
|
- Fix `Work-Ahead HLS Segmenter Limit` setting to properly limit number of channels that can work-ahead at once
|
||||||
|
- Include base path value in generated channel playlist (M3U) and channel guide (XMLTV) links
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
- Add audio stream selector scripts for episodes and movies
|
- Add audio stream selector scripts for episodes and movies
|
||||||
|
|||||||
@@ -2,4 +2,4 @@
|
|||||||
|
|
||||||
namespace ErsatzTV.Application.Channels;
|
namespace ErsatzTV.Application.Channels;
|
||||||
|
|
||||||
public record GetChannelGuide(string Scheme, string Host) : IRequest<ChannelGuide>;
|
public record GetChannelGuide(string Scheme, string Host, string BaseUrl) : IRequest<ChannelGuide>;
|
||||||
|
|||||||
@@ -19,5 +19,11 @@ public class GetChannelGuideHandler : IRequestHandler<GetChannelGuide, ChannelGu
|
|||||||
|
|
||||||
public Task<ChannelGuide> Handle(GetChannelGuide request, CancellationToken cancellationToken) =>
|
public Task<ChannelGuide> Handle(GetChannelGuide request, CancellationToken cancellationToken) =>
|
||||||
_channelRepository.GetAllForGuide()
|
_channelRepository.GetAllForGuide()
|
||||||
.Map(channels => new ChannelGuide(_recyclableMemoryStreamManager, request.Scheme, request.Host, channels));
|
.Map(
|
||||||
|
channels => new ChannelGuide(
|
||||||
|
_recyclableMemoryStreamManager,
|
||||||
|
request.Scheme,
|
||||||
|
request.Host,
|
||||||
|
request.BaseUrl,
|
||||||
|
channels));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,4 +2,4 @@
|
|||||||
|
|
||||||
namespace ErsatzTV.Application.Channels;
|
namespace ErsatzTV.Application.Channels;
|
||||||
|
|
||||||
public record GetChannelPlaylist(string Scheme, string Host, string Mode) : IRequest<ChannelPlaylist>;
|
public record GetChannelPlaylist(string Scheme, string Host, string BaseUrl, string Mode) : IRequest<ChannelPlaylist>;
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ public class GetChannelPlaylistHandler : IRequestHandler<GetChannelPlaylist, Cha
|
|||||||
public Task<ChannelPlaylist> Handle(GetChannelPlaylist request, CancellationToken cancellationToken) =>
|
public Task<ChannelPlaylist> Handle(GetChannelPlaylist request, CancellationToken cancellationToken) =>
|
||||||
_channelRepository.GetAll()
|
_channelRepository.GetAll()
|
||||||
.Map(channels => EnsureMode(channels, request.Mode))
|
.Map(channels => EnsureMode(channels, request.Mode))
|
||||||
.Map(channels => new ChannelPlaylist(request.Scheme, request.Host, channels));
|
.Map(channels => new ChannelPlaylist(request.Scheme, request.Host, request.BaseUrl, channels));
|
||||||
|
|
||||||
private static List<Channel> EnsureMode(IEnumerable<Channel> channels, string mode)
|
private static List<Channel> EnsureMode(IEnumerable<Channel> channels, string mode)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ public class ChannelGuide
|
|||||||
{
|
{
|
||||||
private readonly List<Channel> _channels;
|
private readonly List<Channel> _channels;
|
||||||
private readonly string _host;
|
private readonly string _host;
|
||||||
|
private readonly string _baseUrl;
|
||||||
private readonly RecyclableMemoryStreamManager _recyclableMemoryStreamManager;
|
private readonly RecyclableMemoryStreamManager _recyclableMemoryStreamManager;
|
||||||
private readonly string _scheme;
|
private readonly string _scheme;
|
||||||
|
|
||||||
@@ -20,11 +21,13 @@ public class ChannelGuide
|
|||||||
RecyclableMemoryStreamManager recyclableMemoryStreamManager,
|
RecyclableMemoryStreamManager recyclableMemoryStreamManager,
|
||||||
string scheme,
|
string scheme,
|
||||||
string host,
|
string host,
|
||||||
|
string baseUrl,
|
||||||
List<Channel> channels)
|
List<Channel> channels)
|
||||||
{
|
{
|
||||||
_recyclableMemoryStreamManager = recyclableMemoryStreamManager;
|
_recyclableMemoryStreamManager = recyclableMemoryStreamManager;
|
||||||
_scheme = scheme;
|
_scheme = scheme;
|
||||||
_host = host;
|
_host = host;
|
||||||
|
_baseUrl = baseUrl;
|
||||||
_channels = channels;
|
_channels = channels;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -67,8 +70,8 @@ public class ChannelGuide
|
|||||||
.Filter(a => a.ArtworkKind == ArtworkKind.Logo)
|
.Filter(a => a.ArtworkKind == ArtworkKind.Logo)
|
||||||
.HeadOrNone()
|
.HeadOrNone()
|
||||||
.Match(
|
.Match(
|
||||||
artwork => $"{_scheme}://{_host}/iptv/logos/{artwork.Path}.jpg",
|
artwork => $"{_scheme}://{_host}{_baseUrl}/iptv/logos/{artwork.Path}.jpg",
|
||||||
() => $"{_scheme}://{_host}/iptv/images/ersatztv-500.png");
|
() => $"{_scheme}://{_host}{_baseUrl}/iptv/images/ersatztv-500.png");
|
||||||
xml.WriteAttributeString("src", logo);
|
xml.WriteAttributeString("src", logo);
|
||||||
xml.WriteEndElement(); // icon
|
xml.WriteEndElement(); // icon
|
||||||
|
|
||||||
@@ -394,7 +397,7 @@ public class ChannelGuide
|
|||||||
_ => "posters"
|
_ => "posters"
|
||||||
};
|
};
|
||||||
|
|
||||||
artworkPath = $"{_scheme}://{_host}/iptv/artwork/{artworkFolder}/{artwork.Path}.jpg";
|
artworkPath = $"{_scheme}://{_host}{_baseUrl}/iptv/artwork/{artworkFolder}/{artwork.Path}.jpg";
|
||||||
}
|
}
|
||||||
|
|
||||||
return artworkPath;
|
return artworkPath;
|
||||||
|
|||||||
@@ -7,12 +7,14 @@ public class ChannelPlaylist
|
|||||||
{
|
{
|
||||||
private readonly List<Channel> _channels;
|
private readonly List<Channel> _channels;
|
||||||
private readonly string _host;
|
private readonly string _host;
|
||||||
|
private readonly string _baseUrl;
|
||||||
private readonly string _scheme;
|
private readonly string _scheme;
|
||||||
|
|
||||||
public ChannelPlaylist(string scheme, string host, List<Channel> channels)
|
public ChannelPlaylist(string scheme, string host, string baseUrl, List<Channel> channels)
|
||||||
{
|
{
|
||||||
_scheme = scheme;
|
_scheme = scheme;
|
||||||
_host = host;
|
_host = host;
|
||||||
|
_baseUrl = baseUrl;
|
||||||
_channels = channels;
|
_channels = channels;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -20,7 +22,7 @@ public class ChannelPlaylist
|
|||||||
{
|
{
|
||||||
var sb = new StringBuilder();
|
var sb = new StringBuilder();
|
||||||
|
|
||||||
var xmltv = $"{_scheme}://{_host}/iptv/xmltv.xml";
|
string xmltv = $"{_scheme}://{_host}{_baseUrl}/iptv/xmltv.xml";
|
||||||
sb.AppendLine($"#EXTM3U url-tvg=\"{xmltv}\" x-tvg-url=\"{xmltv}\"");
|
sb.AppendLine($"#EXTM3U url-tvg=\"{xmltv}\" x-tvg-url=\"{xmltv}\"");
|
||||||
foreach (Channel channel in _channels.OrderBy(c => decimal.Parse(c.Number)))
|
foreach (Channel channel in _channels.OrderBy(c => decimal.Parse(c.Number)))
|
||||||
{
|
{
|
||||||
@@ -28,8 +30,8 @@ public class ChannelPlaylist
|
|||||||
.Filter(a => a.ArtworkKind == ArtworkKind.Logo)
|
.Filter(a => a.ArtworkKind == ArtworkKind.Logo)
|
||||||
.HeadOrNone()
|
.HeadOrNone()
|
||||||
.Match(
|
.Match(
|
||||||
artwork => $"{_scheme}://{_host}/iptv/logos/{artwork.Path}.jpg",
|
artwork => $"{_scheme}://{_host}{_baseUrl}/iptv/logos/{artwork.Path}.jpg",
|
||||||
() => $"{_scheme}://{_host}/iptv/images/ersatztv-500.png");
|
() => $"{_scheme}://{_host}{_baseUrl}/iptv/images/ersatztv-500.png");
|
||||||
|
|
||||||
string shortUniqueId = Convert.ToBase64String(channel.UniqueId.ToByteArray())
|
string shortUniqueId = Convert.ToBase64String(channel.UniqueId.ToByteArray())
|
||||||
.TrimEnd('=')
|
.TrimEnd('=')
|
||||||
@@ -49,7 +51,7 @@ public class ChannelPlaylist
|
|||||||
|
|
||||||
sb.AppendLine(
|
sb.AppendLine(
|
||||||
$"#EXTINF:0 tvg-id=\"{channel.Number}.etv\" channel-id=\"{shortUniqueId}\" channel-number=\"{channel.Number}\" CUID=\"{shortUniqueId}\" tvg-chno=\"{channel.Number}\" tvg-name=\"{channel.Name}\" tvg-logo=\"{logo}\" group-title=\"{channel.Group}\" tvc-stream-vcodec=\"{vcodec}\" tvc-stream-acodec=\"{acodec}\", {channel.Name}");
|
$"#EXTINF:0 tvg-id=\"{channel.Number}.etv\" channel-id=\"{shortUniqueId}\" channel-number=\"{channel.Number}\" CUID=\"{shortUniqueId}\" tvg-chno=\"{channel.Number}\" tvg-name=\"{channel.Name}\" tvg-logo=\"{logo}\" group-title=\"{channel.Group}\" tvc-stream-vcodec=\"{vcodec}\" tvc-stream-acodec=\"{acodec}\", {channel.Name}");
|
||||||
sb.AppendLine($"{_scheme}://{_host}/iptv/channel/{channel.Number}.{format}");
|
sb.AppendLine($"{_scheme}://{_host}{_baseUrl}/iptv/channel/{channel.Number}.{format}");
|
||||||
}
|
}
|
||||||
|
|
||||||
return sb.ToString();
|
return sb.ToString();
|
||||||
|
|||||||
@@ -36,12 +36,12 @@ public class IptvController : ControllerBase
|
|||||||
public Task<IActionResult> GetChannelPlaylist(
|
public Task<IActionResult> GetChannelPlaylist(
|
||||||
[FromQuery]
|
[FromQuery]
|
||||||
string mode = "mixed") =>
|
string mode = "mixed") =>
|
||||||
_mediator.Send(new GetChannelPlaylist(Request.Scheme, Request.Host.ToString(), mode))
|
_mediator.Send(new GetChannelPlaylist(Request.Scheme, Request.Host.ToString(), Request.PathBase, mode))
|
||||||
.Map<ChannelPlaylist, IActionResult>(Ok);
|
.Map<ChannelPlaylist, IActionResult>(Ok);
|
||||||
|
|
||||||
[HttpGet("iptv/xmltv.xml")]
|
[HttpGet("iptv/xmltv.xml")]
|
||||||
public Task<IActionResult> GetGuide() =>
|
public Task<IActionResult> GetGuide() =>
|
||||||
_mediator.Send(new GetChannelGuide(Request.Scheme, Request.Host.ToString()))
|
_mediator.Send(new GetChannelGuide(Request.Scheme, Request.Host.ToString(), Request.PathBase))
|
||||||
.Map<ChannelGuide, IActionResult>(Ok);
|
.Map<ChannelGuide, IActionResult>(Ok);
|
||||||
|
|
||||||
[HttpGet("iptv/hdhr/channel/{channelNumber}.ts")]
|
[HttpGet("iptv/hdhr/channel/{channelNumber}.ts")]
|
||||||
|
|||||||
@@ -43,11 +43,13 @@ public class EndpointValidatorService : IHostedService
|
|||||||
{
|
{
|
||||||
throw new NotSupportedException($"Invalid endpoint format: {urls}");
|
throw new NotSupportedException($"Invalid endpoint format: {urls}");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
string baseUrl = Environment.GetEnvironmentVariable("ETV_BASE_URL");
|
||||||
|
|
||||||
_logger.LogInformation(
|
_logger.LogInformation(
|
||||||
"Server will listen on port {Port} - try UI at {UI}",
|
"Server will listen on port {Port} - try UI at {UI}",
|
||||||
Settings.ListenPort,
|
Settings.ListenPort,
|
||||||
$"http://localhost:{Settings.ListenPort}");
|
$"http://localhost:{Settings.ListenPort}{baseUrl}");
|
||||||
|
|
||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
}
|
}
|
||||||
|
|||||||
+11
-1
@@ -288,7 +288,17 @@ public class Startup
|
|||||||
string baseUrl = Environment.GetEnvironmentVariable("ETV_BASE_URL");
|
string baseUrl = Environment.GetEnvironmentVariable("ETV_BASE_URL");
|
||||||
if (!string.IsNullOrWhiteSpace(baseUrl))
|
if (!string.IsNullOrWhiteSpace(baseUrl))
|
||||||
{
|
{
|
||||||
app.UsePathBase(baseUrl);
|
try
|
||||||
|
{
|
||||||
|
app.UsePathBase(baseUrl);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Log.Error(
|
||||||
|
ex,
|
||||||
|
"Failed to configure ETV_BASE_URL; please check syntax and include leading slash e.g. `/etv`: {BaseUrl}",
|
||||||
|
baseUrl);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
app.UseCors("AllowAll");
|
app.UseCors("AllowAll");
|
||||||
|
|||||||
Reference in New Issue
Block a user