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);
}
}
}