Browse Source

allow ui and streaming to run on different ports (#1992)

* allow ui and streaming to run on different ports

* revert global.json change
pull/1993/head
Jason Dove 3 weeks ago committed by GitHub
parent
commit
b30b458574
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 3
      CHANGELOG.md
  2. 6
      ErsatzTV.Application/Streaming/Queries/GetPlayoutItemProcessByChannelNumberHandler.cs
  3. 10
      ErsatzTV.Application/Subtitles/Commands/ExtractEmbeddedSubtitlesHandler.cs
  4. 6
      ErsatzTV.Application/Subtitles/Queries/GetSubtitlePathByIdHandler.cs
  5. 4
      ErsatzTV.Core/FFmpeg/ConcatPlaylist.cs
  6. 8
      ErsatzTV.Core/FFmpeg/FFmpegLibraryProcessService.cs
  7. 2
      ErsatzTV.Core/Images/ChannelLogoGenerator.cs
  8. 3
      ErsatzTV.Core/Settings.cs
  9. 2
      ErsatzTV.Infrastructure/Streaming/ExternalJsonPlayoutItemProvider.cs
  10. 32
      ErsatzTV/Program.cs
  11. 4
      ErsatzTV/Properties/launchSettings.json
  12. 39
      ErsatzTV/Services/RunOnce/EndpointValidatorService.cs
  13. 9
      ErsatzTV/Shared/MainLayout.razor
  14. 110
      ErsatzTV/Startup.cs
  15. 7
      ErsatzTV/appsettings.json
  16. 1
      docker/Dockerfile
  17. 1
      docker/arm32v7/Dockerfile
  18. 1
      docker/arm64/Dockerfile
  19. 1
      docker/nvidia/Dockerfile
  20. 1
      docker/vaapi/Dockerfile

3
CHANGELOG.md

@ -1855,6 +1855,9 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). @@ -1855,6 +1855,9 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- Use "single-file" deployments for releases
- Non-docker releases will have significantly fewer files
- It is recommended to empty your installation folder before copying in the latest release.
- Add environment variables to allow ETV to run UI and streaming on separate ports
- `ETV_STREAMING_PORT`: port used for streaming requests, defaults to 8409
- `ETV_UI_PORT`: port used for admin UI, defaults to 8409
### Fixed
- Fix some cases where Jellyfin artwork would not display

6
ErsatzTV.Application/Streaming/Queries/GetPlayoutItemProcessByChannelNumberHandler.cs

@ -618,7 +618,7 @@ public class GetPlayoutItemProcessByChannelNumberHandler : FFmpegProcessHandler< @@ -618,7 +618,7 @@ public class GetPlayoutItemProcessByChannelNumberHandler : FFmpegProcessHandler<
return new PlayoutItemWithPath(
playoutItem,
$"http://localhost:{Settings.ListenPort}/media/plex/{plexMediaSourceId}/{pmf.Key}");
$"http://localhost:{Settings.StreamingPort}/media/plex/{plexMediaSourceId}/{pmf.Key}");
}
break;
@ -636,7 +636,7 @@ public class GetPlayoutItemProcessByChannelNumberHandler : FFmpegProcessHandler< @@ -636,7 +636,7 @@ public class GetPlayoutItemProcessByChannelNumberHandler : FFmpegProcessHandler<
{
return new PlayoutItemWithPath(
playoutItem,
$"http://localhost:{Settings.ListenPort}/media/jellyfin/{itemId}");
$"http://localhost:{Settings.StreamingPort}/media/jellyfin/{itemId}");
}
// attempt to remotely stream emby
@ -651,7 +651,7 @@ public class GetPlayoutItemProcessByChannelNumberHandler : FFmpegProcessHandler< @@ -651,7 +651,7 @@ public class GetPlayoutItemProcessByChannelNumberHandler : FFmpegProcessHandler<
{
return new PlayoutItemWithPath(
playoutItem,
$"http://localhost:{Settings.ListenPort}/media/emby/{itemId}");
$"http://localhost:{Settings.StreamingPort}/media/emby/{itemId}");
}
return new PlayoutItemDoesNotExistOnDisk(path);

10
ErsatzTV.Application/Subtitles/Commands/ExtractEmbeddedSubtitlesHandler.cs

@ -505,7 +505,7 @@ public class ExtractEmbeddedSubtitlesHandler : IRequestHandler<ExtractEmbeddedSu @@ -505,7 +505,7 @@ public class ExtractEmbeddedSubtitlesHandler : IRequestHandler<ExtractEmbeddedSu
foreach (int plexMediaSourceId in maybeId)
{
return $"http://localhost:{Settings.ListenPort}/media/plex/{plexMediaSourceId}/{pmf.Key}";
return $"http://localhost:{Settings.StreamingPort}/media/plex/{plexMediaSourceId}/{pmf.Key}";
}
break;
@ -514,11 +514,11 @@ public class ExtractEmbeddedSubtitlesHandler : IRequestHandler<ExtractEmbeddedSu @@ -514,11 +514,11 @@ public class ExtractEmbeddedSubtitlesHandler : IRequestHandler<ExtractEmbeddedSu
return mediaItem switch
{
JellyfinMovie jellyfinMovie =>
$"http://localhost:{Settings.ListenPort}/media/jellyfin/{jellyfinMovie.ItemId}",
$"http://localhost:{Settings.StreamingPort}/media/jellyfin/{jellyfinMovie.ItemId}",
JellyfinEpisode jellyfinEpisode =>
$"http://localhost:{Settings.ListenPort}/media/jellyfin/{jellyfinEpisode.ItemId}",
EmbyMovie embyMovie => $"http://localhost:{Settings.ListenPort}/media/emby/{embyMovie.ItemId}",
EmbyEpisode embyEpisode => $"http://localhost:{Settings.ListenPort}/media/emby/{embyEpisode.ItemId}",
$"http://localhost:{Settings.StreamingPort}/media/jellyfin/{jellyfinEpisode.ItemId}",
EmbyMovie embyMovie => $"http://localhost:{Settings.StreamingPort}/media/emby/{embyMovie.ItemId}",
EmbyEpisode embyEpisode => $"http://localhost:{Settings.StreamingPort}/media/emby/{embyEpisode.ItemId}",
_ => file.Path
};
}

6
ErsatzTV.Application/Subtitles/Queries/GetSubtitlePathByIdHandler.cs

@ -79,7 +79,7 @@ public class GetSubtitlePathByIdHandler : IRequestHandler<GetSubtitlePathById, E @@ -79,7 +79,7 @@ public class GetSubtitlePathByIdHandler : IRequestHandler<GetSubtitlePathById, E
{
foreach (string subtitlePath in maybeSubtitle.Map(s => s.Path))
{
return $"http://localhost:{Settings.ListenPort}/media/plex/{plexMediaSourceId}/{subtitlePath}";
return $"http://localhost:{Settings.StreamingPort}/media/plex/{plexMediaSourceId}/{subtitlePath}";
}
}
@ -120,7 +120,7 @@ public class GetSubtitlePathByIdHandler : IRequestHandler<GetSubtitlePathById, E @@ -120,7 +120,7 @@ public class GetSubtitlePathByIdHandler : IRequestHandler<GetSubtitlePathById, E
string extension = Subtitle.ExtensionForCodec(subtitle.Codec);
var subtitlePath =
$"Videos/{jellyfinItemId}/{jellyfinItemId}/Subtitles/{index}/{index}/Stream.{extension}";
return $"http://localhost:{Settings.ListenPort}/media/jellyfin/{subtitlePath}";
return $"http://localhost:{Settings.StreamingPort}/media/jellyfin/{subtitlePath}";
}
}
@ -160,7 +160,7 @@ public class GetSubtitlePathByIdHandler : IRequestHandler<GetSubtitlePathById, E @@ -160,7 +160,7 @@ public class GetSubtitlePathByIdHandler : IRequestHandler<GetSubtitlePathById, E
string extension = Subtitle.ExtensionForCodec(subtitle.Codec);
var subtitlePath =
$"Videos/{embyItemId}/{subtitle.Path}/Subtitles/{subtitle.StreamIndex}/Stream.{extension}";
return $"http://localhost:{Settings.ListenPort}/media/emby/{subtitlePath}";
return $"http://localhost:{Settings.StreamingPort}/media/emby/{subtitlePath}";
}
}

4
ErsatzTV.Core/FFmpeg/ConcatPlaylist.cs

@ -4,6 +4,6 @@ public record ConcatPlaylist(string Scheme, string Host, string ChannelNumber, s @@ -4,6 +4,6 @@ public record ConcatPlaylist(string Scheme, string Host, string ChannelNumber, s
{
public override string ToString() =>
$@"ffconcat version 1.0
file http://localhost:{Settings.ListenPort}/ffmpeg/stream/{ChannelNumber}?mode={Mode}
file http://localhost:{Settings.ListenPort}/ffmpeg/stream/{ChannelNumber}?mode={Mode}";
file http://localhost:{Settings.StreamingPort}/ffmpeg/stream/{ChannelNumber}?mode={Mode}
file http://localhost:{Settings.StreamingPort}/ffmpeg/stream/{ChannelNumber}?mode={Mode}";
}

8
ErsatzTV.Core/FFmpeg/FFmpegLibraryProcessService.cs

@ -108,7 +108,7 @@ public class FFmpegLibraryProcessService : IFFmpegProcessService @@ -108,7 +108,7 @@ public class FFmpegLibraryProcessService : IFFmpegProcessService
if (subtitle.SubtitleKind == SubtitleKind.Sidecar)
{
// proxy to avoid dealing with escaping
subtitle.Path = $"http://localhost:{Settings.ListenPort}/media/subtitle/{subtitle.Id}";
subtitle.Path = $"http://localhost:{Settings.StreamingPort}/media/subtitle/{subtitle.Id}";
}
}
@ -594,7 +594,7 @@ public class FFmpegLibraryProcessService : IFFmpegProcessService @@ -594,7 +594,7 @@ public class FFmpegLibraryProcessService : IFFmpegProcessService
var resolution = new FrameSize(channel.FFmpegProfile.Resolution.Width, channel.FFmpegProfile.Resolution.Height);
var concatInputFile = new ConcatInputFile(
$"http://localhost:{Settings.ListenPort}/ffmpeg/concat/{channel.Number}?mode=ts-legacy",
$"http://localhost:{Settings.StreamingPort}/ffmpeg/concat/{channel.Number}?mode=ts-legacy",
resolution);
IPipelineBuilder pipelineBuilder = await _pipelineBuilderFactory.GetBuilder(
@ -627,7 +627,7 @@ public class FFmpegLibraryProcessService : IFFmpegProcessService @@ -627,7 +627,7 @@ public class FFmpegLibraryProcessService : IFFmpegProcessService
{
var resolution = new FrameSize(channel.FFmpegProfile.Resolution.Width, channel.FFmpegProfile.Resolution.Height);
var concatInputFile = new ConcatInputFile(
$"http://localhost:{Settings.ListenPort}/ffmpeg/concat/{channel.Number}?mode=segmenter-v2",
$"http://localhost:{Settings.StreamingPort}/ffmpeg/concat/{channel.Number}?mode=segmenter-v2",
resolution);
FFmpegPlaybackSettings playbackSettings = FFmpegPlaybackSettingsCalculator.CalculateConcatSegmenterSettings(
@ -787,7 +787,7 @@ public class FFmpegLibraryProcessService : IFFmpegProcessService @@ -787,7 +787,7 @@ public class FFmpegLibraryProcessService : IFFmpegProcessService
: $"&access_token={accessToken}";
var concatInputFile = new ConcatInputFile(
$"http://localhost:{Settings.ListenPort}/iptv/channel/{channel.Number}.m3u8?mode=segmenter{accessTokenQuery}",
$"http://localhost:{Settings.StreamingPort}/iptv/channel/{channel.Number}.m3u8?mode=segmenter{accessTokenQuery}",
resolution);
IPipelineBuilder pipelineBuilder = await _pipelineBuilderFactory.GetBuilder(

2
ErsatzTV.Core/Images/ChannelLogoGenerator.cs

@ -19,7 +19,7 @@ public class ChannelLogoGenerator : IChannelLogoGenerator @@ -19,7 +19,7 @@ public class ChannelLogoGenerator : IChannelLogoGenerator
}
public static Option<string> GenerateChannelLogoUrl(Channel channel) =>
$"http://localhost:{Settings.ListenPort}{GetRoute}?{GetRouteQueryParamName}={channel.WebEncodedName}";
$"http://localhost:{Settings.StreamingPort}{GetRoute}?{GetRouteQueryParamName}={channel.WebEncodedName}";
public Either<BaseError, byte[]> GenerateChannelLogo(
string text,

3
ErsatzTV.Core/Settings.cs

@ -2,5 +2,6 @@ @@ -2,5 +2,6 @@
public static class Settings
{
public static int ListenPort { get; set; }
public static int UiPort { get; set; }
public static int StreamingPort { get; set; }
}

2
ErsatzTV.Infrastructure/Streaming/ExternalJsonPlayoutItemProvider.cs

@ -218,7 +218,7 @@ public class ExternalJsonPlayoutItemProvider : IExternalJsonPlayoutItemProvider @@ -218,7 +218,7 @@ public class ExternalJsonPlayoutItemProvider : IExternalJsonPlayoutItemProvider
return new PlayoutItemWithPath(
GetPlayoutItem(startTime, mediaItem, program),
$"http://localhost:{Settings.ListenPort}/media/plex/{server.Id}/{program.PlexFile}");
$"http://localhost:{Settings.StreamingPort}/media/plex/{server.Id}/{program.PlexFile}");
}
}
}

32
ErsatzTV/Program.cs

@ -131,13 +131,39 @@ public class Program @@ -131,13 +131,39 @@ public class Program
}
}
private static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
private static IHostBuilder CreateHostBuilder(string[] args)
{
string uiPortVariable = Environment.GetEnvironmentVariable("ETV_UI_PORT");
if (!int.TryParse(uiPortVariable, out int uiPort))
{
uiPort = 8409;
}
Settings.UiPort = uiPort;
string streamingPortVariable = Environment.GetEnvironmentVariable("ETV_STREAMING_PORT");
if (!int.TryParse(streamingPortVariable, out int streamingPort))
{
streamingPort = 8409;
}
Settings.StreamingPort = streamingPort;
return Host.CreateDefaultBuilder(args)
.ConfigureServices(services => services.AddSingleton(LoggingLevelSwitches))
.ConfigureWebHostDefaults(
webBuilder => webBuilder.UseStartup<Startup>()
.UseConfiguration(Configuration)
.UseKestrel(options => options.AddServerHeader = false)
.UseKestrel(options =>
{
options.ListenAnyIP(Settings.UiPort);
if (Settings.StreamingPort != Settings.UiPort)
{
options.ListenAnyIP(Settings.StreamingPort);
}
options.AddServerHeader = false;
})
.UseContentRoot(BasePath))
.UseSerilog();
}
}

4
ErsatzTV/Properties/launchSettings.json

@ -5,7 +5,9 @@ @@ -5,7 +5,9 @@
"launchBrowser": false,
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development",
"DOTNET_ENVIRONMENT": "Development"
"DOTNET_ENVIRONMENT": "Development",
"ETV_STREAMING_PORT": "8409",
"ETV_UI_PORT": "8410"
}
}
}

39
ErsatzTV/Services/RunOnce/EndpointValidatorService.cs

@ -1,7 +1,4 @@ @@ -1,7 +1,4 @@
using System.Globalization;
using System.Net;
using System.Text.RegularExpressions;
using ErsatzTV.Core;
using ErsatzTV.Core;
namespace ErsatzTV.Services.RunOnce;
@ -20,38 +17,12 @@ public class EndpointValidatorService : BackgroundService @@ -20,38 +17,12 @@ public class EndpointValidatorService : BackgroundService
{
await Task.Yield();
string urls = _configuration.GetValue<string>("Kestrel:Endpoints:Http:Url");
if (urls.Split(";").Length > 1)
{
throw new NotSupportedException($"Multiple endpoints are not supported: {urls}");
}
const string PATTERN = @"http:\/\/(.*):(\d+)";
Match match = Regex.Match(urls, PATTERN);
if (match.Success)
{
string hostname = match.Groups[1].Value;
Settings.ListenPort = int.Parse(match.Groups[2].Value, CultureInfo.InvariantCulture);
// IP address must be 0.0.0.0 or 127.0.0.1
if (IPAddress.TryParse(hostname, out IPAddress address))
{
if (!address.Equals(IPAddress.Parse("0.0.0.0")) && !IPAddress.IsLoopback(address))
{
throw new NotSupportedException($"Endpoint MUST include loopback: {urls}");
}
}
}
else
{
throw new NotSupportedException($"Invalid endpoint format: {urls}");
}
string baseUrl = Environment.GetEnvironmentVariable("ETV_BASE_URL");
_logger.LogInformation(
"Server will listen on port {Port} - try UI at {UI}",
Settings.ListenPort,
$"http://localhost:{Settings.ListenPort}{baseUrl}");
"Server will listen on streaming port {StreamingPort}, UI port {UiPort} - try UI at {UI}",
Settings.StreamingPort,
Settings.UiPort,
$"http://localhost:{Settings.UiPort}{baseUrl}");
}
}

9
ErsatzTV/Shared/MainLayout.razor

@ -73,8 +73,8 @@ @@ -73,8 +73,8 @@
<MudSpacer/>
@if (SystemStartup.IsDatabaseReady && SystemStartup.IsSearchIndexReady)
{
<MudLink Color="Color.Info" Href="iptv/channels.m3u" Target="_blank" Underline="Underline.None">M3U</MudLink>
<MudLink Color="Color.Info" Href="iptv/xmltv.xml" Target="_blank" Class="mx-4" Underline="Underline.None">XMLTV</MudLink>
<MudLink Color="Color.Info" Href="@IptvUrl("channels.m3u")" Target="_blank" Underline="Underline.None">M3U</MudLink>
<MudLink Color="Color.Info" Href="@IptvUrl("xmltv.xml")" Target="_blank" Class="mx-4" Underline="Underline.None">XMLTV</MudLink>
}
@* <MudLink Color="Color.Info" Href="/swagger" Target="_blank" Class="mr-4" Underline="Underline.None">API</MudLink> *@
<MudTooltip Text="Documentation">
@ -285,4 +285,9 @@ @@ -285,4 +285,9 @@
_ => null
};
private string IptvUrl(string path)
{
var uri = new Uri(NavigationManager.Uri);
return $"{uri.Scheme}://{uri.Host}:{Settings.StreamingPort}/iptv/{path}";
}
}

110
ErsatzTV/Startup.cs

@ -556,36 +556,98 @@ public class Startup @@ -556,36 +556,98 @@ public class Startup
app.UseResponseCompression();
app.MapWhen(
ctx => !ctx.Request.Path.StartsWithSegments("/iptv"),
blazor =>
{
blazor.UseRouting();
if (OidcHelper.IsEnabled)
if (Settings.StreamingPort == Settings.UiPort)
{
app.MapWhen(
ctx => !ctx.Request.Path.StartsWithSegments("/iptv"),
blazor =>
{
blazor.UseAuthentication();
blazor.UseRouting();
if (OidcHelper.IsEnabled)
{
blazor.UseAuthentication();
#pragma warning disable ASP0001
blazor.UseAuthorization();
blazor.UseAuthorization();
#pragma warning restore ASP0001
}
}
blazor.UseEndpoints(
endpoints =>
{
endpoints.MapControllers();
endpoints.MapBlazorHub();
endpoints.MapFallbackToPage("/_Host");
});
});
app.MapWhen(
ctx => ctx.Request.Path.StartsWithSegments("/iptv"),
iptv =>
{
iptv.UseRouting();
iptv.UseEndpoints(endpoints => endpoints.MapControllers());
});
}
else
{
app.MapWhen(
ctx => ctx.Request.Host.Port == Settings.UiPort,
uiApp =>
{
uiApp.UseRouting();
blazor.UseEndpoints(
endpoints =>
uiApp.UseWhen(
c => c.Request.Path.StartsWithSegments("/iptv"), // && !IPAddress.IsLoopback(c.Connection.RemoteIpAddress ?? IPAddress.None),
a =>
{
a.Run(
c =>
{
c.Response.StatusCode = 404;
return Task.CompletedTask;
});
});
if (OidcHelper.IsEnabled)
{
endpoints.MapControllers();
endpoints.MapBlazorHub();
endpoints.MapFallbackToPage("/_Host");
});
});
uiApp.UseAuthentication();
#pragma warning disable ASP0001
uiApp.UseAuthorization();
#pragma warning restore ASP0001
}
app.MapWhen(
ctx => ctx.Request.Path.StartsWithSegments("/iptv"),
iptv =>
{
iptv.UseRouting();
iptv.UseEndpoints(endpoints => endpoints.MapControllers());
});
uiApp.UseEndpoints(
endpoints =>
{
endpoints.MapControllers();
endpoints.MapBlazorHub();
endpoints.MapFallbackToPage("/_Host");
});
});
app.MapWhen(
ctx => ctx.Request.Host.Port == Settings.StreamingPort,
streamingApp =>
{
streamingApp.UseRouting();
streamingApp.UseWhen(
c => !c.Request.Path.StartsWithSegments("/iptv"),
a =>
{
a.Run(
c =>
{
c.Response.StatusCode = 404;
return Task.CompletedTask;
});
});
streamingApp.UseEndpoints(endpoints => endpoints.MapControllers());
});
}
}
private static void CustomServices(IServiceCollection services)

7
ErsatzTV/appsettings.json

@ -16,13 +16,6 @@ @@ -16,13 +16,6 @@
]
},
"AllowedHosts": "*",
"Kestrel": {
"EndPoints": {
"Http": {
"Url": "http://+:8409"
}
}
},
"Trakt": {
"ClientId": "5a4e78338b7f177cf980d6a8881dd5a52dee680746864e9e442b42d5f4d4ac82"
},

1
docker/Dockerfile

@ -42,7 +42,6 @@ FROM runtime-base @@ -42,7 +42,6 @@ FROM runtime-base
ENV FONTCONFIG_PATH=/etc/fonts
RUN fc-cache update
WORKDIR /app
EXPOSE 8409
COPY --from=build /app ./
ENV ETV_CONFIG_FOLDER=/config
ENV ETV_TRANSCODE_FOLDER=/transcode

1
docker/arm32v7/Dockerfile

@ -42,7 +42,6 @@ FROM runtime-base @@ -42,7 +42,6 @@ FROM runtime-base
ENV FONTCONFIG_PATH=/etc/fonts
RUN fc-cache update
WORKDIR /app
EXPOSE 8409
COPY --from=build /app ./
ENV ETV_CONFIG_FOLDER=/config
ENV ETV_TRANSCODE_FOLDER=/transcode

1
docker/arm64/Dockerfile

@ -42,7 +42,6 @@ FROM runtime-base @@ -42,7 +42,6 @@ FROM runtime-base
ENV FONTCONFIG_PATH=/etc/fonts
RUN fc-cache update
WORKDIR /app
EXPOSE 8409
COPY --from=build /app ./
ENV ETV_CONFIG_FOLDER=/config
ENV ETV_TRANSCODE_FOLDER=/transcode

1
docker/nvidia/Dockerfile

@ -48,7 +48,6 @@ FROM runtime-base @@ -48,7 +48,6 @@ FROM runtime-base
ENV FONTCONFIG_PATH=/etc/fonts
RUN fc-cache update
WORKDIR /app
EXPOSE 8409
COPY --from=build /app ./
ENV ETV_CONFIG_FOLDER=/config
ENV ETV_TRANSCODE_FOLDER=/transcode

1
docker/vaapi/Dockerfile

@ -42,7 +42,6 @@ FROM runtime-base @@ -42,7 +42,6 @@ FROM runtime-base
ENV FONTCONFIG_PATH=/etc/fonts
RUN fc-cache update
WORKDIR /app
EXPOSE 8409
COPY --from=build /app ./
ENV ETV_CONFIG_FOLDER=/config
ENV ETV_TRANSCODE_FOLDER=/transcode

Loading…
Cancel
Save