@ -27,15 +27,13 @@ public class HlsSessionWorker : IHlsSessionWorker
@@ -27,15 +27,13 @@ public class HlsSessionWorker : IHlsSessionWorker
private readonly IServiceScopeFactory _ serviceScopeFactory ;
private readonly object _ sync = new ( ) ;
private string _ channelNumber ;
private bool _f irstProcess ;
private bool _ hasWrittenSegments ;
private DateTimeOffset _l astAccess ;
private DateTimeOffset _l astDelete = DateTimeOffset . MinValue ;
private bool _ seekNextItem ;
private Option < int > _ targetFramerate ;
private Timer _ timer ;
private DateTimeOffset _ transcodedUntil ;
private HlsSessionWorkAhead State _ workAheadS tate;
private HlsSessionState _ s tate;
public HlsSessionWorker (
IHlsPlaylistFilter hlsPlaylistFilter ,
@ -104,9 +102,7 @@ public class HlsSessionWorker : IHlsSessionWorker
@@ -104,9 +102,7 @@ public class HlsSessionWorker : IHlsSessionWorker
public void PlayoutUpdated ( )
{
_f irstProcess = true ;
_ seekNextItem = true ;
_ workAheadState = HlsSessionWorkAheadState . SeekAndRealtime ;
_ state = HlsSessionState . PlayoutUpdated ;
}
public async Task Run ( string channelNumber , TimeSpan idleTimeout , CancellationToken incomingCancellationToken )
@ -148,12 +144,8 @@ public class HlsSessionWorker : IHlsSessionWorker
@@ -148,12 +144,8 @@ public class HlsSessionWorker : IHlsSessionWorker
_ transcodedUntil = DateTimeOffset . Now ;
PlaylistStart = _ transcodedUntil ;
_f irstProcess = true ;
bool initialWorkAhead = Volatile . Read ( ref _ workAheadCount ) < await GetWorkAheadLimit ( ) ;
_ workAheadState = initialWorkAhead
? HlsSessionWorkAheadState . MaxSpeed
: HlsSessionWorkAheadState . SeekAndRealtime ;
_ state = initialWorkAhead ? HlsSessionState . SeekAndWorkAhead : HlsSessionState . SeekAndRealtime ;
if ( ! await Transcode ( ! initialWorkAhead , cancellationToken ) )
{
@ -206,6 +198,42 @@ public class HlsSessionWorker : IHlsSessionWorker
@@ -206,6 +198,42 @@ public class HlsSessionWorker : IHlsSessionWorker
}
}
private HlsSessionState NextState ( HlsSessionState state , PlayoutItemProcessModel processModel )
{
bool isComplete = processModel ? . IsComplete = = true ;
HlsSessionState result = state switch
{
// playout updates should have the channel start over, transcode method will throttle if needed
HlsSessionState . PlayoutUpdated = > HlsSessionState . SeekAndWorkAhead ,
// after seeking and NOT completing the item, seek again, transcode method will throttle if needed
HlsSessionState . SeekAndWorkAhead when ! isComplete = > HlsSessionState . SeekAndWorkAhead ,
// after seeking and completing the item, start at zero
HlsSessionState . SeekAndWorkAhead = > HlsSessionState . ZeroAndWorkAhead ,
// after starting and zero and NOT completing the item, seek, transcode method will throttle if needed
HlsSessionState . ZeroAndWorkAhead when ! isComplete = > HlsSessionState . SeekAndWorkAhead ,
// after starting at zero and completing the item, start at zero again, transcode method will throttle if needed
HlsSessionState . ZeroAndWorkAhead = > HlsSessionState . ZeroAndWorkAhead ,
// realtime will always complete items, so start next at zero
HlsSessionState . SeekAndRealtime = > HlsSessionState . ZeroAndRealtime ,
// realtime will always complete items, so start next at zero
HlsSessionState . ZeroAndRealtime = > HlsSessionState . ZeroAndRealtime ,
// this will never happen with the enum
_ = > throw new InvalidOperationException ( )
} ;
_l ogger . LogDebug ( "HLS session state {Last} => {Next}" , state , result ) ;
return result ;
}
private async Task < bool > Transcode (
bool realtime ,
CancellationToken cancellationToken )
@ -226,30 +254,34 @@ public class HlsSessionWorker : IHlsSessionWorker
@@ -226,30 +254,34 @@ public class HlsSessionWorker : IHlsSessionWorker
_ channelNumber ) ;
}
// throttle to realtime if needed
if ( realtime )
{
HlsSessionState nextState = _ state switch
{
HlsSessionState . SeekAndWorkAhead = > HlsSessionState . SeekAndRealtime ,
HlsSessionState . ZeroAndWorkAhead = > HlsSessionState . ZeroAndRealtime ,
_ = > _ state
} ;
if ( nextState ! = _ state )
{
_l ogger . LogDebug ( "HLS session state throttling {Last} => {Next}" , _ state , nextState ) ;
_ state = nextState ;
}
}
IMediator mediator = scope . ServiceProvider . GetRequiredService < IMediator > ( ) ;
long ptsOffset = await GetPtsOffset ( mediator , _ channelNumber , cancellationToken ) ;
// _logger.LogInformation("PTS offset: {PtsOffset}", ptsOffset);
// this shouldn't happen, but respect realtime
if ( realtime & & _ workAheadState is HlsSessionWorkAheadState . MaxSpeed )
{
_ workAheadState = HlsSessionWorkAheadState . SeekAndRealtime ;
}
// this happens when we initially transcode (at max speed) insufficient content to work
// in realtime yet, so we need to reset to max speed
if ( ! realtime & & _ workAheadState is not HlsSessionWorkAheadState . MaxSpeed )
{
_ workAheadState = HlsSessionWorkAheadState . MaxSpeed ;
}
_l ogger . LogInformation ( "Work ahead state: {State}" , _ workAheadState ) ;
_l ogger . LogInformation ( "HLS session state: {State}" , _ state ) ;
DateTimeOffset now = ( _f ir stProcess | | _ workAheadSt ate is HlsSessionWorkAhead State . MaxSpeed )
DateTimeOffset now = _ state is HlsSessionState . SeekAndWorkAhead
? DateTimeOffset . Now
: _ transcodedUntil . AddSeconds ( _ workAheadS tate is HlsSessionWorkAhead State . SeekAndRealtime ? 0 : 1 ) ;
bool startAtZero = _ workAheadS tate is HlsSessionWorkAhead State . RealtimeFromZero & & ! _f irstProcess ;
: _ transcodedUntil . AddSeconds ( _ state is HlsSessionState . SeekAndRealtime ? 0 : 1 ) ;
bool startAtZero = _ state is HlsSessionState . ZeroAndWorkAhead or HlsSessionState . ZeroAndRealtime ;
var request = new GetPlayoutItemProcessByChannelNumber (
_ channelNumber ,
@ -295,20 +327,7 @@ public class HlsSessionWorker : IHlsSessionWorker
@@ -295,20 +327,7 @@ public class HlsSessionWorker : IHlsSessionWorker
_l ogger . LogInformation ( "HLS process has completed for channel {Channel}" , _ channelNumber ) ;
_l ogger . LogDebug ( "Transcoded until: {Until}" , processModel . Until ) ;
_ transcodedUntil = processModel . Until ;
_f irstProcess = false ;
if ( _ seekNextItem )
{
_f irstProcess = true ;
_ seekNextItem = false ;
}
_ workAheadState = _ workAheadState switch
{
HlsSessionWorkAheadState . MaxSpeed = > HlsSessionWorkAheadState . SeekAndRealtime ,
HlsSessionWorkAheadState . SeekAndRealtime = > HlsSessionWorkAheadState . RealtimeFromZero ,
_ = > _ workAheadState
} ;
_ state = NextState ( _ state , processModel ) ;
_ hasWrittenSegments = true ;
return true ;
}
@ -353,12 +372,7 @@ public class HlsSessionWorker : IHlsSessionWorker
@@ -353,12 +372,7 @@ public class HlsSessionWorker : IHlsSessionWorker
if ( commandResult . ExitCode = = 0 )
{
_f irstProcess = false ;
if ( _ seekNextItem )
{
_f irstProcess = true ;
_ seekNextItem = false ;
}
_ state = NextState ( _ state , null ) ;
_ hasWrittenSegments = true ;