142 lines
4.9 KiB
C#
142 lines
4.9 KiB
C#
using System;
|
|
using System.IO;
|
|
using System.Threading;
|
|
using System.Threading.Tasks;
|
|
using Jellyfin.Plugin.SRFPlay.Services;
|
|
using MediaBrowser.Controller.Library;
|
|
using MediaBrowser.Model.Dto;
|
|
using Microsoft.Extensions.Logging;
|
|
|
|
namespace Jellyfin.Plugin.SRFPlay.Providers;
|
|
|
|
/// <summary>
|
|
/// Live stream wrapper for SRF Play streams to handle transcoding sessions.
|
|
/// </summary>
|
|
internal sealed class SRFLiveStream : ILiveStream
|
|
{
|
|
private readonly ILogger _logger;
|
|
private readonly StreamProxyService _proxyService;
|
|
private readonly string _originalItemId;
|
|
private MediaSourceInfo? _mediaSource;
|
|
|
|
/// <summary>
|
|
/// Initializes a new instance of the <see cref="SRFLiveStream"/> class.
|
|
/// </summary>
|
|
/// <param name="logger">The logger.</param>
|
|
/// <param name="proxyService">The stream proxy service.</param>
|
|
/// <param name="originalItemId">The original item ID.</param>
|
|
/// <param name="openToken">The open token.</param>
|
|
/// <param name="loggerFactory">The logger factory.</param>
|
|
public SRFLiveStream(
|
|
ILogger logger,
|
|
StreamProxyService proxyService,
|
|
string originalItemId,
|
|
string openToken,
|
|
ILoggerFactory loggerFactory)
|
|
{
|
|
_logger = logger;
|
|
_proxyService = proxyService;
|
|
_originalItemId = originalItemId;
|
|
OriginalStreamId = openToken;
|
|
UniqueId = openToken;
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public int ConsumerCount { get; set; }
|
|
|
|
/// <inheritdoc />
|
|
public string OriginalStreamId { get; set; }
|
|
|
|
/// <inheritdoc />
|
|
public string UniqueId { get; }
|
|
|
|
/// <inheritdoc />
|
|
public string TunerHostId => string.Empty;
|
|
|
|
/// <inheritdoc />
|
|
public bool EnableStreamSharing => false;
|
|
|
|
/// <inheritdoc />
|
|
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);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public Task Close()
|
|
{
|
|
_logger.LogInformation("Closing SRF live stream for item {OriginalItemId}", _originalItemId);
|
|
return Task.CompletedTask;
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public Task Open(CancellationToken cancellationToken)
|
|
{
|
|
_logger.LogInformation("Opening SRF live stream for item {OriginalItemId}", _originalItemId);
|
|
return Task.CompletedTask;
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public Stream GetStream()
|
|
{
|
|
throw new NotSupportedException("Direct stream access not supported for SRF streams");
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public void Dispose()
|
|
{
|
|
Dispose(true);
|
|
GC.SuppressFinalize(this);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Releases the unmanaged resources used by the SRFLiveStream and optionally releases the managed resources.
|
|
/// </summary>
|
|
/// <param name="disposing">True to release both managed and unmanaged resources; false to release only unmanaged resources.</param>
|
|
private void Dispose(bool disposing)
|
|
{
|
|
if (disposing)
|
|
{
|
|
_logger.LogDebug("Disposing SRF live stream for item {OriginalItemId}", _originalItemId);
|
|
}
|
|
}
|
|
}
|