@ -160,7 +160,6 @@ namespace MediaBrowser.Api.Playback.Hls
var playlistPath = Path . ChangeExtension ( state . OutputFilePath , ".m3u8" ) ;
var segmentPath = GetSegmentPath ( state , playlistPath , requestedIndex ) ;
var segmentLength = state . SegmentLength ;
var segmentExtension = GetSegmentFileExtension ( state ) ;
@ -169,7 +168,7 @@ namespace MediaBrowser.Api.Playback.Hls
if ( File . Exists ( segmentPath ) )
{
job = ApiEntryPoint . Instance . OnTranscodeBeginRequest ( playlistPath , TranscodingJobType ) ;
return await GetSegmentResult ( playlistPath, segmentPath , requestedIndex , segmentLength , job , cancellationToken ) . ConfigureAwait ( false ) ;
return await GetSegmentResult ( state, playlistPath, segmentPath , requestedIndex , job , cancellationToken ) . ConfigureAwait ( false ) ;
}
await ApiEntryPoint . Instance . TranscodingStartLock . WaitAsync ( cancellationTokenSource . Token ) . ConfigureAwait ( false ) ;
@ -178,7 +177,7 @@ namespace MediaBrowser.Api.Playback.Hls
if ( File . Exists ( segmentPath ) )
{
job = ApiEntryPoint . Instance . OnTranscodeBeginRequest ( playlistPath , TranscodingJobType ) ;
return await GetSegmentResult ( playlistPath, segmentPath , requestedIndex , segmentLength , job , cancellationToken ) . ConfigureAwait ( false ) ;
return await GetSegmentResult ( state, playlistPath, segmentPath , requestedIndex , job , cancellationToken ) . ConfigureAwait ( false ) ;
}
else
{
@ -214,7 +213,7 @@ namespace MediaBrowser.Api.Playback.Hls
DeleteLastFile ( playlistPath , segmentExtension , 0 ) ;
}
request . StartTimeTicks = GetS eekPositionTicks( state , playlistPath , requestedIndex ) ;
request . StartTimeTicks = GetS tartPositionTicks( state , requestedIndex ) ;
job = await StartFfMpeg ( state , playlistPath , cancellationTokenSource ) . ConfigureAwait ( false ) ;
}
@ -249,37 +248,76 @@ namespace MediaBrowser.Api.Playback.Hls
Logger . Info ( "returning {0}" , segmentPath ) ;
job = job ? ? ApiEntryPoint . Instance . OnTranscodeBeginRequest ( playlistPath , TranscodingJobType ) ;
return await GetSegmentResult ( playlistPath, segmentPath , requestedIndex , segmentLength , job , cancellationToken ) . ConfigureAwait ( false ) ;
return await GetSegmentResult ( state, playlistPath, segmentPath , requestedIndex , job , cancellationToken ) . ConfigureAwait ( false ) ;
}
// 256k
private const int BufferSize = 262144 ;
private long GetS eek PositionTicks( StreamState state , string playlist , int requestedIndex )
private long GetS tart PositionTicks( StreamState state , int requestedIndex )
{
double startSeconds = 0 ;
var lengths = GetSegmentLengths ( state ) ;
for ( var i = 0 ; i < requestedIndex ; i + + )
{
var segmentPath = GetSegmentPath ( state , playlist , i ) ;
startSeconds + = lengths [ requestedIndex ] ;
}
var position = TimeSpan . FromSeconds ( startSeconds ) . Ticks ;
return position ;
}
//double length;
//if (SegmentLengths.TryGetValue(Path.GetFileName(segmentPath), out length))
//{
// Logger.Debug("Found segment length of {0} for index {1}", length, i);
// startSeconds += length;
//}
//else
//{
// startSeconds += state.SegmentLength;
//}
startSeconds + = state . SegmentLength ;
private long GetEndPositionTicks ( StreamState state , int requestedIndex )
{
double startSeconds = 0 ;
var lengths = GetSegmentLengths ( state ) ;
for ( var i = 0 ; i < = requestedIndex ; i + + )
{
startSeconds + = lengths [ requestedIndex ] ;
}
var position = TimeSpan . FromSeconds ( startSeconds ) . Ticks ;
return position ;
}
private double [ ] GetSegmentLengths ( StreamState state )
{
var result = new List < double > ( ) ;
var encoder = GetVideoEncoder ( state ) ;
if ( string . Equals ( encoder , "copy" , StringComparison . OrdinalIgnoreCase ) )
{
var videoStream = state . VideoStream ;
if ( videoStream . KeyFrames ! = null & & videoStream . KeyFrames . Count > 0 )
{
foreach ( var frame in videoStream . KeyFrames )
{
var seconds = TimeSpan . FromMilliseconds ( frame ) . TotalSeconds ;
seconds - = result . Sum ( ) ;
result . Add ( seconds ) ;
}
return result . ToArray ( ) ;
}
}
var ticks = state . RunTimeTicks ? ? 0 ;
var segmentLengthTicks = TimeSpan . FromSeconds ( state . SegmentLength ) . Ticks ;
while ( ticks > 0 )
{
var length = ticks > = segmentLengthTicks ? segmentLengthTicks : ticks ;
result . Add ( TimeSpan . FromTicks ( length ) . TotalSeconds ) ;
ticks - = length ;
}
return result . ToArray ( ) ;
}
public int? GetCurrentTranscodingIndex ( string playlist , string segmentExtension )
{
var job = ApiEntryPoint . Instance . GetTranscodingJob ( playlist , TranscodingJobType ) ;
@ -384,17 +422,16 @@ namespace MediaBrowser.Api.Playback.Hls
return Path . Combine ( folder , filename + index . ToString ( UsCulture ) + GetSegmentFileExtension ( state ) ) ;
}
private async Task < object > GetSegmentResult ( string playlistPath ,
private async Task < object > GetSegmentResult ( StreamState state , string playlistPath ,
string segmentPath ,
int segmentIndex ,
int segmentLength ,
TranscodingJob transcodingJob ,
CancellationToken cancellationToken )
{
// If all transcoding has completed, just return immediately
if ( transcodingJob ! = null & & transcodingJob . HasExited & & File . Exists ( segmentPath ) )
{
return GetSegmentResult ( s egmentPath, segmentIndex , segmentLength , transcodingJob ) ;
return GetSegmentResult ( s tate, s egmentPath, segmentIndex , transcodingJob ) ;
}
var segmentFilename = Path . GetFileName ( segmentPath ) ;
@ -414,7 +451,7 @@ namespace MediaBrowser.Api.Playback.Hls
{
if ( File . Exists ( segmentPath ) )
{
return GetSegmentResult ( s egmentPath, segmentIndex , segmentLength , transcodingJob ) ;
return GetSegmentResult ( s tate, s egmentPath, segmentIndex , transcodingJob ) ;
}
//break;
}
@ -465,13 +502,12 @@ namespace MediaBrowser.Api.Playback.Hls
//}
cancellationToken . ThrowIfCancellationRequested ( ) ;
return GetSegmentResult ( s egmentPath, segmentIndex , segmentLength , transcodingJob ) ;
return GetSegmentResult ( s tate, s egmentPath, segmentIndex , transcodingJob ) ;
}
private object GetSegmentResult ( string segmentPath , int index , int segmentLength , TranscodingJob transcodingJob )
private object GetSegmentResult ( StreamState state , string segmentPath , int index , TranscodingJob transcodingJob )
{
var segmentEndingSeconds = ( 1 + index ) * segmentLength ;
var segmentEndingPositionTicks = TimeSpan . FromSeconds ( segmentEndingSeconds ) . Ticks ;
var segmentEndingPositionTicks = GetEndPositionTicks ( state , index ) ;
return ResultFactory . GetStaticFileResult ( Request , new StaticFileResultOptions
{
@ -698,26 +734,22 @@ namespace MediaBrowser.Api.Playback.Hls
{
var state = await GetState ( request , CancellationToken . None ) . ConfigureAwait ( false ) ;
var segmentLengths = GetSegmentLengths ( state ) ;
var builder = new StringBuilder ( ) ;
builder . AppendLine ( "#EXTM3U" ) ;
builder . AppendLine ( "#EXT-X-VERSION:3" ) ;
builder . AppendLine ( "#EXT-X-TARGETDURATION:" + ( s tate. SegmentLength ) . ToString ( UsCulture ) ) ;
builder . AppendLine ( "#EXT-X-TARGETDURATION:" + Math . Ceiling ( ( s egmentLengths. Length > 0 ? segmentLengths . Max ( ) : s tate. SegmentLength ) ) . ToString ( UsCulture ) ) ;
builder . AppendLine ( "#EXT-X-MEDIA-SEQUENCE:0" ) ;
var queryStringIndex = Request . RawUrl . IndexOf ( '?' ) ;
var queryString = queryStringIndex = = - 1 ? string . Empty : Request . RawUrl . Substring ( queryStringIndex ) ;
var seconds = TimeSpan . FromTicks ( state . RunTimeTicks ? ? 0 ) . TotalSeconds ;
var index = 0 ;
double segmentLength = state . SegmentLength ;
while ( seconds > 0 )
foreach ( var length in segmentLengths )
{
var length = seconds > = state . SegmentLength ? segmentLength : seconds ;
builder . AppendLine ( "#EXTINF:" + length . ToString ( "0.000000" , UsCulture ) + "," ) ;
builder . AppendLine ( string . Format ( "hlsdynamic/{0}/{1}{2}{3}" ,
@ -727,7 +759,6 @@ namespace MediaBrowser.Api.Playback.Hls
GetSegmentFileExtension ( isOutputVideo ) ,
queryString ) ) ;
seconds - = length ;
index + + ;
}
@ -850,11 +881,6 @@ namespace MediaBrowser.Api.Playback.Hls
args + = " -flags +loop-global_header -sc_threshold 0" ;
}
if ( ! EnableSplitTranscoding ( state ) )
{
//args += " -copyts";
}
return args ;
}
@ -870,21 +896,6 @@ namespace MediaBrowser.Api.Playback.Hls
var toTimeParam = string . Empty ;
var timestampOffsetParam = string . Empty ;
if ( EnableSplitTranscoding ( state ) )
{
var startTime = state . Request . StartTimeTicks ? ? 0 ;
var durationSeconds = ApiEntryPoint . Instance . GetEncodingOptions ( ) . ThrottleThresholdInSeconds ;
var endTime = startTime + TimeSpan . FromSeconds ( durationSeconds ) . Ticks ;
endTime = Math . Min ( endTime , state . RunTimeTicks . Value ) ;
if ( endTime < state . RunTimeTicks . Value )
{
//toTimeParam = " -to " + MediaEncoder.GetTimeParameter(endTime);
toTimeParam = " -t " + MediaEncoder . GetTimeParameter ( TimeSpan . FromSeconds ( durationSeconds ) . Ticks ) ;
}
}
if ( state . IsOutputVideo & & ! string . Equals ( state . OutputVideoCodec , "copy" , StringComparison . OrdinalIgnoreCase ) & & ( state . Request . StartTimeTicks ? ? 0 ) > 0 )
{
timestampOffsetParam = " -output_ts_offset " + MediaEncoder . GetTimeParameter ( state . Request . StartTimeTicks ? ? 0 ) . ToString ( CultureInfo . InvariantCulture ) ;
@ -927,36 +938,7 @@ namespace MediaBrowser.Api.Playback.Hls
protected override bool EnableThrottling ( StreamState state )
{
return ! EnableSplitTranscoding ( state ) ;
}
private bool EnableSplitTranscoding ( StreamState state )
{
return false ;
if ( string . Equals ( Request . QueryString [ "EnableSplitTranscoding" ] , "false" , StringComparison . OrdinalIgnoreCase ) )
{
return false ;
}
if ( string . Equals ( state . OutputVideoCodec , "copy" , StringComparison . OrdinalIgnoreCase ) )
{
return false ;
}
if ( string . Equals ( state . OutputAudioCodec , "copy" , StringComparison . OrdinalIgnoreCase ) )
{
return false ;
}
return state . RunTimeTicks . HasValue & & state . IsOutputVideo ;
}
protected override bool EnableStreamCopy
{
get
{
return false ;
}
return true ;
}
/// <summary>