2020-09-17 17:26:27 +00:00
using System ;
2020-07-31 21:09:17 +00: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-01 23:26:49 +00:00
using Jellyfin.Api.Attributes ;
2023-10-31 17:26:37 +00:00
using Jellyfin.Api.Extensions ;
2020-07-31 21:09:17 +00:00
using Jellyfin.Api.Helpers ;
using Jellyfin.Api.Models.StreamingDtos ;
2023-06-15 11:28:01 +00:00
using Jellyfin.Data.Enums ;
2023-03-02 19:57:59 +00:00
using Jellyfin.Extensions ;
2021-09-23 13:29:12 +00:00
using Jellyfin.MediaEncoding.Hls.Playlist ;
2020-07-31 21:09:17 +00:00
using MediaBrowser.Common.Configuration ;
using MediaBrowser.Controller.Configuration ;
using MediaBrowser.Controller.Library ;
using MediaBrowser.Controller.MediaEncoding ;
2023-10-31 17:26:37 +00:00
using MediaBrowser.Controller.Streaming ;
2023-03-10 00:29:39 +00:00
using MediaBrowser.MediaEncoding.Encoder ;
2020-07-31 21:09:17 +00:00
using MediaBrowser.Model.Configuration ;
using MediaBrowser.Model.Dlna ;
2023-01-02 21:26:54 +00:00
using MediaBrowser.Model.Entities ;
2020-07-31 21:09:17 +00:00
using MediaBrowser.Model.IO ;
using MediaBrowser.Model.Net ;
using Microsoft.AspNetCore.Authorization ;
using Microsoft.AspNetCore.Http ;
using Microsoft.AspNetCore.Mvc ;
using Microsoft.Extensions.Logging ;
2023-01-31 11:18:10 +00:00
namespace Jellyfin.Api.Controllers ;
/// <summary>
/// Dynamic hls controller.
/// </summary>
[Route("")]
2023-02-08 22:55:26 +00:00
[Authorize]
2023-01-31 11:18:10 +00:00
public class DynamicHlsController : BaseJellyfinApiController
2020-07-31 21:09:17 +00:00
{
2023-01-31 11:18:10 +00:00
private const string DefaultVodEncoderPreset = "veryfast" ;
private const string DefaultEventEncoderPreset = "superfast" ;
private const TranscodingJobType TranscodingJobType = MediaBrowser . Controller . MediaEncoding . TranscodingJobType . Hls ;
2023-10-09 15:12:41 +00:00
private readonly Version _minFFmpegFlacInMp4 = new Version ( 6 , 0 ) ;
2024-08-29 16:56:44 +00:00
private readonly Version _minFFmpegX265BframeInFmp4 = new Version ( 7 , 0 , 1 ) ;
2023-10-09 15:12:41 +00:00
2023-01-31 11:18:10 +00:00
private readonly ILibraryManager _libraryManager ;
private readonly IUserManager _userManager ;
private readonly IMediaSourceManager _mediaSourceManager ;
private readonly IServerConfigurationManager _serverConfigurationManager ;
private readonly IMediaEncoder _mediaEncoder ;
private readonly IFileSystem _fileSystem ;
2023-10-31 17:26:37 +00:00
private readonly ITranscodeManager _transcodeManager ;
2023-01-31 11:18:10 +00:00
private readonly ILogger < DynamicHlsController > _logger ;
private readonly EncodingHelper _encodingHelper ;
private readonly IDynamicHlsPlaylistGenerator _dynamicHlsPlaylistGenerator ;
private readonly DynamicHlsHelper _dynamicHlsHelper ;
private readonly EncodingOptions _encodingOptions ;
2020-07-31 21:09:17 +00:00
/// <summary>
2023-01-31 11:18:10 +00:00
/// Initializes a new instance of the <see cref="DynamicHlsController"/> class.
2020-07-31 21:09:17 +00:00
/// </summary>
2023-01-31 11:18:10 +00:00
/// <param name="libraryManager">Instance of the <see cref="ILibraryManager"/> interface.</param>
/// <param name="userManager">Instance of the <see cref="IUserManager"/> 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>
2023-10-31 17:26:37 +00:00
/// <param name="transcodeManager">Instance of the <see cref="ITranscodeManager"/> interface.</param>
2023-01-31 11:18:10 +00:00
/// <param name="logger">Instance of the <see cref="ILogger{DynamicHlsController}"/> interface.</param>
/// <param name="dynamicHlsHelper">Instance of <see cref="DynamicHlsHelper"/>.</param>
/// <param name="encodingHelper">Instance of <see cref="EncodingHelper"/>.</param>
/// <param name="dynamicHlsPlaylistGenerator">Instance of <see cref="IDynamicHlsPlaylistGenerator"/>.</param>
public DynamicHlsController (
ILibraryManager libraryManager ,
IUserManager userManager ,
IMediaSourceManager mediaSourceManager ,
IServerConfigurationManager serverConfigurationManager ,
IMediaEncoder mediaEncoder ,
IFileSystem fileSystem ,
2023-10-31 17:26:37 +00:00
ITranscodeManager transcodeManager ,
2023-01-31 11:18:10 +00:00
ILogger < DynamicHlsController > logger ,
DynamicHlsHelper dynamicHlsHelper ,
EncodingHelper encodingHelper ,
IDynamicHlsPlaylistGenerator dynamicHlsPlaylistGenerator )
2020-07-31 21:09:17 +00:00
{
2023-01-31 11:18:10 +00:00
_libraryManager = libraryManager ;
_userManager = userManager ;
_mediaSourceManager = mediaSourceManager ;
_serverConfigurationManager = serverConfigurationManager ;
_mediaEncoder = mediaEncoder ;
_fileSystem = fileSystem ;
2023-10-31 17:26:37 +00:00
_transcodeManager = transcodeManager ;
2023-01-31 11:18:10 +00:00
_logger = logger ;
_dynamicHlsHelper = dynamicHlsHelper ;
_encodingHelper = encodingHelper ;
_dynamicHlsPlaylistGenerator = dynamicHlsPlaylistGenerator ;
_encodingOptions = serverConfigurationManager . GetEncodingOptions ( ) ;
}
2020-07-31 21:09:17 +00:00
2023-01-31 11:18:10 +00: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>
/// <param name="segmentLength">The segment length.</param>
/// <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>
2024-08-11 16:11:22 +00:00
/// <param name="audioCodec">Optional. Specify an audio codec to encode to, e.g. mp3.</param>
2023-01-31 11:18:10 +00:00
/// <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>
2024-08-11 16:11:22 +00:00
/// <param name="videoCodec">Optional. Specify a video codec to encode to, e.g. h264.</param>
2023-01-31 11:18:10 +00:00
/// <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>
2024-05-06 04:48:50 +00:00
/// <param name="enableAudioVbrEncoding">Optional. Whether to enable Audio Encoding.</param>
2023-01-31 11:18:10 +00:00
/// <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 ,
2024-03-28 15:26:14 +00:00
[FromQuery] [ RegularExpression ( EncodingHelper . ValidationRegex ) ] string? container ,
2023-01-31 11:18:10 +00:00
[FromQuery] bool? @static ,
[FromQuery] string? @params ,
[FromQuery] string? tag ,
2023-11-09 19:42:37 +00:00
[FromQuery, ParameterObsolete] string? deviceProfileId ,
2023-01-31 11:18:10 +00:00
[FromQuery] string? playSessionId ,
2024-03-28 15:26:14 +00:00
[FromQuery] [ RegularExpression ( EncodingHelper . ValidationRegex ) ] string? segmentContainer ,
2023-01-31 11:18:10 +00:00
[FromQuery] int? segmentLength ,
[FromQuery] int? minSegments ,
[FromQuery] string? mediaSourceId ,
[FromQuery] string? deviceId ,
2024-03-28 15:26:14 +00:00
[FromQuery] [ RegularExpression ( EncodingHelper . ValidationRegex ) ] string? audioCodec ,
2023-01-31 11:18:10 +00:00
[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 ,
2024-03-28 15:26:14 +00:00
[FromQuery] [ RegularExpression ( EncodingHelper . ValidationRegex ) ] string? videoCodec ,
[FromQuery] [ RegularExpression ( EncodingHelper . ValidationRegex ) ] string? subtitleCodec ,
2023-01-31 11:18:10 +00:00
[FromQuery] string? transcodeReasons ,
[FromQuery] int? audioStreamIndex ,
[FromQuery] int? videoStreamIndex ,
[FromQuery] EncodingContext ? context ,
[FromQuery] Dictionary < string , string > streamOptions ,
[FromQuery] int? maxWidth ,
[FromQuery] int? maxHeight ,
2024-05-06 04:48:50 +00:00
[FromQuery] bool? enableSubtitlesInManifest ,
2024-07-17 13:52:44 +00:00
[FromQuery] bool enableAudioVbrEncoding = true )
2023-01-31 11:18:10 +00:00
{
VideoRequestDto streamingRequest = new VideoRequestDto
2021-12-03 10:33:19 +00:00
{
2023-01-31 11:18:10 +00:00
Id = itemId ,
Container = container ,
Static = @static ? ? false ,
Params = @params ,
Tag = tag ,
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 ,
2024-05-06 04:48:50 +00:00
EnableSubtitlesInManifest = enableSubtitlesInManifest ? ? true ,
2024-07-17 13:52:44 +00:00
EnableAudioVbrEncoding = enableAudioVbrEncoding
2023-01-31 11:18:10 +00:00
} ;
// 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 ;
var state = await StreamingHelpers . GetStreamingState (
streamingRequest ,
HttpContext ,
_mediaSourceManager ,
_userManager ,
_libraryManager ,
_serverConfigurationManager ,
_mediaEncoder ,
_encodingHelper ,
2023-10-31 17:26:37 +00:00
_transcodeManager ,
2023-01-31 11:18:10 +00:00
TranscodingJobType ,
cancellationToken )
. ConfigureAwait ( false ) ;
2023-10-31 15:31:09 +00:00
TranscodingJob ? job = null ;
2023-01-31 11:18:10 +00:00
var playlistPath = Path . ChangeExtension ( state . OutputFilePath , ".m3u8" ) ;
if ( ! System . IO . File . Exists ( playlistPath ) )
{
2024-01-03 15:47:25 +00:00
using ( await _transcodeManager . LockAsync ( playlistPath , cancellationToken ) . ConfigureAwait ( false ) )
2021-12-03 10:33:19 +00:00
{
2023-01-31 11:18:10 +00:00
if ( ! System . IO . File . Exists ( playlistPath ) )
2021-12-03 10:33:19 +00:00
{
2023-01-31 11:18:10 +00:00
// If the playlist doesn't already exist, startup ffmpeg
try
2021-12-03 10:33:19 +00:00
{
2023-10-31 17:26:37 +00:00
job = await _transcodeManager . StartFfMpeg (
2023-01-31 11:18:10 +00:00
state ,
playlistPath ,
GetCommandLineArguments ( playlistPath , state , true , 0 ) ,
2023-10-31 17:26:37 +00:00
Request . HttpContext . User . GetUserId ( ) ,
2023-01-31 11:18:10 +00:00
TranscodingJobType ,
cancellationTokenSource )
. ConfigureAwait ( false ) ;
job . IsLiveOutput = true ;
}
catch
{
state . Dispose ( ) ;
throw ;
}
2021-12-03 10:33:19 +00:00
2023-01-31 11:18:10 +00:00
minSegments = state . MinSegments ;
if ( minSegments > 0 )
{
await HlsHelpers . WaitForMinimumSegmentCount ( playlistPath , minSegments , _logger , cancellationToken ) . ConfigureAwait ( false ) ;
2021-12-03 10:33:19 +00:00
}
}
}
}
2023-10-31 17:26:37 +00:00
job ? ? = _transcodeManager . OnTranscodeBeginRequest ( playlistPath , TranscodingJobType ) ;
2020-07-31 21:09:17 +00:00
2023-01-31 11:18:10 +00:00
if ( job is not null )
{
2023-10-31 17:26:37 +00:00
_transcodeManager . OnTranscodeEndRequest ( job ) ;
2020-07-31 21:09:17 +00:00
}
2023-01-31 11:18:10 +00:00
var playlistText = HlsHelpers . GetLivePlaylistText ( playlistPath , state ) ;
2020-07-31 21:09:17 +00:00
2023-01-31 11:18:10 +00:00
return Content ( playlistText , MimeTypes . GetMimeType ( "playlist.m3u8" ) ) ;
}
2020-07-31 21:09:17 +00:00
2023-01-31 11:18:10 +00: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>
/// <param name="segmentLength">The segment length.</param>
/// <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>
2024-08-11 16:11:22 +00:00
/// <param name="audioCodec">Optional. Specify an audio codec to encode to, e.g. mp3.</param>
2023-01-31 11:18:10 +00:00
/// <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="maxWidth">Optional. The maximum horizontal resolution of the encoded video.</param>
/// <param name="maxHeight">Optional. The maximum 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>
2024-08-11 16:11:22 +00:00
/// <param name="videoCodec">Optional. Specify a video codec to encode to, e.g. h264.</param>
2023-01-31 11:18:10 +00:00
/// <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="enableAdaptiveBitrateStreaming">Enable adaptive bitrate streaming.</param>
2023-02-23 02:17:54 +00:00
/// <param name="enableTrickplay">Enable trickplay image playlists being added to master playlist.</param>
2024-05-06 04:48:50 +00:00
/// <param name="enableAudioVbrEncoding">Whether to enable Audio Encoding.</param>
2023-01-31 11:18:10 +00:00
/// <response code="200">Video stream returned.</response>
/// <returns>A <see cref="FileResult"/> containing the playlist file.</returns>
[HttpGet("Videos/{itemId}/master.m3u8")]
[HttpHead("Videos/{itemId}/master.m3u8", Name = "HeadMasterHlsVideoPlaylist")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesPlaylistFile]
public async Task < ActionResult > GetMasterHlsVideoPlaylist (
[FromRoute, Required] Guid itemId ,
[FromQuery] bool? @static ,
[FromQuery] string? @params ,
[FromQuery] string? tag ,
2023-11-09 19:42:37 +00:00
[FromQuery, ParameterObsolete] string? deviceProfileId ,
2023-01-31 11:18:10 +00:00
[FromQuery] string? playSessionId ,
2024-03-28 15:26:14 +00:00
[FromQuery] [ RegularExpression ( EncodingHelper . ValidationRegex ) ] string? segmentContainer ,
2023-01-31 11:18:10 +00:00
[FromQuery] int? segmentLength ,
[FromQuery] int? minSegments ,
[FromQuery, Required] string mediaSourceId ,
[FromQuery] string? deviceId ,
2024-03-28 15:26:14 +00:00
[FromQuery] [ RegularExpression ( EncodingHelper . ValidationRegex ) ] string? audioCodec ,
2023-01-31 11:18:10 +00:00
[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? maxWidth ,
[FromQuery] int? maxHeight ,
[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 ,
2024-03-28 15:26:14 +00:00
[FromQuery] [ RegularExpression ( EncodingHelper . ValidationRegex ) ] string? videoCodec ,
[FromQuery] [ RegularExpression ( EncodingHelper . ValidationRegex ) ] string? subtitleCodec ,
2023-01-31 11:18:10 +00:00
[FromQuery] string? transcodeReasons ,
[FromQuery] int? audioStreamIndex ,
[FromQuery] int? videoStreamIndex ,
[FromQuery] EncodingContext ? context ,
[FromQuery] Dictionary < string , string > streamOptions ,
2023-02-23 02:17:54 +00:00
[FromQuery] bool enableAdaptiveBitrateStreaming = true ,
2024-05-06 04:48:50 +00:00
[FromQuery] bool enableTrickplay = true ,
[FromQuery] bool enableAudioVbrEncoding = true )
2023-01-31 11:18:10 +00:00
{
var streamingRequest = new HlsVideoRequestDto
2020-07-31 21:09:17 +00:00
{
2023-01-31 11:18:10 +00:00
Id = itemId ,
Static = @static ? ? false ,
Params = @params ,
Tag = tag ,
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 ,
MaxWidth = maxWidth ,
MaxHeight = maxHeight ,
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 ,
2023-02-23 02:17:54 +00:00
EnableAdaptiveBitrateStreaming = enableAdaptiveBitrateStreaming ,
2024-05-06 04:48:50 +00:00
EnableTrickplay = enableTrickplay ,
EnableAudioVbrEncoding = enableAudioVbrEncoding
2023-01-31 11:18:10 +00:00
} ;
return await _dynamicHlsHelper . GetMasterHlsPlaylist ( TranscodingJobType , streamingRequest , enableAdaptiveBitrateStreaming ) . ConfigureAwait ( false ) ;
}
2020-07-31 21:09:17 +00:00
2023-01-31 11:18:10 +00: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>
/// <param name="segmentLength">The segment length.</param>
/// <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>
2024-08-11 16:11:22 +00:00
/// <param name="audioCodec">Optional. Specify an audio codec to encode to, e.g. mp3.</param>
2023-01-31 11:18:10 +00:00
/// <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="maxStreamingBitrate">Optional. The maximum streaming bitrate.</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>
2024-08-11 16:11:22 +00:00
/// <param name="videoCodec">Optional. Specify a video codec to encode to, e.g. h264.</param>
2023-01-31 11:18:10 +00:00
/// <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="enableAdaptiveBitrateStreaming">Enable adaptive bitrate streaming.</param>
2024-05-06 04:48:50 +00:00
/// <param name="enableAudioVbrEncoding">Optional. Whether to enable Audio Encoding.</param>
2023-01-31 11:18:10 +00:00
/// <response code="200">Audio stream returned.</response>
/// <returns>A <see cref="FileResult"/> containing the playlist file.</returns>
[HttpGet("Audio/{itemId}/master.m3u8")]
[HttpHead("Audio/{itemId}/master.m3u8", Name = "HeadMasterHlsAudioPlaylist")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesPlaylistFile]
public async Task < ActionResult > GetMasterHlsAudioPlaylist (
[FromRoute, Required] Guid itemId ,
[FromQuery] bool? @static ,
[FromQuery] string? @params ,
[FromQuery] string? tag ,
2023-11-09 19:42:37 +00:00
[FromQuery, ParameterObsolete] string? deviceProfileId ,
2023-01-31 11:18:10 +00:00
[FromQuery] string? playSessionId ,
2024-03-28 15:26:14 +00:00
[FromQuery] [ RegularExpression ( EncodingHelper . ValidationRegex ) ] string? segmentContainer ,
2023-01-31 11:18:10 +00:00
[FromQuery] int? segmentLength ,
[FromQuery] int? minSegments ,
[FromQuery, Required] string mediaSourceId ,
[FromQuery] string? deviceId ,
2024-03-28 15:26:14 +00:00
[FromQuery] [ RegularExpression ( EncodingHelper . ValidationRegex ) ] string? audioCodec ,
2023-01-31 11:18:10 +00:00
[FromQuery] bool? enableAutoStreamCopy ,
[FromQuery] bool? allowVideoStreamCopy ,
[FromQuery] bool? allowAudioStreamCopy ,
[FromQuery] bool? breakOnNonKeyFrames ,
[FromQuery] int? audioSampleRate ,
[FromQuery] int? maxAudioBitDepth ,
[FromQuery] int? maxStreamingBitrate ,
[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 ,
2024-03-28 15:26:14 +00:00
[FromQuery] [ RegularExpression ( EncodingHelper . ValidationRegex ) ] string? videoCodec ,
[FromQuery] [ RegularExpression ( EncodingHelper . ValidationRegex ) ] string? subtitleCodec ,
2023-01-31 11:18:10 +00:00
[FromQuery] string? transcodeReasons ,
[FromQuery] int? audioStreamIndex ,
[FromQuery] int? videoStreamIndex ,
[FromQuery] EncodingContext ? context ,
[FromQuery] Dictionary < string , string > streamOptions ,
2024-05-06 04:48:50 +00:00
[FromQuery] bool enableAdaptiveBitrateStreaming = true ,
[FromQuery] bool enableAudioVbrEncoding = true )
2023-01-31 11:18:10 +00:00
{
var streamingRequest = new HlsAudioRequestDto
2020-07-31 21:09:17 +00:00
{
2023-01-31 11:18:10 +00:00
Id = itemId ,
Static = @static ? ? false ,
Params = @params ,
Tag = tag ,
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 ? ? maxStreamingBitrate ,
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 ,
2024-05-06 04:48:50 +00:00
EnableAdaptiveBitrateStreaming = enableAdaptiveBitrateStreaming ,
EnableAudioVbrEncoding = enableAudioVbrEncoding
2023-01-31 11:18:10 +00:00
} ;
return await _dynamicHlsHelper . GetMasterHlsPlaylist ( TranscodingJobType , streamingRequest , enableAdaptiveBitrateStreaming ) . ConfigureAwait ( false ) ;
}
2020-07-31 21:09:17 +00:00
2023-01-31 11:18:10 +00: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>
/// <param name="segmentLength">The segment length.</param>
/// <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>
2024-08-11 16:11:22 +00:00
/// <param name="audioCodec">Optional. Specify an audio codec to encode to, e.g. mp3.</param>
2023-01-31 11:18:10 +00:00
/// <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="maxWidth">Optional. The maximum horizontal resolution of the encoded video.</param>
/// <param name="maxHeight">Optional. The maximum 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>
2024-08-11 16:11:22 +00:00
/// <param name="videoCodec">Optional. Specify a video codec to encode to, e.g. h264.</param>
2023-01-31 11:18:10 +00:00
/// <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>
2024-05-06 04:48:50 +00:00
/// <param name="enableAudioVbrEncoding">Optional. Whether to enable Audio Encoding.</param>
2023-01-31 11:18:10 +00:00
/// <response code="200">Video stream returned.</response>
/// <returns>A <see cref="FileResult"/> containing the audio file.</returns>
[HttpGet("Videos/{itemId}/main.m3u8")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesPlaylistFile]
public async Task < ActionResult > GetVariantHlsVideoPlaylist (
[FromRoute, Required] Guid itemId ,
[FromQuery] bool? @static ,
[FromQuery] string? @params ,
[FromQuery] string? tag ,
2023-11-09 19:42:37 +00:00
[FromQuery, ParameterObsolete] string? deviceProfileId ,
2023-01-31 11:18:10 +00:00
[FromQuery] string? playSessionId ,
2024-03-28 15:26:14 +00:00
[FromQuery] [ RegularExpression ( EncodingHelper . ValidationRegex ) ] string? segmentContainer ,
2023-01-31 11:18:10 +00:00
[FromQuery] int? segmentLength ,
[FromQuery] int? minSegments ,
[FromQuery] string? mediaSourceId ,
[FromQuery] string? deviceId ,
2024-03-28 15:26:14 +00:00
[FromQuery] [ RegularExpression ( EncodingHelper . ValidationRegex ) ] string? audioCodec ,
2023-01-31 11:18:10 +00:00
[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? maxWidth ,
[FromQuery] int? maxHeight ,
[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 ,
2024-03-28 15:26:14 +00:00
[FromQuery] [ RegularExpression ( EncodingHelper . ValidationRegex ) ] string? videoCodec ,
[FromQuery] [ RegularExpression ( EncodingHelper . ValidationRegex ) ] string? subtitleCodec ,
2023-01-31 11:18:10 +00:00
[FromQuery] string? transcodeReasons ,
[FromQuery] int? audioStreamIndex ,
[FromQuery] int? videoStreamIndex ,
[FromQuery] EncodingContext ? context ,
2024-05-06 04:48:50 +00:00
[FromQuery] Dictionary < string , string > streamOptions ,
2024-07-17 13:52:44 +00:00
[FromQuery] bool enableAudioVbrEncoding = true )
2023-01-31 11:18:10 +00:00
{
using var cancellationTokenSource = new CancellationTokenSource ( ) ;
var streamingRequest = new VideoRequestDto
{
Id = itemId ,
Static = @static ? ? false ,
Params = @params ,
Tag = tag ,
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 ,
MaxWidth = maxWidth ,
MaxHeight = maxHeight ,
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 ,
2024-05-06 04:48:50 +00:00
StreamOptions = streamOptions ,
2024-07-17 13:52:44 +00:00
EnableAudioVbrEncoding = enableAudioVbrEncoding
2023-01-31 11:18:10 +00:00
} ;
return await GetVariantPlaylistInternal ( streamingRequest , cancellationTokenSource )
. ConfigureAwait ( false ) ;
}
2020-07-31 21:09:17 +00:00
2023-01-31 11:18:10 +00:00
/// <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>
/// <param name="segmentLength">The segment length.</param>
/// <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>
2024-08-11 16:11:22 +00:00
/// <param name="audioCodec">Optional. Specify an audio codec to encode to, e.g. mp3.</param>
2023-01-31 11:18:10 +00:00
/// <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="maxStreamingBitrate">Optional. The maximum streaming bitrate.</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>
2024-08-11 16:57:50 +00:00
/// <param name="videoCodec">Optional. Specify a video codec to encode to, e.g. h264.</param>
2023-01-31 11:18:10 +00:00
/// <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>
2024-05-06 04:48:50 +00:00
/// <param name="enableAudioVbrEncoding">Optional. Whether to enable Audio Encoding.</param>
2023-01-31 11:18:10 +00:00
/// <response code="200">Audio stream returned.</response>
/// <returns>A <see cref="FileResult"/> containing the audio file.</returns>
[HttpGet("Audio/{itemId}/main.m3u8")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesPlaylistFile]
public async Task < ActionResult > GetVariantHlsAudioPlaylist (
[FromRoute, Required] Guid itemId ,
[FromQuery] bool? @static ,
[FromQuery] string? @params ,
[FromQuery] string? tag ,
2023-11-09 19:42:37 +00:00
[FromQuery, ParameterObsolete] string? deviceProfileId ,
2023-01-31 11:18:10 +00:00
[FromQuery] string? playSessionId ,
2024-03-28 15:26:14 +00:00
[FromQuery] [ RegularExpression ( EncodingHelper . ValidationRegex ) ] string? segmentContainer ,
2023-01-31 11:18:10 +00:00
[FromQuery] int? segmentLength ,
[FromQuery] int? minSegments ,
[FromQuery] string? mediaSourceId ,
[FromQuery] string? deviceId ,
2024-03-28 15:26:14 +00:00
[FromQuery] [ RegularExpression ( EncodingHelper . ValidationRegex ) ] string? audioCodec ,
2023-01-31 11:18:10 +00:00
[FromQuery] bool? enableAutoStreamCopy ,
[FromQuery] bool? allowVideoStreamCopy ,
[FromQuery] bool? allowAudioStreamCopy ,
[FromQuery] bool? breakOnNonKeyFrames ,
[FromQuery] int? audioSampleRate ,
[FromQuery] int? maxAudioBitDepth ,
[FromQuery] int? maxStreamingBitrate ,
[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 ,
2024-03-28 15:26:14 +00:00
[FromQuery] [ RegularExpression ( EncodingHelper . ValidationRegex ) ] string? videoCodec ,
[FromQuery] [ RegularExpression ( EncodingHelper . ValidationRegex ) ] string? subtitleCodec ,
2023-01-31 11:18:10 +00:00
[FromQuery] string? transcodeReasons ,
[FromQuery] int? audioStreamIndex ,
[FromQuery] int? videoStreamIndex ,
[FromQuery] EncodingContext ? context ,
2024-05-06 04:48:50 +00:00
[FromQuery] Dictionary < string , string > streamOptions ,
2024-07-17 13:52:44 +00:00
[FromQuery] bool enableAudioVbrEncoding = true )
2023-01-31 11:18:10 +00:00
{
using var cancellationTokenSource = new CancellationTokenSource ( ) ;
var streamingRequest = new StreamingRequestDto
2020-07-31 21:09:17 +00:00
{
2023-01-31 11:18:10 +00:00
Id = itemId ,
Static = @static ? ? false ,
Params = @params ,
Tag = tag ,
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 ? ? maxStreamingBitrate ,
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 ,
2024-05-06 04:48:50 +00:00
StreamOptions = streamOptions ,
2024-07-17 13:52:44 +00:00
EnableAudioVbrEncoding = enableAudioVbrEncoding
2023-01-31 11:18:10 +00:00
} ;
return await GetVariantPlaylistInternal ( streamingRequest , cancellationTokenSource )
. ConfigureAwait ( false ) ;
}
2020-07-31 21:09:17 +00:00
2023-01-31 11:18:10 +00:00
/// <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>
/// <param name="runtimeTicks">The position of the requested segment in ticks.</param>
/// <param name="actualSegmentLengthTicks">The length of the requested segment in ticks.</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>
/// <param name="segmentLength">The desired segment length.</param>
/// <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>
2024-08-11 16:11:22 +00:00
/// <param name="audioCodec">Optional. Specify an audio codec to encode to, e.g. mp3.</param>
2023-01-31 11:18:10 +00:00
/// <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="maxWidth">Optional. The maximum horizontal resolution of the encoded video.</param>
/// <param name="maxHeight">Optional. The maximum 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>
2024-08-11 16:11:22 +00:00
/// <param name="videoCodec">Optional. Specify a video codec to encode to, e.g. h264.</param>
2023-01-31 11:18:10 +00:00
/// <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>
2024-05-06 04:48:50 +00:00
/// <param name="enableAudioVbrEncoding">Optional. Whether to enable Audio Encoding.</param>
2023-01-31 11:18:10 +00:00
/// <response code="200">Video stream returned.</response>
/// <returns>A <see cref="FileResult"/> containing the audio file.</returns>
[HttpGet("Videos/{itemId}/hls1/{playlistId}/{segmentId}.{container}")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesVideoFile]
[SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "playlistId", Justification = "Imported from ServiceStack")]
public async Task < ActionResult > GetHlsVideoSegment (
[FromRoute, Required] Guid itemId ,
[FromRoute, Required] string playlistId ,
[FromRoute, Required] int segmentId ,
[FromRoute, Required] string container ,
[FromQuery, Required] long runtimeTicks ,
[FromQuery, Required] long actualSegmentLengthTicks ,
[FromQuery] bool? @static ,
[FromQuery] string? @params ,
[FromQuery] string? tag ,
2023-11-09 19:42:37 +00:00
[FromQuery, ParameterObsolete] string? deviceProfileId ,
2023-01-31 11:18:10 +00:00
[FromQuery] string? playSessionId ,
2024-03-28 15:26:14 +00:00
[FromQuery] [ RegularExpression ( EncodingHelper . ValidationRegex ) ] string? segmentContainer ,
2023-01-31 11:18:10 +00:00
[FromQuery] int? segmentLength ,
[FromQuery] int? minSegments ,
[FromQuery] string? mediaSourceId ,
[FromQuery] string? deviceId ,
2024-03-28 15:26:14 +00:00
[FromQuery] [ RegularExpression ( EncodingHelper . ValidationRegex ) ] string? audioCodec ,
2023-01-31 11:18:10 +00:00
[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? maxWidth ,
[FromQuery] int? maxHeight ,
[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 ,
2024-03-28 15:26:14 +00:00
[FromQuery] [ RegularExpression ( EncodingHelper . ValidationRegex ) ] string? videoCodec ,
[FromQuery] [ RegularExpression ( EncodingHelper . ValidationRegex ) ] string? subtitleCodec ,
2023-01-31 11:18:10 +00:00
[FromQuery] string? transcodeReasons ,
[FromQuery] int? audioStreamIndex ,
[FromQuery] int? videoStreamIndex ,
[FromQuery] EncodingContext ? context ,
2024-05-06 04:48:50 +00:00
[FromQuery] Dictionary < string , string > streamOptions ,
2024-07-17 13:52:44 +00:00
[FromQuery] bool enableAudioVbrEncoding = true )
2023-01-31 11:18:10 +00:00
{
var streamingRequest = new VideoRequestDto
{
Id = itemId ,
CurrentRuntimeTicks = runtimeTicks ,
ActualSegmentLengthTicks = actualSegmentLengthTicks ,
Container = container ,
Static = @static ? ? false ,
Params = @params ,
Tag = tag ,
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 ,
MaxWidth = maxWidth ,
MaxHeight = maxHeight ,
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 ,
2024-05-06 04:48:50 +00:00
StreamOptions = streamOptions ,
2024-07-17 13:52:44 +00:00
EnableAudioVbrEncoding = enableAudioVbrEncoding
2023-01-31 11:18:10 +00:00
} ;
return await GetDynamicSegment ( streamingRequest , segmentId )
. ConfigureAwait ( false ) ;
}
2020-07-31 21:09:17 +00:00
2023-01-31 11:18:10 +00:00
/// <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>
/// <param name="runtimeTicks">The position of the requested segment in ticks.</param>
/// <param name="actualSegmentLengthTicks">The length of the requested segment in ticks.</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>
/// <param name="segmentLength">The segment length.</param>
/// <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>
2024-08-11 16:11:22 +00:00
/// <param name="audioCodec">Optional. Specify an audio codec to encode to, e.g. mp3.</param>
2023-01-31 11:18:10 +00:00
/// <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="maxStreamingBitrate">Optional. The maximum streaming bitrate.</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>
2024-08-11 16:57:50 +00:00
/// <param name="videoCodec">Optional. Specify a video codec to encode to, e.g. h264.</param>
2023-01-31 11:18:10 +00:00
/// <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>
2024-05-06 04:48:50 +00:00
/// <param name="enableAudioVbrEncoding">Optional. Whether to enable Audio Encoding.</param>
2023-01-31 11:18:10 +00:00
/// <response code="200">Video stream returned.</response>
/// <returns>A <see cref="FileResult"/> containing the audio file.</returns>
[HttpGet("Audio/{itemId}/hls1/{playlistId}/{segmentId}.{container}")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesAudioFile]
[SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "playlistId", Justification = "Imported from ServiceStack")]
public async Task < ActionResult > GetHlsAudioSegment (
[FromRoute, Required] Guid itemId ,
[FromRoute, Required] string playlistId ,
[FromRoute, Required] int segmentId ,
[FromRoute, Required] string container ,
[FromQuery, Required] long runtimeTicks ,
[FromQuery, Required] long actualSegmentLengthTicks ,
[FromQuery] bool? @static ,
[FromQuery] string? @params ,
[FromQuery] string? tag ,
2023-11-09 19:42:37 +00:00
[FromQuery, ParameterObsolete] string? deviceProfileId ,
2023-01-31 11:18:10 +00:00
[FromQuery] string? playSessionId ,
2024-03-28 15:26:14 +00:00
[FromQuery] [ RegularExpression ( EncodingHelper . ValidationRegex ) ] string? segmentContainer ,
2023-01-31 11:18:10 +00:00
[FromQuery] int? segmentLength ,
[FromQuery] int? minSegments ,
[FromQuery] string? mediaSourceId ,
[FromQuery] string? deviceId ,
2024-03-28 15:26:14 +00:00
[FromQuery] [ RegularExpression ( EncodingHelper . ValidationRegex ) ] string? audioCodec ,
2023-01-31 11:18:10 +00:00
[FromQuery] bool? enableAutoStreamCopy ,
[FromQuery] bool? allowVideoStreamCopy ,
[FromQuery] bool? allowAudioStreamCopy ,
[FromQuery] bool? breakOnNonKeyFrames ,
[FromQuery] int? audioSampleRate ,
[FromQuery] int? maxAudioBitDepth ,
[FromQuery] int? maxStreamingBitrate ,
[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 ,
2024-03-28 15:26:14 +00:00
[FromQuery] [ RegularExpression ( EncodingHelper . ValidationRegex ) ] string? videoCodec ,
[FromQuery] [ RegularExpression ( EncodingHelper . ValidationRegex ) ] string? subtitleCodec ,
2023-01-31 11:18:10 +00:00
[FromQuery] string? transcodeReasons ,
[FromQuery] int? audioStreamIndex ,
[FromQuery] int? videoStreamIndex ,
[FromQuery] EncodingContext ? context ,
2024-05-06 04:48:50 +00:00
[FromQuery] Dictionary < string , string > streamOptions ,
2024-07-17 13:52:44 +00:00
[FromQuery] bool enableAudioVbrEncoding = true )
2023-01-31 11:18:10 +00:00
{
var streamingRequest = new StreamingRequestDto
2020-07-31 21:09:17 +00:00
{
2023-01-31 11:18:10 +00:00
Id = itemId ,
Container = container ,
CurrentRuntimeTicks = runtimeTicks ,
ActualSegmentLengthTicks = actualSegmentLengthTicks ,
Static = @static ? ? false ,
Params = @params ,
Tag = tag ,
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 ? ? maxStreamingBitrate ,
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 ,
2024-05-06 04:48:50 +00:00
StreamOptions = streamOptions ,
2024-07-17 13:52:44 +00:00
EnableAudioVbrEncoding = enableAudioVbrEncoding
2023-01-31 11:18:10 +00:00
} ;
return await GetDynamicSegment ( streamingRequest , segmentId )
. ConfigureAwait ( false ) ;
}
2020-07-31 21:09:17 +00:00
2023-01-31 11:18:10 +00:00
private async Task < ActionResult > GetVariantPlaylistInternal ( StreamingRequestDto streamingRequest , CancellationTokenSource cancellationTokenSource )
{
using var state = await StreamingHelpers . GetStreamingState (
streamingRequest ,
HttpContext ,
_mediaSourceManager ,
_userManager ,
_libraryManager ,
_serverConfigurationManager ,
_mediaEncoder ,
_encodingHelper ,
2023-10-31 17:26:37 +00:00
_transcodeManager ,
2023-01-31 11:18:10 +00:00
TranscodingJobType ,
cancellationTokenSource . Token )
. ConfigureAwait ( false ) ;
var request = new CreateMainPlaylistRequest (
state . MediaPath ,
state . SegmentLength * 1000 ,
state . RunTimeTicks ? ? 0 ,
state . Request . SegmentContainer ? ? string . Empty ,
"hls1/main/" ,
Request . QueryString . ToString ( ) ,
EncodingHelper . IsCopyCodec ( state . OutputVideoCodec ) ) ;
var playlist = _dynamicHlsPlaylistGenerator . CreateMainPlaylist ( request ) ;
return new FileContentResult ( Encoding . UTF8 . GetBytes ( playlist ) , MimeTypes . GetMimeType ( "playlist.m3u8" ) ) ;
}
2020-07-31 21:09:17 +00:00
2023-01-31 11:18:10 +00:00
private async Task < ActionResult > GetDynamicSegment ( StreamingRequestDto streamingRequest , int segmentId )
{
if ( ( streamingRequest . StartTimeTicks ? ? 0 ) > 0 )
2020-07-31 21:09:17 +00:00
{
2023-01-31 11:18:10 +00:00
throw new ArgumentException ( "StartTimeTicks is not allowed." ) ;
2020-07-31 21:09:17 +00:00
}
2023-01-31 11:18:10 +00:00
// CTS lifecycle is managed internally.
var cancellationTokenSource = new CancellationTokenSource ( ) ;
var cancellationToken = cancellationTokenSource . Token ;
2020-07-31 21:09:17 +00:00
2023-01-31 11:18:10 +00:00
var state = await StreamingHelpers . GetStreamingState (
streamingRequest ,
HttpContext ,
_mediaSourceManager ,
_userManager ,
_libraryManager ,
_serverConfigurationManager ,
_mediaEncoder ,
_encodingHelper ,
2023-10-31 17:26:37 +00:00
_transcodeManager ,
2023-01-31 11:18:10 +00:00
TranscodingJobType ,
cancellationToken )
. ConfigureAwait ( false ) ;
2020-07-31 21:09:17 +00:00
2023-01-31 11:18:10 +00:00
var playlistPath = Path . ChangeExtension ( state . OutputFilePath , ".m3u8" ) ;
2020-07-31 21:09:17 +00:00
2023-01-31 11:18:10 +00:00
var segmentPath = GetSegmentPath ( state , playlistPath , segmentId ) ;
2020-07-31 21:09:17 +00:00
2023-01-31 11:18:10 +00:00
var segmentExtension = EncodingHelper . GetSegmentFileExtension ( state . Request . SegmentContainer ) ;
2020-07-31 21:09:17 +00:00
2023-10-31 15:31:09 +00:00
TranscodingJob ? job ;
2020-07-31 21:09:17 +00:00
2023-01-31 11:18:10 +00:00
if ( System . IO . File . Exists ( segmentPath ) )
{
2023-10-31 17:26:37 +00:00
job = _transcodeManager . OnTranscodeBeginRequest ( playlistPath , TranscodingJobType ) ;
2023-01-31 11:18:10 +00:00
_logger . LogDebug ( "returning {0} [it exists, try 1]" , segmentPath ) ;
return await GetSegmentResult ( state , playlistPath , segmentPath , segmentExtension , segmentId , job , cancellationToken ) . ConfigureAwait ( false ) ;
}
2020-07-31 21:09:17 +00:00
2024-01-03 15:47:25 +00:00
using ( await _transcodeManager . LockAsync ( playlistPath , cancellationToken ) . ConfigureAwait ( false ) )
2023-01-31 11:18:10 +00:00
{
2024-01-03 15:47:25 +00:00
var startTranscoding = false ;
2020-07-31 21:09:17 +00:00
if ( System . IO . File . Exists ( segmentPath ) )
{
2023-10-31 17:26:37 +00:00
job = _transcodeManager . OnTranscodeBeginRequest ( playlistPath , TranscodingJobType ) ;
2023-01-31 11:18:10 +00:00
_logger . LogDebug ( "returning {0} [it exists, try 2]" , segmentPath ) ;
2020-07-31 21:09:17 +00:00
return await GetSegmentResult ( state , playlistPath , segmentPath , segmentExtension , segmentId , job , cancellationToken ) . ConfigureAwait ( false ) ;
}
2024-01-03 15:47:25 +00:00
var currentTranscodingIndex = GetCurrentTranscodingIndex ( playlistPath , segmentExtension ) ;
var segmentGapRequiringTranscodingChange = 24 / state . SegmentLength ;
2020-07-31 21:09:17 +00:00
2024-01-03 15:47:25 +00:00
if ( segmentId = = - 1 )
{
_logger . LogDebug ( "Starting transcoding because fmp4 init file is being requested" ) ;
startTranscoding = true ;
segmentId = 0 ;
}
else if ( currentTranscodingIndex is null )
{
_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 ;
}
2020-07-31 21:09:17 +00:00
2024-01-03 15:47:25 +00:00
if ( startTranscoding )
{
// If the playlist doesn't already exist, startup ffmpeg
try
{
await _transcodeManager . KillTranscodingJobs ( streamingRequest . DeviceId , streamingRequest . PlaySessionId , p = > false )
. ConfigureAwait ( false ) ;
2023-01-31 11:18:10 +00:00
2024-01-03 15:47:25 +00:00
if ( currentTranscodingIndex . HasValue )
2020-07-31 21:09:17 +00:00
{
2024-04-14 21:24:34 +00:00
await DeleteLastFile ( playlistPath , segmentExtension , 0 ) . ConfigureAwait ( false ) ;
2020-07-31 21:09:17 +00:00
}
2023-01-31 11:18:10 +00:00
2024-01-03 15:47:25 +00:00
streamingRequest . StartTimeTicks = streamingRequest . CurrentRuntimeTicks ;
state . WaitForPath = segmentPath ;
job = await _transcodeManager . StartFfMpeg (
state ,
playlistPath ,
GetCommandLineArguments ( playlistPath , state , false , segmentId ) ,
Request . HttpContext . User . GetUserId ( ) ,
TranscodingJobType ,
cancellationTokenSource ) . ConfigureAwait ( false ) ;
2020-07-31 21:09:17 +00:00
}
2024-01-03 15:47:25 +00:00
catch
2020-07-31 21:09:17 +00:00
{
2024-01-03 15:47:25 +00:00
state . Dispose ( ) ;
throw ;
2020-07-31 21:09:17 +00:00
}
2024-01-03 15:47:25 +00:00
// await WaitForMinimumSegmentCount(playlistPath, 1, cancellationTokenSource.Token).ConfigureAwait(false);
2020-07-31 21:09:17 +00:00
}
2024-01-03 15:47:25 +00:00
else
2020-07-31 21:09:17 +00:00
{
2024-01-03 15:47:25 +00:00
job = _transcodeManager . OnTranscodeBeginRequest ( playlistPath , TranscodingJobType ) ;
if ( job ? . TranscodingThrottler is not null )
{
await job . TranscodingThrottler . UnpauseTranscoding ( ) . ConfigureAwait ( false ) ;
}
2021-05-07 22:33:24 +00:00
}
2023-01-31 11:18:10 +00:00
}
2020-07-31 21:09:17 +00:00
2023-01-31 11:18:10 +00:00
_logger . LogDebug ( "returning {0} [general case]" , segmentPath ) ;
2023-10-31 17:26:37 +00:00
job ? ? = _transcodeManager . OnTranscodeBeginRequest ( playlistPath , TranscodingJobType ) ;
2023-01-31 11:18:10 +00:00
return await GetSegmentResult ( state , playlistPath , segmentPath , segmentExtension , segmentId , job , cancellationToken ) . ConfigureAwait ( false ) ;
}
2020-07-31 21:09:17 +00:00
2023-01-31 11:18:10 +00:00
private static double [ ] GetSegmentLengths ( StreamState state )
= > GetSegmentLengthsInternal ( state . RunTimeTicks ? ? 0 , state . SegmentLength ) ;
2020-07-31 21:09:17 +00:00
2023-01-31 11:18:10 +00: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 21:09:17 +00:00
2023-01-31 11:18:10 +00:00
var segmentsLen = wholeSegments + ( remainingTicks = = 0 ? 0 : 1 ) ;
var segments = new double [ segmentsLen ] ;
for ( int i = 0 ; i < wholeSegments ; i + + )
2020-07-31 21:09:17 +00:00
{
2023-01-31 11:18:10 +00:00
segments [ i ] = segmentlength ;
2020-07-31 21:09:17 +00:00
}
2023-01-31 11:18:10 +00:00
if ( remainingTicks ! = 0 )
2020-07-31 21:09:17 +00:00
{
2023-01-31 11:18:10 +00:00
segments [ ^ 1 ] = TimeSpan . FromTicks ( remainingTicks ) . TotalSeconds ;
}
2020-07-31 21:09:17 +00:00
2023-01-31 11:18:10 +00:00
return segments ;
}
2020-07-31 21:09:17 +00:00
2023-01-31 11:18:10 +00:00
private string GetCommandLineArguments ( string outputPath , StreamState state , bool isEventPlaylist , int startNumber )
{
var videoCodec = _encodingHelper . GetVideoEncoder ( state , _encodingOptions ) ;
var threads = EncodingHelper . GetNumberOfThreads ( state , _encodingOptions , videoCodec ) ;
2020-07-31 21:09:17 +00:00
2023-01-31 11:18:10 +00: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 ;
}
2022-03-09 13:28:58 +00:00
2023-01-31 11:18:10 +00:00
var mapArgs = state . IsOutputVideo ? _encodingHelper . GetMapArgs ( state ) : string . Empty ;
2020-11-08 07:13:00 +00:00
2023-01-31 11:18:10 +00:00
var directory = Path . GetDirectoryName ( outputPath ) ? ? throw new ArgumentException ( $"Provided path ({outputPath}) is not valid." , nameof ( outputPath ) ) ;
var outputFileNameWithoutExtension = Path . GetFileNameWithoutExtension ( outputPath ) ;
var outputPrefix = Path . Combine ( directory , outputFileNameWithoutExtension ) ;
var outputExtension = EncodingHelper . GetSegmentFileExtension ( state . Request . SegmentContainer ) ;
var outputTsArg = outputPrefix + "%d" + outputExtension ;
2020-07-31 21:09:17 +00:00
2023-01-31 11:18:10 +00:00
var segmentFormat = string . Empty ;
var segmentContainer = outputExtension . TrimStart ( '.' ) ;
var inputModifier = _encodingHelper . GetInputModifier ( state , _encodingOptions , segmentContainer ) ;
2020-08-24 18:20:46 +00:00
2023-01-31 11:18:10 +00:00
if ( string . Equals ( segmentContainer , "ts" , StringComparison . OrdinalIgnoreCase ) )
{
segmentFormat = "mpegts" ;
}
else if ( string . Equals ( segmentContainer , "mp4" , StringComparison . OrdinalIgnoreCase ) )
{
var outputFmp4HeaderArg = OperatingSystem . IsWindows ( ) switch
2021-12-03 10:33:19 +00:00
{
2023-01-31 11:18:10 +00:00
// on Windows, the path of fmp4 header file needs to be configured
true = > " -hls_fmp4_init_filename \"" + outputPrefix + "-1" + outputExtension + "\"" ,
// on Linux/Unix, ffmpeg generate fmp4 header file to m3u8 output folder
false = > " -hls_fmp4_init_filename \"" + outputFileNameWithoutExtension + "-1" + outputExtension + "\""
} ;
2021-12-03 10:33:19 +00:00
2023-01-31 11:18:10 +00:00
segmentFormat = "fmp4" + outputFmp4HeaderArg ;
2020-07-31 21:09:17 +00:00
}
2023-01-31 11:18:10 +00:00
else
2022-11-14 09:13:17 +00:00
{
2023-01-31 11:18:10 +00:00
_logger . LogError ( "Invalid HLS segment container: {SegmentContainer}, default to mpegts" , segmentContainer ) ;
segmentFormat = "mpegts" ;
}
2022-11-14 09:13:17 +00:00
2023-01-31 11:18:10 +00:00
var maxMuxingQueueSize = _encodingOptions . MaxMuxingQueueSize > 128
? _encodingOptions . MaxMuxingQueueSize . ToString ( CultureInfo . InvariantCulture )
: "128" ;
2021-12-03 10:33:19 +00:00
2023-06-15 17:55:11 +00:00
var baseUrlParam = string . Empty ;
if ( isEventPlaylist )
{
baseUrlParam = string . Format (
CultureInfo . InvariantCulture ,
" -hls_base_url \"hls/{0}/\"" ,
Path . GetFileNameWithoutExtension ( outputPath ) ) ;
}
2024-03-17 12:44:42 +00:00
var hlsArguments = $"-hls_playlist_type {(isEventPlaylist ? " event " : " vod ")} -hls_list_size 0" ;
2020-07-31 21:09:17 +00:00
2023-01-31 11:18:10 +00:00
return string . Format (
CultureInfo . InvariantCulture ,
2023-06-15 17:38:42 +00: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}\"" ,
2023-01-31 11:18:10 +00:00
inputModifier ,
_encodingHelper . GetInputArgument ( state , _encodingOptions , segmentContainer ) ,
threads ,
mapArgs ,
2023-09-01 16:05:00 +00:00
GetVideoArguments ( state , startNumber , isEventPlaylist , segmentContainer ) ,
2023-01-31 11:18:10 +00:00
GetAudioArguments ( state ) ,
maxMuxingQueueSize ,
state . SegmentLength . ToString ( CultureInfo . InvariantCulture ) ,
segmentFormat ,
startNumber . ToString ( CultureInfo . InvariantCulture ) ,
baseUrlParam ,
2023-03-10 00:29:39 +00:00
EncodingUtils . NormalizePath ( outputTsArg ) ,
2023-06-15 17:38:42 +00:00
hlsArguments ,
2023-03-10 00:29:39 +00:00
EncodingUtils . NormalizePath ( outputPath ) ) . Trim ( ) ;
2023-01-31 11:18:10 +00:00
}
2022-11-14 09:13:17 +00:00
2023-01-31 11:18:10 +00: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 )
{
if ( state . AudioStream is null )
2020-07-31 21:09:17 +00:00
{
2023-01-31 11:18:10 +00:00
return string . Empty ;
}
2020-11-11 11:04:58 +00:00
2023-01-31 11:18:10 +00:00
var audioCodec = _encodingHelper . GetAudioEncoder ( state ) ;
2023-08-30 23:18:18 +00:00
var bitStreamArgs = EncodingHelper . GetAudioBitStreamArguments ( state , state . Request . SegmentContainer , state . MediaSource . Container ) ;
2020-07-31 21:09:17 +00:00
2023-10-09 15:12:41 +00:00
// opus, dts, truehd and flac (in FFmpeg 5 and older) are experimental in mp4 muxer
2023-08-01 15:28:49 +00:00
var strictArgs = string . Empty ;
var actualOutputAudioCodec = state . ActualOutputAudioCodec ;
2023-10-09 15:12:41 +00:00
if ( string . Equals ( actualOutputAudioCodec , "opus" , StringComparison . OrdinalIgnoreCase )
2023-08-01 15:28:49 +00:00
| | string . Equals ( actualOutputAudioCodec , "dts" , StringComparison . OrdinalIgnoreCase )
2023-10-09 15:12:41 +00:00
| | string . Equals ( actualOutputAudioCodec , "truehd" , StringComparison . OrdinalIgnoreCase )
| | ( string . Equals ( actualOutputAudioCodec , "flac" , StringComparison . OrdinalIgnoreCase )
& & _mediaEncoder . EncoderVersion < _minFFmpegFlacInMp4 ) )
2023-08-01 15:28:49 +00:00
{
strictArgs = " -strict -2" ;
}
2020-07-31 21:09:17 +00:00
2023-01-31 11:18:10 +00:00
if ( ! state . IsOutputVideo )
{
2023-10-10 20:48:52 +00:00
var audioTranscodeParams = string . Empty ;
// -vn to drop any video streams
audioTranscodeParams + = "-vn" ;
2023-01-31 11:18:10 +00:00
if ( EncodingHelper . IsCopyCodec ( audioCodec ) )
2020-07-31 21:09:17 +00:00
{
2023-10-10 20:48:52 +00:00
return audioTranscodeParams + " -acodec copy" + bitStreamArgs + strictArgs ;
2023-01-31 11:18:10 +00:00
}
2020-07-31 21:09:17 +00:00
2023-10-10 20:48:52 +00:00
audioTranscodeParams + = " -acodec " + audioCodec + bitStreamArgs + strictArgs ;
2020-07-31 21:09:17 +00:00
2022-11-06 23:15:04 +00:00
var audioBitrate = state . OutputAudioBitrate ;
var audioChannels = state . OutputAudioChannels ;
2020-07-31 21:09:17 +00:00
2023-03-02 19:57:59 +00:00
if ( audioBitrate . HasValue & & ! EncodingHelper . LosslessAudioCodecs . Contains ( state . ActualOutputAudioCodec , StringComparison . OrdinalIgnoreCase ) )
2023-01-31 11:18:10 +00:00
{
2024-04-22 13:02:20 +00:00
var vbrParam = _encodingHelper . GetAudioVbrModeParam ( audioCodec , audioBitrate . Value , audioChannels ? ? 2 ) ;
2024-05-06 04:48:50 +00:00
if ( _encodingOptions . EnableAudioVbr & & state . EnableAudioVbrEncoding & & vbrParam is not null )
2020-07-31 21:09:17 +00:00
{
2022-11-06 23:15:04 +00:00
audioTranscodeParams + = vbrParam ;
2020-07-31 21:09:17 +00:00
}
2022-11-06 23:15:04 +00:00
else
2020-07-31 21:09:17 +00:00
{
2022-11-06 23:15:04 +00:00
audioTranscodeParams + = " -ab " + audioBitrate . Value . ToString ( CultureInfo . InvariantCulture ) ;
2020-07-31 21:09:17 +00:00
}
}
2022-11-06 23:15:04 +00:00
if ( audioChannels . HasValue )
2022-06-06 15:29:39 +00:00
{
2022-11-06 23:15:04 +00:00
audioTranscodeParams + = " -ac " + audioChannels . Value . ToString ( CultureInfo . InvariantCulture ) ;
2022-06-06 15:29:39 +00:00
}
2023-01-31 11:18:10 +00:00
if ( state . OutputAudioSampleRate . HasValue )
2020-07-31 21:09:17 +00:00
{
2023-01-31 11:18:10 +00:00
audioTranscodeParams + = " -ar " + state . OutputAudioSampleRate . Value . ToString ( CultureInfo . InvariantCulture ) ;
2020-07-31 21:09:17 +00:00
}
2023-01-31 11:18:10 +00:00
return audioTranscodeParams ;
}
2020-07-31 21:09:17 +00:00
2023-01-31 11:18:10 +00:00
if ( EncodingHelper . IsCopyCodec ( audioCodec ) )
{
var videoCodec = _encodingHelper . GetVideoEncoder ( state , _encodingOptions ) ;
var copyArgs = "-codec:a:0 copy" + bitStreamArgs + strictArgs ;
2020-07-31 21:09:17 +00:00
2023-01-31 11:18:10 +00:00
if ( EncodingHelper . IsCopyCodec ( videoCodec ) & & state . EnableBreakOnNonKeyFrames ( videoCodec ) )
2020-07-31 21:09:17 +00:00
{
2023-01-31 11:18:10 +00:00
return copyArgs + " -copypriorss:a:0 0" ;
2020-07-31 21:09:17 +00:00
}
2023-01-31 11:18:10 +00:00
return copyArgs ;
}
2020-07-31 21:09:17 +00:00
2023-08-30 23:18:18 +00:00
var args = "-codec:a:0 " + audioCodec + bitStreamArgs + strictArgs ;
2020-07-31 21:09:17 +00:00
2023-01-31 11:18:10 +00:00
var channels = state . OutputAudioChannels ;
2020-07-31 21:09:17 +00:00
2024-07-30 15:50:22 +00:00
var useDownMixAlgorithm = DownMixAlgorithmsHelper . AlgorithmFilterStrings . ContainsKey ( ( _encodingOptions . DownMixStereoAlgorithm , DownMixAlgorithmsHelper . InferChannelLayout ( state . AudioStream ) ) ) ;
2024-04-22 16:23:36 +00:00
2023-01-31 11:18:10 +00:00
if ( channels . HasValue
& & ( channels . Value ! = 2
2024-04-22 16:23:36 +00:00
| | ( state . AudioStream ? . Channels ! = null & & ! useDownMixAlgorithm ) ) )
2023-01-31 11:18:10 +00:00
{
args + = " -ac " + channels . Value ;
}
2020-07-31 21:09:17 +00:00
2023-01-31 11:18:10 +00:00
var bitrate = state . OutputAudioBitrate ;
2023-03-02 19:57:59 +00:00
if ( bitrate . HasValue & & ! EncodingHelper . LosslessAudioCodecs . Contains ( actualOutputAudioCodec , StringComparison . OrdinalIgnoreCase ) )
2023-01-31 11:18:10 +00:00
{
2024-04-22 13:02:20 +00:00
var vbrParam = _encodingHelper . GetAudioVbrModeParam ( audioCodec , bitrate . Value , channels ? ? 2 ) ;
2024-05-06 04:48:50 +00:00
if ( _encodingOptions . EnableAudioVbr & & state . EnableAudioVbrEncoding & & vbrParam is not null )
2020-07-31 21:09:17 +00:00
{
2022-11-06 23:15:04 +00:00
args + = vbrParam ;
2020-07-31 21:09:17 +00:00
}
2022-11-06 23:15:04 +00:00
else
2020-07-31 21:09:17 +00:00
{
2022-11-06 23:15:04 +00:00
args + = " -ab " + bitrate . Value . ToString ( CultureInfo . InvariantCulture ) ;
2020-07-31 21:09:17 +00:00
}
}
2024-07-18 03:45:16 +00:00
if ( state . OutputAudioSampleRate . HasValue )
{
args + = " -ar " + state . OutputAudioSampleRate . Value . ToString ( CultureInfo . InvariantCulture ) ;
}
else if ( state . AudioStream ? . CodecTag is not null & & state . AudioStream . CodecTag . Equals ( "ac-4" , StringComparison . Ordinal ) )
2024-07-17 16:35:40 +00:00
{
// ac-4 audio tends to hava a super weird sample rate that will fail most encoders
// force resample it to 48KHz
args + = " -ar 48000" ;
}
2023-01-31 11:18:10 +00:00
args + = _encodingHelper . GetAudioFilterParam ( state , _encodingOptions ) ;
2020-07-31 21:09:17 +00:00
2023-01-31 11:18:10 +00:00
return args ;
}
2020-07-31 21:09:17 +00:00
2023-01-31 11:18:10 +00: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>
/// <param name="isEventPlaylist">Whether the playlist is EVENT or VOD.</param>
2023-09-01 16:05:00 +00:00
/// <param name="segmentContainer">The segment container.</param>
2023-01-31 11:18:10 +00:00
/// <returns>The command line arguments for video transcoding.</returns>
2023-09-01 16:05:00 +00:00
private string GetVideoArguments ( StreamState state , int startNumber , bool isEventPlaylist , string segmentContainer )
2023-01-31 11:18:10 +00:00
{
if ( state . VideoStream is null )
{
return string . Empty ;
}
2020-07-31 21:09:17 +00:00
2023-01-31 11:18:10 +00:00
if ( ! state . IsOutputVideo )
{
return string . Empty ;
2020-07-31 21:09:17 +00:00
}
2023-01-31 11:18:10 +00:00
var codec = _encodingHelper . GetVideoEncoder ( state , _encodingOptions ) ;
2020-07-31 21:09:17 +00:00
2023-01-31 11:18:10 +00:00
var args = "-codec:v:0 " + codec ;
2020-07-31 21:09:17 +00:00
2023-01-31 11:18:10 +00: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 ) )
2020-07-31 21:09:17 +00:00
{
2024-03-23 22:44:10 +00:00
var requestedRange = state . GetRequestedRangeTypes ( state . ActualOutputVideoCodec ) ;
2024-03-23 13:39:49 +00:00
var requestHasDOVI = requestedRange . Contains ( VideoRangeType . DOVI . ToString ( ) , StringComparison . OrdinalIgnoreCase ) ;
var requestHasDOVIWithHDR10 = requestedRange . Contains ( VideoRangeType . DOVIWithHDR10 . ToString ( ) , StringComparison . OrdinalIgnoreCase ) ;
var requestHasDOVIWithHLG = requestedRange . Contains ( VideoRangeType . DOVIWithHLG . ToString ( ) , StringComparison . OrdinalIgnoreCase ) ;
var requestHasDOVIWithSDR = requestedRange . Contains ( VideoRangeType . DOVIWithSDR . ToString ( ) , StringComparison . OrdinalIgnoreCase ) ;
2023-01-31 11:18:10 +00:00
if ( EncodingHelper . IsCopyCodec ( codec )
2024-03-23 13:39:49 +00:00
& & ( ( state . VideoStream . VideoRangeType = = VideoRangeType . DOVI & & requestHasDOVI )
| | ( state . VideoStream . VideoRangeType = = VideoRangeType . DOVIWithHDR10 & & requestHasDOVIWithHDR10 )
| | ( state . VideoStream . VideoRangeType = = VideoRangeType . DOVIWithHLG & & requestHasDOVIWithHLG )
| | ( state . VideoStream . VideoRangeType = = VideoRangeType . DOVIWithSDR & & requestHasDOVIWithSDR ) ) )
2020-11-13 19:47:54 +00:00
{
2023-01-31 11:18:10 +00:00
// Prefer dvh1 to dvhe
args + = " -tag:v:0 dvh1 -strict -2" ;
2020-11-13 19:47:54 +00:00
}
2020-07-31 21:09:17 +00:00
else
{
2023-01-31 11:18:10 +00:00
// Prefer hvc1 to hev1
args + = " -tag:v:0 hvc1" ;
2020-07-31 21:09:17 +00:00
}
2023-01-31 11:18:10 +00:00
}
2020-07-31 21:09:17 +00:00
2023-01-31 11:18:10 +00:00
// if (state.EnableMpegtsM2TsMode)
// {
// args += " -mpegts_m2ts_mode 1";
// }
2020-07-31 21:09:17 +00:00
2023-01-31 11:18:10 +00:00
// See if we can save come cpu cycles by avoiding encoding.
if ( EncodingHelper . IsCopyCodec ( codec ) )
{
// If h264_mp4toannexb is ever added, do not use it for live tv.
if ( state . VideoStream is not null & & ! string . Equals ( state . VideoStream . NalLengthSize , "0" , StringComparison . OrdinalIgnoreCase ) )
2020-11-07 17:39:32 +00:00
{
2023-01-31 11:18:10 +00:00
string bitStreamArgs = EncodingHelper . GetBitStreamArgs ( state . VideoStream ) ;
if ( ! string . IsNullOrEmpty ( bitStreamArgs ) )
2022-05-08 19:06:03 +00:00
{
2023-01-31 11:18:10 +00:00
args + = " " + bitStreamArgs ;
2022-05-08 19:06:03 +00:00
}
2020-11-07 17:39:32 +00:00
}
2023-01-31 11:18:10 +00:00
args + = " -start_at_zero" ;
}
else
{
args + = _encodingHelper . GetVideoQualityParam ( state , codec , _encodingOptions , isEventPlaylist ? DefaultEventEncoderPreset : DefaultVodEncoderPreset ) ;
2020-07-31 21:09:17 +00:00
2023-01-31 11:18:10 +00:00
// Set the key frame params for video encoding to match the hls segment time.
args + = _encodingHelper . GetHlsVideoKeyFrameArguments ( state , codec , state . SegmentLength , isEventPlaylist , startNumber ) ;
2020-07-31 21:09:17 +00:00
2023-01-31 11:18:10 +00:00
// Currently b-frames in libx265 breaks the FMP4-HLS playback on iOS, disable it for now.
2024-08-29 16:56:44 +00:00
if ( string . Equals ( codec , "libx265" , StringComparison . OrdinalIgnoreCase )
& & _mediaEncoder . EncoderVersion < _minFFmpegX265BframeInFmp4 )
2020-07-31 21:09:17 +00:00
{
2023-01-31 11:18:10 +00:00
args + = " -bf 0" ;
2020-07-31 21:09:17 +00:00
}
2023-01-31 11:18:10 +00:00
// video processing filters.
2023-03-10 00:38:15 +00:00
var videoProcessParam = _encodingHelper . GetVideoProcessingFilterParam ( state , _encodingOptions , codec ) ;
2020-07-31 21:09:17 +00:00
2023-03-10 00:38:15 +00:00
var negativeMapArgs = _encodingHelper . GetNegativeMapArgsByFilters ( state , videoProcessParam ) ;
2020-07-31 21:09:17 +00:00
2023-03-10 00:38:15 +00:00
args = negativeMapArgs + args + videoProcessParam ;
2020-07-31 21:09:17 +00:00
2023-01-31 11:18:10 +00:00
// -start_at_zero is necessary to use with -ss when seeking,
// otherwise the target position cannot be determined.
if ( state . SubtitleStream is not null )
{
// Disable start_at_zero for external graphical subs
if ( ! ( state . SubtitleStream . IsExternal & & ! state . SubtitleStream . IsTextSubtitleStream ) )
2020-07-31 21:09:17 +00:00
{
2023-01-31 11:18:10 +00:00
args + = " -start_at_zero" ;
2020-07-31 21:09:17 +00:00
}
2021-12-03 10:33:19 +00:00
}
2020-07-31 21:09:17 +00:00
}
2023-01-31 11:18:10 +00:00
// TODO why was this not enabled for VOD?
2023-09-01 16:05:00 +00:00
if ( isEventPlaylist & & string . Equals ( segmentContainer , "ts" , StringComparison . OrdinalIgnoreCase ) )
2020-07-31 21:09:17 +00:00
{
2023-01-31 11:18:10 +00:00
args + = " -flags -global_header" ;
}
2020-07-31 21:09:17 +00:00
2023-01-31 11:18:10 +00:00
if ( ! string . IsNullOrEmpty ( state . OutputVideoSync ) )
{
args + = " -vsync " + state . OutputVideoSync ;
2020-07-31 21:09:17 +00:00
}
2023-01-31 11:18:10 +00:00
args + = _encodingHelper . GetOutputFFlags ( state ) ;
2020-07-31 21:09:17 +00:00
2023-01-31 11:18:10 +00:00
return args ;
}
2020-07-31 21:09:17 +00:00
2023-01-31 11:18:10 +00:00
private string GetSegmentPath ( StreamState state , string playlist , int index )
{
var folder = Path . GetDirectoryName ( playlist ) ? ? throw new ArgumentException ( $"Provided path ({playlist}) is not valid." , nameof ( playlist ) ) ;
var filename = Path . GetFileNameWithoutExtension ( playlist ) ;
2020-07-31 21:09:17 +00:00
2023-01-31 11:18:10 +00:00
return Path . Combine ( folder , filename + index . ToString ( CultureInfo . InvariantCulture ) + EncodingHelper . GetSegmentFileExtension ( state . Request . SegmentContainer ) ) ;
}
2020-07-31 21:09:17 +00:00
2023-01-31 11:18:10 +00:00
private async Task < ActionResult > GetSegmentResult (
StreamState state ,
string playlistPath ,
string segmentPath ,
string segmentExtension ,
int segmentIndex ,
2023-10-31 15:31:09 +00:00
TranscodingJob ? transcodingJob ,
2023-01-31 11:18:10 +00:00
CancellationToken cancellationToken )
{
var segmentExists = System . IO . File . Exists ( segmentPath ) ;
if ( segmentExists )
2020-07-31 21:09:17 +00:00
{
2023-01-31 11:18:10 +00:00
if ( transcodingJob is not null & & transcodingJob . HasExited )
2020-07-31 21:09:17 +00:00
{
2023-01-31 11:18:10 +00:00
// Transcoding job is over, so assume all existing files are ready
_logger . LogDebug ( "serving up {0} as transcode is over" , segmentPath ) ;
return GetSegmentResult ( state , segmentPath , transcodingJob ) ;
}
2020-07-31 21:09:17 +00:00
2023-01-31 11:18:10 +00:00
var currentTranscodingIndex = GetCurrentTranscodingIndex ( playlistPath , segmentExtension ) ;
2020-07-31 21:09:17 +00:00
2023-01-31 11:18:10 +00:00
// 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 ) ;
return GetSegmentResult ( state , segmentPath , transcodingJob ) ;
2020-07-31 21:09:17 +00:00
}
2023-01-31 11:18:10 +00:00
}
2020-07-31 21:09:17 +00:00
2023-01-31 11:18:10 +00:00
var nextSegmentPath = GetSegmentPath ( state , playlistPath , segmentIndex + 1 ) ;
if ( transcodingJob is not null )
{
while ( ! cancellationToken . IsCancellationRequested & & ! transcodingJob . HasExited )
2020-07-31 21:09:17 +00:00
{
2023-01-31 11:18:10 +00:00
// 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 )
2020-07-31 21:09:17 +00:00
{
2023-01-31 11:18:10 +00:00
if ( transcodingJob . HasExited | | System . IO . File . Exists ( nextSegmentPath ) )
2020-07-31 21:09:17 +00:00
{
2023-01-31 11:18:10 +00:00
_logger . LogDebug ( "Serving up {SegmentPath} as it deemed ready" , segmentPath ) ;
return GetSegmentResult ( state , segmentPath , transcodingJob ) ;
2020-07-31 21:09:17 +00:00
}
}
else
{
2023-01-31 11:18:10 +00:00
segmentExists = System . IO . File . Exists ( segmentPath ) ;
if ( segmentExists )
{
continue ; // avoid unnecessary waiting if segment just became available
}
2020-07-31 21:09:17 +00:00
}
2023-01-31 11:18:10 +00:00
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 ) ;
2020-07-31 21:09:17 +00:00
}
else
{
2023-01-31 11:18:10 +00:00
_logger . LogDebug ( "serving {0} as it's on disk and transcoding stopped" , segmentPath ) ;
2020-07-31 21:09:17 +00:00
}
2023-01-31 11:18:10 +00:00
cancellationToken . ThrowIfCancellationRequested ( ) ;
2020-07-31 21:09:17 +00:00
}
2023-01-31 11:18:10 +00:00
else
2020-07-31 21:09:17 +00:00
{
2023-01-31 11:18:10 +00:00
_logger . LogWarning ( "cannot serve {0} as it doesn't exist and no transcode is running" , segmentPath ) ;
}
2020-07-31 21:09:17 +00:00
2023-01-31 11:18:10 +00:00
return GetSegmentResult ( state , segmentPath , transcodingJob ) ;
}
2020-07-31 21:09:17 +00:00
2023-10-31 15:31:09 +00:00
private ActionResult GetSegmentResult ( StreamState state , string segmentPath , TranscodingJob ? transcodingJob )
2023-01-31 11:18:10 +00:00
{
var segmentEndingPositionTicks = state . Request . CurrentRuntimeTicks + state . Request . ActualSegmentLengthTicks ;
2020-07-31 21:09:17 +00:00
2023-01-31 11:18:10 +00:00
Response . OnCompleted ( ( ) = >
2020-07-31 21:09:17 +00:00
{
2023-01-31 11:18:10 +00:00
_logger . LogDebug ( "Finished serving {SegmentPath}" , segmentPath ) ;
if ( transcodingJob is not null )
2020-07-31 21:09:17 +00:00
{
2023-01-31 11:18:10 +00:00
transcodingJob . DownloadPositionTicks = Math . Max ( transcodingJob . DownloadPositionTicks ? ? segmentEndingPositionTicks , segmentEndingPositionTicks ) ;
2023-10-31 17:26:37 +00:00
_transcodeManager . OnTranscodeEndRequest ( transcodingJob ) ;
2020-07-31 21:09:17 +00:00
}
2023-01-31 11:18:10 +00:00
return Task . CompletedTask ;
} ) ;
2020-07-31 21:09:17 +00:00
2023-01-31 11:18:10 +00:00
return FileStreamResponseHelpers . GetStaticFileResult ( segmentPath , MimeTypes . GetMimeType ( segmentPath ) ) ;
}
2020-07-31 21:09:17 +00:00
2023-01-31 11:18:10 +00:00
private int? GetCurrentTranscodingIndex ( string playlist , string segmentExtension )
{
2023-10-31 17:26:37 +00:00
var job = _transcodeManager . GetTranscodingJob ( playlist , TranscodingJobType ) ;
2020-07-31 21:09:17 +00:00
2023-01-31 11:18:10 +00:00
if ( job is null | | job . HasExited )
{
return null ;
2020-07-31 21:09:17 +00:00
}
2023-01-31 11:18:10 +00:00
var file = GetLastTranscodingFile ( playlist , segmentExtension , _fileSystem ) ;
if ( file is null )
2020-07-31 21:09:17 +00:00
{
2023-01-31 11:18:10 +00:00
return null ;
}
2020-07-31 21:09:17 +00:00
2023-10-05 22:40:09 +00:00
var playlistFilename = Path . GetFileNameWithoutExtension ( playlist . AsSpan ( ) ) ;
2020-07-31 21:09:17 +00:00
2023-10-05 22:40:09 +00:00
var indexString = Path . GetFileNameWithoutExtension ( file . Name . AsSpan ( ) ) . Slice ( playlistFilename . Length ) ;
2020-07-31 21:09:17 +00:00
2023-01-31 11:18:10 +00:00
return int . Parse ( indexString , NumberStyles . Integer , CultureInfo . InvariantCulture ) ;
}
private static FileSystemMetadata ? GetLastTranscodingFile ( string playlist , string segmentExtension , IFileSystem fileSystem )
{
var folder = Path . GetDirectoryName ( playlist ) ? ? throw new ArgumentException ( "Path can't be a root directory." , nameof ( playlist ) ) ;
2020-07-31 21:09:17 +00:00
2023-01-31 11:18:10 +00:00
var filePrefix = Path . GetFileNameWithoutExtension ( playlist ) ;
try
2020-07-31 21:09:17 +00:00
{
2023-01-31 11:18:10 +00:00
return fileSystem . GetFiles ( folder , new [ ] { segmentExtension } , true , false )
. Where ( i = > Path . GetFileNameWithoutExtension ( i . Name ) . StartsWith ( filePrefix , StringComparison . OrdinalIgnoreCase ) )
2023-04-01 21:00:51 +00:00
. MaxBy ( fileSystem . GetLastWriteTimeUtc ) ;
2023-01-31 11:18:10 +00:00
}
catch ( IOException )
2020-07-31 21:09:17 +00:00
{
2023-01-31 11:18:10 +00:00
return null ;
}
}
2020-07-31 21:09:17 +00:00
2024-04-14 21:24:34 +00:00
private Task DeleteLastFile ( string playlistPath , string segmentExtension , int retryCount )
2023-01-31 11:18:10 +00:00
{
var file = GetLastTranscodingFile ( playlistPath , segmentExtension , _fileSystem ) ;
2024-04-14 21:24:34 +00:00
if ( file is null )
2023-01-31 11:18:10 +00:00
{
2024-04-14 21:24:34 +00:00
return Task . CompletedTask ;
2020-07-31 21:09:17 +00:00
}
2024-04-14 21:24:34 +00:00
return DeleteFile ( file . FullName , retryCount ) ;
2023-01-31 11:18:10 +00:00
}
2020-07-31 21:09:17 +00:00
2024-04-14 21:24:34 +00:00
private async Task DeleteFile ( string path , int retryCount )
2023-01-31 11:18:10 +00:00
{
if ( retryCount > = 5 )
2020-07-31 21:09:17 +00:00
{
2023-01-31 11:18:10 +00:00
return ;
}
2020-07-31 21:09:17 +00:00
2023-01-31 11:18:10 +00:00
_logger . LogDebug ( "Deleting partial HLS file {Path}" , path ) ;
2020-07-31 21:09:17 +00:00
2023-01-31 11:18:10 +00:00
try
{
_fileSystem . DeleteFile ( path ) ;
}
catch ( IOException ex )
{
_logger . LogError ( ex , "Error deleting partial stream file(s) {Path}" , path ) ;
2020-07-31 21:09:17 +00:00
2024-04-14 21:24:34 +00:00
await Task . Delay ( 100 ) . ConfigureAwait ( false ) ;
await DeleteFile ( path , retryCount + 1 ) . ConfigureAwait ( false ) ;
2023-01-31 11:18:10 +00:00
}
catch ( Exception ex )
{
_logger . LogError ( ex , "Error deleting partial stream file(s) {Path}" , path ) ;
2020-07-31 21:09:17 +00:00
}
}
}