@ -30,27 +30,60 @@ namespace MediaBrowser.Api.Playback.Hls
/// </summary>
/// </summary>
[Route("/Videos/{Id}/master.m3u8", "GET", Summary = "Gets a video stream using HTTP live streaming.")]
[Route("/Videos/{Id}/master.m3u8", "GET", Summary = "Gets a video stream using HTTP live streaming.")]
[Route("/Videos/{Id}/master.m3u8", "HEAD", Summary = "Gets a video stream using HTTP live streaming.")]
[Route("/Videos/{Id}/master.m3u8", "HEAD", Summary = "Gets a video stream using HTTP live streaming.")]
public class GetMasterHlsVideo Stream : VideoStream Request
public class GetMasterHlsVideo Playlist : VideoStreamRequest , IMasterHls Request
{
{
public bool EnableAdaptiveBitrateStreaming { get ; set ; }
public bool EnableAdaptiveBitrateStreaming { get ; set ; }
public GetMasterHlsVideo Stream ( )
public GetMasterHlsVideo Playlist ( )
{
{
EnableAdaptiveBitrateStreaming = true ;
EnableAdaptiveBitrateStreaming = true ;
}
}
}
}
[Route("/Audio/{Id}/master.m3u8", "GET", Summary = "Gets an audio stream using HTTP live streaming.")]
[Route("/Audio/{Id}/master.m3u8", "HEAD", Summary = "Gets an audio stream using HTTP live streaming.")]
public class GetMasterHlsAudioPlaylist : StreamRequest , IMasterHlsRequest
{
public bool EnableAdaptiveBitrateStreaming { get ; set ; }
public GetMasterHlsAudioPlaylist ( )
{
EnableAdaptiveBitrateStreaming = true ;
}
}
public interface IMasterHlsRequest
{
bool EnableAdaptiveBitrateStreaming { get ; set ; }
}
[Route("/Videos/{Id}/main.m3u8", "GET", Summary = "Gets a video stream using HTTP live streaming.")]
[Route("/Videos/{Id}/main.m3u8", "GET", Summary = "Gets a video stream using HTTP live streaming.")]
public class GetMainHlsVideoStream : VideoStreamRequest
public class Get VariantHlsVideoPlaylist : VideoStreamRequest
{
{
}
}
[Route("/Audio/{Id}/main.m3u8", "GET", Summary = "Gets an audio stream using HTTP live streaming.")]
public class GetVariantHlsAudioPlaylist : StreamRequest
{
}
[Route("/Videos/{Id}/hlsdynamic/{PlaylistId}/{SegmentId}.ts", "GET")]
[Api(Description = "Gets an Http live streaming segment file. Internal use only.")]
public class GetHlsVideoSegment : VideoStreamRequest
{
public string PlaylistId { get ; set ; }
/// <summary>
/// <summary>
/// Class GetHlsVideoSegment
/// Gets or sets the segment id.
/// </summary>
/// </summary>
[Route("/Videos/{Id}/hlsdynamic/{PlaylistId}/{SegmentId}.ts", "GET")]
/// <value>The segment id.</value>
public string SegmentId { get ; set ; }
}
[Route("/Audio/{Id}/hlsdynamic/{PlaylistId}/{SegmentId}.aac", "GET")]
[Route("/Audio/{Id}/hlsdynamic/{PlaylistId}/{SegmentId}.ts", "GET")]
[Api(Description = "Gets an Http live streaming segment file. Internal use only.")]
[Api(Description = "Gets an Http live streaming segment file. Internal use only.")]
public class GetDynamicHlsVideoSegment : VideoStreamRequest
public class Get HlsAudioSegment : StreamRequest
{
{
public string PlaylistId { get ; set ; }
public string PlaylistId { get ; set ; }
@ -71,27 +104,47 @@ namespace MediaBrowser.Api.Playback.Hls
protected INetworkManager NetworkManager { get ; private set ; }
protected INetworkManager NetworkManager { get ; private set ; }
public Task < object > Get ( GetMasterHlsVideoStream request )
public Task < object > Get ( GetMasterHlsVideoPlaylist request )
{
return GetMasterPlaylistInternal ( request , "GET" ) ;
}
public Task < object > Head ( GetMasterHlsVideoPlaylist request )
{
return GetMasterPlaylistInternal ( request , "HEAD" ) ;
}
public Task < object > Get ( GetMasterHlsAudioPlaylist request )
{
return GetMasterPlaylistInternal ( request , "GET" ) ;
}
public Task < object > Head ( GetMasterHlsAudioPlaylist request )
{
return GetMasterPlaylistInternal ( request , "HEAD" ) ;
}
public Task < object > Get ( GetVariantHlsVideoPlaylist request )
{
{
return GetAsync ( request , "GET" ) ;
return Get VariantPlaylistInternal( request , true , "main ") ;
}
}
public Task < object > Head ( GetMasterHlsVideoStream request )
public Task < object > Get( GetVariantHlsAudioPlaylist request )
{
{
return GetAsync ( request , "HEAD" ) ;
return Get VariantPlaylistInternal( request , false , "main ") ;
}
}
public Task < object > Get ( GetMainHlsVideoStream request )
public Task < object > Get ( Get HlsVideoSegment request )
{
{
return GetPlaylistAsync ( request , "main" ) ;
return Get DynamicSegment( request , request . SegmentId ) ;
}
}
public Task < object > Get ( GetDynamicHlsVideoSegment request )
public Task < object > Get ( Get HlsAudi oSegment request )
{
{
return GetDynamicSegment ( request , request . SegmentId ) ;
return GetDynamicSegment ( request , request . SegmentId ) ;
}
}
private async Task < object > GetDynamicSegment ( VideoStreamRequest request , string segmentId )
private async Task < object > GetDynamicSegment ( StreamRequest request , string segmentId )
{
{
if ( ( request . StartTimeTicks ? ? 0 ) > 0 )
if ( ( request . StartTimeTicks ? ? 0 ) > 0 )
{
{
@ -107,7 +160,7 @@ namespace MediaBrowser.Api.Playback.Hls
var playlistPath = Path . ChangeExtension ( state . OutputFilePath , ".m3u8" ) ;
var playlistPath = Path . ChangeExtension ( state . OutputFilePath , ".m3u8" ) ;
var segmentPath = GetSegmentPath ( playlistPath, requestedIndex ) ;
var segmentPath = GetSegmentPath ( state, playlistPath, requestedIndex ) ;
var segmentLength = state . SegmentLength ;
var segmentLength = state . SegmentLength ;
var segmentExtension = GetSegmentFileExtension ( state ) ;
var segmentExtension = GetSegmentFileExtension ( state ) ;
@ -191,11 +244,11 @@ namespace MediaBrowser.Api.Playback.Hls
ApiEntryPoint . Instance . TranscodingStartLock . Release ( ) ;
ApiEntryPoint . Instance . TranscodingStartLock . Release ( ) ;
}
}
Logger . Info ( "waiting for {0}" , segmentPath ) ;
//Logger.Info("waiting for {0}", segmentPath) ;
while ( ! File . Exists ( segmentPath ) )
//while (!File.Exists(segmentPath) )
{
// {
await Task . Delay ( 50 , cancellationToken ) . ConfigureAwait ( false ) ;
// await Task.Delay(50, cancellationToken).ConfigureAwait(false);
}
// }
Logger . Info ( "returning {0}" , segmentPath ) ;
Logger . Info ( "returning {0}" , segmentPath ) ;
job = job ? ? ApiEntryPoint . Instance . OnTranscodeBeginRequest ( playlistPath , TranscodingJobType ) ;
job = job ? ? ApiEntryPoint . Instance . OnTranscodeBeginRequest ( playlistPath , TranscodingJobType ) ;
@ -254,7 +307,7 @@ namespace MediaBrowser.Api.Playback.Hls
for ( var i = 0 ; i < requestedIndex ; i + + )
for ( var i = 0 ; i < requestedIndex ; i + + )
{
{
var segmentPath = GetSegmentPath ( playlist, i ) ;
var segmentPath = GetSegmentPath ( state, playlist, i ) ;
double length ;
double length ;
if ( SegmentLengths . TryGetValue ( Path . GetFileName ( segmentPath ) , out length ) )
if ( SegmentLengths . TryGetValue ( Path . GetFileName ( segmentPath ) , out length ) )
@ -360,7 +413,7 @@ namespace MediaBrowser.Api.Playback.Hls
{
{
var segmentId = "0" ;
var segmentId = "0" ;
var segmentRequest = request as Get Dynamic HlsVideoSegment;
var segmentRequest = request as Get HlsVideoSegment;
if ( segmentRequest ! = null )
if ( segmentRequest ! = null )
{
{
segmentId = segmentRequest . SegmentId ;
segmentId = segmentRequest . SegmentId ;
@ -369,13 +422,13 @@ namespace MediaBrowser.Api.Playback.Hls
return int . Parse ( segmentId , NumberStyles . Integer , UsCulture ) ;
return int . Parse ( segmentId , NumberStyles . Integer , UsCulture ) ;
}
}
private string GetSegmentPath ( string playlist , int index )
private string GetSegmentPath ( StreamState state , string playlist , int index )
{
{
var folder = Path . GetDirectoryName ( playlist ) ;
var folder = Path . GetDirectoryName ( playlist ) ;
var filename = Path . GetFileNameWithoutExtension ( playlist ) ;
var filename = Path . GetFileNameWithoutExtension ( playlist ) ;
return Path . Combine ( folder , filename + index . ToString ( UsCulture ) + ".ts" ) ;
return Path . Combine ( folder , filename + index . ToString ( UsCulture ) + GetSegmentFileExtension ( state ) ) ;
}
}
private async Task < object > GetSegmentResult ( string playlistPath ,
private async Task < object > GetSegmentResult ( string playlistPath ,
@ -474,7 +527,7 @@ namespace MediaBrowser.Api.Playback.Hls
} ) ;
} ) ;
}
}
private async Task < object > Get Async( GetMasterHlsVideoStream request , string method )
private async Task < object > Get MasterPlaylistInternal( StreamRequest request , string method )
{
{
var state = await GetState ( request , CancellationToken . None ) . ConfigureAwait ( false ) ;
var state = await GetState ( request , CancellationToken . None ) . ConfigureAwait ( false ) ;
@ -511,14 +564,16 @@ namespace MediaBrowser.Api.Playback.Hls
var playlistUrl = isLiveStream ? "live.m3u8" : "main.m3u8" ;
var playlistUrl = isLiveStream ? "live.m3u8" : "main.m3u8" ;
playlistUrl + = queryString ;
playlistUrl + = queryString ;
var request = ( GetMasterHlsVideoStream ) state . Request ;
var request = state . Request ;
var subtitleStreams = state . MediaSource
var subtitleStreams = state . MediaSource
. MediaStreams
. MediaStreams
. Where ( i = > i . IsTextSubtitleStream )
. Where ( i = > i . IsTextSubtitleStream )
. ToList ( ) ;
. ToList ( ) ;
var subtitleGroup = subtitleStreams . Count > 0 & & request . SubtitleMethod = = SubtitleDeliveryMethod . Hls ?
var subtitleGroup = subtitleStreams . Count > 0 & &
( request is GetMasterHlsVideoPlaylist ) & &
( ( GetMasterHlsVideoPlaylist ) request ) . SubtitleMethod = = SubtitleDeliveryMethod . Hls ?
"subs" :
"subs" :
null ;
null ;
@ -526,7 +581,7 @@ namespace MediaBrowser.Api.Playback.Hls
if ( EnableAdaptiveBitrateStreaming ( state , isLiveStream ) )
if ( EnableAdaptiveBitrateStreaming ( state , isLiveStream ) )
{
{
var requestedVideoBitrate = state . VideoRequest . VideoBitRate . Value ;
var requestedVideoBitrate = state . VideoRequest = = null ? 0 : state . VideoRequest . VideoBitRate ? ? 0 ;
// By default, vary by just 200k
// By default, vary by just 200k
var variation = GetBitrateVariation ( totalBitrate ) ;
var variation = GetBitrateVariation ( totalBitrate ) ;
@ -596,7 +651,7 @@ namespace MediaBrowser.Api.Playback.Hls
return false ;
return false ;
}
}
var request = state . Request as GetMasterHlsVideoStream ;
var request = state . Request as IMasterHlsRequest ;
if ( request ! = null & & ! request . EnableAdaptiveBitrateStreaming )
if ( request ! = null & & ! request . EnableAdaptiveBitrateStreaming )
{
{
return false ;
return false ;
@ -618,6 +673,11 @@ namespace MediaBrowser.Api.Playback.Hls
return false ;
return false ;
}
}
if ( ! state . IsOutputVideo )
{
return false ;
}
// Having problems in android
// Having problems in android
return false ;
return false ;
//return state.VideoRequest.VideoBitRate.HasValue;
//return state.VideoRequest.VideoBitRate.HasValue;
@ -673,7 +733,7 @@ namespace MediaBrowser.Api.Playback.Hls
return variation ;
return variation ;
}
}
private async Task < object > Get PlaylistAsync( VideoStreamRequest request , string name )
private async Task < object > Get VariantPlaylistInternal( StreamRequest request , bool isOutputVideo , string name )
{
{
var state = await GetState ( request , CancellationToken . None ) . ConfigureAwait ( false ) ;
var state = await GetState ( request , CancellationToken . None ) . ConfigureAwait ( false ) ;
@ -697,10 +757,11 @@ namespace MediaBrowser.Api.Playback.Hls
builder . AppendLine ( "#EXTINF:" + length . ToString ( UsCulture ) + "," ) ;
builder . AppendLine ( "#EXTINF:" + length . ToString ( UsCulture ) + "," ) ;
builder . AppendLine ( string . Format ( "hlsdynamic/{0}/{1} .ts {2}",
builder . AppendLine ( string . Format ( "hlsdynamic/{0}/{1} {2}{3 }",
name ,
name ,
index . ToString ( UsCulture ) ,
index . ToString ( UsCulture ) ,
GetSegmentFileExtension ( isOutputVideo ) ,
queryString ) ) ;
queryString ) ) ;
seconds - = state . SegmentLength ;
seconds - = state . SegmentLength ;
@ -716,6 +777,28 @@ namespace MediaBrowser.Api.Playback.Hls
protected override string GetAudioArguments ( StreamState state )
protected override string GetAudioArguments ( StreamState state )
{
{
if ( ! state . IsOutputVideo )
{
var audioTranscodeParams = new List < string > ( ) ;
if ( state . OutputAudioBitrate . HasValue )
{
audioTranscodeParams . Add ( "-ab " + state . OutputAudioBitrate . Value . ToString ( UsCulture ) ) ;
}
if ( state . OutputAudioChannels . HasValue )
{
audioTranscodeParams . Add ( "-ac " + state . OutputAudioChannels . Value . ToString ( UsCulture ) ) ;
}
if ( state . OutputAudioSampleRate . HasValue )
{
audioTranscodeParams . Add ( "-ar " + state . OutputAudioSampleRate . Value . ToString ( UsCulture ) ) ;
}
audioTranscodeParams . Add ( "-vn" ) ;
return string . Join ( " " , audioTranscodeParams . ToArray ( ) ) ;
}
var codec = state . OutputAudioCodec ;
var codec = state . OutputAudioCodec ;
if ( string . Equals ( codec , "copy" , StringComparison . OrdinalIgnoreCase ) )
if ( string . Equals ( codec , "copy" , StringComparison . OrdinalIgnoreCase ) )
@ -746,6 +829,11 @@ namespace MediaBrowser.Api.Playback.Hls
protected override string GetVideoArguments ( StreamState state )
protected override string GetVideoArguments ( StreamState state )
{
{
if ( ! state . IsOutputVideo )
{
return string . Empty ;
}
var codec = state . OutputVideoCodec ;
var codec = state . OutputVideoCodec ;
var args = "-codec:v:0 " + codec ;
var args = "-codec:v:0 " + codec ;
@ -758,11 +846,12 @@ namespace MediaBrowser.Api.Playback.Hls
// See if we can save come cpu cycles by avoiding encoding
// See if we can save come cpu cycles by avoiding encoding
if ( codec . Equals ( "copy" , StringComparison . OrdinalIgnoreCase ) )
if ( codec . Equals ( "copy" , StringComparison . OrdinalIgnoreCase ) )
{
{
return state . VideoStream ! = null & & IsH264 ( state . VideoStream ) ?
args + = state . VideoStream ! = null & & IsH264 ( state . VideoStream )
args + " -bsf:v h264_mp4toannexb" :
? args + " -bsf:v h264_mp4toannexb"
args ;
: args ;
}
}
else
{
var keyFrameArg = string . Format ( " -force_key_frames expr:gte(t,n_forced*{0})" ,
var keyFrameArg = string . Format ( " -force_key_frames expr:gte(t,n_forced*{0})" ,
state . SegmentLength . ToString ( UsCulture ) ) ;
state . SegmentLength . ToString ( UsCulture ) ) ;
@ -783,6 +872,9 @@ namespace MediaBrowser.Api.Playback.Hls
{
{
args + = GetGraphicalSubtitleParam ( state , codec ) ;
args + = GetGraphicalSubtitleParam ( state , codec ) ;
}
}
}
args + = " -flags +loop-global_header -sc_threshold 0" ;
return args ;
return args ;
}
}
@ -797,7 +889,7 @@ namespace MediaBrowser.Api.Playback.Hls
var startNumberParam = isEncoding ? GetStartNumber ( state ) . ToString ( UsCulture ) : "0" ;
var startNumberParam = isEncoding ? GetStartNumber ( state ) . ToString ( UsCulture ) : "0" ;
var toTimeParam = string . Empty ;
var toTimeParam = string . Empty ;
if ( state . RunTimeTicks . HasValue )
if ( state . RunTimeTicks . HasValue & & state . IsOutputVideo )
{
{
var startTime = state . Request . StartTimeTicks ? ? 0 ;
var startTime = state . Request . StartTimeTicks ? ? 0 ;
var durationSeconds = ApiEntryPoint . Instance . GetEncodingOptions ( ) . ThrottleThresholdInSeconds ;
var durationSeconds = ApiEntryPoint . Instance . GetEncodingOptions ( ) . ThrottleThresholdInSeconds ;
@ -812,46 +904,43 @@ namespace MediaBrowser.Api.Playback.Hls
}
}
}
}
var slowSeekParam = GetSlowSeekCommandLineParameter ( state . Request ) ;
var timestampOffsetParam = string . Empty ;
if ( ! string . IsNullOrWhiteSpace ( slowSeekParam ) )
if ( state . IsOutputVideo )
{
{
slowSeekParam = " " + slowSeekParam ;
timestampOffsetParam = " -output_ts_offset " + MediaEncoder . GetTimeParameter ( state . Request . StartTimeTicks ? ? 0 ) . ToString ( CultureInfo . InvariantCulture ) ;
}
}
//state.EnableGenericHlsSegmenter = true ;
var mapArgs = state . IsOutputVideo ? GetMapArgs ( state ) : string . Empty ;
if ( state . EnableGenericHlsSegmenter )
//var outputTsArg = Path.Combine(Path.GetDirectoryName(outputPath), Path.GetFileNameWithoutExtension(outputPath)) + "%d" + GetSegmentFileExtension(state);
{
var outputTsArg = Path . Combine ( Path . GetDirectoryName ( outputPath ) , Path . GetFileNameWithoutExtension ( outputPath ) ) + "%d.ts" ;
return string . Format ( "{0} {11} {1}{10} -map_metadata -1 -threads {2} {3} {4} -flags -global_header -sc_threshold 0 {5} -f segment -segment_time {6} -segment_format mpegts -segment_list_type m3u8 -segment_start_number {7} -segment_list \"{8}\" -y \"{9}\"" ,
//return string.Format("{0} {11} {1}{10} -map_metadata -1 -threads {2} {3} {4} {5} -f segment -segment_time {6} -segment_format mpegts -segment_list_type m3u8 -segment_start_number {7} -segment_list \"{8}\" -y \"{9}\"",
inputModifier ,
// inputModifier,
GetInputArgument ( state ) ,
// GetInputArgument(state),
threads ,
// threads,
GetMapArgs ( state ) ,
// mapArgs,
GetVideoArguments ( state ) ,
// GetVideoArguments(state),
GetAudioArguments ( state ) ,
// GetAudioArguments(state),
state . SegmentLength . ToString ( UsCulture ) ,
// state.SegmentLength.ToString(UsCulture),
startNumberParam ,
// startNumberParam,
outputPath ,
// outputPath,
outputTsArg ,
// outputTsArg,
slowSeekParam ,
// slowSeekParam,
toTimeParam
// toTimeParam
) . Trim ( ) ;
// ).Trim();
}
return string . Format ( "{0}{11} {1} {10} -map_metadata -1 -threads {2} {3} {4} -output_ts_offset " + MediaEncoder . GetTimeParameter ( state . Request . StartTimeTicks ? ? 0 ) + " -flags -global_header -sc_threshold 0 {5} -hls_time {6} -start_number {7} -hls_list_size {8} -y \"{9 }\"",
return string . Format ( "{0}{11} {1} -map_metadata -1 -threads {2} {3} {4}{5} {6} -hls_time {7} -start_number {8} -hls_list_size {9} -y \"{10 }\"",
inputModifier ,
inputModifier ,
GetInputArgument ( state ) ,
GetInputArgument ( state ) ,
threads ,
threads ,
GetMapArgs( state ) ,
mapArgs ,
GetVideoArguments ( state ) ,
GetVideoArguments ( state ) ,
timestampOffsetParam ,
GetAudioArguments ( state ) ,
GetAudioArguments ( state ) ,
state . SegmentLength . ToString ( UsCulture ) ,
state . SegmentLength . ToString ( UsCulture ) ,
startNumberParam ,
startNumberParam ,
state . HlsListSize . ToString ( UsCulture ) ,
state . HlsListSize . ToString ( UsCulture ) ,
outputPath ,
outputPath ,
slowSeekParam ,
toTimeParam
toTimeParam
) . Trim ( ) ;
) . Trim ( ) ;
}
}
@ -872,14 +961,6 @@ namespace MediaBrowser.Api.Playback.Hls
}
}
}
}
protected override bool EnableSlowSeek
{
get
{
return true ;
}
}
/// <summary>
/// <summary>
/// Gets the segment file extension.
/// Gets the segment file extension.
/// </summary>
/// </summary>
@ -887,7 +968,12 @@ namespace MediaBrowser.Api.Playback.Hls
/// <returns>System.String.</returns>
/// <returns>System.String.</returns>
protected override string GetSegmentFileExtension ( StreamState state )
protected override string GetSegmentFileExtension ( StreamState state )
{
{
return ".ts" ;
return GetSegmentFileExtension ( state . IsOutputVideo ) ;
}
protected string GetSegmentFileExtension ( bool isOutputVideo )
{
return isOutputVideo ? ".ts" : ".ts" ;
}
}
}
}
}
}