@ -30,27 +30,60 @@ namespace MediaBrowser.Api.Playback.Hls
/// </summary>
[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.")]
public class GetMasterHlsVideo Stream : VideoStream Request
public class GetMasterHlsVideo Playlist : VideoStreamRequest , IMasterHls Request
{
public bool EnableAdaptiveBitrateStreaming { get ; set ; }
public GetMasterHlsVideo Stream ( )
public GetMasterHlsVideo Playlist ( )
{
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.")]
public class GetMainHlsVideoStream : VideoStreamRequest
public class GetVariantHlsVideoPlaylist : VideoStreamRequest
{
}
[Route("/Audio/{Id}/main.m3u8", "GET", Summary = "Gets an audio stream using HTTP live streaming.")]
public class GetVariantHlsAudioPlaylist : StreamRequest
{
}
/// <summary>
/// Class GetHlsVideoSegment
/// </summary>
[Route("/Videos/{Id}/hlsdynamic/{PlaylistId}/{SegmentId}.ts", "GET")]
[Api(Description = "Gets an Http live streaming segment file. Internal use only.")]
public class GetDynamicHlsVideoSegment : VideoStreamRequest
public class GetHlsVideoSegment : VideoStreamRequest
{
public string PlaylistId { get ; set ; }
/// <summary>
/// Gets or sets the segment id.
/// </summary>
/// <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.")]
public class GetHlsAudioSegment : StreamRequest
{
public string PlaylistId { get ; set ; }
@ -71,27 +104,47 @@ namespace MediaBrowser.Api.Playback.Hls
protected INetworkManager NetworkManager { get ; private set ; }
public Task < object > Get ( GetMasterHlsVideo Stream request )
public Task < object > Get ( GetMasterHlsVideo Playlist request )
{
return Get Async ( request , "GET" ) ;
return Get MasterPlaylistInternal ( request , "GET" ) ;
}
public Task < object > Head ( GetMasterHlsVideo Stream request )
public Task < object > Head ( GetMasterHlsVideo Playlist request )
{
return Get Async ( request , "HEAD" ) ;
return Get MasterPlaylistInternal ( request , "HEAD" ) ;
}
public Task < object > Get ( GetMa inHlsVideoStream request )
public Task < object > Get ( GetMa sterHlsAudioPlaylist request )
{
return Get PlaylistAsync( request , "main ") ;
return Get MasterPlaylistInternal( request , "GET ") ;
}
public Task < object > Get ( GetDynamicHlsVideoSegment request )
public Task < object > Head ( GetMasterHlsAudioPlaylist request )
{
return GetMasterPlaylistInternal ( request , "HEAD" ) ;
}
public Task < object > Get ( GetVariantHlsVideoPlaylist request )
{
return GetVariantPlaylistInternal ( request , true , "main" ) ;
}
public Task < object > Get ( GetVariantHlsAudioPlaylist request )
{
return GetVariantPlaylistInternal ( request , false , "main" ) ;
}
public Task < object > Get ( GetHlsVideoSegment request )
{
return GetDynamicSegment ( request , request . SegmentId ) ;
}
public Task < object > Get ( GetHlsAudioSegment request )
{
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 )
{
@ -107,7 +160,7 @@ namespace MediaBrowser.Api.Playback.Hls
var playlistPath = Path . ChangeExtension ( state . OutputFilePath , ".m3u8" ) ;
var segmentPath = GetSegmentPath ( playlistPath, requestedIndex ) ;
var segmentPath = GetSegmentPath ( state, playlistPath, requestedIndex ) ;
var segmentLength = state . SegmentLength ;
var segmentExtension = GetSegmentFileExtension ( state ) ;
@ -191,11 +244,11 @@ namespace MediaBrowser.Api.Playback.Hls
ApiEntryPoint . Instance . TranscodingStartLock . Release ( ) ;
}
Logger . Info ( "waiting for {0}" , segmentPath ) ;
while ( ! File . Exists ( segmentPath ) )
{
await Task . Delay ( 50 , cancellationToken ) . ConfigureAwait ( false ) ;
}
//Logger.Info("waiting for {0}", segmentPath) ;
//while (!File.Exists(segmentPath) )
// {
// await Task.Delay(50, cancellationToken).ConfigureAwait(false);
// }
Logger . Info ( "returning {0}" , segmentPath ) ;
job = job ? ? ApiEntryPoint . Instance . OnTranscodeBeginRequest ( playlistPath , TranscodingJobType ) ;
@ -254,7 +307,7 @@ namespace MediaBrowser.Api.Playback.Hls
for ( var i = 0 ; i < requestedIndex ; i + + )
{
var segmentPath = GetSegmentPath ( playlist, i ) ;
var segmentPath = GetSegmentPath ( state, playlist, i ) ;
double length ;
if ( SegmentLengths . TryGetValue ( Path . GetFileName ( segmentPath ) , out length ) )
@ -360,7 +413,7 @@ namespace MediaBrowser.Api.Playback.Hls
{
var segmentId = "0" ;
var segmentRequest = request as Get Dynamic HlsVideoSegment;
var segmentRequest = request as Get HlsVideoSegment;
if ( segmentRequest ! = null )
{
segmentId = segmentRequest . SegmentId ;
@ -369,13 +422,13 @@ namespace MediaBrowser.Api.Playback.Hls
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 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 ,
@ -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 ) ;
@ -511,14 +564,16 @@ namespace MediaBrowser.Api.Playback.Hls
var playlistUrl = isLiveStream ? "live.m3u8" : "main.m3u8" ;
playlistUrl + = queryString ;
var request = ( GetMasterHlsVideoStream ) state . Request ;
var request = state . Request ;
var subtitleStreams = state . MediaSource
. MediaStreams
. Where ( i = > i . IsTextSubtitleStream )
. ToList ( ) ;
var subtitleGroup = subtitleStreams . Count > 0 & & request . SubtitleMethod = = SubtitleDeliveryMethod . Hls ?
var subtitleGroup = subtitleStreams . Count > 0 & &
( request is GetMasterHlsVideoPlaylist ) & &
( ( GetMasterHlsVideoPlaylist ) request ) . SubtitleMethod = = SubtitleDeliveryMethod . Hls ?
"subs" :
null ;
@ -526,7 +581,7 @@ namespace MediaBrowser.Api.Playback.Hls
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
var variation = GetBitrateVariation ( totalBitrate ) ;
@ -596,7 +651,7 @@ namespace MediaBrowser.Api.Playback.Hls
return false ;
}
var request = state . Request as GetMasterHlsVideoStream ;
var request = state . Request as IMasterHlsRequest ;
if ( request ! = null & & ! request . EnableAdaptiveBitrateStreaming )
{
return false ;
@ -618,6 +673,11 @@ namespace MediaBrowser.Api.Playback.Hls
return false ;
}
if ( ! state . IsOutputVideo )
{
return false ;
}
// Having problems in android
return false ;
//return state.VideoRequest.VideoBitRate.HasValue;
@ -673,7 +733,7 @@ namespace MediaBrowser.Api.Playback.Hls
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 ) ;
@ -697,10 +757,11 @@ namespace MediaBrowser.Api.Playback.Hls
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 ,
index . ToString ( UsCulture ) ,
GetSegmentFileExtension ( isOutputVideo ) ,
queryString ) ) ;
seconds - = state . SegmentLength ;
@ -716,6 +777,28 @@ namespace MediaBrowser.Api.Playback.Hls
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 ;
if ( string . Equals ( codec , "copy" , StringComparison . OrdinalIgnoreCase ) )
@ -746,6 +829,11 @@ namespace MediaBrowser.Api.Playback.Hls
protected override string GetVideoArguments ( StreamState state )
{
if ( ! state . IsOutputVideo )
{
return string . Empty ;
}
var codec = state . OutputVideoCodec ;
var args = "-codec:v:0 " + codec ;
@ -758,31 +846,35 @@ namespace MediaBrowser.Api.Playback.Hls
// See if we can save come cpu cycles by avoiding encoding
if ( codec . Equals ( "copy" , StringComparison . OrdinalIgnoreCase ) )
{
return state . VideoStream ! = null & & IsH264 ( state . VideoStream ) ?
args + " -bsf:v h264_mp4toannexb" :
args ;
args + = state . VideoStream ! = null & & IsH264 ( state . VideoStream )
? args + " -bsf:v h264_mp4toannexb"
: args ;
}
else
{
var keyFrameArg = string . Format ( " -force_key_frames expr:gte(t,n_forced*{0})" ,
state . SegmentLength . ToString ( UsCulture ) ) ;
var keyFrameArg = string . Format ( " -force_key_frames expr:gte(t,n_forced*{0})" ,
state . SegmentLength . ToString ( UsCulture ) ) ;
var hasGraphicalSubs = state . SubtitleStream ! = null & & ! state . SubtitleStream . IsTextSubtitleStream ;
var hasGraphicalSubs = state . SubtitleStream ! = null & & ! state . SubtitleStream . IsTextSubtitleStream ;
args + = " " + GetVideoQualityParam ( state , H264Encoder , true ) + keyFrameArg ;
args + = " " + GetVideoQualityParam ( state , H264Encoder , true ) + keyFrameArg ;
//args += " -mixed-refs 0 -refs 3 -x264opts b_pyramid=0:weightb=0:weightp=0" ;
//args += " -mixed-refs 0 -refs 3 -x264opts b_pyramid=0:weightb=0:weightp=0";
// Add resolution params, if specified
if ( ! hasGraphicalSubs )
{
args + = GetOutputSizeParam ( state , codec , false ) ;
}
// Add resolution params, if specified
if ( ! hasGraphicalSubs )
{
args + = GetOutputSizeParam ( state , codec , false ) ;
// This is for internal graphical subs
if ( hasGraphicalSubs )
{
args + = GetGraphicalSubtitleParam ( state , codec ) ;
}
}
// This is for internal graphical subs
if ( hasGraphicalSubs )
{
args + = GetGraphicalSubtitleParam ( state , codec ) ;
}
args + = " -flags +loop-global_header -sc_threshold 0" ;
return args ;
}
@ -797,7 +889,7 @@ namespace MediaBrowser.Api.Playback.Hls
var startNumberParam = isEncoding ? GetStartNumber ( state ) . ToString ( UsCulture ) : "0" ;
var toTimeParam = string . Empty ;
if ( state . RunTimeTicks . HasValue )
if ( state . RunTimeTicks . HasValue & & state . IsOutputVideo )
{
var startTime = state . Request . StartTimeTicks ? ? 0 ;
var durationSeconds = ApiEntryPoint . Instance . GetEncodingOptions ( ) . ThrottleThresholdInSeconds ;
@ -812,46 +904,43 @@ namespace MediaBrowser.Api.Playback.Hls
}
}
var slowSeekParam = GetSlowSeekCommandLineParameter ( state . Request ) ;
if ( ! string . IsNullOrWhiteSpace ( slowSeekParam ) )
var timestampOffsetParam = string . Empty ;
if ( state . IsOutputVideo )
{
slowSeekParam = " " + slowSeekParam ;
timestampOffsetParam = " -output_ts_offset " + MediaEncoder . GetTimeParameter ( state . Request . StartTimeTicks ? ? 0 ) . ToString ( CultureInfo . InvariantCulture ) ;
}
var mapArgs = state . IsOutputVideo ? GetMapArgs ( state ) : string . Empty ;
//state.EnableGenericHlsSegmenter = true;
// var outputTsArg = Path.Combine(Path.GetDirectoryName(outputPath), Path.GetFileNameWithoutExtension(outputPath)) + "%d" + GetSegmentFileExtension(state) ;
if ( state . EnableGenericHlsSegmenter )
{
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} {5} -f segment -segment_time {6} -segment_format mpegts -segment_list_type m3u8 -segment_start_number {7} -segment_list \"{8}\" -y \"{9}\"",
// inputModifier,
// GetInputArgument(state),
// threads,
// mapArgs,
// GetVideoArguments(state),
// GetAudioArguments(state),
// state.SegmentLength.ToString(UsCulture),
// startNumberParam,
// outputPath,
// outputTsArg,
// slowSeekParam,
// toTimeParam
// ).Trim();
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}\"" ,
inputModifier ,
GetInputArgument ( state ) ,
threads ,
GetMapArgs ( state ) ,
GetVideoArguments ( state ) ,
GetAudioArguments ( state ) ,
state . SegmentLength . ToString ( UsCulture ) ,
startNumberParam ,
outputPath ,
outputTsArg ,
slowSeekParam ,
toTimeParam
) . 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 ,
GetInputArgument ( state ) ,
threads ,
GetMapArgs( state ) ,
mapArgs ,
GetVideoArguments ( state ) ,
timestampOffsetParam ,
GetAudioArguments ( state ) ,
state . SegmentLength . ToString ( UsCulture ) ,
startNumberParam ,
state . HlsListSize . ToString ( UsCulture ) ,
outputPath ,
slowSeekParam ,
toTimeParam
) . Trim ( ) ;
}
@ -872,14 +961,6 @@ namespace MediaBrowser.Api.Playback.Hls
}
}
protected override bool EnableSlowSeek
{
get
{
return true ;
}
}
/// <summary>
/// Gets the segment file extension.
/// </summary>
@ -887,7 +968,12 @@ namespace MediaBrowser.Api.Playback.Hls
/// <returns>System.String.</returns>
protected override string GetSegmentFileExtension ( StreamState state )
{
return ".ts" ;
return GetSegmentFileExtension ( state . IsOutputVideo ) ;
}
protected string GetSegmentFileExtension ( bool isOutputVideo )
{
return isOutputVideo ? ".ts" : ".ts" ;
}
}
}