Browse Source

add next playout models

pull/2854/head
Jason Dove 3 months ago
parent
commit
aac6231b67
No known key found for this signature in database
  1. 4
      ErsatzTV.Application/Streaming/Commands/StartFFmpegNextSessionHandler.cs
  2. 3
      ErsatzTV.Application/Streaming/NextSessionWorker.cs
  3. 2
      ErsatzTV.Core/ErsatzTV.Core.csproj
  4. 8
      ErsatzTV.Core/Next/Config/ChannelConfig.cs
  5. 259
      ErsatzTV.Core/Next/Playout.cs

4
ErsatzTV.Application/Streaming/Commands/StartFFmpegNextSessionHandler.cs

@ -14,7 +14,7 @@ using ErsatzTV.Core.FFmpeg; @@ -14,7 +14,7 @@ using ErsatzTV.Core.FFmpeg;
using ErsatzTV.Core.Interfaces.FFmpeg;
using ErsatzTV.Core.Interfaces.Metadata;
using ErsatzTV.Core.Interfaces.Repositories;
using ErsatzTV.Core.Next;
using ErsatzTV.Core.Next.Config;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
@ -351,7 +351,7 @@ public class StartFFmpegNextSessionHandler( @@ -351,7 +351,7 @@ public class StartFFmpegNextSessionHandler(
return new ChannelConfig
{
Playout = new Core.Next.Playout
Playout = new Core.Next.Config.Playout
{
Folder = playoutFolder
},

3
ErsatzTV.Application/Streaming/NextSessionWorker.cs

@ -1,11 +1,10 @@ @@ -1,11 +1,10 @@
using System.IO.Abstractions;
using System.Text;
using CliWrap;
using ErsatzTV.Core;
using ErsatzTV.Core.FFmpeg;
using ErsatzTV.Core.Interfaces.FFmpeg;
using ErsatzTV.Core.Interfaces.Metadata;
using ErsatzTV.Core.Next;
using ErsatzTV.Core.Next.Config;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;

2
ErsatzTV.Core/ErsatzTV.Core.csproj

@ -45,7 +45,7 @@ @@ -45,7 +45,7 @@
<ItemGroup>
<Folder Include="Api\" />
<Folder Include="Next\" />
<Folder Include="Next\Config\" />
</ItemGroup>
</Project>

8
ErsatzTV.Core/Next/ChannelConfig.cs → ErsatzTV.Core/Next/Config/ChannelConfig.cs

@ -2,11 +2,11 @@ @@ -2,11 +2,11 @@
//
// To parse this JSON data, add NuGet 'Newtonsoft.Json' then do:
//
// using ErsatzTV.Core.Next;
// using ErsatzTV.Core.Next.Config;
//
// var channelConfig = ChannelConfig.FromJson(jsonString);
namespace ErsatzTV.Core.Next
namespace ErsatzTV.Core.Next.Config
{
using System;
using System.Collections.Generic;
@ -139,12 +139,12 @@ namespace ErsatzTV.Core.Next @@ -139,12 +139,12 @@ namespace ErsatzTV.Core.Next
public partial class ChannelConfig
{
public static ChannelConfig FromJson(string json) => JsonConvert.DeserializeObject<ChannelConfig>(json, ErsatzTV.Core.Next.Converter.Settings);
public static ChannelConfig FromJson(string json) => JsonConvert.DeserializeObject<ChannelConfig>(json, ErsatzTV.Core.Next.Config.Converter.Settings);
}
public static class Serialize
{
public static string ToJson(this ChannelConfig self) => JsonConvert.SerializeObject(self, ErsatzTV.Core.Next.Converter.Settings);
public static string ToJson(this ChannelConfig self) => JsonConvert.SerializeObject(self, ErsatzTV.Core.Next.Config.Converter.Settings);
}
internal static class Converter

259
ErsatzTV.Core/Next/Playout.cs

@ -0,0 +1,259 @@ @@ -0,0 +1,259 @@
// <auto-generated />
//
// To parse this JSON data, add NuGet 'Newtonsoft.Json' then do:
//
// using ErsatzTV.Core.Next;
//
// var playout = Playout.FromJson(jsonString);
namespace ErsatzTV.Core.Next
{
using System;
using System.Collections.Generic;
using System.Globalization;
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;
/// <summary>
/// A playout schedule for a single time window.
///
/// Files should be named `{start}_{finish}.json` using compact ISO 8601
/// (no separators), e.g.
/// `20260413T000000.000000000-0500_20260414T002131.620000000-0500.json`,
/// so that the channel can locate the correct file for the current time.
/// </summary>
public partial class Playout
{
[JsonProperty("items")]
public List<ItemElement> Items { get; set; }
/// <summary>
/// URI identifying the schema version, e.g. "https://ersatztv.org/playout/version/0.0.1"
/// </summary>
[JsonProperty("version")]
public string Version { get; set; }
}
public partial class ItemElement
{
/// <summary>
/// RFC3339 formatted date/time, e.g. 2026-04-13T00:24:21.527-05:00
/// </summary>
[JsonProperty("finish")]
public string Finish { get; set; }
[JsonProperty("id")]
public string Id { get; set; }
[JsonProperty("source")]
public ItemSource Source { get; set; }
/// <summary>
/// RFC3339 formatted date/time, e.g. 2026-04-13T00:24:21.527-05:00
/// </summary>
[JsonProperty("start")]
public string Start { get; set; }
[JsonProperty("tracks")]
public TracksClass Tracks { get; set; }
}
public partial class ItemSource
{
[JsonProperty("in_point_ms")]
public long? InPointMs { get; set; }
[JsonProperty("out_point_ms")]
public long? OutPointMs { get; set; }
[JsonProperty("path", NullValueHandling = NullValueHandling.Ignore)]
public string Path { get; set; }
[JsonProperty("source_type")]
public SourceType SourceType { get; set; }
[JsonProperty("params", NullValueHandling = NullValueHandling.Ignore)]
public string Params { get; set; }
/// <summary>
/// Custom HTTP headers, e.g. ["Authorization: Bearer {{TOKEN}}"]
/// </summary>
[JsonProperty("headers")]
public List<string> Headers { get; set; }
/// <summary>
/// Enable reconnect on failure (default: true)
/// </summary>
[JsonProperty("reconnect")]
public bool? Reconnect { get; set; }
/// <summary>
/// Max reconnect delay in seconds
/// </summary>
[JsonProperty("reconnect_delay_max")]
public long? ReconnectDelayMax { get; set; }
/// <summary>
/// Socket timeout in microseconds
/// </summary>
[JsonProperty("timeout_us")]
public long? TimeoutUs { get; set; }
/// <summary>
/// URI template, e.g. "https://example.com/file.mkv?token={{MY_SECRET}}"
/// </summary>
[JsonProperty("uri", NullValueHandling = NullValueHandling.Ignore)]
public string Uri { get; set; }
/// <summary>
/// Custom user-agent string
/// </summary>
[JsonProperty("user_agent")]
public string UserAgent { get; set; }
}
public partial class TracksClass
{
[JsonProperty("audio")]
public AudioClass Audio { get; set; }
[JsonProperty("video")]
public AudioClass Video { get; set; }
}
public partial class AudioClass
{
[JsonProperty("source", NullValueHandling = NullValueHandling.Ignore)]
public AudioSource Source { get; set; }
[JsonProperty("stream_index", NullValueHandling = NullValueHandling.Ignore)]
public long? StreamIndex { get; set; }
}
public partial class AudioSource
{
[JsonProperty("in_point_ms")]
public long? InPointMs { get; set; }
[JsonProperty("out_point_ms")]
public long? OutPointMs { get; set; }
[JsonProperty("path", NullValueHandling = NullValueHandling.Ignore)]
public string Path { get; set; }
[JsonProperty("source_type")]
public SourceType SourceType { get; set; }
[JsonProperty("params", NullValueHandling = NullValueHandling.Ignore)]
public string Params { get; set; }
/// <summary>
/// Custom HTTP headers, e.g. ["Authorization: Bearer {{TOKEN}}"]
/// </summary>
[JsonProperty("headers")]
public List<string> Headers { get; set; }
/// <summary>
/// Enable reconnect on failure (default: true)
/// </summary>
[JsonProperty("reconnect")]
public bool? Reconnect { get; set; }
/// <summary>
/// Max reconnect delay in seconds
/// </summary>
[JsonProperty("reconnect_delay_max")]
public long? ReconnectDelayMax { get; set; }
/// <summary>
/// Socket timeout in microseconds
/// </summary>
[JsonProperty("timeout_us")]
public long? TimeoutUs { get; set; }
/// <summary>
/// URI template, e.g. "https://example.com/file.mkv?token={{MY_SECRET}}"
/// </summary>
[JsonProperty("uri", NullValueHandling = NullValueHandling.Ignore)]
public string Uri { get; set; }
/// <summary>
/// Custom user-agent string
/// </summary>
[JsonProperty("user_agent")]
public string UserAgent { get; set; }
}
public enum SourceType { Http, Lavfi, Local };
public partial class Playout
{
public static Playout FromJson(string json) => JsonConvert.DeserializeObject<Playout>(json, ErsatzTV.Core.Next.Converter.Settings);
}
public static class Serialize
{
public static string ToJson(this Playout self) => JsonConvert.SerializeObject(self, ErsatzTV.Core.Next.Converter.Settings);
}
internal static class Converter
{
public static readonly JsonSerializerSettings Settings = new JsonSerializerSettings
{
MetadataPropertyHandling = MetadataPropertyHandling.Ignore,
DateParseHandling = DateParseHandling.None,
Converters =
{
SourceTypeConverter.Singleton,
new IsoDateTimeConverter { DateTimeStyles = DateTimeStyles.AssumeUniversal }
},
};
}
internal class SourceTypeConverter : JsonConverter
{
public override bool CanConvert(Type t) => t == typeof(SourceType) || t == typeof(SourceType?);
public override object ReadJson(JsonReader reader, Type t, object existingValue, JsonSerializer serializer)
{
if (reader.TokenType == JsonToken.Null) return null;
var value = serializer.Deserialize<string>(reader);
switch (value)
{
case "http":
return SourceType.Http;
case "lavfi":
return SourceType.Lavfi;
case "local":
return SourceType.Local;
}
throw new Exception("Cannot unmarshal type SourceType");
}
public override void WriteJson(JsonWriter writer, object untypedValue, JsonSerializer serializer)
{
if (untypedValue == null)
{
serializer.Serialize(writer, null);
return;
}
var value = (SourceType)untypedValue;
switch (value)
{
case SourceType.Http:
serializer.Serialize(writer, "http");
return;
case SourceType.Lavfi:
serializer.Serialize(writer, "lavfi");
return;
case SourceType.Local:
serializer.Serialize(writer, "local");
return;
}
throw new Exception("Cannot marshal type SourceType");
}
public static readonly SourceTypeConverter Singleton = new SourceTypeConverter();
}
}
Loading…
Cancel
Save