mirror of
https://github.com/jellyfin/jellyfin.git
synced 2025-05-31 12:14:21 -04:00
Migrate AudioService to Jellyfin.Api
This commit is contained in:
parent
2eef7d4913
commit
2328ec59c9
183
Jellyfin.Api/Controllers/AudioController.cs
Normal file
183
Jellyfin.Api/Controllers/AudioController.cs
Normal file
@ -0,0 +1,183 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Jellyfin.Api.Helpers;
|
||||||
|
using MediaBrowser.Controller.Dlna;
|
||||||
|
using MediaBrowser.Controller.MediaEncoding;
|
||||||
|
using MediaBrowser.Controller.Net;
|
||||||
|
using MediaBrowser.Model.MediaInfo;
|
||||||
|
using MediaBrowser.Model.Net;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using Microsoft.Net.Http.Headers;
|
||||||
|
|
||||||
|
namespace Jellyfin.Api.Controllers
|
||||||
|
{
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The audio controller.
|
||||||
|
/// </summary>
|
||||||
|
public class AudioController : BaseJellyfinApiController
|
||||||
|
{
|
||||||
|
private readonly IDlnaManager _dlnaManager;
|
||||||
|
private readonly ILogger _logger;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="AudioController"/> class.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="dlnaManager">Instance of the <see cref="IDlnaManager"/> interface.</param>
|
||||||
|
/// <param name="logger">Instance of the <see cref="ILogger{AuidoController}"/> interface.</param>
|
||||||
|
public AudioController(IDlnaManager dlnaManager, ILogger<AudioController> logger)
|
||||||
|
{
|
||||||
|
_dlnaManager = dlnaManager;
|
||||||
|
_logger = logger;
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet("{id}/stream.{container}")]
|
||||||
|
[HttpGet("{id}/stream")]
|
||||||
|
[HttpHead("{id}/stream.{container}")]
|
||||||
|
[HttpGet("{id}/stream")]
|
||||||
|
public async Task<ActionResult> GetAudioStream(
|
||||||
|
[FromRoute] string id,
|
||||||
|
[FromRoute] string container,
|
||||||
|
[FromQuery] bool Static,
|
||||||
|
[FromQuery] string tag)
|
||||||
|
{
|
||||||
|
bool isHeadRequest = Request.Method == System.Net.WebRequestMethods.Http.Head;
|
||||||
|
|
||||||
|
var cancellationTokenSource = new CancellationTokenSource();
|
||||||
|
|
||||||
|
var state = await GetState(request, cancellationTokenSource.Token).ConfigureAwait(false);
|
||||||
|
|
||||||
|
if (Static && state.DirectStreamProvider != null)
|
||||||
|
{
|
||||||
|
StreamingHelpers.AddDlnaHeaders(state, Response.Headers, true, Request, _dlnaManager);
|
||||||
|
|
||||||
|
using (state)
|
||||||
|
{
|
||||||
|
var outputHeaders = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
|
||||||
|
|
||||||
|
// TODO: Don't hardcode this
|
||||||
|
outputHeaders[HeaderNames.ContentType] = MimeTypes.GetMimeType("file.ts");
|
||||||
|
|
||||||
|
return new ProgressiveFileCopier(state.DirectStreamProvider, outputHeaders, null, _logger, CancellationToken.None)
|
||||||
|
{
|
||||||
|
AllowEndOfFile = false
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Static remote stream
|
||||||
|
if (Static && state.InputProtocol == MediaProtocol.Http)
|
||||||
|
{
|
||||||
|
StreamingHelpers.AddDlnaHeaders(state, Response.Headers, true, Request, _dlnaManager);
|
||||||
|
|
||||||
|
using (state)
|
||||||
|
{
|
||||||
|
return await GetStaticRemoteStreamResult(state, responseHeaders, isHeadRequest, cancellationTokenSource).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Static && state.InputProtocol != MediaProtocol.File)
|
||||||
|
{
|
||||||
|
throw new ArgumentException(string.Format($"Input protocol {state.InputProtocol} cannot be streamed statically."));
|
||||||
|
}
|
||||||
|
|
||||||
|
var outputPath = state.OutputFilePath;
|
||||||
|
var outputPathExists = File.Exists(outputPath);
|
||||||
|
|
||||||
|
var transcodingJob = TranscodingJobHelper.GetTranscodingJob(outputPath, TranscodingJobType.Progressive);
|
||||||
|
var isTranscodeCached = outputPathExists && transcodingJob != null;
|
||||||
|
|
||||||
|
StreamingHelpers.AddDlnaHeaders(state, Response.Headers, Static || isTranscodeCached, Request, _dlnaManager);
|
||||||
|
|
||||||
|
// Static stream
|
||||||
|
if (Static)
|
||||||
|
{
|
||||||
|
var contentType = state.GetMimeType("." + state.OutputContainer, false) ?? state.GetMimeType(state.MediaPath);
|
||||||
|
|
||||||
|
using (state)
|
||||||
|
{
|
||||||
|
if (state.MediaSource.IsInfiniteStream)
|
||||||
|
{
|
||||||
|
var outputHeaders = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
|
||||||
|
{
|
||||||
|
[HeaderNames.ContentType] = contentType
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
return new ProgressiveFileCopier(FileSystem, state.MediaPath, outputHeaders, null, _logger, CancellationToken.None)
|
||||||
|
{
|
||||||
|
AllowEndOfFile = false
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
TimeSpan? cacheDuration = null;
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(tag))
|
||||||
|
{
|
||||||
|
cacheDuration = TimeSpan.FromDays(365);
|
||||||
|
}
|
||||||
|
|
||||||
|
return await ResultFactory.GetStaticFileResult(Request, new StaticFileResultOptions
|
||||||
|
{
|
||||||
|
ResponseHeaders = responseHeaders,
|
||||||
|
ContentType = contentType,
|
||||||
|
IsHeadRequest = isHeadRequest,
|
||||||
|
Path = state.MediaPath,
|
||||||
|
CacheDuration = cacheDuration
|
||||||
|
|
||||||
|
}).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//// Not static but transcode cache file exists
|
||||||
|
//if (isTranscodeCached && state.VideoRequest == null)
|
||||||
|
//{
|
||||||
|
// var contentType = state.GetMimeType(outputPath);
|
||||||
|
|
||||||
|
// try
|
||||||
|
// {
|
||||||
|
// if (transcodingJob != null)
|
||||||
|
// {
|
||||||
|
// ApiEntryPoint.Instance.OnTranscodeBeginRequest(transcodingJob);
|
||||||
|
// }
|
||||||
|
|
||||||
|
// return await ResultFactory.GetStaticFileResult(Request, new StaticFileResultOptions
|
||||||
|
// {
|
||||||
|
// ResponseHeaders = responseHeaders,
|
||||||
|
// ContentType = contentType,
|
||||||
|
// IsHeadRequest = isHeadRequest,
|
||||||
|
// Path = outputPath,
|
||||||
|
// FileShare = FileShare.ReadWrite,
|
||||||
|
// OnComplete = () =>
|
||||||
|
// {
|
||||||
|
// if (transcodingJob != null)
|
||||||
|
// {
|
||||||
|
// ApiEntryPoint.Instance.OnTranscodeEndRequest(transcodingJob);
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
// }).ConfigureAwait(false);
|
||||||
|
// }
|
||||||
|
// finally
|
||||||
|
// {
|
||||||
|
// state.Dispose();
|
||||||
|
// }
|
||||||
|
//}
|
||||||
|
|
||||||
|
// Need to start ffmpeg
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return await GetStreamResult(request, state, responseHeaders, isHeadRequest, cancellationTokenSource).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
state.Dispose();
|
||||||
|
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
194
Jellyfin.Api/Helpers/StreamingHelpers.cs
Normal file
194
Jellyfin.Api/Helpers/StreamingHelpers.cs
Normal file
@ -0,0 +1,194 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Globalization;
|
||||||
|
using System.Linq;
|
||||||
|
using Jellyfin.Api.Models;
|
||||||
|
using MediaBrowser.Controller.Dlna;
|
||||||
|
using MediaBrowser.Model.Dlna;
|
||||||
|
using Microsoft.AspNetCore.Http;
|
||||||
|
using Microsoft.Extensions.Primitives;
|
||||||
|
|
||||||
|
namespace Jellyfin.Api.Helpers
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The streaming helpers
|
||||||
|
/// </summary>
|
||||||
|
public class StreamingHelpers
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Adds the dlna headers.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="state">The state.</param>
|
||||||
|
/// <param name="responseHeaders">The response headers.</param>
|
||||||
|
/// <param name="isStaticallyStreamed">if set to <c>true</c> [is statically streamed].</param>
|
||||||
|
/// <param name="request">The <see cref="HttpRequest"/>.</param>
|
||||||
|
/// <param name="dlnaManager">Instance of the <see cref="IDlnaManager"/> interface.</param>
|
||||||
|
public static void AddDlnaHeaders(
|
||||||
|
StreamState state,
|
||||||
|
IHeaderDictionary responseHeaders,
|
||||||
|
bool isStaticallyStreamed,
|
||||||
|
HttpRequest request,
|
||||||
|
IDlnaManager dlnaManager)
|
||||||
|
{
|
||||||
|
if (!state.EnableDlnaHeaders)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var profile = state.DeviceProfile;
|
||||||
|
|
||||||
|
StringValues transferMode = request.Headers["transferMode.dlna.org"];
|
||||||
|
responseHeaders.Add("transferMode.dlna.org", string.IsNullOrEmpty(transferMode) ? "Streaming" : transferMode.ToString());
|
||||||
|
responseHeaders.Add("realTimeInfo.dlna.org", "DLNA.ORG_TLAG=*");
|
||||||
|
|
||||||
|
if (state.RunTimeTicks.HasValue)
|
||||||
|
{
|
||||||
|
if (string.Equals(request.Headers["getMediaInfo.sec"], "1", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
var ms = TimeSpan.FromTicks(state.RunTimeTicks.Value).TotalMilliseconds;
|
||||||
|
responseHeaders.Add("MediaInfo.sec", string.Format(
|
||||||
|
CultureInfo.InvariantCulture,
|
||||||
|
"SEC_Duration={0};",
|
||||||
|
Convert.ToInt32(ms)));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isStaticallyStreamed && profile != null)
|
||||||
|
{
|
||||||
|
AddTimeSeekResponseHeaders(state, responseHeaders);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (profile == null)
|
||||||
|
{
|
||||||
|
profile = dlnaManager.GetDefaultProfile();
|
||||||
|
}
|
||||||
|
|
||||||
|
var audioCodec = state.ActualOutputAudioCodec;
|
||||||
|
|
||||||
|
if (state.VideoRequest == null)
|
||||||
|
{
|
||||||
|
responseHeaders.Add("contentFeatures.dlna.org", new ContentFeatureBuilder(profile).BuildAudioHeader(
|
||||||
|
state.OutputContainer,
|
||||||
|
audioCodec,
|
||||||
|
state.OutputAudioBitrate,
|
||||||
|
state.OutputAudioSampleRate,
|
||||||
|
state.OutputAudioChannels,
|
||||||
|
state.OutputAudioBitDepth,
|
||||||
|
isStaticallyStreamed,
|
||||||
|
state.RunTimeTicks,
|
||||||
|
state.TranscodeSeekInfo));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var videoCodec = state.ActualOutputVideoCodec;
|
||||||
|
|
||||||
|
responseHeaders.Add("contentFeatures.dlna.org", new ContentFeatureBuilder(profile).BuildVideoHeader(
|
||||||
|
state.OutputContainer,
|
||||||
|
videoCodec,
|
||||||
|
audioCodec,
|
||||||
|
state.OutputWidth,
|
||||||
|
state.OutputHeight,
|
||||||
|
state.TargetVideoBitDepth,
|
||||||
|
state.OutputVideoBitrate,
|
||||||
|
state.TargetTimestamp,
|
||||||
|
isStaticallyStreamed,
|
||||||
|
state.RunTimeTicks,
|
||||||
|
state.TargetVideoProfile,
|
||||||
|
state.TargetVideoLevel,
|
||||||
|
state.TargetFramerate,
|
||||||
|
state.TargetPacketLength,
|
||||||
|
state.TranscodeSeekInfo,
|
||||||
|
state.IsTargetAnamorphic,
|
||||||
|
state.IsTargetInterlaced,
|
||||||
|
state.TargetRefFrames,
|
||||||
|
state.TargetVideoStreamCount,
|
||||||
|
state.TargetAudioStreamCount,
|
||||||
|
state.TargetVideoCodecTag,
|
||||||
|
state.IsTargetAVC).FirstOrDefault() ?? string.Empty);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Parses the dlna headers.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="startTimeTicks">The start time ticks.</param>
|
||||||
|
/// <param name="request">The <see cref="HttpRequest"/>.</param>
|
||||||
|
public void ParseDlnaHeaders(long? startTimeTicks, HttpRequest request)
|
||||||
|
{
|
||||||
|
if (!startTimeTicks.HasValue)
|
||||||
|
{
|
||||||
|
var timeSeek = request.Headers["TimeSeekRange.dlna.org"];
|
||||||
|
|
||||||
|
startTimeTicks = ParseTimeSeekHeader(timeSeek);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Parses the time seek header.
|
||||||
|
/// </summary>
|
||||||
|
public long? ParseTimeSeekHeader(string value)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(value))
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const string Npt = "npt=";
|
||||||
|
if (!value.StartsWith(Npt, StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
throw new ArgumentException("Invalid timeseek header");
|
||||||
|
}
|
||||||
|
int index = value.IndexOf('-');
|
||||||
|
value = index == -1
|
||||||
|
? value.Substring(Npt.Length)
|
||||||
|
: value.Substring(Npt.Length, index - Npt.Length);
|
||||||
|
|
||||||
|
if (value.IndexOf(':') == -1)
|
||||||
|
{
|
||||||
|
// Parses npt times in the format of '417.33'
|
||||||
|
if (double.TryParse(value, NumberStyles.Any, CultureInfo.InvariantCulture, out var seconds))
|
||||||
|
{
|
||||||
|
return TimeSpan.FromSeconds(seconds).Ticks;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new ArgumentException("Invalid timeseek header");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parses npt times in the format of '10:19:25.7'
|
||||||
|
var tokens = value.Split(new[] { ':' }, 3);
|
||||||
|
double secondsSum = 0;
|
||||||
|
var timeFactor = 3600;
|
||||||
|
|
||||||
|
foreach (var time in tokens)
|
||||||
|
{
|
||||||
|
if (double.TryParse(time, NumberStyles.Any, CultureInfo.InvariantCulture, out var digit))
|
||||||
|
{
|
||||||
|
secondsSum += digit * timeFactor;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
throw new ArgumentException("Invalid timeseek header");
|
||||||
|
}
|
||||||
|
timeFactor /= 60;
|
||||||
|
}
|
||||||
|
return TimeSpan.FromSeconds(secondsSum).Ticks;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void AddTimeSeekResponseHeaders(StreamState state, IHeaderDictionary responseHeaders)
|
||||||
|
{
|
||||||
|
var runtimeSeconds = TimeSpan.FromTicks(state.RunTimeTicks.Value).TotalSeconds.ToString(CultureInfo.InvariantCulture);
|
||||||
|
var startSeconds = TimeSpan.FromTicks(state.Request.StartTimeTicks ?? 0).TotalSeconds.ToString(CultureInfo.InvariantCulture);
|
||||||
|
|
||||||
|
responseHeaders.Add("TimeSeekRange.dlna.org", string.Format(
|
||||||
|
CultureInfo.InvariantCulture,
|
||||||
|
"npt={0}-{1}/{1}",
|
||||||
|
startSeconds,
|
||||||
|
runtimeSeconds));
|
||||||
|
responseHeaders.Add("X-AvailableSeekRange", string.Format(
|
||||||
|
CultureInfo.InvariantCulture,
|
||||||
|
"1 npt={0}-{1}",
|
||||||
|
startSeconds,
|
||||||
|
runtimeSeconds));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -5,10 +5,12 @@ using System.IO;
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using Jellyfin.Api.Models;
|
||||||
using Jellyfin.Api.Models.PlaybackDtos;
|
using Jellyfin.Api.Models.PlaybackDtos;
|
||||||
using MediaBrowser.Controller.Library;
|
using MediaBrowser.Controller.Library;
|
||||||
using MediaBrowser.Controller.MediaEncoding;
|
using MediaBrowser.Controller.MediaEncoding;
|
||||||
using MediaBrowser.Model.IO;
|
using MediaBrowser.Model.IO;
|
||||||
|
using MediaBrowser.Model.Session;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
namespace Jellyfin.Api.Helpers
|
namespace Jellyfin.Api.Helpers
|
||||||
@ -61,6 +63,14 @@ namespace Jellyfin.Api.Helpers
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static TranscodingJobDto GetTranscodingJob(string path, TranscodingJobType type)
|
||||||
|
{
|
||||||
|
lock (_activeTranscodingJobs)
|
||||||
|
{
|
||||||
|
return _activeTranscodingJobs.FirstOrDefault(j => j.Type == type && string.Equals(j.Path, path, StringComparison.OrdinalIgnoreCase));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Ping transcoding job.
|
/// Ping transcoding job.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -350,5 +360,50 @@ namespace Jellyfin.Api.Helpers
|
|||||||
throw new AggregateException("Error deleting HLS files", exs);
|
throw new AggregateException("Error deleting HLS files", exs);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void ReportTranscodingProgress(
|
||||||
|
TranscodingJob job,
|
||||||
|
StreamState state,
|
||||||
|
TimeSpan? transcodingPosition,
|
||||||
|
float? framerate,
|
||||||
|
double? percentComplete,
|
||||||
|
long? bytesTranscoded,
|
||||||
|
int? bitRate)
|
||||||
|
{
|
||||||
|
var ticks = transcodingPosition?.Ticks;
|
||||||
|
|
||||||
|
if (job != null)
|
||||||
|
{
|
||||||
|
job.Framerate = framerate;
|
||||||
|
job.CompletionPercentage = percentComplete;
|
||||||
|
job.TranscodingPositionTicks = ticks;
|
||||||
|
job.BytesTranscoded = bytesTranscoded;
|
||||||
|
job.BitRate = bitRate;
|
||||||
|
}
|
||||||
|
|
||||||
|
var deviceId = state.Request.DeviceId;
|
||||||
|
|
||||||
|
if (!string.IsNullOrWhiteSpace(deviceId))
|
||||||
|
{
|
||||||
|
var audioCodec = state.ActualOutputAudioCodec;
|
||||||
|
var videoCodec = state.ActualOutputVideoCodec;
|
||||||
|
|
||||||
|
_sessionManager.ReportTranscodingInfo(deviceId, new TranscodingInfo
|
||||||
|
{
|
||||||
|
Bitrate = bitRate ?? state.TotalOutputBitrate,
|
||||||
|
AudioCodec = audioCodec,
|
||||||
|
VideoCodec = videoCodec,
|
||||||
|
Container = state.OutputContainer,
|
||||||
|
Framerate = framerate,
|
||||||
|
CompletionPercentage = percentComplete,
|
||||||
|
Width = state.OutputWidth,
|
||||||
|
Height = state.OutputHeight,
|
||||||
|
AudioChannels = state.OutputAudioChannels,
|
||||||
|
IsAudioDirect = EncodingHelper.IsCopyCodec(state.OutputAudioCodec),
|
||||||
|
IsVideoDirect = EncodingHelper.IsCopyCodec(state.OutputVideoCodec),
|
||||||
|
TranscodeReasons = state.TranscodeReasons
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
145
Jellyfin.Api/Models/StreamState.cs
Normal file
145
Jellyfin.Api/Models/StreamState.cs
Normal file
@ -0,0 +1,145 @@
|
|||||||
|
using System;
|
||||||
|
using Jellyfin.Api.Helpers;
|
||||||
|
using Jellyfin.Api.Models.PlaybackDtos;
|
||||||
|
using MediaBrowser.Controller.Library;
|
||||||
|
using MediaBrowser.Controller.MediaEncoding;
|
||||||
|
using MediaBrowser.Model.Dlna;
|
||||||
|
|
||||||
|
namespace Jellyfin.Api.Models
|
||||||
|
{
|
||||||
|
public class StreamState : EncodingJobInfo, IDisposable
|
||||||
|
{
|
||||||
|
private readonly IMediaSourceManager _mediaSourceManager;
|
||||||
|
private bool _disposed = false;
|
||||||
|
|
||||||
|
public string RequestedUrl { get; set; }
|
||||||
|
|
||||||
|
public StreamRequest Request
|
||||||
|
{
|
||||||
|
get => (StreamRequest)BaseRequest;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
BaseRequest = value;
|
||||||
|
|
||||||
|
IsVideoRequest = VideoRequest != null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public TranscodingThrottler TranscodingThrottler { get; set; }
|
||||||
|
|
||||||
|
public VideoStreamRequest VideoRequest => Request as VideoStreamRequest;
|
||||||
|
|
||||||
|
public IDirectStreamProvider DirectStreamProvider { get; set; }
|
||||||
|
|
||||||
|
public string WaitForPath { get; set; }
|
||||||
|
|
||||||
|
public bool IsOutputVideo => Request is VideoStreamRequest;
|
||||||
|
|
||||||
|
public int SegmentLength
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (Request.SegmentLength.HasValue)
|
||||||
|
{
|
||||||
|
return Request.SegmentLength.Value;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (EncodingHelper.IsCopyCodec(OutputVideoCodec))
|
||||||
|
{
|
||||||
|
var userAgent = UserAgent ?? string.Empty;
|
||||||
|
|
||||||
|
if (userAgent.IndexOf("AppleTV", StringComparison.OrdinalIgnoreCase) != -1 ||
|
||||||
|
userAgent.IndexOf("cfnetwork", StringComparison.OrdinalIgnoreCase) != -1 ||
|
||||||
|
userAgent.IndexOf("ipad", StringComparison.OrdinalIgnoreCase) != -1 ||
|
||||||
|
userAgent.IndexOf("iphone", StringComparison.OrdinalIgnoreCase) != -1 ||
|
||||||
|
userAgent.IndexOf("ipod", StringComparison.OrdinalIgnoreCase) != -1)
|
||||||
|
{
|
||||||
|
if (IsSegmentedLiveStream)
|
||||||
|
{
|
||||||
|
return 6;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 6;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (IsSegmentedLiveStream)
|
||||||
|
{
|
||||||
|
return 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 6;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 3;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public int MinSegments
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (Request.MinSegments.HasValue)
|
||||||
|
{
|
||||||
|
return Request.MinSegments.Value;
|
||||||
|
}
|
||||||
|
|
||||||
|
return SegmentLength >= 10 ? 2 : 3;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public string UserAgent { get; set; }
|
||||||
|
|
||||||
|
public bool EstimateContentLength { get; set; }
|
||||||
|
|
||||||
|
public TranscodeSeekInfo TranscodeSeekInfo { get; set; }
|
||||||
|
|
||||||
|
public bool EnableDlnaHeaders { get; set; }
|
||||||
|
|
||||||
|
public DeviceProfile DeviceProfile { get; set; }
|
||||||
|
|
||||||
|
public TranscodingJobDto TranscodingJob { get; set; }
|
||||||
|
|
||||||
|
public StreamState(IMediaSourceManager mediaSourceManager, TranscodingJobType transcodingType)
|
||||||
|
: base(transcodingType)
|
||||||
|
{
|
||||||
|
_mediaSourceManager = mediaSourceManager;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void ReportTranscodingProgress(TimeSpan? transcodingPosition, float? framerate, double? percentComplete, long? bytesTranscoded, int? bitRate)
|
||||||
|
{
|
||||||
|
TranscodingJobHelper.ReportTranscodingProgress(TranscodingJob, this, transcodingPosition, framerate, percentComplete, bytesTranscoded, bitRate);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
Dispose(true);
|
||||||
|
GC.SuppressFinalize(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected virtual void Dispose(bool disposing)
|
||||||
|
{
|
||||||
|
if (_disposed)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (disposing)
|
||||||
|
{
|
||||||
|
// REVIEW: Is this the right place for this?
|
||||||
|
if (MediaSource.RequiresClosing
|
||||||
|
&& string.IsNullOrWhiteSpace(Request.LiveStreamId)
|
||||||
|
&& !string.IsNullOrWhiteSpace(MediaSource.LiveStreamId))
|
||||||
|
{
|
||||||
|
_mediaSourceManager.CloseLiveStream(MediaSource.LiveStreamId).GetAwaiter().GetResult();
|
||||||
|
}
|
||||||
|
|
||||||
|
TranscodingThrottler?.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
TranscodingThrottler = null;
|
||||||
|
TranscodingJob = null;
|
||||||
|
|
||||||
|
_disposed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user