2020-09-17 19:26:27 +02:00
using System ;
2020-07-31 23:09:17 +02:00
using System.Collections.Generic ;
using System.ComponentModel.DataAnnotations ;
using System.Diagnostics.CodeAnalysis ;
using System.Globalization ;
using System.IO ;
using System.Linq ;
using System.Text ;
using System.Threading ;
using System.Threading.Tasks ;
2020-09-02 01:26:49 +02:00
using Jellyfin.Api.Attributes ;
2020-07-31 23:09:17 +02:00
using Jellyfin.Api.Constants ;
using Jellyfin.Api.Helpers ;
using Jellyfin.Api.Models.PlaybackDtos ;
using Jellyfin.Api.Models.StreamingDtos ;
2021-09-23 15:29:12 +02:00
using Jellyfin.MediaEncoding.Hls.Playlist ;
2020-07-31 23:09:17 +02:00
using MediaBrowser.Common.Configuration ;
using MediaBrowser.Controller.Configuration ;
using MediaBrowser.Controller.Devices ;
using MediaBrowser.Controller.Dlna ;
using MediaBrowser.Controller.Library ;
using MediaBrowser.Controller.MediaEncoding ;
using MediaBrowser.Model.Configuration ;
using MediaBrowser.Model.Dlna ;
using MediaBrowser.Model.IO ;
using MediaBrowser.Model.Net ;
using Microsoft.AspNetCore.Authorization ;
using Microsoft.AspNetCore.Http ;
using Microsoft.AspNetCore.Mvc ;
using Microsoft.Extensions.Logging ;
namespace Jellyfin.Api.Controllers
{
/// <summary>
/// Dynamic hls controller.
/// </summary>
2020-08-04 20:48:53 +02:00
[Route("")]
2020-07-31 23:09:17 +02:00
[Authorize(Policy = Policies.DefaultAuthorization)]
public class DynamicHlsController : BaseJellyfinApiController
{
2021-12-03 11:33:19 +01:00
private const string DefaultVodEncoderPreset = "veryfast" ;
private const string DefaultEventEncoderPreset = "superfast" ;
2020-11-20 02:56:08 +01:00
private const TranscodingJobType TranscodingJobType = MediaBrowser . Controller . MediaEncoding . TranscodingJobType . Hls ;
2020-11-13 20:47:54 +01:00
2020-07-31 23:09:17 +02:00
private readonly ILibraryManager _libraryManager ;
private readonly IUserManager _userManager ;
private readonly IDlnaManager _dlnaManager ;
private readonly IMediaSourceManager _mediaSourceManager ;
private readonly IServerConfigurationManager _serverConfigurationManager ;
private readonly IMediaEncoder _mediaEncoder ;
private readonly IFileSystem _fileSystem ;
private readonly IDeviceManager _deviceManager ;
private readonly TranscodingJobHelper _transcodingJobHelper ;
private readonly ILogger < DynamicHlsController > _logger ;
private readonly EncodingHelper _encodingHelper ;
2021-09-23 15:29:12 +02:00
private readonly IDynamicHlsPlaylistGenerator _dynamicHlsPlaylistGenerator ;
2020-08-10 01:20:14 +02:00
private readonly DynamicHlsHelper _dynamicHlsHelper ;
2020-11-13 20:47:54 +01:00
private readonly EncodingOptions _encodingOptions ;
2020-07-31 23:09:17 +02:00
/// <summary>
/// Initializes a new instance of the <see cref="DynamicHlsController"/> class.
/// </summary>
/// <param name="libraryManager">Instance of the <see cref="ILibraryManager"/> interface.</param>
/// <param name="userManager">Instance of the <see cref="IUserManager"/> interface.</param>
/// <param name="dlnaManager">Instance of the <see cref="IDlnaManager"/> interface.</param>
/// <param name="mediaSourceManager">Instance of the <see cref="IMediaSourceManager"/> interface.</param>
/// <param name="serverConfigurationManager">Instance of the <see cref="IServerConfigurationManager"/> interface.</param>
/// <param name="mediaEncoder">Instance of the <see cref="IMediaEncoder"/> interface.</param>
/// <param name="fileSystem">Instance of the <see cref="IFileSystem"/> interface.</param>
/// <param name="deviceManager">Instance of the <see cref="IDeviceManager"/> interface.</param>
/// <param name="transcodingJobHelper">Instance of the <see cref="TranscodingJobHelper"/> class.</param>
/// <param name="logger">Instance of the <see cref="ILogger{DynamicHlsController}"/> interface.</param>
2020-08-10 01:20:14 +02:00
/// <param name="dynamicHlsHelper">Instance of <see cref="DynamicHlsHelper"/>.</param>
2021-04-20 23:03:36 +02:00
/// <param name="encodingHelper">Instance of <see cref="EncodingHelper"/>.</param>
2021-09-23 15:29:12 +02:00
/// <param name="dynamicHlsPlaylistGenerator">Instance of <see cref="IDynamicHlsPlaylistGenerator"/>.</param>
2020-07-31 23:09:17 +02:00
public DynamicHlsController (
ILibraryManager libraryManager ,
IUserManager userManager ,
IDlnaManager dlnaManager ,
IMediaSourceManager mediaSourceManager ,
IServerConfigurationManager serverConfigurationManager ,
IMediaEncoder mediaEncoder ,
IFileSystem fileSystem ,
IDeviceManager deviceManager ,
TranscodingJobHelper transcodingJobHelper ,
2020-08-10 01:20:14 +02:00
ILogger < DynamicHlsController > logger ,
2021-04-20 23:03:36 +02:00
DynamicHlsHelper dynamicHlsHelper ,
2021-09-23 15:29:12 +02:00
EncodingHelper encodingHelper ,
IDynamicHlsPlaylistGenerator dynamicHlsPlaylistGenerator )
2020-07-31 23:09:17 +02:00
{
_libraryManager = libraryManager ;
_userManager = userManager ;
_dlnaManager = dlnaManager ;
_mediaSourceManager = mediaSourceManager ;
_serverConfigurationManager = serverConfigurationManager ;
_mediaEncoder = mediaEncoder ;
_fileSystem = fileSystem ;
_deviceManager = deviceManager ;
_transcodingJobHelper = transcodingJobHelper ;
_logger = logger ;
2020-08-10 01:20:14 +02:00
_dynamicHlsHelper = dynamicHlsHelper ;
2021-04-20 23:03:36 +02:00
_encodingHelper = encodingHelper ;
2021-09-23 15:29:12 +02:00
_dynamicHlsPlaylistGenerator = dynamicHlsPlaylistGenerator ;
2021-04-20 23:03:36 +02:00
2020-11-13 20:47:54 +01:00
_encodingOptions = serverConfigurationManager . GetEncodingOptions ( ) ;
2020-07-31 23:09:17 +02:00
}
2021-12-03 11:33:19 +01:00
/// <summary>
/// Gets a hls live stream.
/// </summary>
/// <param name="itemId">The item id.</param>
/// <param name="container">The audio container.</param>
/// <param name="static">Optional. If true, the original file will be streamed statically without any encoding. Use either no url extension or the original file extension. true/false.</param>
/// <param name="params">The streaming parameters.</param>
/// <param name="tag">The tag.</param>
/// <param name="deviceProfileId">Optional. The dlna device profile id to utilize.</param>
/// <param name="playSessionId">The play session id.</param>
/// <param name="segmentContainer">The segment container.</param>
2022-08-15 12:48:34 +02:00
/// <param name="segmentLength">The segment length.</param>
2021-12-03 11:33:19 +01:00
/// <param name="minSegments">The minimum number of segments.</param>
/// <param name="mediaSourceId">The media version id, if playing an alternate version.</param>
/// <param name="deviceId">The device id of the client requesting. Used to stop encoding processes when needed.</param>
/// <param name="audioCodec">Optional. Specify a audio codec to encode to, e.g. mp3. If omitted the server will auto-select using the url's extension. Options: aac, mp3, vorbis, wma.</param>
/// <param name="enableAutoStreamCopy">Whether or not to allow automatic stream copy if requested values match the original source. Defaults to true.</param>
/// <param name="allowVideoStreamCopy">Whether or not to allow copying of the video stream url.</param>
/// <param name="allowAudioStreamCopy">Whether or not to allow copying of the audio stream url.</param>
/// <param name="breakOnNonKeyFrames">Optional. Whether to break on non key frames.</param>
/// <param name="audioSampleRate">Optional. Specify a specific audio sample rate, e.g. 44100.</param>
/// <param name="maxAudioBitDepth">Optional. The maximum audio bit depth.</param>
/// <param name="audioBitRate">Optional. Specify an audio bitrate to encode to, e.g. 128000. If omitted this will be left to encoder defaults.</param>
/// <param name="audioChannels">Optional. Specify a specific number of audio channels to encode to, e.g. 2.</param>
/// <param name="maxAudioChannels">Optional. Specify a maximum number of audio channels to encode to, e.g. 2.</param>
/// <param name="profile">Optional. Specify a specific an encoder profile (varies by encoder), e.g. main, baseline, high.</param>
/// <param name="level">Optional. Specify a level for the encoder profile (varies by encoder), e.g. 3, 3.1.</param>
/// <param name="framerate">Optional. A specific video framerate to encode to, e.g. 23.976. Generally this should be omitted unless the device has specific requirements.</param>
/// <param name="maxFramerate">Optional. A specific maximum video framerate to encode to, e.g. 23.976. Generally this should be omitted unless the device has specific requirements.</param>
/// <param name="copyTimestamps">Whether or not to copy timestamps when transcoding with an offset. Defaults to false.</param>
/// <param name="startTimeTicks">Optional. Specify a starting offset, in ticks. 1 tick = 10000 ms.</param>
/// <param name="width">Optional. The fixed horizontal resolution of the encoded video.</param>
/// <param name="height">Optional. The fixed vertical resolution of the encoded video.</param>
/// <param name="videoBitRate">Optional. Specify a video bitrate to encode to, e.g. 500000. If omitted this will be left to encoder defaults.</param>
/// <param name="subtitleStreamIndex">Optional. The index of the subtitle stream to use. If omitted no subtitles will be used.</param>
/// <param name="subtitleMethod">Optional. Specify the subtitle delivery method.</param>
/// <param name="maxRefFrames">Optional.</param>
/// <param name="maxVideoBitDepth">Optional. The maximum video bit depth.</param>
/// <param name="requireAvc">Optional. Whether to require avc.</param>
/// <param name="deInterlace">Optional. Whether to deinterlace the video.</param>
/// <param name="requireNonAnamorphic">Optional. Whether to require a non anamorphic stream.</param>
/// <param name="transcodingMaxAudioChannels">Optional. The maximum number of audio channels to transcode.</param>
/// <param name="cpuCoreLimit">Optional. The limit of how many cpu cores to use.</param>
/// <param name="liveStreamId">The live stream id.</param>
/// <param name="enableMpegtsM2TsMode">Optional. Whether to enable the MpegtsM2Ts mode.</param>
/// <param name="videoCodec">Optional. Specify a video codec to encode to, e.g. h264. If omitted the server will auto-select using the url's extension. Options: h265, h264, mpeg4, theora, vp8, vp9, vpx (deprecated), wmv.</param>
/// <param name="subtitleCodec">Optional. Specify a subtitle codec to encode to.</param>
/// <param name="transcodeReasons">Optional. The transcoding reason.</param>
/// <param name="audioStreamIndex">Optional. The index of the audio stream to use. If omitted the first audio stream will be used.</param>
/// <param name="videoStreamIndex">Optional. The index of the video stream to use. If omitted the first video stream will be used.</param>
/// <param name="context">Optional. The <see cref="EncodingContext"/>.</param>
/// <param name="streamOptions">Optional. The streaming options.</param>
/// <param name="maxWidth">Optional. The max width.</param>
/// <param name="maxHeight">Optional. The max height.</param>
/// <param name="enableSubtitlesInManifest">Optional. Whether to enable subtitles in the manifest.</param>
/// <response code="200">Hls live stream retrieved.</response>
/// <returns>A <see cref="FileResult"/> containing the hls file.</returns>
[HttpGet("Videos/{itemId}/live.m3u8")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesPlaylistFile]
public async Task < ActionResult > GetLiveHlsStream (
[FromRoute, Required] Guid itemId ,
[FromQuery] string? container ,
[FromQuery] bool? @static ,
[FromQuery] string? @params ,
[FromQuery] string? tag ,
[FromQuery] string? deviceProfileId ,
[FromQuery] string? playSessionId ,
[FromQuery] string? segmentContainer ,
[FromQuery] int? segmentLength ,
[FromQuery] int? minSegments ,
[FromQuery] string? mediaSourceId ,
[FromQuery] string? deviceId ,
[FromQuery] string? audioCodec ,
[FromQuery] bool? enableAutoStreamCopy ,
[FromQuery] bool? allowVideoStreamCopy ,
[FromQuery] bool? allowAudioStreamCopy ,
[FromQuery] bool? breakOnNonKeyFrames ,
[FromQuery] int? audioSampleRate ,
[FromQuery] int? maxAudioBitDepth ,
[FromQuery] int? audioBitRate ,
[FromQuery] int? audioChannels ,
[FromQuery] int? maxAudioChannels ,
[FromQuery] string? profile ,
[FromQuery] string? level ,
[FromQuery] float? framerate ,
[FromQuery] float? maxFramerate ,
[FromQuery] bool? copyTimestamps ,
[FromQuery] long? startTimeTicks ,
[FromQuery] int? width ,
[FromQuery] int? height ,
[FromQuery] int? videoBitRate ,
[FromQuery] int? subtitleStreamIndex ,
[FromQuery] SubtitleDeliveryMethod ? subtitleMethod ,
[FromQuery] int? maxRefFrames ,
[FromQuery] int? maxVideoBitDepth ,
[FromQuery] bool? requireAvc ,
[FromQuery] bool? deInterlace ,
[FromQuery] bool? requireNonAnamorphic ,
[FromQuery] int? transcodingMaxAudioChannels ,
[FromQuery] int? cpuCoreLimit ,
[FromQuery] string? liveStreamId ,
[FromQuery] bool? enableMpegtsM2TsMode ,
[FromQuery] string? videoCodec ,
[FromQuery] string? subtitleCodec ,
[FromQuery] string? transcodeReasons ,
[FromQuery] int? audioStreamIndex ,
[FromQuery] int? videoStreamIndex ,
[FromQuery] EncodingContext ? context ,
[FromQuery] Dictionary < string , string > streamOptions ,
[FromQuery] int? maxWidth ,
[FromQuery] int? maxHeight ,
[FromQuery] bool? enableSubtitlesInManifest )
{
VideoRequestDto streamingRequest = new VideoRequestDto
{
Id = itemId ,
Container = container ,
Static = @static ? ? false ,
Params = @params ,
Tag = tag ,
DeviceProfileId = deviceProfileId ,
PlaySessionId = playSessionId ,
SegmentContainer = segmentContainer ,
SegmentLength = segmentLength ,
MinSegments = minSegments ,
MediaSourceId = mediaSourceId ,
DeviceId = deviceId ,
AudioCodec = audioCodec ,
EnableAutoStreamCopy = enableAutoStreamCopy ? ? true ,
AllowAudioStreamCopy = allowAudioStreamCopy ? ? true ,
AllowVideoStreamCopy = allowVideoStreamCopy ? ? true ,
BreakOnNonKeyFrames = breakOnNonKeyFrames ? ? false ,
AudioSampleRate = audioSampleRate ,
MaxAudioChannels = maxAudioChannels ,
AudioBitRate = audioBitRate ,
MaxAudioBitDepth = maxAudioBitDepth ,
AudioChannels = audioChannels ,
Profile = profile ,
Level = level ,
Framerate = framerate ,
MaxFramerate = maxFramerate ,
CopyTimestamps = copyTimestamps ? ? false ,
StartTimeTicks = startTimeTicks ,
Width = width ,
Height = height ,
VideoBitRate = videoBitRate ,
SubtitleStreamIndex = subtitleStreamIndex ,
SubtitleMethod = subtitleMethod ? ? SubtitleDeliveryMethod . Encode ,
MaxRefFrames = maxRefFrames ,
MaxVideoBitDepth = maxVideoBitDepth ,
RequireAvc = requireAvc ? ? false ,
DeInterlace = deInterlace ? ? false ,
RequireNonAnamorphic = requireNonAnamorphic ? ? false ,
TranscodingMaxAudioChannels = transcodingMaxAudioChannels ,
CpuCoreLimit = cpuCoreLimit ,
LiveStreamId = liveStreamId ,
EnableMpegtsM2TsMode = enableMpegtsM2TsMode ? ? false ,
VideoCodec = videoCodec ,
SubtitleCodec = subtitleCodec ,
TranscodeReasons = transcodeReasons ,
AudioStreamIndex = audioStreamIndex ,
VideoStreamIndex = videoStreamIndex ,
Context = context ? ? EncodingContext . Streaming ,
StreamOptions = streamOptions ,
MaxHeight = maxHeight ,
MaxWidth = maxWidth ,
EnableSubtitlesInManifest = enableSubtitlesInManifest ? ? true
} ;
// CTS lifecycle is managed internally.
var cancellationTokenSource = new CancellationTokenSource ( ) ;
// Due to CTS.Token calling ThrowIfDisposed (https://github.com/dotnet/runtime/issues/29970) we have to "cache" the token
// since it gets disposed when ffmpeg exits
var cancellationToken = cancellationTokenSource . Token ;
2022-06-27 02:53:03 +02:00
var state = await StreamingHelpers . GetStreamingState (
2021-12-03 11:33:19 +01:00
streamingRequest ,
2022-10-06 13:57:47 +02:00
HttpContext ,
2021-12-03 11:33:19 +01:00
_mediaSourceManager ,
_userManager ,
_libraryManager ,
_serverConfigurationManager ,
_mediaEncoder ,
_encodingHelper ,
_dlnaManager ,
_deviceManager ,
_transcodingJobHelper ,
TranscodingJobType ,
cancellationToken )
. ConfigureAwait ( false ) ;
TranscodingJobDto ? job = null ;
var playlistPath = Path . ChangeExtension ( state . OutputFilePath , ".m3u8" ) ;
if ( ! System . IO . File . Exists ( playlistPath ) )
{
var transcodingLock = _transcodingJobHelper . GetTranscodingLock ( playlistPath ) ;
await transcodingLock . WaitAsync ( cancellationToken ) . ConfigureAwait ( false ) ;
try
{
if ( ! System . IO . File . Exists ( playlistPath ) )
{
// If the playlist doesn't already exist, startup ffmpeg
try
{
job = await _transcodingJobHelper . StartFfMpeg (
state ,
playlistPath ,
GetCommandLineArguments ( playlistPath , state , true , 0 ) ,
Request ,
TranscodingJobType ,
cancellationTokenSource )
. ConfigureAwait ( false ) ;
job . IsLiveOutput = true ;
}
catch
{
state . Dispose ( ) ;
throw ;
}
minSegments = state . MinSegments ;
if ( minSegments > 0 )
{
await HlsHelpers . WaitForMinimumSegmentCount ( playlistPath , minSegments , _logger , cancellationToken ) . ConfigureAwait ( false ) ;
}
}
}
finally
{
transcodingLock . Release ( ) ;
}
}
job ? ? = _transcodingJobHelper . OnTranscodeBeginRequest ( playlistPath , TranscodingJobType ) ;
if ( job ! = null )
{
_transcodingJobHelper . OnTranscodeEndRequest ( job ) ;
}
var playlistText = HlsHelpers . GetLivePlaylistText ( playlistPath , state ) ;
return Content ( playlistText , MimeTypes . GetMimeType ( "playlist.m3u8" ) ) ;
}
2020-07-31 23:09:17 +02:00
/// <summary>
/// Gets a video hls playlist stream.
/// </summary>
/// <param name="itemId">The item id.</param>
/// <param name="static">Optional. If true, the original file will be streamed statically without any encoding. Use either no url extension or the original file extension. true/false.</param>
/// <param name="params">The streaming parameters.</param>
/// <param name="tag">The tag.</param>
/// <param name="deviceProfileId">Optional. The dlna device profile id to utilize.</param>
/// <param name="playSessionId">The play session id.</param>
/// <param name="segmentContainer">The segment container.</param>
2020-11-18 14:23:45 +01:00
/// <param name="segmentLength">The segment length.</param>
2020-07-31 23:09:17 +02:00
/// <param name="minSegments">The minimum number of segments.</param>
/// <param name="mediaSourceId">The media version id, if playing an alternate version.</param>
/// <param name="deviceId">The device id of the client requesting. Used to stop encoding processes when needed.</param>
/// <param name="audioCodec">Optional. Specify a audio codec to encode to, e.g. mp3. If omitted the server will auto-select using the url's extension. Options: aac, mp3, vorbis, wma.</param>
/// <param name="enableAutoStreamCopy">Whether or not to allow automatic stream copy if requested values match the original source. Defaults to true.</param>
/// <param name="allowVideoStreamCopy">Whether or not to allow copying of the video stream url.</param>
/// <param name="allowAudioStreamCopy">Whether or not to allow copying of the audio stream url.</param>
/// <param name="breakOnNonKeyFrames">Optional. Whether to break on non key frames.</param>
/// <param name="audioSampleRate">Optional. Specify a specific audio sample rate, e.g. 44100.</param>
/// <param name="maxAudioBitDepth">Optional. The maximum audio bit depth.</param>
/// <param name="audioBitRate">Optional. Specify an audio bitrate to encode to, e.g. 128000. If omitted this will be left to encoder defaults.</param>
/// <param name="audioChannels">Optional. Specify a specific number of audio channels to encode to, e.g. 2.</param>
/// <param name="maxAudioChannels">Optional. Specify a maximum number of audio channels to encode to, e.g. 2.</param>
/// <param name="profile">Optional. Specify a specific an encoder profile (varies by encoder), e.g. main, baseline, high.</param>
/// <param name="level">Optional. Specify a level for the encoder profile (varies by encoder), e.g. 3, 3.1.</param>
/// <param name="framerate">Optional. A specific video framerate to encode to, e.g. 23.976. Generally this should be omitted unless the device has specific requirements.</param>
/// <param name="maxFramerate">Optional. A specific maximum video framerate to encode to, e.g. 23.976. Generally this should be omitted unless the device has specific requirements.</param>
/// <param name="copyTimestamps">Whether or not to copy timestamps when transcoding with an offset. Defaults to false.</param>
/// <param name="startTimeTicks">Optional. Specify a starting offset, in ticks. 1 tick = 10000 ms.</param>
/// <param name="width">Optional. The fixed horizontal resolution of the encoded video.</param>
/// <param name="height">Optional. The fixed vertical resolution of the encoded video.</param>
2021-04-04 01:29:25 +02:00
/// <param name="maxWidth">Optional. The maximum horizontal resolution of the encoded video.</param>
/// <param name="maxHeight">Optional. The maximum vertical resolution of the encoded video.</param>
2020-07-31 23:09:17 +02:00
/// <param name="videoBitRate">Optional. Specify a video bitrate to encode to, e.g. 500000. If omitted this will be left to encoder defaults.</param>
/// <param name="subtitleStreamIndex">Optional. The index of the subtitle stream to use. If omitted no subtitles will be used.</param>
/// <param name="subtitleMethod">Optional. Specify the subtitle delivery method.</param>
/// <param name="maxRefFrames">Optional.</param>
/// <param name="maxVideoBitDepth">Optional. The maximum video bit depth.</param>
/// <param name="requireAvc">Optional. Whether to require avc.</param>
/// <param name="deInterlace">Optional. Whether to deinterlace the video.</param>
2020-11-18 14:23:45 +01:00
/// <param name="requireNonAnamorphic">Optional. Whether to require a non anamorphic stream.</param>
2020-07-31 23:09:17 +02:00
/// <param name="transcodingMaxAudioChannels">Optional. The maximum number of audio channels to transcode.</param>
/// <param name="cpuCoreLimit">Optional. The limit of how many cpu cores to use.</param>
/// <param name="liveStreamId">The live stream id.</param>
/// <param name="enableMpegtsM2TsMode">Optional. Whether to enable the MpegtsM2Ts mode.</param>
2021-08-20 20:22:21 +02:00
/// <param name="videoCodec">Optional. Specify a video codec to encode to, e.g. h264. If omitted the server will auto-select using the url's extension. Options: h265, h264, mpeg4, theora, vp8, vp9, vpx (deprecated), wmv.</param>
2020-07-31 23:09:17 +02:00
/// <param name="subtitleCodec">Optional. Specify a subtitle codec to encode to.</param>
2020-11-30 11:36:11 +01:00
/// <param name="transcodeReasons">Optional. The transcoding reason.</param>
2020-07-31 23:09:17 +02:00
/// <param name="audioStreamIndex">Optional. The index of the audio stream to use. If omitted the first audio stream will be used.</param>
/// <param name="videoStreamIndex">Optional. The index of the video stream to use. If omitted the first video stream will be used.</param>
/// <param name="context">Optional. The <see cref="EncodingContext"/>.</param>
/// <param name="streamOptions">Optional. The streaming options.</param>
/// <param name="enableAdaptiveBitrateStreaming">Enable adaptive bitrate streaming.</param>
/// <response code="200">Video stream returned.</response>
/// <returns>A <see cref="FileResult"/> containing the playlist file.</returns>
2020-08-04 20:48:53 +02:00
[HttpGet("Videos/{itemId}/master.m3u8")]
[HttpHead("Videos/{itemId}/master.m3u8", Name = "HeadMasterHlsVideoPlaylist")]
2020-07-31 23:09:17 +02:00
[ProducesResponseType(StatusCodes.Status200OK)]
2020-09-02 01:26:49 +02:00
[ProducesPlaylistFile]
2020-07-31 23:09:17 +02:00
public async Task < ActionResult > GetMasterHlsVideoPlaylist (
2020-09-06 17:07:27 +02:00
[FromRoute, Required] Guid itemId ,
2020-07-31 23:09:17 +02:00
[FromQuery] bool? @static ,
[FromQuery] string? @params ,
[FromQuery] string? tag ,
[FromQuery] string? deviceProfileId ,
[FromQuery] string? playSessionId ,
[FromQuery] string? segmentContainer ,
[FromQuery] int? segmentLength ,
[FromQuery] int? minSegments ,
2020-09-09 22:28:30 +02:00
[FromQuery, Required] string mediaSourceId ,
2020-07-31 23:09:17 +02:00
[FromQuery] string? deviceId ,
[FromQuery] string? audioCodec ,
[FromQuery] bool? enableAutoStreamCopy ,
[FromQuery] bool? allowVideoStreamCopy ,
[FromQuery] bool? allowAudioStreamCopy ,
[FromQuery] bool? breakOnNonKeyFrames ,
[FromQuery] int? audioSampleRate ,
[FromQuery] int? maxAudioBitDepth ,
[FromQuery] int? audioBitRate ,
[FromQuery] int? audioChannels ,
[FromQuery] int? maxAudioChannels ,
[FromQuery] string? profile ,
[FromQuery] string? level ,
[FromQuery] float? framerate ,
[FromQuery] float? maxFramerate ,
[FromQuery] bool? copyTimestamps ,
[FromQuery] long? startTimeTicks ,
[FromQuery] int? width ,
[FromQuery] int? height ,
2021-04-04 01:29:25 +02:00
[FromQuery] int? maxWidth ,
[FromQuery] int? maxHeight ,
2020-07-31 23:09:17 +02:00
[FromQuery] int? videoBitRate ,
[FromQuery] int? subtitleStreamIndex ,
2021-03-05 02:25:52 +01:00
[FromQuery] SubtitleDeliveryMethod ? subtitleMethod ,
2020-07-31 23:09:17 +02:00
[FromQuery] int? maxRefFrames ,
[FromQuery] int? maxVideoBitDepth ,
[FromQuery] bool? requireAvc ,
[FromQuery] bool? deInterlace ,
[FromQuery] bool? requireNonAnamorphic ,
[FromQuery] int? transcodingMaxAudioChannels ,
[FromQuery] int? cpuCoreLimit ,
[FromQuery] string? liveStreamId ,
[FromQuery] bool? enableMpegtsM2TsMode ,
[FromQuery] string? videoCodec ,
[FromQuery] string? subtitleCodec ,
2020-11-30 11:36:11 +01:00
[FromQuery] string? transcodeReasons ,
2020-07-31 23:09:17 +02:00
[FromQuery] int? audioStreamIndex ,
[FromQuery] int? videoStreamIndex ,
2021-03-05 02:25:52 +01:00
[FromQuery] EncodingContext ? context ,
2020-07-31 23:09:17 +02:00
[FromQuery] Dictionary < string , string > streamOptions ,
[FromQuery] bool enableAdaptiveBitrateStreaming = true )
{
var streamingRequest = new HlsVideoRequestDto
{
Id = itemId ,
2021-03-22 20:53:55 +01:00
Static = @static ? ? false ,
2020-07-31 23:09:17 +02:00
Params = @params ,
Tag = tag ,
DeviceProfileId = deviceProfileId ,
PlaySessionId = playSessionId ,
SegmentContainer = segmentContainer ,
SegmentLength = segmentLength ,
MinSegments = minSegments ,
MediaSourceId = mediaSourceId ,
DeviceId = deviceId ,
AudioCodec = audioCodec ,
EnableAutoStreamCopy = enableAutoStreamCopy ? ? true ,
AllowAudioStreamCopy = allowAudioStreamCopy ? ? true ,
AllowVideoStreamCopy = allowVideoStreamCopy ? ? true ,
BreakOnNonKeyFrames = breakOnNonKeyFrames ? ? false ,
AudioSampleRate = audioSampleRate ,
MaxAudioChannels = maxAudioChannels ,
AudioBitRate = audioBitRate ,
MaxAudioBitDepth = maxAudioBitDepth ,
AudioChannels = audioChannels ,
Profile = profile ,
Level = level ,
Framerate = framerate ,
MaxFramerate = maxFramerate ,
2021-03-22 20:53:55 +01:00
CopyTimestamps = copyTimestamps ? ? false ,
2020-07-31 23:09:17 +02:00
StartTimeTicks = startTimeTicks ,
Width = width ,
Height = height ,
2021-04-04 01:29:25 +02:00
MaxWidth = maxWidth ,
MaxHeight = maxHeight ,
2020-07-31 23:09:17 +02:00
VideoBitRate = videoBitRate ,
SubtitleStreamIndex = subtitleStreamIndex ,
2021-03-05 02:25:52 +01:00
SubtitleMethod = subtitleMethod ? ? SubtitleDeliveryMethod . Encode ,
2020-07-31 23:09:17 +02:00
MaxRefFrames = maxRefFrames ,
MaxVideoBitDepth = maxVideoBitDepth ,
2021-03-22 20:53:55 +01:00
RequireAvc = requireAvc ? ? false ,
DeInterlace = deInterlace ? ? false ,
RequireNonAnamorphic = requireNonAnamorphic ? ? false ,
2020-07-31 23:09:17 +02:00
TranscodingMaxAudioChannels = transcodingMaxAudioChannels ,
CpuCoreLimit = cpuCoreLimit ,
LiveStreamId = liveStreamId ,
2021-03-22 20:53:55 +01:00
EnableMpegtsM2TsMode = enableMpegtsM2TsMode ? ? false ,
2020-07-31 23:09:17 +02:00
VideoCodec = videoCodec ,
SubtitleCodec = subtitleCodec ,
2020-11-30 11:36:11 +01:00
TranscodeReasons = transcodeReasons ,
2020-07-31 23:09:17 +02:00
AudioStreamIndex = audioStreamIndex ,
VideoStreamIndex = videoStreamIndex ,
2021-03-05 02:25:52 +01:00
Context = context ? ? EncodingContext . Streaming ,
2020-07-31 23:09:17 +02:00
StreamOptions = streamOptions ,
EnableAdaptiveBitrateStreaming = enableAdaptiveBitrateStreaming
} ;
2020-11-20 02:56:08 +01:00
return await _dynamicHlsHelper . GetMasterHlsPlaylist ( TranscodingJobType , streamingRequest , enableAdaptiveBitrateStreaming ) . ConfigureAwait ( false ) ;
2020-07-31 23:09:17 +02:00
}
/// <summary>
/// Gets an audio hls playlist stream.
/// </summary>
/// <param name="itemId">The item id.</param>
/// <param name="static">Optional. If true, the original file will be streamed statically without any encoding. Use either no url extension or the original file extension. true/false.</param>
/// <param name="params">The streaming parameters.</param>
/// <param name="tag">The tag.</param>
/// <param name="deviceProfileId">Optional. The dlna device profile id to utilize.</param>
/// <param name="playSessionId">The play session id.</param>
/// <param name="segmentContainer">The segment container.</param>
2020-11-18 14:23:45 +01:00
/// <param name="segmentLength">The segment length.</param>
2020-07-31 23:09:17 +02:00
/// <param name="minSegments">The minimum number of segments.</param>
/// <param name="mediaSourceId">The media version id, if playing an alternate version.</param>
/// <param name="deviceId">The device id of the client requesting. Used to stop encoding processes when needed.</param>
/// <param name="audioCodec">Optional. Specify a audio codec to encode to, e.g. mp3. If omitted the server will auto-select using the url's extension. Options: aac, mp3, vorbis, wma.</param>
/// <param name="enableAutoStreamCopy">Whether or not to allow automatic stream copy if requested values match the original source. Defaults to true.</param>
/// <param name="allowVideoStreamCopy">Whether or not to allow copying of the video stream url.</param>
/// <param name="allowAudioStreamCopy">Whether or not to allow copying of the audio stream url.</param>
/// <param name="breakOnNonKeyFrames">Optional. Whether to break on non key frames.</param>
/// <param name="audioSampleRate">Optional. Specify a specific audio sample rate, e.g. 44100.</param>
/// <param name="maxAudioBitDepth">Optional. The maximum audio bit depth.</param>
2020-10-31 09:09:22 +01:00
/// <param name="maxStreamingBitrate">Optional. The maximum streaming bitrate.</param>
2020-07-31 23:09:17 +02:00
/// <param name="audioBitRate">Optional. Specify an audio bitrate to encode to, e.g. 128000. If omitted this will be left to encoder defaults.</param>
/// <param name="audioChannels">Optional. Specify a specific number of audio channels to encode to, e.g. 2.</param>
/// <param name="maxAudioChannels">Optional. Specify a maximum number of audio channels to encode to, e.g. 2.</param>
/// <param name="profile">Optional. Specify a specific an encoder profile (varies by encoder), e.g. main, baseline, high.</param>
/// <param name="level">Optional. Specify a level for the encoder profile (varies by encoder), e.g. 3, 3.1.</param>
/// <param name="framerate">Optional. A specific video framerate to encode to, e.g. 23.976. Generally this should be omitted unless the device has specific requirements.</param>
/// <param name="maxFramerate">Optional. A specific maximum video framerate to encode to, e.g. 23.976. Generally this should be omitted unless the device has specific requirements.</param>
/// <param name="copyTimestamps">Whether or not to copy timestamps when transcoding with an offset. Defaults to false.</param>
/// <param name="startTimeTicks">Optional. Specify a starting offset, in ticks. 1 tick = 10000 ms.</param>
/// <param name="width">Optional. The fixed horizontal resolution of the encoded video.</param>
/// <param name="height">Optional. The fixed vertical resolution of the encoded video.</param>
/// <param name="videoBitRate">Optional. Specify a video bitrate to encode to, e.g. 500000. If omitted this will be left to encoder defaults.</param>
/// <param name="subtitleStreamIndex">Optional. The index of the subtitle stream to use. If omitted no subtitles will be used.</param>
/// <param name="subtitleMethod">Optional. Specify the subtitle delivery method.</param>
/// <param name="maxRefFrames">Optional.</param>
/// <param name="maxVideoBitDepth">Optional. The maximum video bit depth.</param>
/// <param name="requireAvc">Optional. Whether to require avc.</param>
/// <param name="deInterlace">Optional. Whether to deinterlace the video.</param>
2020-11-18 14:23:45 +01:00
/// <param name="requireNonAnamorphic">Optional. Whether to require a non anamorphic stream.</param>
2020-07-31 23:09:17 +02:00
/// <param name="transcodingMaxAudioChannels">Optional. The maximum number of audio channels to transcode.</param>
/// <param name="cpuCoreLimit">Optional. The limit of how many cpu cores to use.</param>
/// <param name="liveStreamId">The live stream id.</param>
/// <param name="enableMpegtsM2TsMode">Optional. Whether to enable the MpegtsM2Ts mode.</param>
2021-08-20 20:22:21 +02:00
/// <param name="videoCodec">Optional. Specify a video codec to encode to, e.g. h264. If omitted the server will auto-select using the url's extension. Options: h265, h264, mpeg4, theora, vp8, vp9, vpx (deprecated), wmv.</param>
2020-07-31 23:09:17 +02:00
/// <param name="subtitleCodec">Optional. Specify a subtitle codec to encode to.</param>
2020-11-30 11:36:11 +01:00
/// <param name="transcodeReasons">Optional. The transcoding reason.</param>
2020-07-31 23:09:17 +02:00
/// <param name="audioStreamIndex">Optional. The index of the audio stream to use. If omitted the first audio stream will be used.</param>
/// <param name="videoStreamIndex">Optional. The index of the video stream to use. If omitted the first video stream will be used.</param>
/// <param name="context">Optional. The <see cref="EncodingContext"/>.</param>
/// <param name="streamOptions">Optional. The streaming options.</param>
/// <param name="enableAdaptiveBitrateStreaming">Enable adaptive bitrate streaming.</param>
/// <response code="200">Audio stream returned.</response>
/// <returns>A <see cref="FileResult"/> containing the playlist file.</returns>
2020-08-04 20:48:53 +02:00
[HttpGet("Audio/{itemId}/master.m3u8")]
[HttpHead("Audio/{itemId}/master.m3u8", Name = "HeadMasterHlsAudioPlaylist")]
2020-07-31 23:09:17 +02:00
[ProducesResponseType(StatusCodes.Status200OK)]
2020-09-02 01:26:49 +02:00
[ProducesPlaylistFile]
2020-07-31 23:09:17 +02:00
public async Task < ActionResult > GetMasterHlsAudioPlaylist (
2020-09-06 17:07:27 +02:00
[FromRoute, Required] Guid itemId ,
2020-07-31 23:09:17 +02:00
[FromQuery] bool? @static ,
[FromQuery] string? @params ,
[FromQuery] string? tag ,
[FromQuery] string? deviceProfileId ,
[FromQuery] string? playSessionId ,
[FromQuery] string? segmentContainer ,
[FromQuery] int? segmentLength ,
[FromQuery] int? minSegments ,
2020-09-08 02:45:06 +02:00
[FromQuery, Required] string mediaSourceId ,
2020-07-31 23:09:17 +02:00
[FromQuery] string? deviceId ,
[FromQuery] string? audioCodec ,
[FromQuery] bool? enableAutoStreamCopy ,
[FromQuery] bool? allowVideoStreamCopy ,
[FromQuery] bool? allowAudioStreamCopy ,
[FromQuery] bool? breakOnNonKeyFrames ,
[FromQuery] int? audioSampleRate ,
[FromQuery] int? maxAudioBitDepth ,
2020-10-31 09:09:22 +01:00
[FromQuery] int? maxStreamingBitrate ,
2020-07-31 23:09:17 +02:00
[FromQuery] int? audioBitRate ,
[FromQuery] int? audioChannels ,
[FromQuery] int? maxAudioChannels ,
[FromQuery] string? profile ,
[FromQuery] string? level ,
[FromQuery] float? framerate ,
[FromQuery] float? maxFramerate ,
[FromQuery] bool? copyTimestamps ,
[FromQuery] long? startTimeTicks ,
[FromQuery] int? width ,
[FromQuery] int? height ,
[FromQuery] int? videoBitRate ,
[FromQuery] int? subtitleStreamIndex ,
2021-03-05 02:25:52 +01:00
[FromQuery] SubtitleDeliveryMethod ? subtitleMethod ,
2020-07-31 23:09:17 +02:00
[FromQuery] int? maxRefFrames ,
[FromQuery] int? maxVideoBitDepth ,
[FromQuery] bool? requireAvc ,
[FromQuery] bool? deInterlace ,
[FromQuery] bool? requireNonAnamorphic ,
[FromQuery] int? transcodingMaxAudioChannels ,
[FromQuery] int? cpuCoreLimit ,
[FromQuery] string? liveStreamId ,
[FromQuery] bool? enableMpegtsM2TsMode ,
[FromQuery] string? videoCodec ,
[FromQuery] string? subtitleCodec ,
2020-11-30 11:36:11 +01:00
[FromQuery] string? transcodeReasons ,
2020-07-31 23:09:17 +02:00
[FromQuery] int? audioStreamIndex ,
[FromQuery] int? videoStreamIndex ,
2021-03-05 02:25:52 +01:00
[FromQuery] EncodingContext ? context ,
2020-07-31 23:09:17 +02:00
[FromQuery] Dictionary < string , string > streamOptions ,
[FromQuery] bool enableAdaptiveBitrateStreaming = true )
{
var streamingRequest = new HlsAudioRequestDto
{
Id = itemId ,
2021-03-22 20:53:55 +01:00
Static = @static ? ? false ,
2020-07-31 23:09:17 +02:00
Params = @params ,
Tag = tag ,
DeviceProfileId = deviceProfileId ,
PlaySessionId = playSessionId ,
SegmentContainer = segmentContainer ,
SegmentLength = segmentLength ,
MinSegments = minSegments ,
MediaSourceId = mediaSourceId ,
DeviceId = deviceId ,
AudioCodec = audioCodec ,
EnableAutoStreamCopy = enableAutoStreamCopy ? ? true ,
AllowAudioStreamCopy = allowAudioStreamCopy ? ? true ,
AllowVideoStreamCopy = allowVideoStreamCopy ? ? true ,
BreakOnNonKeyFrames = breakOnNonKeyFrames ? ? false ,
AudioSampleRate = audioSampleRate ,
MaxAudioChannels = maxAudioChannels ,
2020-10-31 09:09:22 +01:00
AudioBitRate = audioBitRate ? ? maxStreamingBitrate ,
2020-07-31 23:09:17 +02:00
MaxAudioBitDepth = maxAudioBitDepth ,
AudioChannels = audioChannels ,
Profile = profile ,
Level = level ,
Framerate = framerate ,
MaxFramerate = maxFramerate ,
2021-03-22 20:53:55 +01:00
CopyTimestamps = copyTimestamps ? ? false ,
2020-07-31 23:09:17 +02:00
StartTimeTicks = startTimeTicks ,
Width = width ,
Height = height ,
VideoBitRate = videoBitRate ,
SubtitleStreamIndex = subtitleStreamIndex ,
2021-03-05 02:25:52 +01:00
SubtitleMethod = subtitleMethod ? ? SubtitleDeliveryMethod . Encode ,
2020-07-31 23:09:17 +02:00
MaxRefFrames = maxRefFrames ,
MaxVideoBitDepth = maxVideoBitDepth ,
2021-03-22 20:53:55 +01:00
RequireAvc = requireAvc ? ? false ,
DeInterlace = deInterlace ? ? false ,
RequireNonAnamorphic = requireNonAnamorphic ? ? false ,
2020-07-31 23:09:17 +02:00
TranscodingMaxAudioChannels = transcodingMaxAudioChannels ,
CpuCoreLimit = cpuCoreLimit ,
LiveStreamId = liveStreamId ,
2021-03-22 20:53:55 +01:00
EnableMpegtsM2TsMode = enableMpegtsM2TsMode ? ? false ,
2020-07-31 23:09:17 +02:00
VideoCodec = videoCodec ,
SubtitleCodec = subtitleCodec ,
2020-11-30 11:36:11 +01:00
TranscodeReasons = transcodeReasons ,
2020-07-31 23:09:17 +02:00
AudioStreamIndex = audioStreamIndex ,
VideoStreamIndex = videoStreamIndex ,
2021-03-05 02:25:52 +01:00
Context = context ? ? EncodingContext . Streaming ,
2020-07-31 23:09:17 +02:00
StreamOptions = streamOptions ,
EnableAdaptiveBitrateStreaming = enableAdaptiveBitrateStreaming
} ;
2020-11-20 02:56:08 +01:00
return await _dynamicHlsHelper . GetMasterHlsPlaylist ( TranscodingJobType , streamingRequest , enableAdaptiveBitrateStreaming ) . ConfigureAwait ( false ) ;
2020-07-31 23:09:17 +02:00
}
/// <summary>
/// Gets a video stream using HTTP live streaming.
/// </summary>
/// <param name="itemId">The item id.</param>
/// <param name="static">Optional. If true, the original file will be streamed statically without any encoding. Use either no url extension or the original file extension. true/false.</param>
/// <param name="params">The streaming parameters.</param>
/// <param name="tag">The tag.</param>
/// <param name="deviceProfileId">Optional. The dlna device profile id to utilize.</param>
/// <param name="playSessionId">The play session id.</param>
/// <param name="segmentContainer">The segment container.</param>
2020-11-18 14:23:45 +01:00
/// <param name="segmentLength">The segment length.</param>
2020-07-31 23:09:17 +02:00
/// <param name="minSegments">The minimum number of segments.</param>
/// <param name="mediaSourceId">The media version id, if playing an alternate version.</param>
/// <param name="deviceId">The device id of the client requesting. Used to stop encoding processes when needed.</param>
/// <param name="audioCodec">Optional. Specify a audio codec to encode to, e.g. mp3. If omitted the server will auto-select using the url's extension. Options: aac, mp3, vorbis, wma.</param>
/// <param name="enableAutoStreamCopy">Whether or not to allow automatic stream copy if requested values match the original source. Defaults to true.</param>
/// <param name="allowVideoStreamCopy">Whether or not to allow copying of the video stream url.</param>
/// <param name="allowAudioStreamCopy">Whether or not to allow copying of the audio stream url.</param>
/// <param name="breakOnNonKeyFrames">Optional. Whether to break on non key frames.</param>
/// <param name="audioSampleRate">Optional. Specify a specific audio sample rate, e.g. 44100.</param>
/// <param name="maxAudioBitDepth">Optional. The maximum audio bit depth.</param>
/// <param name="audioBitRate">Optional. Specify an audio bitrate to encode to, e.g. 128000. If omitted this will be left to encoder defaults.</param>
/// <param name="audioChannels">Optional. Specify a specific number of audio channels to encode to, e.g. 2.</param>
/// <param name="maxAudioChannels">Optional. Specify a maximum number of audio channels to encode to, e.g. 2.</param>
/// <param name="profile">Optional. Specify a specific an encoder profile (varies by encoder), e.g. main, baseline, high.</param>
/// <param name="level">Optional. Specify a level for the encoder profile (varies by encoder), e.g. 3, 3.1.</param>
/// <param name="framerate">Optional. A specific video framerate to encode to, e.g. 23.976. Generally this should be omitted unless the device has specific requirements.</param>
/// <param name="maxFramerate">Optional. A specific maximum video framerate to encode to, e.g. 23.976. Generally this should be omitted unless the device has specific requirements.</param>
/// <param name="copyTimestamps">Whether or not to copy timestamps when transcoding with an offset. Defaults to false.</param>
/// <param name="startTimeTicks">Optional. Specify a starting offset, in ticks. 1 tick = 10000 ms.</param>
/// <param name="width">Optional. The fixed horizontal resolution of the encoded video.</param>
/// <param name="height">Optional. The fixed vertical resolution of the encoded video.</param>
2021-04-04 01:29:25 +02:00
/// <param name="maxWidth">Optional. The maximum horizontal resolution of the encoded video.</param>
/// <param name="maxHeight">Optional. The maximum vertical resolution of the encoded video.</param>
2020-07-31 23:09:17 +02:00
/// <param name="videoBitRate">Optional. Specify a video bitrate to encode to, e.g. 500000. If omitted this will be left to encoder defaults.</param>
/// <param name="subtitleStreamIndex">Optional. The index of the subtitle stream to use. If omitted no subtitles will be used.</param>
/// <param name="subtitleMethod">Optional. Specify the subtitle delivery method.</param>
/// <param name="maxRefFrames">Optional.</param>
/// <param name="maxVideoBitDepth">Optional. The maximum video bit depth.</param>
/// <param name="requireAvc">Optional. Whether to require avc.</param>
/// <param name="deInterlace">Optional. Whether to deinterlace the video.</param>
2020-11-18 14:23:45 +01:00
/// <param name="requireNonAnamorphic">Optional. Whether to require a non anamorphic stream.</param>
2020-07-31 23:09:17 +02:00
/// <param name="transcodingMaxAudioChannels">Optional. The maximum number of audio channels to transcode.</param>
/// <param name="cpuCoreLimit">Optional. The limit of how many cpu cores to use.</param>
/// <param name="liveStreamId">The live stream id.</param>
/// <param name="enableMpegtsM2TsMode">Optional. Whether to enable the MpegtsM2Ts mode.</param>
2021-08-20 20:22:21 +02:00
/// <param name="videoCodec">Optional. Specify a video codec to encode to, e.g. h264. If omitted the server will auto-select using the url's extension. Options: h265, h264, mpeg4, theora, vp8, vp9, vpx (deprecated), wmv.</param>
2020-07-31 23:09:17 +02:00
/// <param name="subtitleCodec">Optional. Specify a subtitle codec to encode to.</param>
2020-11-30 11:36:11 +01:00
/// <param name="transcodeReasons">Optional. The transcoding reason.</param>
2020-07-31 23:09:17 +02:00
/// <param name="audioStreamIndex">Optional. The index of the audio stream to use. If omitted the first audio stream will be used.</param>
/// <param name="videoStreamIndex">Optional. The index of the video stream to use. If omitted the first video stream will be used.</param>
/// <param name="context">Optional. The <see cref="EncodingContext"/>.</param>
/// <param name="streamOptions">Optional. The streaming options.</param>
/// <response code="200">Video stream returned.</response>
/// <returns>A <see cref="FileResult"/> containing the audio file.</returns>
2020-08-04 20:48:53 +02:00
[HttpGet("Videos/{itemId}/main.m3u8")]
2020-07-31 23:09:17 +02:00
[ProducesResponseType(StatusCodes.Status200OK)]
2020-09-02 01:26:49 +02:00
[ProducesPlaylistFile]
2020-07-31 23:09:17 +02:00
public async Task < ActionResult > GetVariantHlsVideoPlaylist (
2020-09-06 17:07:27 +02:00
[FromRoute, Required] Guid itemId ,
2020-07-31 23:09:17 +02:00
[FromQuery] bool? @static ,
[FromQuery] string? @params ,
[FromQuery] string? tag ,
[FromQuery] string? deviceProfileId ,
[FromQuery] string? playSessionId ,
[FromQuery] string? segmentContainer ,
[FromQuery] int? segmentLength ,
[FromQuery] int? minSegments ,
[FromQuery] string? mediaSourceId ,
[FromQuery] string? deviceId ,
[FromQuery] string? audioCodec ,
[FromQuery] bool? enableAutoStreamCopy ,
[FromQuery] bool? allowVideoStreamCopy ,
[FromQuery] bool? allowAudioStreamCopy ,
[FromQuery] bool? breakOnNonKeyFrames ,
[FromQuery] int? audioSampleRate ,
[FromQuery] int? maxAudioBitDepth ,
[FromQuery] int? audioBitRate ,
[FromQuery] int? audioChannels ,
[FromQuery] int? maxAudioChannels ,
[FromQuery] string? profile ,
[FromQuery] string? level ,
[FromQuery] float? framerate ,
[FromQuery] float? maxFramerate ,
[FromQuery] bool? copyTimestamps ,
[FromQuery] long? startTimeTicks ,
[FromQuery] int? width ,
[FromQuery] int? height ,
2021-04-04 01:29:25 +02:00
[FromQuery] int? maxWidth ,
[FromQuery] int? maxHeight ,
2020-07-31 23:09:17 +02:00
[FromQuery] int? videoBitRate ,
[FromQuery] int? subtitleStreamIndex ,
2021-03-05 02:25:52 +01:00
[FromQuery] SubtitleDeliveryMethod ? subtitleMethod ,
2020-07-31 23:09:17 +02:00
[FromQuery] int? maxRefFrames ,
[FromQuery] int? maxVideoBitDepth ,
[FromQuery] bool? requireAvc ,
[FromQuery] bool? deInterlace ,
[FromQuery] bool? requireNonAnamorphic ,
[FromQuery] int? transcodingMaxAudioChannels ,
[FromQuery] int? cpuCoreLimit ,
[FromQuery] string? liveStreamId ,
[FromQuery] bool? enableMpegtsM2TsMode ,
[FromQuery] string? videoCodec ,
[FromQuery] string? subtitleCodec ,
2020-11-30 11:36:11 +01:00
[FromQuery] string? transcodeReasons ,
2020-07-31 23:09:17 +02:00
[FromQuery] int? audioStreamIndex ,
[FromQuery] int? videoStreamIndex ,
2021-03-05 02:25:52 +01:00
[FromQuery] EncodingContext ? context ,
2020-07-31 23:09:17 +02:00
[FromQuery] Dictionary < string , string > streamOptions )
{
2021-05-08 00:33:24 +02:00
using var cancellationTokenSource = new CancellationTokenSource ( ) ;
2020-07-31 23:09:17 +02:00
var streamingRequest = new VideoRequestDto
{
Id = itemId ,
2021-03-22 20:53:55 +01:00
Static = @static ? ? false ,
2020-07-31 23:09:17 +02:00
Params = @params ,
Tag = tag ,
DeviceProfileId = deviceProfileId ,
PlaySessionId = playSessionId ,
SegmentContainer = segmentContainer ,
SegmentLength = segmentLength ,
MinSegments = minSegments ,
MediaSourceId = mediaSourceId ,
DeviceId = deviceId ,
AudioCodec = audioCodec ,
EnableAutoStreamCopy = enableAutoStreamCopy ? ? true ,
AllowAudioStreamCopy = allowAudioStreamCopy ? ? true ,
AllowVideoStreamCopy = allowVideoStreamCopy ? ? true ,
BreakOnNonKeyFrames = breakOnNonKeyFrames ? ? false ,
AudioSampleRate = audioSampleRate ,
MaxAudioChannels = maxAudioChannels ,
AudioBitRate = audioBitRate ,
MaxAudioBitDepth = maxAudioBitDepth ,
AudioChannels = audioChannels ,
Profile = profile ,
Level = level ,
Framerate = framerate ,
MaxFramerate = maxFramerate ,
2021-03-22 20:53:55 +01:00
CopyTimestamps = copyTimestamps ? ? false ,
2020-07-31 23:09:17 +02:00
StartTimeTicks = startTimeTicks ,
Width = width ,
Height = height ,
2021-04-04 01:29:25 +02:00
MaxWidth = maxWidth ,
MaxHeight = maxHeight ,
2020-07-31 23:09:17 +02:00
VideoBitRate = videoBitRate ,
SubtitleStreamIndex = subtitleStreamIndex ,
2021-03-05 02:25:52 +01:00
SubtitleMethod = subtitleMethod ? ? SubtitleDeliveryMethod . Encode ,
2020-07-31 23:09:17 +02:00
MaxRefFrames = maxRefFrames ,
MaxVideoBitDepth = maxVideoBitDepth ,
2021-03-22 20:53:55 +01:00
RequireAvc = requireAvc ? ? false ,
DeInterlace = deInterlace ? ? false ,
RequireNonAnamorphic = requireNonAnamorphic ? ? false ,
2020-07-31 23:09:17 +02:00
TranscodingMaxAudioChannels = transcodingMaxAudioChannels ,
CpuCoreLimit = cpuCoreLimit ,
LiveStreamId = liveStreamId ,
2021-03-22 20:53:55 +01:00
EnableMpegtsM2TsMode = enableMpegtsM2TsMode ? ? false ,
2020-07-31 23:09:17 +02:00
VideoCodec = videoCodec ,
SubtitleCodec = subtitleCodec ,
2020-11-30 11:36:11 +01:00
TranscodeReasons = transcodeReasons ,
2020-07-31 23:09:17 +02:00
AudioStreamIndex = audioStreamIndex ,
VideoStreamIndex = videoStreamIndex ,
2021-03-05 02:25:52 +01:00
Context = context ? ? EncodingContext . Streaming ,
2020-07-31 23:09:17 +02:00
StreamOptions = streamOptions
} ;
2022-01-11 23:30:30 +01:00
return await GetVariantPlaylistInternal ( streamingRequest , cancellationTokenSource )
2020-07-31 23:09:17 +02:00
. ConfigureAwait ( false ) ;
}
/// <summary>
/// Gets an audio stream using HTTP live streaming.
/// </summary>
/// <param name="itemId">The item id.</param>
/// <param name="static">Optional. If true, the original file will be streamed statically without any encoding. Use either no url extension or the original file extension. true/false.</param>
/// <param name="params">The streaming parameters.</param>
/// <param name="tag">The tag.</param>
/// <param name="deviceProfileId">Optional. The dlna device profile id to utilize.</param>
/// <param name="playSessionId">The play session id.</param>
/// <param name="segmentContainer">The segment container.</param>
2020-11-18 14:23:45 +01:00
/// <param name="segmentLength">The segment length.</param>
2020-07-31 23:09:17 +02:00
/// <param name="minSegments">The minimum number of segments.</param>
/// <param name="mediaSourceId">The media version id, if playing an alternate version.</param>
/// <param name="deviceId">The device id of the client requesting. Used to stop encoding processes when needed.</param>
/// <param name="audioCodec">Optional. Specify a audio codec to encode to, e.g. mp3. If omitted the server will auto-select using the url's extension. Options: aac, mp3, vorbis, wma.</param>
/// <param name="enableAutoStreamCopy">Whether or not to allow automatic stream copy if requested values match the original source. Defaults to true.</param>
/// <param name="allowVideoStreamCopy">Whether or not to allow copying of the video stream url.</param>
/// <param name="allowAudioStreamCopy">Whether or not to allow copying of the audio stream url.</param>
/// <param name="breakOnNonKeyFrames">Optional. Whether to break on non key frames.</param>
/// <param name="audioSampleRate">Optional. Specify a specific audio sample rate, e.g. 44100.</param>
/// <param name="maxAudioBitDepth">Optional. The maximum audio bit depth.</param>
2020-10-31 09:09:22 +01:00
/// <param name="maxStreamingBitrate">Optional. The maximum streaming bitrate.</param>
2020-07-31 23:09:17 +02:00
/// <param name="audioBitRate">Optional. Specify an audio bitrate to encode to, e.g. 128000. If omitted this will be left to encoder defaults.</param>
/// <param name="audioChannels">Optional. Specify a specific number of audio channels to encode to, e.g. 2.</param>
/// <param name="maxAudioChannels">Optional. Specify a maximum number of audio channels to encode to, e.g. 2.</param>
/// <param name="profile">Optional. Specify a specific an encoder profile (varies by encoder), e.g. main, baseline, high.</param>
/// <param name="level">Optional. Specify a level for the encoder profile (varies by encoder), e.g. 3, 3.1.</param>
/// <param name="framerate">Optional. A specific video framerate to encode to, e.g. 23.976. Generally this should be omitted unless the device has specific requirements.</param>
/// <param name="maxFramerate">Optional. A specific maximum video framerate to encode to, e.g. 23.976. Generally this should be omitted unless the device has specific requirements.</param>
/// <param name="copyTimestamps">Whether or not to copy timestamps when transcoding with an offset. Defaults to false.</param>
/// <param name="startTimeTicks">Optional. Specify a starting offset, in ticks. 1 tick = 10000 ms.</param>
/// <param name="width">Optional. The fixed horizontal resolution of the encoded video.</param>
/// <param name="height">Optional. The fixed vertical resolution of the encoded video.</param>
/// <param name="videoBitRate">Optional. Specify a video bitrate to encode to, e.g. 500000. If omitted this will be left to encoder defaults.</param>
/// <param name="subtitleStreamIndex">Optional. The index of the subtitle stream to use. If omitted no subtitles will be used.</param>
/// <param name="subtitleMethod">Optional. Specify the subtitle delivery method.</param>
/// <param name="maxRefFrames">Optional.</param>
/// <param name="maxVideoBitDepth">Optional. The maximum video bit depth.</param>
/// <param name="requireAvc">Optional. Whether to require avc.</param>
/// <param name="deInterlace">Optional. Whether to deinterlace the video.</param>
2020-11-18 14:23:45 +01:00
/// <param name="requireNonAnamorphic">Optional. Whether to require a non anamorphic stream.</param>
2020-07-31 23:09:17 +02:00
/// <param name="transcodingMaxAudioChannels">Optional. The maximum number of audio channels to transcode.</param>
/// <param name="cpuCoreLimit">Optional. The limit of how many cpu cores to use.</param>
/// <param name="liveStreamId">The live stream id.</param>
/// <param name="enableMpegtsM2TsMode">Optional. Whether to enable the MpegtsM2Ts mode.</param>
/// <param name="videoCodec">Optional. Specify a video codec to encode to, e.g. h264. If omitted the server will auto-select using the url's extension. Options: h265, h264, mpeg4, theora, vpx, wmv.</param>
/// <param name="subtitleCodec">Optional. Specify a subtitle codec to encode to.</param>
2020-11-30 11:36:11 +01:00
/// <param name="transcodeReasons">Optional. The transcoding reason.</param>
2020-07-31 23:09:17 +02:00
/// <param name="audioStreamIndex">Optional. The index of the audio stream to use. If omitted the first audio stream will be used.</param>
/// <param name="videoStreamIndex">Optional. The index of the video stream to use. If omitted the first video stream will be used.</param>
/// <param name="context">Optional. The <see cref="EncodingContext"/>.</param>
/// <param name="streamOptions">Optional. The streaming options.</param>
/// <response code="200">Audio stream returned.</response>
/// <returns>A <see cref="FileResult"/> containing the audio file.</returns>
2020-08-04 20:48:53 +02:00
[HttpGet("Audio/{itemId}/main.m3u8")]
2020-07-31 23:09:17 +02:00
[ProducesResponseType(StatusCodes.Status200OK)]
2020-09-02 01:26:49 +02:00
[ProducesPlaylistFile]
2020-07-31 23:09:17 +02:00
public async Task < ActionResult > GetVariantHlsAudioPlaylist (
2020-09-06 17:07:27 +02:00
[FromRoute, Required] Guid itemId ,
2020-07-31 23:09:17 +02:00
[FromQuery] bool? @static ,
[FromQuery] string? @params ,
[FromQuery] string? tag ,
[FromQuery] string? deviceProfileId ,
[FromQuery] string? playSessionId ,
[FromQuery] string? segmentContainer ,
[FromQuery] int? segmentLength ,
[FromQuery] int? minSegments ,
[FromQuery] string? mediaSourceId ,
[FromQuery] string? deviceId ,
[FromQuery] string? audioCodec ,
[FromQuery] bool? enableAutoStreamCopy ,
[FromQuery] bool? allowVideoStreamCopy ,
[FromQuery] bool? allowAudioStreamCopy ,
[FromQuery] bool? breakOnNonKeyFrames ,
[FromQuery] int? audioSampleRate ,
[FromQuery] int? maxAudioBitDepth ,
2020-10-31 09:09:22 +01:00
[FromQuery] int? maxStreamingBitrate ,
2020-07-31 23:09:17 +02:00
[FromQuery] int? audioBitRate ,
[FromQuery] int? audioChannels ,
[FromQuery] int? maxAudioChannels ,
[FromQuery] string? profile ,
[FromQuery] string? level ,
[FromQuery] float? framerate ,
[FromQuery] float? maxFramerate ,
[FromQuery] bool? copyTimestamps ,
[FromQuery] long? startTimeTicks ,
[FromQuery] int? width ,
[FromQuery] int? height ,
[FromQuery] int? videoBitRate ,
[FromQuery] int? subtitleStreamIndex ,
2021-03-05 02:25:52 +01:00
[FromQuery] SubtitleDeliveryMethod ? subtitleMethod ,
2020-07-31 23:09:17 +02:00
[FromQuery] int? maxRefFrames ,
[FromQuery] int? maxVideoBitDepth ,
[FromQuery] bool? requireAvc ,
[FromQuery] bool? deInterlace ,
[FromQuery] bool? requireNonAnamorphic ,
[FromQuery] int? transcodingMaxAudioChannels ,
[FromQuery] int? cpuCoreLimit ,
[FromQuery] string? liveStreamId ,
[FromQuery] bool? enableMpegtsM2TsMode ,
[FromQuery] string? videoCodec ,
[FromQuery] string? subtitleCodec ,
2020-11-30 11:36:11 +01:00
[FromQuery] string? transcodeReasons ,
2020-07-31 23:09:17 +02:00
[FromQuery] int? audioStreamIndex ,
[FromQuery] int? videoStreamIndex ,
2021-03-05 02:25:52 +01:00
[FromQuery] EncodingContext ? context ,
2020-07-31 23:09:17 +02:00
[FromQuery] Dictionary < string , string > streamOptions )
{
2021-05-08 00:33:24 +02:00
using var cancellationTokenSource = new CancellationTokenSource ( ) ;
2020-07-31 23:09:17 +02:00
var streamingRequest = new StreamingRequestDto
{
Id = itemId ,
2021-03-22 20:53:55 +01:00
Static = @static ? ? false ,
2020-07-31 23:09:17 +02:00
Params = @params ,
Tag = tag ,
DeviceProfileId = deviceProfileId ,
PlaySessionId = playSessionId ,
SegmentContainer = segmentContainer ,
SegmentLength = segmentLength ,
MinSegments = minSegments ,
MediaSourceId = mediaSourceId ,
DeviceId = deviceId ,
AudioCodec = audioCodec ,
EnableAutoStreamCopy = enableAutoStreamCopy ? ? true ,
AllowAudioStreamCopy = allowAudioStreamCopy ? ? true ,
AllowVideoStreamCopy = allowVideoStreamCopy ? ? true ,
BreakOnNonKeyFrames = breakOnNonKeyFrames ? ? false ,
AudioSampleRate = audioSampleRate ,
MaxAudioChannels = maxAudioChannels ,
2020-10-31 09:09:22 +01:00
AudioBitRate = audioBitRate ? ? maxStreamingBitrate ,
2020-07-31 23:09:17 +02:00
MaxAudioBitDepth = maxAudioBitDepth ,
AudioChannels = audioChannels ,
Profile = profile ,
Level = level ,
Framerate = framerate ,
MaxFramerate = maxFramerate ,
2021-03-22 20:53:55 +01:00
CopyTimestamps = copyTimestamps ? ? false ,
2020-07-31 23:09:17 +02:00
StartTimeTicks = startTimeTicks ,
Width = width ,
Height = height ,
VideoBitRate = videoBitRate ,
SubtitleStreamIndex = subtitleStreamIndex ,
2021-03-05 02:25:52 +01:00
SubtitleMethod = subtitleMethod ? ? SubtitleDeliveryMethod . Encode ,
2020-07-31 23:09:17 +02:00
MaxRefFrames = maxRefFrames ,
MaxVideoBitDepth = maxVideoBitDepth ,
2021-03-22 20:53:55 +01:00
RequireAvc = requireAvc ? ? false ,
DeInterlace = deInterlace ? ? false ,
RequireNonAnamorphic = requireNonAnamorphic ? ? false ,
2020-07-31 23:09:17 +02:00
TranscodingMaxAudioChannels = transcodingMaxAudioChannels ,
CpuCoreLimit = cpuCoreLimit ,
LiveStreamId = liveStreamId ,
2021-03-22 20:53:55 +01:00
EnableMpegtsM2TsMode = enableMpegtsM2TsMode ? ? false ,
2020-07-31 23:09:17 +02:00
VideoCodec = videoCodec ,
SubtitleCodec = subtitleCodec ,
2020-11-30 11:36:11 +01:00
TranscodeReasons = transcodeReasons ,
2020-07-31 23:09:17 +02:00
AudioStreamIndex = audioStreamIndex ,
VideoStreamIndex = videoStreamIndex ,
2021-03-05 02:25:52 +01:00
Context = context ? ? EncodingContext . Streaming ,
2020-07-31 23:09:17 +02:00
StreamOptions = streamOptions
} ;
2022-01-11 23:30:30 +01:00
return await GetVariantPlaylistInternal ( streamingRequest , cancellationTokenSource )
2020-07-31 23:09:17 +02:00
. ConfigureAwait ( false ) ;
}
/// <summary>
/// Gets a video stream using HTTP live streaming.
/// </summary>
/// <param name="itemId">The item id.</param>
/// <param name="playlistId">The playlist id.</param>
/// <param name="segmentId">The segment id.</param>
/// <param name="container">The video container. Possible values are: ts, webm, asf, wmv, ogv, mp4, m4v, mkv, mpeg, mpg, avi, 3gp, wmv, wtv, m2ts, mov, iso, flv. </param>
2021-09-23 15:29:12 +02:00
/// <param name="runtimeTicks">The position of the requested segment in ticks.</param>
/// <param name="actualSegmentLengthTicks">The length of the requested segment in ticks.</param>
2020-07-31 23:09:17 +02:00
/// <param name="static">Optional. If true, the original file will be streamed statically without any encoding. Use either no url extension or the original file extension. true/false.</param>
/// <param name="params">The streaming parameters.</param>
/// <param name="tag">The tag.</param>
/// <param name="deviceProfileId">Optional. The dlna device profile id to utilize.</param>
/// <param name="playSessionId">The play session id.</param>
/// <param name="segmentContainer">The segment container.</param>
2021-09-23 15:29:12 +02:00
/// <param name="segmentLength">The desired segment length.</param>
2020-07-31 23:09:17 +02:00
/// <param name="minSegments">The minimum number of segments.</param>
/// <param name="mediaSourceId">The media version id, if playing an alternate version.</param>
/// <param name="deviceId">The device id of the client requesting. Used to stop encoding processes when needed.</param>
/// <param name="audioCodec">Optional. Specify a audio codec to encode to, e.g. mp3. If omitted the server will auto-select using the url's extension. Options: aac, mp3, vorbis, wma.</param>
/// <param name="enableAutoStreamCopy">Whether or not to allow automatic stream copy if requested values match the original source. Defaults to true.</param>
/// <param name="allowVideoStreamCopy">Whether or not to allow copying of the video stream url.</param>
/// <param name="allowAudioStreamCopy">Whether or not to allow copying of the audio stream url.</param>
/// <param name="breakOnNonKeyFrames">Optional. Whether to break on non key frames.</param>
/// <param name="audioSampleRate">Optional. Specify a specific audio sample rate, e.g. 44100.</param>
/// <param name="maxAudioBitDepth">Optional. The maximum audio bit depth.</param>
/// <param name="audioBitRate">Optional. Specify an audio bitrate to encode to, e.g. 128000. If omitted this will be left to encoder defaults.</param>
/// <param name="audioChannels">Optional. Specify a specific number of audio channels to encode to, e.g. 2.</param>
/// <param name="maxAudioChannels">Optional. Specify a maximum number of audio channels to encode to, e.g. 2.</param>
/// <param name="profile">Optional. Specify a specific an encoder profile (varies by encoder), e.g. main, baseline, high.</param>
/// <param name="level">Optional. Specify a level for the encoder profile (varies by encoder), e.g. 3, 3.1.</param>
/// <param name="framerate">Optional. A specific video framerate to encode to, e.g. 23.976. Generally this should be omitted unless the device has specific requirements.</param>
/// <param name="maxFramerate">Optional. A specific maximum video framerate to encode to, e.g. 23.976. Generally this should be omitted unless the device has specific requirements.</param>
/// <param name="copyTimestamps">Whether or not to copy timestamps when transcoding with an offset. Defaults to false.</param>
/// <param name="startTimeTicks">Optional. Specify a starting offset, in ticks. 1 tick = 10000 ms.</param>
/// <param name="width">Optional. The fixed horizontal resolution of the encoded video.</param>
/// <param name="height">Optional. The fixed vertical resolution of the encoded video.</param>
2021-04-04 01:29:25 +02:00
/// <param name="maxWidth">Optional. The maximum horizontal resolution of the encoded video.</param>
/// <param name="maxHeight">Optional. The maximum vertical resolution of the encoded video.</param>
2020-07-31 23:09:17 +02:00
/// <param name="videoBitRate">Optional. Specify a video bitrate to encode to, e.g. 500000. If omitted this will be left to encoder defaults.</param>
/// <param name="subtitleStreamIndex">Optional. The index of the subtitle stream to use. If omitted no subtitles will be used.</param>
/// <param name="subtitleMethod">Optional. Specify the subtitle delivery method.</param>
/// <param name="maxRefFrames">Optional.</param>
/// <param name="maxVideoBitDepth">Optional. The maximum video bit depth.</param>
/// <param name="requireAvc">Optional. Whether to require avc.</param>
/// <param name="deInterlace">Optional. Whether to deinterlace the video.</param>
2020-11-18 14:23:45 +01:00
/// <param name="requireNonAnamorphic">Optional. Whether to require a non anamorphic stream.</param>
2020-07-31 23:09:17 +02:00
/// <param name="transcodingMaxAudioChannels">Optional. The maximum number of audio channels to transcode.</param>
/// <param name="cpuCoreLimit">Optional. The limit of how many cpu cores to use.</param>
/// <param name="liveStreamId">The live stream id.</param>
/// <param name="enableMpegtsM2TsMode">Optional. Whether to enable the MpegtsM2Ts mode.</param>
2021-08-20 20:22:21 +02:00
/// <param name="videoCodec">Optional. Specify a video codec to encode to, e.g. h264. If omitted the server will auto-select using the url's extension. Options: h265, h264, mpeg4, theora, vp8, vp9, vpx (deprecated), wmv.</param>
2020-07-31 23:09:17 +02:00
/// <param name="subtitleCodec">Optional. Specify a subtitle codec to encode to.</param>
2020-11-30 11:36:11 +01:00
/// <param name="transcodeReasons">Optional. The transcoding reason.</param>
2020-07-31 23:09:17 +02:00
/// <param name="audioStreamIndex">Optional. The index of the audio stream to use. If omitted the first audio stream will be used.</param>
/// <param name="videoStreamIndex">Optional. The index of the video stream to use. If omitted the first video stream will be used.</param>
/// <param name="context">Optional. The <see cref="EncodingContext"/>.</param>
/// <param name="streamOptions">Optional. The streaming options.</param>
/// <response code="200">Video stream returned.</response>
/// <returns>A <see cref="FileResult"/> containing the audio file.</returns>
2020-08-04 20:48:53 +02:00
[HttpGet("Videos/{itemId}/hls1/{playlistId}/{segmentId}.{container}")]
2020-07-31 23:09:17 +02:00
[ProducesResponseType(StatusCodes.Status200OK)]
2020-09-02 01:26:49 +02:00
[ProducesVideoFile]
2020-07-31 23:09:17 +02:00
[SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "playlistId", Justification = "Imported from ServiceStack")]
public async Task < ActionResult > GetHlsVideoSegment (
2020-09-06 17:07:27 +02:00
[FromRoute, Required] Guid itemId ,
[FromRoute, Required] string playlistId ,
[FromRoute, Required] int segmentId ,
2020-11-15 18:58:39 +01:00
[FromRoute, Required] string container ,
2021-09-23 15:29:12 +02:00
[FromQuery, Required] long runtimeTicks ,
[FromQuery, Required] long actualSegmentLengthTicks ,
2020-07-31 23:09:17 +02:00
[FromQuery] bool? @static ,
[FromQuery] string? @params ,
[FromQuery] string? tag ,
[FromQuery] string? deviceProfileId ,
[FromQuery] string? playSessionId ,
[FromQuery] string? segmentContainer ,
[FromQuery] int? segmentLength ,
[FromQuery] int? minSegments ,
[FromQuery] string? mediaSourceId ,
[FromQuery] string? deviceId ,
[FromQuery] string? audioCodec ,
[FromQuery] bool? enableAutoStreamCopy ,
[FromQuery] bool? allowVideoStreamCopy ,
[FromQuery] bool? allowAudioStreamCopy ,
[FromQuery] bool? breakOnNonKeyFrames ,
[FromQuery] int? audioSampleRate ,
[FromQuery] int? maxAudioBitDepth ,
[FromQuery] int? audioBitRate ,
[FromQuery] int? audioChannels ,
[FromQuery] int? maxAudioChannels ,
[FromQuery] string? profile ,
[FromQuery] string? level ,
[FromQuery] float? framerate ,
[FromQuery] float? maxFramerate ,
[FromQuery] bool? copyTimestamps ,
[FromQuery] long? startTimeTicks ,
[FromQuery] int? width ,
[FromQuery] int? height ,
2021-04-04 01:29:25 +02:00
[FromQuery] int? maxWidth ,
[FromQuery] int? maxHeight ,
2020-07-31 23:09:17 +02:00
[FromQuery] int? videoBitRate ,
[FromQuery] int? subtitleStreamIndex ,
2021-03-05 02:25:52 +01:00
[FromQuery] SubtitleDeliveryMethod ? subtitleMethod ,
2020-07-31 23:09:17 +02:00
[FromQuery] int? maxRefFrames ,
[FromQuery] int? maxVideoBitDepth ,
[FromQuery] bool? requireAvc ,
[FromQuery] bool? deInterlace ,
[FromQuery] bool? requireNonAnamorphic ,
[FromQuery] int? transcodingMaxAudioChannels ,
[FromQuery] int? cpuCoreLimit ,
[FromQuery] string? liveStreamId ,
[FromQuery] bool? enableMpegtsM2TsMode ,
[FromQuery] string? videoCodec ,
[FromQuery] string? subtitleCodec ,
2020-11-30 11:36:11 +01:00
[FromQuery] string? transcodeReasons ,
2020-07-31 23:09:17 +02:00
[FromQuery] int? audioStreamIndex ,
[FromQuery] int? videoStreamIndex ,
2021-03-05 02:25:52 +01:00
[FromQuery] EncodingContext ? context ,
2020-07-31 23:09:17 +02:00
[FromQuery] Dictionary < string , string > streamOptions )
{
var streamingRequest = new VideoRequestDto
{
Id = itemId ,
2021-09-23 15:29:12 +02:00
CurrentRuntimeTicks = runtimeTicks ,
ActualSegmentLengthTicks = actualSegmentLengthTicks ,
2020-07-31 23:09:17 +02:00
Container = container ,
2021-03-22 20:53:55 +01:00
Static = @static ? ? false ,
2020-07-31 23:09:17 +02:00
Params = @params ,
Tag = tag ,
DeviceProfileId = deviceProfileId ,
PlaySessionId = playSessionId ,
SegmentContainer = segmentContainer ,
SegmentLength = segmentLength ,
MinSegments = minSegments ,
MediaSourceId = mediaSourceId ,
DeviceId = deviceId ,
AudioCodec = audioCodec ,
EnableAutoStreamCopy = enableAutoStreamCopy ? ? true ,
AllowAudioStreamCopy = allowAudioStreamCopy ? ? true ,
AllowVideoStreamCopy = allowVideoStreamCopy ? ? true ,
BreakOnNonKeyFrames = breakOnNonKeyFrames ? ? false ,
AudioSampleRate = audioSampleRate ,
MaxAudioChannels = maxAudioChannels ,
AudioBitRate = audioBitRate ,
MaxAudioBitDepth = maxAudioBitDepth ,
AudioChannels = audioChannels ,
Profile = profile ,
Level = level ,
Framerate = framerate ,
MaxFramerate = maxFramerate ,
2021-03-22 20:53:55 +01:00
CopyTimestamps = copyTimestamps ? ? false ,
2020-07-31 23:09:17 +02:00
StartTimeTicks = startTimeTicks ,
Width = width ,
Height = height ,
2021-04-04 01:29:25 +02:00
MaxWidth = maxWidth ,
MaxHeight = maxHeight ,
2020-07-31 23:09:17 +02:00
VideoBitRate = videoBitRate ,
SubtitleStreamIndex = subtitleStreamIndex ,
2021-03-05 02:25:52 +01:00
SubtitleMethod = subtitleMethod ? ? SubtitleDeliveryMethod . Encode ,
2020-07-31 23:09:17 +02:00
MaxRefFrames = maxRefFrames ,
MaxVideoBitDepth = maxVideoBitDepth ,
2021-03-22 20:53:55 +01:00
RequireAvc = requireAvc ? ? false ,
DeInterlace = deInterlace ? ? false ,
RequireNonAnamorphic = requireNonAnamorphic ? ? false ,
2020-07-31 23:09:17 +02:00
TranscodingMaxAudioChannels = transcodingMaxAudioChannels ,
CpuCoreLimit = cpuCoreLimit ,
LiveStreamId = liveStreamId ,
2021-03-22 20:53:55 +01:00
EnableMpegtsM2TsMode = enableMpegtsM2TsMode ? ? false ,
2020-07-31 23:09:17 +02:00
VideoCodec = videoCodec ,
SubtitleCodec = subtitleCodec ,
2020-11-30 11:36:11 +01:00
TranscodeReasons = transcodeReasons ,
2020-07-31 23:09:17 +02:00
AudioStreamIndex = audioStreamIndex ,
VideoStreamIndex = videoStreamIndex ,
2021-03-05 02:25:52 +01:00
Context = context ? ? EncodingContext . Streaming ,
2020-07-31 23:09:17 +02:00
StreamOptions = streamOptions
} ;
return await GetDynamicSegment ( streamingRequest , segmentId )
. ConfigureAwait ( false ) ;
}
/// <summary>
/// Gets a video stream using HTTP live streaming.
/// </summary>
/// <param name="itemId">The item id.</param>
/// <param name="playlistId">The playlist id.</param>
/// <param name="segmentId">The segment id.</param>
/// <param name="container">The video container. Possible values are: ts, webm, asf, wmv, ogv, mp4, m4v, mkv, mpeg, mpg, avi, 3gp, wmv, wtv, m2ts, mov, iso, flv. </param>
2021-09-23 15:29:12 +02:00
/// <param name="runtimeTicks">The position of the requested segment in ticks.</param>
/// <param name="actualSegmentLengthTicks">The length of the requested segment in ticks.</param>
2020-07-31 23:09:17 +02:00
/// <param name="static">Optional. If true, the original file will be streamed statically without any encoding. Use either no url extension or the original file extension. true/false.</param>
/// <param name="params">The streaming parameters.</param>
/// <param name="tag">The tag.</param>
/// <param name="deviceProfileId">Optional. The dlna device profile id to utilize.</param>
/// <param name="playSessionId">The play session id.</param>
/// <param name="segmentContainer">The segment container.</param>
2020-11-18 14:23:45 +01:00
/// <param name="segmentLength">The segment length.</param>
2020-07-31 23:09:17 +02:00
/// <param name="minSegments">The minimum number of segments.</param>
/// <param name="mediaSourceId">The media version id, if playing an alternate version.</param>
/// <param name="deviceId">The device id of the client requesting. Used to stop encoding processes when needed.</param>
/// <param name="audioCodec">Optional. Specify a audio codec to encode to, e.g. mp3. If omitted the server will auto-select using the url's extension. Options: aac, mp3, vorbis, wma.</param>
/// <param name="enableAutoStreamCopy">Whether or not to allow automatic stream copy if requested values match the original source. Defaults to true.</param>
/// <param name="allowVideoStreamCopy">Whether or not to allow copying of the video stream url.</param>
/// <param name="allowAudioStreamCopy">Whether or not to allow copying of the audio stream url.</param>
/// <param name="breakOnNonKeyFrames">Optional. Whether to break on non key frames.</param>
/// <param name="audioSampleRate">Optional. Specify a specific audio sample rate, e.g. 44100.</param>
/// <param name="maxAudioBitDepth">Optional. The maximum audio bit depth.</param>
2020-10-31 09:09:22 +01:00
/// <param name="maxStreamingBitrate">Optional. The maximum streaming bitrate.</param>
2020-07-31 23:09:17 +02:00
/// <param name="audioBitRate">Optional. Specify an audio bitrate to encode to, e.g. 128000. If omitted this will be left to encoder defaults.</param>
/// <param name="audioChannels">Optional. Specify a specific number of audio channels to encode to, e.g. 2.</param>
/// <param name="maxAudioChannels">Optional. Specify a maximum number of audio channels to encode to, e.g. 2.</param>
/// <param name="profile">Optional. Specify a specific an encoder profile (varies by encoder), e.g. main, baseline, high.</param>
/// <param name="level">Optional. Specify a level for the encoder profile (varies by encoder), e.g. 3, 3.1.</param>
/// <param name="framerate">Optional. A specific video framerate to encode to, e.g. 23.976. Generally this should be omitted unless the device has specific requirements.</param>
/// <param name="maxFramerate">Optional. A specific maximum video framerate to encode to, e.g. 23.976. Generally this should be omitted unless the device has specific requirements.</param>
/// <param name="copyTimestamps">Whether or not to copy timestamps when transcoding with an offset. Defaults to false.</param>
/// <param name="startTimeTicks">Optional. Specify a starting offset, in ticks. 1 tick = 10000 ms.</param>
/// <param name="width">Optional. The fixed horizontal resolution of the encoded video.</param>
/// <param name="height">Optional. The fixed vertical resolution of the encoded video.</param>
/// <param name="videoBitRate">Optional. Specify a video bitrate to encode to, e.g. 500000. If omitted this will be left to encoder defaults.</param>
/// <param name="subtitleStreamIndex">Optional. The index of the subtitle stream to use. If omitted no subtitles will be used.</param>
/// <param name="subtitleMethod">Optional. Specify the subtitle delivery method.</param>
/// <param name="maxRefFrames">Optional.</param>
/// <param name="maxVideoBitDepth">Optional. The maximum video bit depth.</param>
/// <param name="requireAvc">Optional. Whether to require avc.</param>
/// <param name="deInterlace">Optional. Whether to deinterlace the video.</param>
2020-11-18 14:23:45 +01:00
/// <param name="requireNonAnamorphic">Optional. Whether to require a non anamorphic stream.</param>
2020-07-31 23:09:17 +02:00
/// <param name="transcodingMaxAudioChannels">Optional. The maximum number of audio channels to transcode.</param>
/// <param name="cpuCoreLimit">Optional. The limit of how many cpu cores to use.</param>
/// <param name="liveStreamId">The live stream id.</param>
/// <param name="enableMpegtsM2TsMode">Optional. Whether to enable the MpegtsM2Ts mode.</param>
/// <param name="videoCodec">Optional. Specify a video codec to encode to, e.g. h264. If omitted the server will auto-select using the url's extension. Options: h265, h264, mpeg4, theora, vpx, wmv.</param>
/// <param name="subtitleCodec">Optional. Specify a subtitle codec to encode to.</param>
2020-11-30 11:36:11 +01:00
/// <param name="transcodeReasons">Optional. The transcoding reason.</param>
2020-07-31 23:09:17 +02:00
/// <param name="audioStreamIndex">Optional. The index of the audio stream to use. If omitted the first audio stream will be used.</param>
/// <param name="videoStreamIndex">Optional. The index of the video stream to use. If omitted the first video stream will be used.</param>
/// <param name="context">Optional. The <see cref="EncodingContext"/>.</param>
/// <param name="streamOptions">Optional. The streaming options.</param>
/// <response code="200">Video stream returned.</response>
/// <returns>A <see cref="FileResult"/> containing the audio file.</returns>
2020-08-04 20:48:53 +02:00
[HttpGet("Audio/{itemId}/hls1/{playlistId}/{segmentId}.{container}")]
2020-07-31 23:09:17 +02:00
[ProducesResponseType(StatusCodes.Status200OK)]
2020-09-02 01:26:49 +02:00
[ProducesAudioFile]
2020-07-31 23:09:17 +02:00
[SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "playlistId", Justification = "Imported from ServiceStack")]
public async Task < ActionResult > GetHlsAudioSegment (
2020-09-06 17:07:27 +02:00
[FromRoute, Required] Guid itemId ,
[FromRoute, Required] string playlistId ,
[FromRoute, Required] int segmentId ,
2020-11-15 18:58:39 +01:00
[FromRoute, Required] string container ,
2021-09-23 15:29:12 +02:00
[FromQuery, Required] long runtimeTicks ,
[FromQuery, Required] long actualSegmentLengthTicks ,
2020-07-31 23:09:17 +02:00
[FromQuery] bool? @static ,
[FromQuery] string? @params ,
[FromQuery] string? tag ,
[FromQuery] string? deviceProfileId ,
[FromQuery] string? playSessionId ,
[FromQuery] string? segmentContainer ,
[FromQuery] int? segmentLength ,
[FromQuery] int? minSegments ,
[FromQuery] string? mediaSourceId ,
[FromQuery] string? deviceId ,
[FromQuery] string? audioCodec ,
[FromQuery] bool? enableAutoStreamCopy ,
[FromQuery] bool? allowVideoStreamCopy ,
[FromQuery] bool? allowAudioStreamCopy ,
[FromQuery] bool? breakOnNonKeyFrames ,
[FromQuery] int? audioSampleRate ,
[FromQuery] int? maxAudioBitDepth ,
2020-10-31 09:09:22 +01:00
[FromQuery] int? maxStreamingBitrate ,
2020-07-31 23:09:17 +02:00
[FromQuery] int? audioBitRate ,
[FromQuery] int? audioChannels ,
[FromQuery] int? maxAudioChannels ,
[FromQuery] string? profile ,
[FromQuery] string? level ,
[FromQuery] float? framerate ,
[FromQuery] float? maxFramerate ,
[FromQuery] bool? copyTimestamps ,
[FromQuery] long? startTimeTicks ,
[FromQuery] int? width ,
[FromQuery] int? height ,
[FromQuery] int? videoBitRate ,
[FromQuery] int? subtitleStreamIndex ,
2021-03-05 02:25:52 +01:00
[FromQuery] SubtitleDeliveryMethod ? subtitleMethod ,
2020-07-31 23:09:17 +02:00
[FromQuery] int? maxRefFrames ,
[FromQuery] int? maxVideoBitDepth ,
[FromQuery] bool? requireAvc ,
[FromQuery] bool? deInterlace ,
[FromQuery] bool? requireNonAnamorphic ,
[FromQuery] int? transcodingMaxAudioChannels ,
[FromQuery] int? cpuCoreLimit ,
[FromQuery] string? liveStreamId ,
[FromQuery] bool? enableMpegtsM2TsMode ,
[FromQuery] string? videoCodec ,
[FromQuery] string? subtitleCodec ,
2020-11-30 11:36:11 +01:00
[FromQuery] string? transcodeReasons ,
2020-07-31 23:09:17 +02:00
[FromQuery] int? audioStreamIndex ,
[FromQuery] int? videoStreamIndex ,
2021-03-05 02:25:52 +01:00
[FromQuery] EncodingContext ? context ,
2020-07-31 23:09:17 +02:00
[FromQuery] Dictionary < string , string > streamOptions )
{
var streamingRequest = new StreamingRequestDto
{
Id = itemId ,
Container = container ,
2021-09-23 15:29:12 +02:00
CurrentRuntimeTicks = runtimeTicks ,
ActualSegmentLengthTicks = actualSegmentLengthTicks ,
2021-03-22 20:53:55 +01:00
Static = @static ? ? false ,
2020-07-31 23:09:17 +02:00
Params = @params ,
Tag = tag ,
DeviceProfileId = deviceProfileId ,
PlaySessionId = playSessionId ,
SegmentContainer = segmentContainer ,
SegmentLength = segmentLength ,
MinSegments = minSegments ,
MediaSourceId = mediaSourceId ,
DeviceId = deviceId ,
AudioCodec = audioCodec ,
EnableAutoStreamCopy = enableAutoStreamCopy ? ? true ,
AllowAudioStreamCopy = allowAudioStreamCopy ? ? true ,
AllowVideoStreamCopy = allowVideoStreamCopy ? ? true ,
BreakOnNonKeyFrames = breakOnNonKeyFrames ? ? false ,
AudioSampleRate = audioSampleRate ,
MaxAudioChannels = maxAudioChannels ,
2020-10-31 09:09:22 +01:00
AudioBitRate = audioBitRate ? ? maxStreamingBitrate ,
2020-07-31 23:09:17 +02:00
MaxAudioBitDepth = maxAudioBitDepth ,
AudioChannels = audioChannels ,
Profile = profile ,
Level = level ,
Framerate = framerate ,
MaxFramerate = maxFramerate ,
2021-03-22 20:53:55 +01:00
CopyTimestamps = copyTimestamps ? ? false ,
2020-07-31 23:09:17 +02:00
StartTimeTicks = startTimeTicks ,
Width = width ,
Height = height ,
VideoBitRate = videoBitRate ,
SubtitleStreamIndex = subtitleStreamIndex ,
2021-03-05 02:25:52 +01:00
SubtitleMethod = subtitleMethod ? ? SubtitleDeliveryMethod . Encode ,
2020-07-31 23:09:17 +02:00
MaxRefFrames = maxRefFrames ,
MaxVideoBitDepth = maxVideoBitDepth ,
2021-03-22 20:53:55 +01:00
RequireAvc = requireAvc ? ? false ,
DeInterlace = deInterlace ? ? false ,
RequireNonAnamorphic = requireNonAnamorphic ? ? false ,
2020-07-31 23:09:17 +02:00
TranscodingMaxAudioChannels = transcodingMaxAudioChannels ,
CpuCoreLimit = cpuCoreLimit ,
LiveStreamId = liveStreamId ,
2021-03-22 20:53:55 +01:00
EnableMpegtsM2TsMode = enableMpegtsM2TsMode ? ? false ,
2020-07-31 23:09:17 +02:00
VideoCodec = videoCodec ,
SubtitleCodec = subtitleCodec ,
2020-11-30 11:36:11 +01:00
TranscodeReasons = transcodeReasons ,
2020-07-31 23:09:17 +02:00
AudioStreamIndex = audioStreamIndex ,
VideoStreamIndex = videoStreamIndex ,
2021-03-05 02:25:52 +01:00
Context = context ? ? EncodingContext . Streaming ,
2020-07-31 23:09:17 +02:00
StreamOptions = streamOptions
} ;
return await GetDynamicSegment ( streamingRequest , segmentId )
. ConfigureAwait ( false ) ;
}
2022-01-11 23:30:30 +01:00
private async Task < ActionResult > GetVariantPlaylistInternal ( StreamingRequestDto streamingRequest , CancellationTokenSource cancellationTokenSource )
2020-07-31 23:09:17 +02:00
{
using var state = await StreamingHelpers . GetStreamingState (
streamingRequest ,
2022-10-06 13:57:47 +02:00
HttpContext ,
2020-07-31 23:09:17 +02:00
_mediaSourceManager ,
_userManager ,
_libraryManager ,
_serverConfigurationManager ,
_mediaEncoder ,
2021-04-20 23:03:36 +02:00
_encodingHelper ,
2020-07-31 23:09:17 +02:00
_dlnaManager ,
_deviceManager ,
_transcodingJobHelper ,
2020-11-20 02:56:08 +01:00
TranscodingJobType ,
2020-07-31 23:09:17 +02:00
cancellationTokenSource . Token )
. ConfigureAwait ( false ) ;
2021-09-23 15:29:12 +02:00
var request = new CreateMainPlaylistRequest (
state . MediaPath ,
state . SegmentLength * 1000 ,
state . RunTimeTicks ? ? 0 ,
state . Request . SegmentContainer ? ? string . Empty ,
"hls1/main/" ,
2022-06-14 00:27:31 +02:00
Request . QueryString . ToString ( ) ,
EncodingHelper . IsCopyCodec ( state . OutputVideoCodec ) ) ;
2021-09-23 15:29:12 +02:00
var playlist = _dynamicHlsPlaylistGenerator . CreateMainPlaylist ( request ) ;
2020-07-31 23:09:17 +02:00
2021-09-23 15:29:12 +02:00
return new FileContentResult ( Encoding . UTF8 . GetBytes ( playlist ) , MimeTypes . GetMimeType ( "playlist.m3u8" ) ) ;
2020-07-31 23:09:17 +02:00
}
private async Task < ActionResult > GetDynamicSegment ( StreamingRequestDto streamingRequest , int segmentId )
{
if ( ( streamingRequest . StartTimeTicks ? ? 0 ) > 0 )
{
throw new ArgumentException ( "StartTimeTicks is not allowed." ) ;
}
2021-06-04 14:36:58 +02:00
// CTS lifecycle is managed internally.
var cancellationTokenSource = new CancellationTokenSource ( ) ;
2020-07-31 23:09:17 +02:00
var cancellationToken = cancellationTokenSource . Token ;
2022-06-27 02:53:03 +02:00
var state = await StreamingHelpers . GetStreamingState (
2020-07-31 23:09:17 +02:00
streamingRequest ,
2022-10-06 13:57:47 +02:00
HttpContext ,
2020-07-31 23:09:17 +02:00
_mediaSourceManager ,
_userManager ,
_libraryManager ,
_serverConfigurationManager ,
_mediaEncoder ,
2021-04-20 23:03:36 +02:00
_encodingHelper ,
2020-07-31 23:09:17 +02:00
_dlnaManager ,
_deviceManager ,
_transcodingJobHelper ,
2020-11-20 02:56:08 +01:00
TranscodingJobType ,
2021-05-08 00:33:24 +02:00
cancellationToken )
2020-07-31 23:09:17 +02:00
. ConfigureAwait ( false ) ;
var playlistPath = Path . ChangeExtension ( state . OutputFilePath , ".m3u8" ) ;
var segmentPath = GetSegmentPath ( state , playlistPath , segmentId ) ;
2021-12-03 11:33:19 +01:00
var segmentExtension = EncodingHelper . GetSegmentFileExtension ( state . Request . SegmentContainer ) ;
2020-07-31 23:09:17 +02:00
TranscodingJobDto ? job ;
if ( System . IO . File . Exists ( segmentPath ) )
{
2020-11-20 02:56:08 +01:00
job = _transcodingJobHelper . OnTranscodeBeginRequest ( playlistPath , TranscodingJobType ) ;
2020-07-31 23:09:17 +02:00
_logger . LogDebug ( "returning {0} [it exists, try 1]" , segmentPath ) ;
return await GetSegmentResult ( state , playlistPath , segmentPath , segmentExtension , segmentId , job , cancellationToken ) . ConfigureAwait ( false ) ;
}
var transcodingLock = _transcodingJobHelper . GetTranscodingLock ( playlistPath ) ;
2021-05-08 00:33:24 +02:00
await transcodingLock . WaitAsync ( cancellationToken ) . ConfigureAwait ( false ) ;
2020-07-31 23:09:17 +02:00
var released = false ;
var startTranscoding = false ;
try
{
if ( System . IO . File . Exists ( segmentPath ) )
{
2020-11-20 02:56:08 +01:00
job = _transcodingJobHelper . OnTranscodeBeginRequest ( playlistPath , TranscodingJobType ) ;
2020-07-31 23:09:17 +02:00
transcodingLock . Release ( ) ;
released = true ;
_logger . LogDebug ( "returning {0} [it exists, try 2]" , segmentPath ) ;
return await GetSegmentResult ( state , playlistPath , segmentPath , segmentExtension , segmentId , job , cancellationToken ) . ConfigureAwait ( false ) ;
}
else
{
var currentTranscodingIndex = GetCurrentTranscodingIndex ( playlistPath , segmentExtension ) ;
var segmentGapRequiringTranscodingChange = 24 / state . SegmentLength ;
2020-11-07 18:39:32 +01:00
if ( segmentId = = - 1 )
{
2020-11-13 20:47:54 +01:00
_logger . LogDebug ( "Starting transcoding because fmp4 init file is being requested" ) ;
2020-11-07 18:39:32 +01:00
startTranscoding = true ;
segmentId = 0 ;
}
else if ( currentTranscodingIndex = = null )
2020-07-31 23:09:17 +02:00
{
_logger . LogDebug ( "Starting transcoding because currentTranscodingIndex=null" ) ;
startTranscoding = true ;
}
else if ( segmentId < currentTranscodingIndex . Value )
{
_logger . LogDebug ( "Starting transcoding because requestedIndex={0} and currentTranscodingIndex={1}" , segmentId , currentTranscodingIndex ) ;
startTranscoding = true ;
}
else if ( segmentId - currentTranscodingIndex . Value > segmentGapRequiringTranscodingChange )
{
_logger . LogDebug ( "Starting transcoding because segmentGap is {0} and max allowed gap is {1}. requestedIndex={2}" , segmentId - currentTranscodingIndex . Value , segmentGapRequiringTranscodingChange , segmentId ) ;
startTranscoding = true ;
}
if ( startTranscoding )
{
// If the playlist doesn't already exist, startup ffmpeg
try
{
2022-11-14 14:45:42 +01:00
// Delete old HLS files when segment deletion is active since ffmpeg doesn't clean them up by itself
var deleteFiles = _encodingOptions . EnableThrottling & & _encodingOptions . EnableSegmentDeletion ;
await _transcodingJobHelper . KillTranscodingJobs ( streamingRequest . DeviceId , streamingRequest . PlaySessionId , _ = > deleteFiles )
2020-07-31 23:09:17 +02:00
. ConfigureAwait ( false ) ;
if ( currentTranscodingIndex . HasValue )
{
DeleteLastFile ( playlistPath , segmentExtension , 0 ) ;
}
2021-09-23 15:29:12 +02:00
streamingRequest . StartTimeTicks = streamingRequest . CurrentRuntimeTicks ;
2020-07-31 23:09:17 +02:00
state . WaitForPath = segmentPath ;
job = await _transcodingJobHelper . StartFfMpeg (
state ,
playlistPath ,
2021-12-03 11:33:19 +01:00
GetCommandLineArguments ( playlistPath , state , false , segmentId ) ,
2020-07-31 23:09:17 +02:00
Request ,
2020-11-20 02:56:08 +01:00
TranscodingJobType ,
2020-07-31 23:09:17 +02:00
cancellationTokenSource ) . ConfigureAwait ( false ) ;
}
catch
{
state . Dispose ( ) ;
throw ;
}
// await WaitForMinimumSegmentCount(playlistPath, 1, cancellationTokenSource.Token).ConfigureAwait(false);
}
else
{
2020-11-20 02:56:08 +01:00
job = _transcodingJobHelper . OnTranscodeBeginRequest ( playlistPath , TranscodingJobType ) ;
2020-07-31 23:09:17 +02:00
if ( job ? . TranscodingThrottler ! = null )
{
await job . TranscodingThrottler . UnpauseTranscoding ( ) . ConfigureAwait ( false ) ;
}
}
}
}
finally
{
if ( ! released )
{
transcodingLock . Release ( ) ;
}
}
_logger . LogDebug ( "returning {0} [general case]" , segmentPath ) ;
2020-11-20 02:56:08 +01:00
job ? ? = _transcodingJobHelper . OnTranscodeBeginRequest ( playlistPath , TranscodingJobType ) ;
2020-07-31 23:09:17 +02:00
return await GetSegmentResult ( state , playlistPath , segmentPath , segmentExtension , segmentId , job , cancellationToken ) . ConfigureAwait ( false ) ;
}
2021-05-08 00:33:24 +02:00
private static double [ ] GetSegmentLengths ( StreamState state )
= > GetSegmentLengthsInternal ( state . RunTimeTicks ? ? 0 , state . SegmentLength ) ;
2020-07-31 23:09:17 +02:00
2021-05-08 00:33:24 +02:00
internal static double [ ] GetSegmentLengthsInternal ( long runtimeTicks , int segmentlength )
{
var segmentLengthTicks = TimeSpan . FromSeconds ( segmentlength ) . Ticks ;
var wholeSegments = runtimeTicks / segmentLengthTicks ;
var remainingTicks = runtimeTicks % segmentLengthTicks ;
2020-07-31 23:09:17 +02:00
2021-05-08 00:33:24 +02:00
var segmentsLen = wholeSegments + ( remainingTicks = = 0 ? 0 : 1 ) ;
var segments = new double [ segmentsLen ] ;
for ( int i = 0 ; i < wholeSegments ; i + + )
2020-07-31 23:09:17 +02:00
{
2021-05-08 00:33:24 +02:00
segments [ i ] = segmentlength ;
}
2020-07-31 23:09:17 +02:00
2021-05-08 00:33:24 +02:00
if ( remainingTicks ! = 0 )
{
segments [ ^ 1 ] = TimeSpan . FromTicks ( remainingTicks ) . TotalSeconds ;
2020-07-31 23:09:17 +02:00
}
2021-05-08 00:33:24 +02:00
return segments ;
2020-07-31 23:09:17 +02:00
}
2021-12-03 11:33:19 +01:00
private string GetCommandLineArguments ( string outputPath , StreamState state , bool isEventPlaylist , int startNumber )
2020-07-31 23:09:17 +02:00
{
2020-11-13 20:47:54 +01:00
var videoCodec = _encodingHelper . GetVideoEncoder ( state , _encodingOptions ) ;
2020-11-20 03:01:17 +01:00
var threads = EncodingHelper . GetNumberOfThreads ( state , _encodingOptions , videoCodec ) ;
2020-07-31 23:09:17 +02:00
if ( state . BaseRequest . BreakOnNonKeyFrames )
{
// FIXME: this is actually a workaround, as ideally it really should be the client which decides whether non-keyframe
// breakpoints are supported; but current implementation always uses "ffmpeg input seeking" which is liable
// to produce a missing part of video stream before first keyframe is encountered, which may lead to
// awkward cases like a few starting HLS segments having no video whatsoever, which breaks hls.js
_logger . LogInformation ( "Current HLS implementation doesn't support non-keyframe breaks but one is requested, ignoring that request" ) ;
state . BaseRequest . BreakOnNonKeyFrames = false ;
}
var mapArgs = state . IsOutputVideo ? _encodingHelper . GetMapArgs ( state ) : string . Empty ;
2020-11-14 02:04:06 +01:00
var directory = Path . GetDirectoryName ( outputPath ) ? ? throw new ArgumentException ( $"Provided path ({outputPath}) is not valid." , nameof ( outputPath ) ) ;
2020-11-08 08:13:00 +01:00
var outputFileNameWithoutExtension = Path . GetFileNameWithoutExtension ( outputPath ) ;
2020-11-16 05:36:55 +01:00
var outputPrefix = Path . Combine ( directory , outputFileNameWithoutExtension ) ;
2021-12-03 11:33:19 +01:00
var outputExtension = EncodingHelper . GetSegmentFileExtension ( state . Request . SegmentContainer ) ;
2020-11-07 18:39:32 +01:00
var outputTsArg = outputPrefix + "%d" + outputExtension ;
2020-07-31 23:09:17 +02:00
2022-03-09 14:28:58 +01:00
var segmentFormat = string . Empty ;
var segmentContainer = outputExtension . TrimStart ( '.' ) ;
var inputModifier = _encodingHelper . GetInputModifier ( state , _encodingOptions , segmentContainer ) ;
if ( string . Equals ( segmentContainer , "ts" , StringComparison . OrdinalIgnoreCase ) )
2020-07-31 23:09:17 +02:00
{
segmentFormat = "mpegts" ;
}
2022-03-09 14:28:58 +01:00
else if ( string . Equals ( segmentContainer , "mp4" , StringComparison . OrdinalIgnoreCase ) )
2020-11-07 18:39:32 +01:00
{
2021-07-12 20:20:50 +02:00
var outputFmp4HeaderArg = OperatingSystem . IsWindows ( ) switch
2020-11-08 08:13:00 +01:00
{
// on Windows, the path of fmp4 header file needs to be configured
2021-05-08 00:33:24 +02:00
true = > " -hls_fmp4_init_filename \"" + outputPrefix + "-1" + outputExtension + "\"" ,
2020-11-08 08:13:00 +01:00
// on Linux/Unix, ffmpeg generate fmp4 header file to m3u8 output folder
2021-05-08 00:33:24 +02:00
false = > " -hls_fmp4_init_filename \"" + outputFileNameWithoutExtension + "-1" + outputExtension + "\""
} ;
2020-11-08 08:13:00 +01:00
2020-11-07 18:39:32 +01:00
segmentFormat = "fmp4" + outputFmp4HeaderArg ;
}
else
{
2022-03-09 14:28:58 +01:00
_logger . LogError ( "Invalid HLS segment container: {SegmentContainer}, default to mpegts" , segmentContainer ) ;
segmentFormat = "mpegts" ;
2020-11-07 18:39:32 +01:00
}
2020-07-31 23:09:17 +02:00
2020-11-13 20:47:54 +01:00
var maxMuxingQueueSize = _encodingOptions . MaxMuxingQueueSize > 128
? _encodingOptions . MaxMuxingQueueSize . ToString ( CultureInfo . InvariantCulture )
2020-08-24 20:20:46 +02:00
: "128" ;
2021-12-03 11:33:19 +01:00
var baseUrlParam = string . Empty ;
if ( isEventPlaylist )
{
baseUrlParam = string . Format (
CultureInfo . InvariantCulture ,
" -hls_base_url \"hls/{0}/\"" ,
Path . GetFileNameWithoutExtension ( outputPath ) ) ;
}
2022-11-14 10:13:17 +01:00
var hlsArguments = GetHlsArguments ( isEventPlaylist , state . SegmentLength ) ;
2020-07-31 23:09:17 +02:00
return string . Format (
CultureInfo . InvariantCulture ,
2022-11-14 10:13:17 +01:00
"{0} {1} -map_metadata -1 -map_chapters -1 -threads {2} {3} {4} {5} -copyts -avoid_negative_ts disabled -max_muxing_queue_size {6} -f hls -max_delay 5000000 -hls_time {7} -hls_segment_type {8} -start_number {9}{10} -hls_segment_filename \"{11}\" {12} -y \"{13}\"" ,
2020-07-31 23:09:17 +02:00
inputModifier ,
2022-03-09 14:28:58 +01:00
_encodingHelper . GetInputArgument ( state , _encodingOptions , segmentContainer ) ,
2020-07-31 23:09:17 +02:00
threads ,
mapArgs ,
2021-12-03 11:33:19 +01:00
GetVideoArguments ( state , startNumber , isEventPlaylist ) ,
2020-11-13 20:47:54 +01:00
GetAudioArguments ( state ) ,
2020-08-24 20:20:46 +02:00
maxMuxingQueueSize ,
2020-07-31 23:09:17 +02:00
state . SegmentLength . ToString ( CultureInfo . InvariantCulture ) ,
segmentFormat ,
2021-12-03 11:33:19 +01:00
startNumber . ToString ( CultureInfo . InvariantCulture ) ,
baseUrlParam ,
2020-07-31 23:09:17 +02:00
outputTsArg ,
2022-11-14 10:13:17 +01:00
hlsArguments ,
2020-07-31 23:09:17 +02:00
outputPath ) . Trim ( ) ;
}
2022-11-14 10:13:17 +01:00
/// <summary>
/// Gets the HLS arguments for transcoding.
/// </summary>
/// <returns>The command line arguments for HLS transcoding.</returns>
private string GetHlsArguments ( bool isEventPlaylist , int segmentLength )
{
var enableThrottling = _encodingOptions . EnableThrottling ;
var enableSegmentDeletion = _encodingOptions . EnableSegmentDeletion ;
// Only enable segment deletion when throttling is enabled
if ( enableThrottling & & enableSegmentDeletion )
{
// Store enough segments for configured seconds of playback; this needs to be above throttling settings
var segmentCount = _encodingOptions . SegmentKeepSeconds / segmentLength ;
_logger . LogDebug ( "Using throttling and segment deletion, keeping {0} segments" , segmentCount ) ;
2022-11-14 14:39:08 +01:00
return string . Format ( CultureInfo . InvariantCulture , "-hls_list_size {0} -hls_flags delete_segments" , segmentCount . ToString ( CultureInfo . InvariantCulture ) ) ;
2022-11-14 10:13:17 +01:00
}
else
{
_logger . LogDebug ( "Using normal playback, is event playlist? {0}" , isEventPlaylist ) ;
return string . Format ( CultureInfo . InvariantCulture , "-hls_playlist_type {0} -hls_list_size 0" , isEventPlaylist ? "event" : "vod" ) ;
}
}
2020-11-13 20:47:54 +01:00
/// <summary>
/// Gets the audio arguments for transcoding.
/// </summary>
/// <param name="state">The <see cref="StreamState"/>.</param>
/// <returns>The command line arguments for audio transcoding.</returns>
private string GetAudioArguments ( StreamState state )
2020-07-31 23:09:17 +02:00
{
2020-11-11 12:04:58 +01:00
if ( state . AudioStream = = null )
{
return string . Empty ;
}
2020-07-31 23:09:17 +02:00
var audioCodec = _encodingHelper . GetAudioEncoder ( state ) ;
if ( ! state . IsOutputVideo )
{
if ( EncodingHelper . IsCopyCodec ( audioCodec ) )
{
2020-11-19 15:46:02 +01:00
var bitStreamArgs = EncodingHelper . GetAudioBitStreamArguments ( state , state . Request . SegmentContainer , state . MediaSource . Container ) ;
2020-11-14 07:22:15 +01:00
return "-acodec copy -strict -2" + bitStreamArgs ;
2020-07-31 23:09:17 +02:00
}
2020-11-19 15:46:02 +01:00
var audioTranscodeParams = string . Empty ;
2020-07-31 23:09:17 +02:00
2020-11-19 15:46:02 +01:00
audioTranscodeParams + = "-acodec " + audioCodec ;
2020-07-31 23:09:17 +02:00
if ( state . OutputAudioBitrate . HasValue )
{
2020-11-19 15:46:02 +01:00
audioTranscodeParams + = " -ab " + state . OutputAudioBitrate . Value . ToString ( CultureInfo . InvariantCulture ) ;
2020-07-31 23:09:17 +02:00
}
if ( state . OutputAudioChannels . HasValue )
{
2020-11-19 15:46:02 +01:00
audioTranscodeParams + = " -ac " + state . OutputAudioChannels . Value . ToString ( CultureInfo . InvariantCulture ) ;
2020-07-31 23:09:17 +02:00
}
if ( state . OutputAudioSampleRate . HasValue )
{
2020-11-19 15:46:02 +01:00
audioTranscodeParams + = " -ar " + state . OutputAudioSampleRate . Value . ToString ( CultureInfo . InvariantCulture ) ;
2020-07-31 23:09:17 +02:00
}
2020-11-19 15:46:02 +01:00
audioTranscodeParams + = " -vn" ;
return audioTranscodeParams ;
2020-07-31 23:09:17 +02:00
}
2022-06-06 17:29:39 +02:00
// flac and opus are experimental in mp4 muxer
var strictArgs = string . Empty ;
if ( string . Equals ( state . ActualOutputAudioCodec , "flac" , StringComparison . OrdinalIgnoreCase )
| | string . Equals ( state . ActualOutputAudioCodec , "opus" , StringComparison . OrdinalIgnoreCase ) )
{
strictArgs = " -strict -2" ;
}
2020-07-31 23:09:17 +02:00
if ( EncodingHelper . IsCopyCodec ( audioCodec ) )
{
2020-11-13 20:47:54 +01:00
var videoCodec = _encodingHelper . GetVideoEncoder ( state , _encodingOptions ) ;
2020-11-19 15:46:02 +01:00
var bitStreamArgs = EncodingHelper . GetAudioBitStreamArguments ( state , state . Request . SegmentContainer , state . MediaSource . Container ) ;
2022-06-06 17:29:39 +02:00
var copyArgs = "-codec:a:0 copy" + bitStreamArgs + strictArgs ;
2020-07-31 23:09:17 +02:00
if ( EncodingHelper . IsCopyCodec ( videoCodec ) & & state . EnableBreakOnNonKeyFrames ( videoCodec ) )
{
2022-06-06 17:29:39 +02:00
return copyArgs + " -copypriorss:a:0 0" ;
2020-07-31 23:09:17 +02:00
}
2022-06-06 17:29:39 +02:00
return copyArgs ;
2020-07-31 23:09:17 +02:00
}
2022-06-06 17:29:39 +02:00
var args = "-codec:a:0 " + audioCodec + strictArgs ;
2020-07-31 23:09:17 +02:00
var channels = state . OutputAudioChannels ;
if ( channels . HasValue )
{
args + = " -ac " + channels . Value ;
}
var bitrate = state . OutputAudioBitrate ;
if ( bitrate . HasValue )
{
args + = " -ab " + bitrate . Value . ToString ( CultureInfo . InvariantCulture ) ;
}
if ( state . OutputAudioSampleRate . HasValue )
{
args + = " -ar " + state . OutputAudioSampleRate . Value . ToString ( CultureInfo . InvariantCulture ) ;
}
2021-07-10 18:09:02 +02:00
args + = _encodingHelper . GetAudioFilterParam ( state , _encodingOptions ) ;
2020-07-31 23:09:17 +02:00
return args ;
}
2020-11-13 20:47:54 +01:00
/// <summary>
/// Gets the video arguments for transcoding.
/// </summary>
/// <param name="state">The <see cref="StreamState"/>.</param>
/// <param name="startNumber">The first number in the hls sequence.</param>
2021-12-03 11:33:19 +01:00
/// <param name="isEventPlaylist">Whether the playlist is EVENT or VOD.</param>
2020-11-13 20:47:54 +01:00
/// <returns>The command line arguments for video transcoding.</returns>
2021-12-03 11:33:19 +01:00
private string GetVideoArguments ( StreamState state , int startNumber , bool isEventPlaylist )
2020-07-31 23:09:17 +02:00
{
2020-11-13 20:47:54 +01:00
if ( state . VideoStream = = null )
{
return string . Empty ;
}
2020-07-31 23:09:17 +02:00
if ( ! state . IsOutputVideo )
{
return string . Empty ;
}
2020-11-13 20:47:54 +01:00
var codec = _encodingHelper . GetVideoEncoder ( state , _encodingOptions ) ;
2020-07-31 23:09:17 +02:00
var args = "-codec:v:0 " + codec ;
2020-11-07 18:39:32 +01:00
if ( string . Equals ( state . ActualOutputVideoCodec , "h265" , StringComparison . OrdinalIgnoreCase )
| | string . Equals ( state . ActualOutputVideoCodec , "hevc" , StringComparison . OrdinalIgnoreCase )
| | string . Equals ( codec , "h265" , StringComparison . OrdinalIgnoreCase )
| | string . Equals ( codec , "hevc" , StringComparison . OrdinalIgnoreCase ) )
{
2022-05-08 21:06:03 +02:00
if ( EncodingHelper . IsCopyCodec ( codec )
2022-08-01 20:25:45 +02:00
& & ( string . Equals ( state . VideoStream . VideoRangeType , "DOVI" , StringComparison . OrdinalIgnoreCase )
| | string . Equals ( state . VideoStream . CodecTag , "dovi" , StringComparison . OrdinalIgnoreCase )
2022-06-06 17:29:39 +02:00
| | string . Equals ( state . VideoStream . CodecTag , "dvh1" , StringComparison . OrdinalIgnoreCase )
2022-05-08 21:06:03 +02:00
| | string . Equals ( state . VideoStream . CodecTag , "dvhe" , StringComparison . OrdinalIgnoreCase ) ) )
{
// Prefer dvh1 to dvhe
2022-06-06 17:29:39 +02:00
args + = " -tag:v:0 dvh1 -strict -2" ;
2022-05-08 21:06:03 +02:00
}
else
{
// Prefer hvc1 to hev1
args + = " -tag:v:0 hvc1" ;
}
2020-11-07 18:39:32 +01:00
}
2020-09-20 15:35:46 +02:00
// if (state.EnableMpegtsM2TsMode)
2020-07-31 23:09:17 +02:00
// {
// args += " -mpegts_m2ts_mode 1";
// }
2020-11-13 20:47:54 +01:00
// See if we can save come cpu cycles by avoiding encoding.
2020-07-31 23:09:17 +02:00
if ( EncodingHelper . IsCopyCodec ( codec ) )
{
2021-12-03 11:33:19 +01:00
// If h264_mp4toannexb is ever added, do not use it for live tv.
2020-07-31 23:09:17 +02:00
if ( state . VideoStream ! = null & & ! string . Equals ( state . VideoStream . NalLengthSize , "0" , StringComparison . OrdinalIgnoreCase ) )
{
2020-11-19 15:46:02 +01:00
string bitStreamArgs = EncodingHelper . GetBitStreamArgs ( state . VideoStream ) ;
2020-07-31 23:09:17 +02:00
if ( ! string . IsNullOrEmpty ( bitStreamArgs ) )
{
args + = " " + bitStreamArgs ;
}
}
2020-11-14 08:46:17 +01:00
args + = " -start_at_zero" ;
2020-07-31 23:09:17 +02:00
}
else
{
2021-12-03 11:33:19 +01:00
args + = _encodingHelper . GetVideoQualityParam ( state , codec , _encodingOptions , isEventPlaylist ? DefaultEventEncoderPreset : DefaultVodEncoderPreset ) ;
2020-07-31 23:09:17 +02:00
2020-11-19 15:46:02 +01:00
// Set the key frame params for video encoding to match the hls segment time.
2021-12-03 11:33:19 +01:00
args + = _encodingHelper . GetHlsVideoKeyFrameArguments ( state , codec , state . SegmentLength , isEventPlaylist , startNumber ) ;
2020-07-31 23:09:17 +02:00
2022-08-15 12:48:34 +02:00
// Currently b-frames in libx265 breaks the FMP4-HLS playback on iOS, disable it for now.
2020-11-07 18:39:32 +01:00
if ( string . Equals ( codec , "libx265" , StringComparison . OrdinalIgnoreCase ) )
2020-07-31 23:09:17 +02:00
{
2020-11-07 18:39:32 +01:00
args + = " -bf 0" ;
2020-07-31 23:09:17 +02:00
}
// args += " -mixed-refs 0 -refs 3 -x264opts b_pyramid=0:weightb=0:weightp=0";
2021-12-01 17:49:50 +01:00
// video processing filters.
args + = _encodingHelper . GetVideoProcessingFilterParam ( state , _encodingOptions , codec ) ;
2020-07-31 23:09:17 +02:00
// -start_at_zero is necessary to use with -ss when seeking,
// otherwise the target position cannot be determined.
2021-12-01 17:49:50 +01:00
if ( state . SubtitleStream ! = null )
2020-07-31 23:09:17 +02:00
{
2021-12-01 17:49:50 +01:00
// Disable start_at_zero for external graphical subs
if ( ! ( state . SubtitleStream . IsExternal & & ! state . SubtitleStream . IsTextSubtitleStream ) )
{
args + = " -start_at_zero" ;
}
2020-07-31 23:09:17 +02:00
}
2021-12-03 11:33:19 +01:00
}
2020-07-31 23:09:17 +02:00
2021-12-03 11:33:19 +01:00
// TODO why was this not enabled for VOD?
if ( isEventPlaylist )
{
args + = " -flags -global_header" ;
2020-07-31 23:09:17 +02:00
}
if ( ! string . IsNullOrEmpty ( state . OutputVideoSync ) )
{
args + = " -vsync " + state . OutputVideoSync ;
}
args + = _encodingHelper . GetOutputFFlags ( state ) ;
return args ;
}
private string GetSegmentPath ( StreamState state , string playlist , int index )
{
2020-11-14 02:04:06 +01:00
var folder = Path . GetDirectoryName ( playlist ) ? ? throw new ArgumentException ( $"Provided path ({playlist}) is not valid." , nameof ( playlist ) ) ;
2020-07-31 23:09:17 +02:00
var filename = Path . GetFileNameWithoutExtension ( playlist ) ;
2021-12-03 11:33:19 +01:00
return Path . Combine ( folder , filename + index . ToString ( CultureInfo . InvariantCulture ) + EncodingHelper . GetSegmentFileExtension ( state . Request . SegmentContainer ) ) ;
2020-07-31 23:09:17 +02:00
}
private async Task < ActionResult > GetSegmentResult (
StreamState state ,
string playlistPath ,
string segmentPath ,
string segmentExtension ,
int segmentIndex ,
TranscodingJobDto ? transcodingJob ,
CancellationToken cancellationToken )
{
var segmentExists = System . IO . File . Exists ( segmentPath ) ;
if ( segmentExists )
{
if ( transcodingJob ! = null & & transcodingJob . HasExited )
{
// Transcoding job is over, so assume all existing files are ready
_logger . LogDebug ( "serving up {0} as transcode is over" , segmentPath ) ;
2021-09-23 15:29:12 +02:00
return GetSegmentResult ( state , segmentPath , transcodingJob ) ;
2020-07-31 23:09:17 +02:00
}
var currentTranscodingIndex = GetCurrentTranscodingIndex ( playlistPath , segmentExtension ) ;
// If requested segment is less than transcoding position, we can't transcode backwards, so assume it's ready
if ( segmentIndex < currentTranscodingIndex )
{
_logger . LogDebug ( "serving up {0} as transcode index {1} is past requested point {2}" , segmentPath , currentTranscodingIndex , segmentIndex ) ;
2021-09-23 15:29:12 +02:00
return GetSegmentResult ( state , segmentPath , transcodingJob ) ;
2020-07-31 23:09:17 +02:00
}
}
var nextSegmentPath = GetSegmentPath ( state , playlistPath , segmentIndex + 1 ) ;
if ( transcodingJob ! = null )
{
while ( ! cancellationToken . IsCancellationRequested & & ! transcodingJob . HasExited )
{
// To be considered ready, the segment file has to exist AND
// either the transcoding job should be done or next segment should also exist
if ( segmentExists )
{
if ( transcodingJob . HasExited | | System . IO . File . Exists ( nextSegmentPath ) )
{
2021-09-23 15:29:12 +02:00
_logger . LogDebug ( "Serving up {SegmentPath} as it deemed ready" , segmentPath ) ;
return GetSegmentResult ( state , segmentPath , transcodingJob ) ;
2020-07-31 23:09:17 +02:00
}
}
else
{
segmentExists = System . IO . File . Exists ( segmentPath ) ;
if ( segmentExists )
{
continue ; // avoid unnecessary waiting if segment just became available
}
}
await Task . Delay ( 100 , cancellationToken ) . ConfigureAwait ( false ) ;
}
if ( ! System . IO . File . Exists ( segmentPath ) )
{
_logger . LogWarning ( "cannot serve {0} as transcoding quit before we got there" , segmentPath ) ;
}
else
{
_logger . LogDebug ( "serving {0} as it's on disk and transcoding stopped" , segmentPath ) ;
}
cancellationToken . ThrowIfCancellationRequested ( ) ;
}
else
{
_logger . LogWarning ( "cannot serve {0} as it doesn't exist and no transcode is running" , segmentPath ) ;
}
2021-09-23 15:29:12 +02:00
return GetSegmentResult ( state , segmentPath , transcodingJob ) ;
2020-07-31 23:09:17 +02:00
}
2021-09-23 15:29:12 +02:00
private ActionResult GetSegmentResult ( StreamState state , string segmentPath , TranscodingJobDto ? transcodingJob )
2020-07-31 23:09:17 +02:00
{
2021-09-23 15:29:12 +02:00
var segmentEndingPositionTicks = state . Request . CurrentRuntimeTicks + state . Request . ActualSegmentLengthTicks ;
2020-07-31 23:09:17 +02:00
Response . OnCompleted ( ( ) = >
{
2021-09-23 15:29:12 +02:00
_logger . LogDebug ( "Finished serving {SegmentPath}" , segmentPath ) ;
2020-07-31 23:09:17 +02:00
if ( transcodingJob ! = null )
{
transcodingJob . DownloadPositionTicks = Math . Max ( transcodingJob . DownloadPositionTicks ? ? segmentEndingPositionTicks , segmentEndingPositionTicks ) ;
_transcodingJobHelper . OnTranscodeEndRequest ( transcodingJob ) ;
}
return Task . CompletedTask ;
} ) ;
2022-01-09 02:53:53 +01:00
return FileStreamResponseHelpers . GetStaticFileResult ( segmentPath , MimeTypes . GetMimeType ( segmentPath ) ) ;
2020-07-31 23:09:17 +02:00
}
private int? GetCurrentTranscodingIndex ( string playlist , string segmentExtension )
{
2020-11-20 02:56:08 +01:00
var job = _transcodingJobHelper . GetTranscodingJob ( playlist , TranscodingJobType ) ;
2020-07-31 23:09:17 +02:00
if ( job = = null | | job . HasExited )
{
return null ;
}
var file = GetLastTranscodingFile ( playlist , segmentExtension , _fileSystem ) ;
if ( file = = null )
{
return null ;
}
var playlistFilename = Path . GetFileNameWithoutExtension ( playlist ) ;
var indexString = Path . GetFileNameWithoutExtension ( file . Name ) . Substring ( playlistFilename . Length ) ;
return int . Parse ( indexString , NumberStyles . Integer , CultureInfo . InvariantCulture ) ;
}
private static FileSystemMetadata ? GetLastTranscodingFile ( string playlist , string segmentExtension , IFileSystem fileSystem )
{
2021-05-20 21:28:18 +02:00
var folder = Path . GetDirectoryName ( playlist ) ? ? throw new ArgumentException ( "Path can't be a root directory." , nameof ( playlist ) ) ;
2020-07-31 23:09:17 +02:00
2021-05-20 21:28:18 +02:00
var filePrefix = Path . GetFileNameWithoutExtension ( playlist ) ;
2020-07-31 23:09:17 +02:00
try
{
return fileSystem . GetFiles ( folder , new [ ] { segmentExtension } , true , false )
. Where ( i = > Path . GetFileNameWithoutExtension ( i . Name ) . StartsWith ( filePrefix , StringComparison . OrdinalIgnoreCase ) )
. OrderByDescending ( fileSystem . GetLastWriteTimeUtc )
. FirstOrDefault ( ) ;
}
catch ( IOException )
{
return null ;
}
}
private void DeleteLastFile ( string playlistPath , string segmentExtension , int retryCount )
{
var file = GetLastTranscodingFile ( playlistPath , segmentExtension , _fileSystem ) ;
if ( file ! = null )
{
DeleteFile ( file . FullName , retryCount ) ;
}
}
private void DeleteFile ( string path , int retryCount )
{
if ( retryCount > = 5 )
{
return ;
}
2021-11-09 13:14:31 +01:00
_logger . LogDebug ( "Deleting partial HLS file {Path}" , path ) ;
2020-07-31 23:09:17 +02:00
try
{
_fileSystem . DeleteFile ( path ) ;
}
catch ( IOException ex )
{
2021-11-09 13:14:31 +01:00
_logger . LogError ( ex , "Error deleting partial stream file(s) {Path}" , path ) ;
2020-07-31 23:09:17 +02:00
var task = Task . Delay ( 100 ) ;
2021-11-08 20:39:02 +01:00
task . Wait ( ) ;
2020-07-31 23:09:17 +02:00
DeleteFile ( path , retryCount + 1 ) ;
}
catch ( Exception ex )
{
2021-11-09 13:14:31 +01:00
_logger . LogError ( ex , "Error deleting partial stream file(s) {Path}" , path ) ;
2020-07-31 23:09:17 +02:00
}
}
}
}