using System; using System.IO; using System.Threading; using System.Threading.Tasks; using Jellyfin.Plugin.SRFPlay.Services.Interfaces; using MediaBrowser.Controller.Library; using MediaBrowser.Model.Dto; using Microsoft.Extensions.Logging; namespace Jellyfin.Plugin.SRFPlay.Providers; /// /// Live stream wrapper for SRF Play streams to handle transcoding sessions. /// internal sealed class SRFLiveStream : ILiveStream { private readonly ILogger _logger; private readonly IStreamProxyService _proxyService; private readonly string _originalItemId; private MediaSourceInfo? _mediaSource; /// /// Initializes a new instance of the class. /// /// The logger. /// The stream proxy service. /// The original item ID. /// The open token. public SRFLiveStream( ILogger logger, IStreamProxyService proxyService, string originalItemId, string openToken) { _logger = logger; _proxyService = proxyService; _originalItemId = originalItemId; OriginalStreamId = openToken; UniqueId = openToken; } /// public int ConsumerCount { get; set; } /// public string OriginalStreamId { get; set; } /// public string UniqueId { get; } /// public string TunerHostId => string.Empty; /// public bool EnableStreamSharing => false; /// public MediaSourceInfo MediaSource { get => _mediaSource ?? throw new InvalidOperationException("MediaSource not set"); set { _mediaSource = value; _logger.LogInformation( "SRFLiveStream MediaSource set - Id: {MediaSourceId}, Path: {Path}, OriginalItemId: {OriginalItemId}", value.Id, value.Path, _originalItemId); // When Jellyfin assigns a live stream ID (for transcoding), register the stream with that ID too if (value.Id != _originalItemId) { _logger.LogInformation( "Transcoding session detected - LiveStream ID {LiveStreamId} differs from original item ID {OriginalItemId}. Registering stream with both IDs.", value.Id, _originalItemId); // Get the authenticated URL and metadata from the original registration var authenticatedUrl = _proxyService.GetAuthenticatedUrl(_originalItemId); var metadata = _proxyService.GetStreamMetadata(_originalItemId); if (authenticatedUrl != null) { // Register the same stream URL with the transcoding session ID, preserving metadata var urn = metadata?.Urn; var isLiveStream = metadata?.IsLiveStream ?? false; _proxyService.RegisterStream(value.Id, authenticatedUrl, urn, isLiveStream); _logger.LogInformation( "Registered stream for transcoding session ID: {LiveStreamId} (URN: {Urn}, IsLiveStream: {IsLiveStream})", value.Id, urn ?? "null", isLiveStream); } else { _logger.LogWarning("Could not find authenticated URL for original item {OriginalItemId}", _originalItemId); } } } } /// public Task Close() { _logger.LogInformation("Closing SRF live stream for item {OriginalItemId}", _originalItemId); return Task.CompletedTask; } /// public Task Open(CancellationToken cancellationToken) { _logger.LogInformation("Opening SRF live stream for item {OriginalItemId}", _originalItemId); return Task.CompletedTask; } /// public Stream GetStream() { throw new NotSupportedException("Direct stream access not supported for SRF streams"); } /// public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } /// /// Releases the unmanaged resources used by the SRFLiveStream and optionally releases the managed resources. /// /// True to release both managed and unmanaged resources; false to release only unmanaged resources. private void Dispose(bool disposing) { if (disposing) { _logger.LogDebug("Disposing SRF live stream for item {OriginalItemId}", _originalItemId); } } }