@ -330,11 +330,16 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
{
if ( DateTime . UtcNow > timer . EndDate & & ! _activeRecordings . ContainsKey ( timer . Id ) )
{
_timerProvider. Dele te( timer ) ;
OnTimerOutOfDa te( timer ) ;
}
}
}
private void OnTimerOutOfDate ( TimerInfo timer )
{
_timerProvider . Delete ( timer ) ;
}
private List < ChannelInfo > _channelCache = null ;
private async Task < IEnumerable < ChannelInfo > > GetChannelsAsync ( bool enableCache , CancellationToken cancellationToken )
{
@ -424,7 +429,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
foreach ( var timer in timers )
{
CancelTimerInternal ( timer . Id );
CancelTimerInternal ( timer . Id , true );
}
var remove = _seriesTimerProvider . GetAll ( ) . FirstOrDefault ( r = > string . Equals ( r . Id , timerId , StringComparison . OrdinalIgnoreCase ) ) ;
@ -435,12 +440,20 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
return Task . FromResult ( true ) ;
}
private void CancelTimerInternal ( string timerId )
private void CancelTimerInternal ( string timerId , bool isSeriesCancelled )
{
var remove = _timerProvider . GetAll ( ) . FirstOrDefault ( r = > string . Equals ( r . Id , timerId , StringComparison . OrdinalIgnoreCase ) ) ;
if ( remove ! = null )
var timer = _timerProvider . GetAll ( ) . FirstOrDefault ( r = > string . Equals ( r . Id , timerId , StringComparison . OrdinalIgnoreCase ) ) ;
if ( timer ! = null )
{
_timerProvider . Delete ( remove ) ;
if ( string . IsNullOrWhiteSpace ( timer . SeriesTimerId ) | | isSeriesCancelled )
{
_timerProvider . Delete ( timer ) ;
}
else
{
timer . Status = RecordingStatus . Cancelled ;
_timerProvider . AddOrUpdate ( timer , false ) ;
}
}
ActiveRecordingInfo activeRecordingInfo ;
@ -452,7 +465,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
public Task CancelTimerAsync ( string timerId , CancellationToken cancellationToken )
{
CancelTimerInternal ( timerId );
CancelTimerInternal ( timerId , false );
return Task . FromResult ( true ) ;
}
@ -463,6 +476,22 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
public Task CreateTimerAsync ( TimerInfo info , CancellationToken cancellationToken )
{
var existingTimer = _timerProvider . GetAll ( )
. FirstOrDefault ( i = > string . Equals ( info . ProgramId , i . ProgramId , StringComparison . OrdinalIgnoreCase ) ) ;
if ( existingTimer ! = null )
{
if ( existingTimer . Status = = RecordingStatus . Cancelled )
{
existingTimer . Status = RecordingStatus . New ;
_timerProvider . Update ( existingTimer ) ;
}
else
{
throw new ArgumentException ( "A scheduled recording already exists for this program." ) ;
}
}
return CreateTimer ( info , cancellationToken ) ;
}
@ -549,6 +578,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
instance . RecordNewOnly = info . RecordNewOnly ;
instance . SkipEpisodesInLibrary = info . SkipEpisodesInLibrary ;
instance . KeepUpTo = info . KeepUpTo ;
instance . KeepUntil = info . KeepUntil ;
instance . StartDate = info . StartDate ;
_seriesTimerProvider . Update ( instance ) ;
@ -569,12 +599,54 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
}
}
public Task UpdateTimerAsync ( TimerInfo info , CancellationToken cancellationToken )
public Task UpdateTimerAsync ( TimerInfo updatedTimer , CancellationToken cancellationToken )
{
_timerProvider . Update ( info ) ;
var existingTimer = _timerProvider
. GetAll ( )
. FirstOrDefault ( i = > string . Equals ( i . Id , updatedTimer . Id , StringComparison . OrdinalIgnoreCase ) ) ;
if ( existingTimer = = null )
{
throw new ResourceNotFoundException ( ) ;
}
// Only update if not currently active
ActiveRecordingInfo activeRecordingInfo ;
if ( ! _activeRecordings . TryGetValue ( updatedTimer . Id , out activeRecordingInfo ) )
{
UpdateExistingTimerWithNewData ( existingTimer , updatedTimer ) ;
_timerProvider . Update ( existingTimer ) ;
}
return Task . FromResult ( true ) ;
}
private void UpdateExistingTimerWithNewData ( TimerInfo existingTimer , TimerInfo updatedTimer )
{
// Update the program info but retain the status
existingTimer . ChannelId = updatedTimer . ChannelId ;
existingTimer . CommunityRating = updatedTimer . CommunityRating ;
existingTimer . EndDate = updatedTimer . EndDate ;
existingTimer . EpisodeNumber = updatedTimer . EpisodeNumber ;
existingTimer . EpisodeTitle = updatedTimer . EpisodeTitle ;
existingTimer . Genres = updatedTimer . Genres ;
existingTimer . HomePageUrl = updatedTimer . HomePageUrl ;
existingTimer . IsKids = updatedTimer . IsKids ;
existingTimer . IsMovie = updatedTimer . IsMovie ;
existingTimer . IsProgramSeries = updatedTimer . IsProgramSeries ;
existingTimer . IsSports = updatedTimer . IsSports ;
existingTimer . Name = updatedTimer . Name ;
existingTimer . OfficialRating = updatedTimer . OfficialRating ;
existingTimer . OriginalAirDate = updatedTimer . OriginalAirDate ;
existingTimer . Overview = updatedTimer . Overview ;
existingTimer . ProductionYear = updatedTimer . ProductionYear ;
existingTimer . ProgramId = updatedTimer . ProgramId ;
existingTimer . SeasonNumber = updatedTimer . SeasonNumber ;
existingTimer . ShortOverview = updatedTimer . ShortOverview ;
existingTimer . StartDate = updatedTimer . StartDate ;
}
public Task < ImageStream > GetChannelImageAsync ( string channelId , CancellationToken cancellationToken )
{
throw new NotImplementedException ( ) ;
@ -597,7 +669,16 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
public Task < IEnumerable < TimerInfo > > GetTimersAsync ( CancellationToken cancellationToken )
{
return Task . FromResult ( ( IEnumerable < TimerInfo > ) _timerProvider . GetAll ( ) ) ;
var excludeStatues = new List < RecordingStatus >
{
RecordingStatus . Completed ,
RecordingStatus . Cancelled
} ;
var timers = _timerProvider . GetAll ( )
. Where ( i = > ! excludeStatues . Contains ( i . Status ) ) ;
return Task . FromResult ( timers ) ;
}
public Task < SeriesTimerInfo > GetNewTimerDefaultsAsync ( CancellationToken cancellationToken , ProgramInfo program = null )
@ -630,6 +711,9 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
defaults . ProgramId = program . Id ;
}
defaults . SkipEpisodesInLibrary = true ;
defaults . KeepUntil = KeepUntil . UntilDeleted ;
return Task . FromResult ( defaults ) ;
}
@ -860,8 +944,8 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
if ( recordingEndDate < = DateTime . UtcNow )
{
_logger . Warn ( "Recording timer fired for timer {0}, Id: {1}, but the program has already ended.", timer . Name , timer . Id ) ;
_timerProvider. Dele te( timer ) ;
_logger . Warn ( "Recording timer fired for upda tedT imer {0}, Id: {1}, but the program has already ended.", timer . Name , timer . Id ) ;
OnTimerOutOfDa te( timer ) ;
return ;
}
@ -982,7 +1066,8 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
return Path . Combine ( recordPath , recordingFileName ) ;
}
private async Task RecordStream ( TimerInfo timer , DateTime recordingEndDate , ActiveRecordingInfo activeRecordingInfo , CancellationToken cancellationToken )
private async Task RecordStream ( TimerInfo timer , DateTime recordingEndDate ,
ActiveRecordingInfo activeRecordingInfo , CancellationToken cancellationToken )
{
if ( timer = = null )
{
@ -1014,9 +1099,13 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
try
{
var allMediaSources = await GetChannelStreamMediaSources ( timer . ChannelId , CancellationToken . None ) . ConfigureAwait ( false ) ;
var allMediaSources =
await GetChannelStreamMediaSources ( timer . ChannelId , CancellationToken . None ) . ConfigureAwait ( false ) ;
var liveStreamInfo = await GetChannelStreamInternal ( timer . ChannelId , allMediaSources [ 0 ] . Id , CancellationToken . None ) . ConfigureAwait ( false ) ;
var liveStreamInfo =
await
GetChannelStreamInternal ( timer . ChannelId , allMediaSources [ 0 ] . Id , CancellationToken . None )
. ConfigureAwait ( false ) ;
liveStream = liveStreamInfo . Item1 ;
var mediaStreamInfo = liveStreamInfo . Item1 . PublicMediaSource ;
var tunerHost = liveStreamInfo . Item2 ;
@ -1036,7 +1125,8 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
var duration = recordingEndDate - DateTime . UtcNow ;
_logger . Info ( "Beginning recording. Will record for {0} minutes." , duration . TotalMinutes . ToString ( CultureInfo . InvariantCulture ) ) ;
_logger . Info ( "Beginning recording. Will record for {0} minutes." ,
duration . TotalMinutes . ToString ( CultureInfo . InvariantCulture ) ) ;
_logger . Info ( "Writing file to path: " + recordPath ) ;
_logger . Info ( "Opening recording stream from tuner provider" ) ;
@ -1058,7 +1148,9 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
mediaStreamInfo . RunTimeTicks = duration . Ticks ;
}
await recorder . Record ( mediaStreamInfo , recordPath , duration , onStarted , cancellationToken ) . ConfigureAwait ( false ) ;
await
recorder . Record ( mediaStreamInfo , recordPath , duration , onStarted , cancellationToken )
. ConfigureAwait ( false ) ;
recordingStatus = RecordingStatus . Completed ;
_logger . Info ( "Recording completed: {0}" , recordPath ) ;
@ -1092,14 +1184,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
ActiveRecordingInfo removed ;
_activeRecordings . TryRemove ( timer . Id , out removed ) ;
if ( recordingStatus = = RecordingStatus . Completed )
{
timer . Status = RecordingStatus . Completed ;
_timerProvider . Delete ( timer ) ;
OnSuccessfulRecording ( timer , recordPath ) ;
}
else if ( DateTime . UtcNow < timer . EndDate )
if ( recordingStatus ! = RecordingStatus . Completed & & DateTime . UtcNow < timer . EndDate )
{
const int retryIntervalSeconds = 60 ;
_logger . Info ( "Retrying recording in {0} seconds." , retryIntervalSeconds ) ;
@ -1108,6 +1193,12 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
timer . StartDate = DateTime . UtcNow . AddSeconds ( retryIntervalSeconds ) ;
_timerProvider . AddOrUpdate ( timer ) ;
}
else if ( File . Exists ( recordPath ) )
{
timer . Status = RecordingStatus . Completed ;
_timerProvider . AddOrUpdate ( timer , false ) ;
OnSuccessfulRecording ( timer , recordPath ) ;
}
else
{
_timerProvider . Delete ( timer ) ;
@ -1353,41 +1444,78 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
return _config . GetConfiguration < LiveTvOptions > ( "livetv" ) ;
}
private bool ShouldCancelTimerForSeriesTimer ( SeriesTimerInfo seriesTimer , TimerInfo timer )
{
return seriesTimer . SkipEpisodesInLibrary & & IsProgramAlreadyInLibrary ( timer ) ;
}
private async Task UpdateTimersForSeriesTimer ( List < ProgramInfo > epgData , SeriesTimerInfo seriesTimer , bool deleteInvalidTimers )
{
var newTimers = GetTimersForSeries ( seriesTimer , epgData , true ) . ToList ( ) ;
var allTimers = GetTimersForSeries ( seriesTimer , epgData )
. ToList ( ) ;
var registration = await _liveTvManager . GetRegistrationInfo ( "seriesrecordings" ) . ConfigureAwait ( false ) ;
if ( registration . IsValid )
{
foreach ( var timer in new Timers)
foreach ( var timer in all Timers)
{
_timerProvider . AddOrUpdate ( timer ) ;
var existingTimer = _timerProvider
. GetAll ( )
. FirstOrDefault ( i = > string . Equals ( i . Id , timer . Id , StringComparison . OrdinalIgnoreCase ) ) ;
if ( existingTimer = = null )
{
if ( ShouldCancelTimerForSeriesTimer ( seriesTimer , timer ) )
{
timer . Status = RecordingStatus . Cancelled ;
}
_timerProvider . Add ( timer ) ;
}
else
{
// Only update if not currently active
ActiveRecordingInfo activeRecordingInfo ;
if ( ! _activeRecordings . TryGetValue ( timer . Id , out activeRecordingInfo ) )
{
UpdateExistingTimerWithNewData ( existingTimer , timer ) ;
if ( ShouldCancelTimerForSeriesTimer ( seriesTimer , timer ) )
{
existingTimer . Status = RecordingStatus . Cancelled ;
}
_timerProvider . Update ( existingTimer ) ;
}
}
}
}
if ( deleteInvalidTimers )
{
var allTimers = GetTimersForSeries ( seriesTimer , epgData , false )
var allTimer Ids = allTimers
. Select ( i = > i . Id )
. ToList ( ) ;
var deleteStatuses = new List < RecordingStatus >
{
RecordingStatus . New
} ;
var deletes = _timerProvider . GetAll ( )
. Where ( i = > string . Equals ( i . SeriesTimerId , seriesTimer . Id , StringComparison . OrdinalIgnoreCase ) )
. Where ( i = > ! allTimers . Contains ( i . Id , StringComparer . OrdinalIgnoreCase ) & & i . StartDate > DateTime . UtcNow )
. Where ( i = > ! allTimerIds . Contains ( i . Id , StringComparer . OrdinalIgnoreCase ) & & i . StartDate > DateTime . UtcNow )
. Where ( i = > deleteStatuses . Contains ( i . Status ) )
. ToList ( ) ;
foreach ( var timer in deletes )
{
await CancelTimerAsync ( timer . Id , CancellationToken . None ) . ConfigureAwait ( false ) ;
CancelTimerInternal ( timer . Id , false ) ;
}
}
}
private IEnumerable < TimerInfo > GetTimersForSeries ( SeriesTimerInfo seriesTimer ,
IEnumerable < ProgramInfo > allPrograms ,
bool filterByCurrentRecordings )
IEnumerable < ProgramInfo > allPrograms )
{
if ( seriesTimer = = null )
{
@ -1403,15 +1531,10 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
allPrograms = GetProgramsForSeries ( seriesTimer , allPrograms ) ;
if ( filterByCurrentRecordings & & seriesTimer . SkipEpisodesInLibrary )
{
allPrograms = allPrograms . Where ( i = > ! IsProgramAlreadyInLibrary ( i ) ) ;
}
return allPrograms . Select ( i = > RecordingHelper . CreateTimer ( i , seriesTimer ) ) ;
}
private bool IsProgramAlreadyInLibrary ( Program Info program )
private bool IsProgramAlreadyInLibrary ( TimerInfo program )
{
if ( ( program . EpisodeNumber . HasValue & & program . SeasonNumber . HasValue ) | | ! string . IsNullOrWhiteSpace ( program . EpisodeTitle ) )
{