Browse Source

add remote stream is_live property (#2181)

pull/2182/head
Jason Dove 10 months ago committed by GitHub
parent
commit
2a05cc6e32
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 4
      CHANGELOG.md
  2. 11
      ErsatzTV.Application/Streaming/Queries/GetPlayoutItemProcessByChannelNumberHandler.cs
  3. 11
      ErsatzTV.Application/Troubleshooting/Commands/PrepareTroubleshootingPlaybackHandler.cs
  4. 1
      ErsatzTV.Core/Domain/MediaItem/RemoteStream.cs
  5. 4
      ErsatzTV.Core/Streaming/YamlRemoteStreamDefinition.cs
  6. 6141
      ErsatzTV.Infrastructure.MySql/Migrations/20250721130355_Add_RemoteStreamIsLive.Designer.cs
  7. 29
      ErsatzTV.Infrastructure.MySql/Migrations/20250721130355_Add_RemoteStreamIsLive.cs
  8. 3
      ErsatzTV.Infrastructure.MySql/Migrations/TvContextModelSnapshot.cs
  9. 5978
      ErsatzTV.Infrastructure.Sqlite/Migrations/20250721130322_Add_RemoteStreamIsLive.Designer.cs
  10. 29
      ErsatzTV.Infrastructure.Sqlite/Migrations/20250721130322_Add_RemoteStreamIsLive.cs
  11. 3
      ErsatzTV.Infrastructure.Sqlite/Migrations/TvContextModelSnapshot.cs
  12. 3
      ErsatzTV.Infrastructure/Data/Repositories/RemoteStreamRepository.cs
  13. 10
      ErsatzTV.Scanner/Core/Metadata/RemoteStreamFolderScanner.cs

4
CHANGELOG.md

@ -108,6 +108,10 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). @@ -108,6 +108,10 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- `url`: the URL of the content that can be played directly by ffmpeg
- `script`: the process name and arguments for a command that will output content to stdout
- `duration`: when the content is "live" and does not have duration metadata, this must be provided to allow scheduling
- `is_live`: *required* property that indicates whether the remote stream contains live content
- When this is set to `true`, ETV cannot work ahead on transcoding this item, which is a necessary tradeoff for supporting live content
- When this is set to `false`, ETV will treat the stream as VOD and attempt to work ahead on transcoding like any other local item
- This *will* cause errors when the content is actually live, so it's important to configure this correctly
- The remote stream definition (YAML file) may provide either a `url` or a `script`
- If both are provided, `url` will be used

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

@ -295,15 +295,8 @@ public class GetPlayoutItemProcessByChannelNumberHandler : FFmpegProcessHandler< @@ -295,15 +295,8 @@ public class GetPlayoutItemProcessByChannelNumberHandler : FFmpegProcessHandler<
audioPath = string.Empty;
}
bool hlsRealtime = request.HlsRealtime;
if (playoutItemWithPath.PlayoutItem.MediaItem is RemoteStream remoteStream)
{
// duration implies live input which we cannot burst
if (remoteStream.Duration.HasValue)
{
hlsRealtime = true;
}
}
// we cannot burst live input
bool hlsRealtime = request.HlsRealtime || playoutItemWithPath.PlayoutItem.MediaItem is RemoteStream { IsLive: true };
bool saveReports = await dbContext.ConfigElements
.GetValue<bool>(ConfigElementKey.FFmpegSaveReports)

11
ErsatzTV.Application/Troubleshooting/Commands/PrepareTroubleshootingPlaybackHandler.cs

@ -93,15 +93,8 @@ public class PrepareTroubleshootingPlaybackHandler( @@ -93,15 +93,8 @@ public class PrepareTroubleshootingPlaybackHandler(
duration = TimeSpan.FromSeconds(30);
}
var hlsRealtime = false;
if (mediaItem is RemoteStream remoteStream)
{
// duration implies live input which we cannot burst
if (remoteStream.Duration.HasValue)
{
hlsRealtime = true;
}
}
// we cannot burst live input
bool hlsRealtime = mediaItem is RemoteStream { IsLive: true };
Command process = await ffmpegProcessService.ForPlayoutItem(
ffmpegPath,

1
ErsatzTV.Core/Domain/MediaItem/RemoteStream.cs

@ -9,6 +9,7 @@ public class RemoteStream : MediaItem @@ -9,6 +9,7 @@ public class RemoteStream : MediaItem
public string Script { get; set; }
public TimeSpan? Duration { get; set; }
public string FallbackQuery { get; set; }
public bool IsLive { get; set; }
public List<RemoteStreamMetadata> RemoteStreamMetadata { get; set; }
public List<MediaVersion> MediaVersions { get; set; }
}

4
ErsatzTV.Core/Streaming/YamlRemoteStreamDefinition.cs

@ -7,6 +7,10 @@ public class YamlRemoteStreamDefinition @@ -7,6 +7,10 @@ public class YamlRemoteStreamDefinition
public string Url { get; set; }
public string Script { get; set; }
public string Duration { get; set; }
[YamlMember(Alias = "fallback_query", ApplyNamingConventions = false)]
public string FallbackQuery { get; set; }
[YamlMember(Alias = "is_live", ApplyNamingConventions = false)]
public bool? IsLive { get; set; }
}

6141
ErsatzTV.Infrastructure.MySql/Migrations/20250721130355_Add_RemoteStreamIsLive.Designer.cs generated

File diff suppressed because it is too large Load Diff

29
ErsatzTV.Infrastructure.MySql/Migrations/20250721130355_Add_RemoteStreamIsLive.cs

@ -0,0 +1,29 @@ @@ -0,0 +1,29 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace ErsatzTV.Infrastructure.MySql.Migrations
{
/// <inheritdoc />
public partial class Add_RemoteStreamIsLive : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<bool>(
name: "IsLive",
table: "RemoteStream",
type: "tinyint(1)",
nullable: false,
defaultValue: false);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "IsLive",
table: "RemoteStream");
}
}
}

3
ErsatzTV.Infrastructure.MySql/Migrations/TvContextModelSnapshot.cs

@ -3480,6 +3480,9 @@ namespace ErsatzTV.Infrastructure.MySql.Migrations @@ -3480,6 +3480,9 @@ namespace ErsatzTV.Infrastructure.MySql.Migrations
b.Property<string>("FallbackQuery")
.HasColumnType("longtext");
b.Property<bool>("IsLive")
.HasColumnType("tinyint(1)");
b.Property<string>("Script")
.HasColumnType("longtext");

5978
ErsatzTV.Infrastructure.Sqlite/Migrations/20250721130322_Add_RemoteStreamIsLive.Designer.cs generated

File diff suppressed because it is too large Load Diff

29
ErsatzTV.Infrastructure.Sqlite/Migrations/20250721130322_Add_RemoteStreamIsLive.cs

@ -0,0 +1,29 @@ @@ -0,0 +1,29 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace ErsatzTV.Infrastructure.Sqlite.Migrations
{
/// <inheritdoc />
public partial class Add_RemoteStreamIsLive : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<bool>(
name: "IsLive",
table: "RemoteStream",
type: "INTEGER",
nullable: false,
defaultValue: false);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "IsLive",
table: "RemoteStream");
}
}
}

3
ErsatzTV.Infrastructure.Sqlite/Migrations/TvContextModelSnapshot.cs

@ -3317,6 +3317,9 @@ namespace ErsatzTV.Infrastructure.Sqlite.Migrations @@ -3317,6 +3317,9 @@ namespace ErsatzTV.Infrastructure.Sqlite.Migrations
b.Property<string>("FallbackQuery")
.HasColumnType("TEXT");
b.Property<bool>("IsLive")
.HasColumnType("INTEGER");
b.Property<string>("Script")
.HasColumnType("TEXT");

3
ErsatzTV.Infrastructure/Data/Repositories/RemoteStreamRepository.cs

@ -125,7 +125,8 @@ public class RemoteStreamRepository( @@ -125,7 +125,8 @@ public class RemoteStreamRepository(
.SetProperty(rs => rs.Url, remoteStream.Url)
.SetProperty(rs => rs.Script, remoteStream.Script)
.SetProperty(rs => rs.Duration, remoteStream.Duration)
.SetProperty(rs => rs.FallbackQuery, remoteStream.FallbackQuery));
.SetProperty(rs => rs.FallbackQuery, remoteStream.FallbackQuery)
.SetProperty(rs => rs.IsLive, remoteStream.IsLive));
}
private async Task<Either<BaseError, MediaItemScanResult<RemoteStream>>> AddRemoteStream(

10
ErsatzTV.Scanner/Core/Metadata/RemoteStreamFolderScanner.cs

@ -279,8 +279,18 @@ public class RemoteStreamFolderScanner : LocalFolderScanner, IRemoteStreamFolder @@ -279,8 +279,18 @@ public class RemoteStreamFolderScanner : LocalFolderScanner, IRemoteStreamFolder
string path = remoteStream.GetHeadVersion().MediaFiles.Head().Path;
string yaml = await File.ReadAllTextAsync(path, cancellationToken);
YamlRemoteStreamDefinition definition = deserializer.Deserialize<YamlRemoteStreamDefinition>(yaml);
if (!definition.IsLive.HasValue)
{
return BaseError.New($"Remote stream definition is missing required `is_live` property");
}
bool updated = false;
if (remoteStream.IsLive != definition.IsLive.Value)
{
remoteStream.IsLive = definition.IsLive.Value;
updated = true;
}
if (remoteStream.Url != definition.Url)
{
remoteStream.Url = definition.Url;

Loading…
Cancel
Save