mixed refactor
This commit is contained in:
@@ -0,0 +1,173 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Jellyfin.Plugin.SRFPlay.Api.Models;
|
||||
using Jellyfin.Plugin.SRFPlay.Configuration;
|
||||
using Jellyfin.Plugin.SRFPlay.Constants;
|
||||
using Jellyfin.Plugin.SRFPlay.Services.Interfaces;
|
||||
using MediaBrowser.Controller;
|
||||
using MediaBrowser.Model.Dto;
|
||||
using MediaBrowser.Model.Entities;
|
||||
using MediaBrowser.Model.MediaInfo;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Jellyfin.Plugin.SRFPlay.Services;
|
||||
|
||||
/// <summary>
|
||||
/// Factory for creating MediaSourceInfo objects with consistent configuration.
|
||||
/// Consolidates duplicated logic from SRFMediaProvider and SRFPlayChannel.
|
||||
/// </summary>
|
||||
public class MediaSourceFactory : IMediaSourceFactory
|
||||
{
|
||||
private readonly ILogger<MediaSourceFactory> _logger;
|
||||
private readonly IStreamUrlResolver _streamResolver;
|
||||
private readonly IStreamProxyService _proxyService;
|
||||
private readonly IServerApplicationHost _appHost;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="MediaSourceFactory"/> class.
|
||||
/// </summary>
|
||||
/// <param name="logger">The logger.</param>
|
||||
/// <param name="streamResolver">The stream URL resolver.</param>
|
||||
/// <param name="proxyService">The stream proxy service.</param>
|
||||
/// <param name="appHost">The server application host.</param>
|
||||
public MediaSourceFactory(
|
||||
ILogger<MediaSourceFactory> logger,
|
||||
IStreamUrlResolver streamResolver,
|
||||
IStreamProxyService proxyService,
|
||||
IServerApplicationHost appHost)
|
||||
{
|
||||
_logger = logger;
|
||||
_streamResolver = streamResolver;
|
||||
_proxyService = proxyService;
|
||||
_appHost = appHost;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<MediaSourceInfo?> CreateMediaSourceAsync(
|
||||
Chapter chapter,
|
||||
string itemId,
|
||||
string urn,
|
||||
QualityPreference qualityPreference,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
// Get stream URL based on quality preference (unauthenticated)
|
||||
var streamUrl = _streamResolver.GetStreamUrl(chapter, qualityPreference);
|
||||
|
||||
if (string.IsNullOrEmpty(streamUrl))
|
||||
{
|
||||
_logger.LogWarning("Could not resolve stream URL for chapter: {ChapterId}", chapter.Id);
|
||||
return Task.FromResult<MediaSourceInfo?>(null);
|
||||
}
|
||||
|
||||
// Detect if this is a live stream
|
||||
var isLiveStream = chapter.Type == "SCHEDULED_LIVESTREAM" ||
|
||||
urn.Contains("livestream", StringComparison.OrdinalIgnoreCase);
|
||||
|
||||
// Register stream with UNAUTHENTICATED URL - proxy will authenticate on-demand
|
||||
// This avoids wasting 30-second tokens during category browsing
|
||||
_proxyService.RegisterStreamDeferred(itemId, streamUrl, urn, isLiveStream);
|
||||
|
||||
// Build proxy URL
|
||||
var proxyUrl = BuildProxyUrl(itemId);
|
||||
|
||||
_logger.LogDebug(
|
||||
"Created media source for {Title} - ItemId: {ItemId}, IsLiveStream: {IsLiveStream}",
|
||||
chapter.Title,
|
||||
itemId,
|
||||
isLiveStream);
|
||||
|
||||
// Create MediaSourceInfo with minimal settings to let clients determine playback
|
||||
// Don't specify Container or MediaStreams - let the .m3u8 path trigger HLS detection
|
||||
var mediaSource = new MediaSourceInfo
|
||||
{
|
||||
Id = itemId,
|
||||
Name = chapter.Title,
|
||||
Path = proxyUrl,
|
||||
Protocol = MediaProtocol.Http,
|
||||
// Empty container - let clients detect HLS from .m3u8 extension
|
||||
Container = string.Empty,
|
||||
SupportsDirectStream = true,
|
||||
SupportsDirectPlay = true,
|
||||
SupportsTranscoding = false,
|
||||
IsRemote = true,
|
||||
Type = MediaSourceType.Default,
|
||||
RunTimeTicks = chapter.Duration > 0 ? TimeSpan.FromMilliseconds(chapter.Duration).Ticks : null,
|
||||
VideoType = VideoType.VideoFile,
|
||||
IsInfiniteStream = isLiveStream,
|
||||
RequiresOpening = false,
|
||||
RequiresClosing = false,
|
||||
SupportsProbing = false,
|
||||
ReadAtNativeFramerate = isLiveStream,
|
||||
// Don't specify MediaStreams - let client determine codec compatibility
|
||||
MediaStreams = new List<MediaStream>(),
|
||||
// Reduce analyze duration for faster startup (3000ms is Jellyfin default, 1000ms for live)
|
||||
AnalyzeDurationMs = isLiveStream ? 1000 : 3000,
|
||||
// Ignore DTS timestamps for live streams to avoid sync issues
|
||||
IgnoreDts = isLiveStream,
|
||||
// Ignore index for live streams
|
||||
IgnoreIndex = isLiveStream,
|
||||
};
|
||||
|
||||
return Task.FromResult<MediaSourceInfo?>(mediaSource);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public string BuildProxyUrl(string itemId)
|
||||
{
|
||||
return $"{GetServerBaseUrl()}{ApiEndpoints.ProxyMasterManifestPath.Replace("{0}", itemId, StringComparison.Ordinal)}";
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public string GetServerBaseUrl()
|
||||
{
|
||||
var config = Plugin.Instance?.Configuration;
|
||||
return config != null && !string.IsNullOrWhiteSpace(config.PublicServerUrl)
|
||||
? config.PublicServerUrl.TrimEnd('/')
|
||||
: _appHost.GetSmartApiUrl(string.Empty);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IReadOnlyList<MediaStream> CreateMediaStreams(QualityPreference quality)
|
||||
{
|
||||
// Set resolution/bitrate based on quality preference
|
||||
var (width, height, videoBitrate) = quality switch
|
||||
{
|
||||
QualityPreference.SD => (1280, 720, 2500000),
|
||||
QualityPreference.HD => (1920, 1080, 5000000),
|
||||
_ => (1280, 720, 3000000)
|
||||
};
|
||||
|
||||
return new List<MediaStream>
|
||||
{
|
||||
new MediaStream
|
||||
{
|
||||
Type = MediaStreamType.Video,
|
||||
Codec = "h264",
|
||||
Profile = "high",
|
||||
Level = 40,
|
||||
Width = width,
|
||||
Height = height,
|
||||
BitRate = videoBitrate,
|
||||
BitDepth = 8,
|
||||
IsInterlaced = false,
|
||||
IsDefault = true,
|
||||
Index = 0,
|
||||
IsAVC = true,
|
||||
PixelFormat = "yuv420p"
|
||||
},
|
||||
new MediaStream
|
||||
{
|
||||
Type = MediaStreamType.Audio,
|
||||
Codec = "aac",
|
||||
Profile = "LC",
|
||||
Channels = 2,
|
||||
SampleRate = 48000,
|
||||
BitRate = 128000,
|
||||
IsDefault = true,
|
||||
Index = 1
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user