async void _timerProvider_TimerFired(object sender, GenericEventArgs <TimerInfo> e) { var timer = e.Argument; _logger.Info("Recording timer fired."); try { var recordingEndDate = timer.EndDate.AddSeconds(timer.PostPaddingSeconds); if (recordingEndDate <= DateTime.UtcNow) { _logger.Warn("Recording timer fired for timer {0}, Id: {1}, but the program has already ended.", timer.Name, timer.Id); return; } var activeRecordingInfo = new ActiveRecordingInfo { CancellationTokenSource = new CancellationTokenSource(), TimerId = timer.Id }; if (_activeRecordings.TryAdd(timer.Id, activeRecordingInfo)) { await RecordStream(timer, recordingEndDate, activeRecordingInfo, activeRecordingInfo.CancellationTokenSource.Token).ConfigureAwait(false); } else { _logger.Info("Skipping RecordStream because it's already in progress."); } } catch (OperationCanceledException) { } catch (Exception ex) { _logger.ErrorException("Error recording stream", ex); } }
private async Task RecordStream(TimerInfo timer, DateTime recordingEndDate, ActiveRecordingInfo activeRecordingInfo, CancellationToken cancellationToken) { if (timer == null) { throw new ArgumentNullException("timer"); } ProgramInfo info = null; if (string.IsNullOrWhiteSpace(timer.ProgramId)) { _logger.Info("Timer {0} has null programId", timer.Id); } else { info = GetProgramInfoFromCache(timer.ChannelId, timer.ProgramId); } if (info == null) { _logger.Info("Unable to find program with Id {0}. Will search using start date", timer.ProgramId); info = GetProgramInfoFromCache(timer.ChannelId, timer.StartDate); } if (info == null) { throw new InvalidOperationException(string.Format("Program with Id {0} not found", timer.ProgramId)); } var recordPath = GetRecordingPath(timer, info); var recordingStatus = RecordingStatus.New; var isResourceOpen = false; SemaphoreSlim semaphore = null; try { var result = await GetChannelStreamInternal(timer.ChannelId, null, CancellationToken.None).ConfigureAwait(false); isResourceOpen = true; semaphore = result.Item3; var mediaStreamInfo = result.Item1; // HDHR doesn't seem to release the tuner right away after first probing with ffmpeg //await Task.Delay(3000, cancellationToken).ConfigureAwait(false); var recorder = await GetRecorder().ConfigureAwait(false); recordPath = recorder.GetOutputPath(mediaStreamInfo, recordPath); recordPath = EnsureFileUnique(recordPath, timer.Id); _libraryMonitor.ReportFileSystemChangeBeginning(recordPath); _fileSystem.CreateDirectory(Path.GetDirectoryName(recordPath)); activeRecordingInfo.Path = recordPath; var duration = recordingEndDate - DateTime.UtcNow; _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"); Action onStarted = () => { timer.Status = RecordingStatus.InProgress; _timerProvider.AddOrUpdate(timer, false); result.Item3.Release(); isResourceOpen = false; }; var pathWithDuration = result.Item2.ApplyDuration(mediaStreamInfo.Path, duration); // If it supports supplying duration via url if (!string.Equals(pathWithDuration, mediaStreamInfo.Path, StringComparison.OrdinalIgnoreCase)) { mediaStreamInfo.Path = pathWithDuration; mediaStreamInfo.RunTimeTicks = duration.Ticks; } await recorder.Record(mediaStreamInfo, recordPath, duration, onStarted, cancellationToken).ConfigureAwait(false); recordingStatus = RecordingStatus.Completed; _logger.Info("Recording completed: {0}", recordPath); } catch (OperationCanceledException) { _logger.Info("Recording stopped: {0}", recordPath); recordingStatus = RecordingStatus.Completed; } catch (Exception ex) { _logger.ErrorException("Error recording to {0}", ex, recordPath); recordingStatus = RecordingStatus.Error; } finally { if (isResourceOpen && semaphore != null) { semaphore.Release(); } _libraryMonitor.ReportFileSystemChangeComplete(recordPath, true); ActiveRecordingInfo removed; _activeRecordings.TryRemove(timer.Id, out removed); } if (recordingStatus == RecordingStatus.Completed) { timer.Status = RecordingStatus.Completed; _timerProvider.Delete(timer); OnSuccessfulRecording(info.IsSeries, recordPath); } else if (DateTime.UtcNow < timer.EndDate) { const int retryIntervalSeconds = 60; _logger.Info("Retrying recording in {0} seconds.", retryIntervalSeconds); timer.Status = RecordingStatus.New; timer.StartDate = DateTime.UtcNow.AddSeconds(retryIntervalSeconds); _timerProvider.AddOrUpdate(timer); } else { _timerProvider.Delete(timer); } }
async void _timerProvider_TimerFired(object sender, GenericEventArgs<TimerInfo> e) { var timer = e.Argument; _logger.Info("Recording timer fired."); try { var recordingEndDate = timer.EndDate.AddSeconds(timer.PostPaddingSeconds); if (recordingEndDate <= DateTime.UtcNow) { _logger.Warn("Recording timer fired for timer {0}, Id: {1}, but the program has already ended.", timer.Name, timer.Id); return; } var activeRecordingInfo = new ActiveRecordingInfo { CancellationTokenSource = new CancellationTokenSource(), TimerId = timer.Id }; if (_activeRecordings.TryAdd(timer.Id, activeRecordingInfo)) { await RecordStream(timer, recordingEndDate, activeRecordingInfo, activeRecordingInfo.CancellationTokenSource.Token).ConfigureAwait(false); } else { _logger.Info("Skipping RecordStream because it's already in progress."); } } catch (OperationCanceledException) { } catch (Exception ex) { _logger.ErrorException("Error recording stream", ex); } }
private async Task <IEnumerable <MediaSourceInfo> > GetMediaSourcesInternal(BaseItem item, ActiveRecordingInfo activeRecordingInfo, CancellationToken cancellationToken) { IEnumerable <MediaSourceInfo> sources; var forceRequireOpening = false; try { if (activeRecordingInfo != null) { sources = await EmbyTV.EmbyTV.Current.GetRecordingStreamMediaSources(activeRecordingInfo, cancellationToken) .ConfigureAwait(false); } else { sources = await _liveTvManager.GetChannelMediaSources(item, cancellationToken) .ConfigureAwait(false); } } catch (NotImplementedException) { sources = _mediaSourceManager.GetStaticMediaSources(item, false); forceRequireOpening = true; } var list = sources.ToList(); var serverUrl = await _appHost.GetLocalApiUrl(cancellationToken).ConfigureAwait(false); foreach (var source in list) { source.Type = MediaSourceType.Default; source.BufferMs ??= 1500; if (source.RequiresOpening || forceRequireOpening) { source.RequiresOpening = true; } if (source.RequiresOpening) { var openKeys = new List <string> { item.GetType().Name, item.Id.ToString("N", CultureInfo.InvariantCulture), source.Id ?? string.Empty }; source.OpenToken = string.Join(StreamIdDelimeterString, openKeys); } // Dummy this up so that direct play checks can still run if (string.IsNullOrEmpty(source.Path) && source.Protocol == MediaProtocol.Http) { source.Path = serverUrl; } } _logger.LogDebug("MediaSources: {@MediaSources}", list); return(list); }
private async Task RecordStream(TimerInfo timer, DateTime recordingEndDate, ActiveRecordingInfo activeRecordingInfo, CancellationToken cancellationToken) { if (timer == null) { throw new ArgumentNullException("timer"); } ProgramInfo info = null; if (string.IsNullOrWhiteSpace(timer.ProgramId)) { _logger.Info("Timer {0} has null programId", timer.Id); } else { info = GetProgramInfoFromCache(timer.ChannelId, timer.ProgramId); } if (info == null) { _logger.Info("Unable to find program with Id {0}. Will search using start date", timer.ProgramId); info = GetProgramInfoFromCache(timer.ChannelId, timer.StartDate); } if (info == null) { throw new InvalidOperationException(string.Format("Program with Id {0} not found", timer.ProgramId)); } var recordPath = RecordingPath; if (info.IsMovie) { recordPath = Path.Combine(recordPath, "Movies", _fileSystem.GetValidFilename(info.Name).Trim()); } else if (info.IsSeries) { recordPath = Path.Combine(recordPath, "Series", _fileSystem.GetValidFilename(info.Name).Trim()); } else if (info.IsKids) { recordPath = Path.Combine(recordPath, "Kids", _fileSystem.GetValidFilename(info.Name).Trim()); } else if (info.IsSports) { recordPath = Path.Combine(recordPath, "Sports", _fileSystem.GetValidFilename(info.Name).Trim()); } else { recordPath = Path.Combine(recordPath, "Other", _fileSystem.GetValidFilename(info.Name).Trim()); } var recordingFileName = _fileSystem.GetValidFilename(RecordingHelper.GetRecordingName(timer, info)).Trim() + ".ts"; recordPath = Path.Combine(recordPath, recordingFileName); var recordingId = info.Id.GetMD5().ToString("N"); var recording = _recordingProvider.GetAll().FirstOrDefault(x => string.Equals(x.Id, recordingId, StringComparison.OrdinalIgnoreCase)); if (recording == null) { recording = new RecordingInfo { ChannelId = info.ChannelId, Id = recordingId, StartDate = info.StartDate, EndDate = info.EndDate, Genres = info.Genres, IsKids = info.IsKids, IsLive = info.IsLive, IsMovie = info.IsMovie, IsHD = info.IsHD, IsNews = info.IsNews, IsPremiere = info.IsPremiere, IsSeries = info.IsSeries, IsSports = info.IsSports, IsRepeat = !info.IsPremiere, Name = info.Name, EpisodeTitle = info.EpisodeTitle, ProgramId = info.Id, ImagePath = info.ImagePath, ImageUrl = info.ImageUrl, OriginalAirDate = info.OriginalAirDate, Status = RecordingStatus.Scheduled, Overview = info.Overview, SeriesTimerId = timer.SeriesTimerId, TimerId = timer.Id, ShowId = info.ShowId }; _recordingProvider.AddOrUpdate(recording); } try { var result = await GetChannelStreamInternal(timer.ChannelId, null, CancellationToken.None).ConfigureAwait(false); var mediaStreamInfo = result.Item1; var isResourceOpen = true; // Unfortunately due to the semaphore we have to have a nested try/finally try { // HDHR doesn't seem to release the tuner right away after first probing with ffmpeg //await Task.Delay(3000, cancellationToken).ConfigureAwait(false); var duration = recordingEndDate - DateTime.UtcNow; var recorder = await GetRecorder().ConfigureAwait(false); if (recorder is EncodedRecorder) { recordPath = Path.ChangeExtension(recordPath, ".mp4"); } recordPath = EnsureFileUnique(recordPath, timer.Id); _fileSystem.CreateDirectory(Path.GetDirectoryName(recordPath)); activeRecordingInfo.Path = recordPath; _libraryMonitor.ReportFileSystemChangeBeginning(recordPath); recording.Path = recordPath; recording.Status = RecordingStatus.InProgress; recording.DateLastUpdated = DateTime.UtcNow; _recordingProvider.AddOrUpdate(recording); _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"); Action onStarted = () => { result.Item2.Release(); isResourceOpen = false; }; await recorder.Record(mediaStreamInfo, recordPath, duration, onStarted, cancellationToken).ConfigureAwait(false); recording.Status = RecordingStatus.Completed; _logger.Info("Recording completed: {0}", recordPath); } finally { if (isResourceOpen) { result.Item2.Release(); } _libraryMonitor.ReportFileSystemChangeComplete(recordPath, false); } } catch (OperationCanceledException) { _logger.Info("Recording stopped: {0}", recordPath); recording.Status = RecordingStatus.Completed; } catch (Exception ex) { _logger.ErrorException("Error recording to {0}", ex, recordPath); recording.Status = RecordingStatus.Error; } finally { ActiveRecordingInfo removed; _activeRecordings.TryRemove(timer.Id, out removed); } recording.DateLastUpdated = DateTime.UtcNow; _recordingProvider.AddOrUpdate(recording); if (recording.Status == RecordingStatus.Completed) { OnSuccessfulRecording(recording); _timerProvider.Delete(timer); } else if (DateTime.UtcNow < timer.EndDate) { const int retryIntervalSeconds = 60; _logger.Info("Retrying recording in {0} seconds.", retryIntervalSeconds); _timerProvider.StartTimer(timer, TimeSpan.FromSeconds(retryIntervalSeconds)); } else { _timerProvider.Delete(timer); _recordingProvider.Delete(recording); } }
private RecordingInfo GetRecordingInfo(ActiveRecordingInfo info) { var timer = info.Timer; var program = info.Program; var result = new RecordingInfo { ChannelId = timer.ChannelId, CommunityRating = timer.CommunityRating, DateLastUpdated = DateTime.UtcNow, EndDate = timer.EndDate, EpisodeTitle = timer.EpisodeTitle, Genres = timer.Genres, Id = "recording" + timer.Id, IsKids = timer.IsKids, IsMovie = timer.IsMovie, IsNews = timer.IsNews, IsRepeat = timer.IsRepeat, IsSeries = timer.IsProgramSeries, IsSports = timer.IsSports, Name = timer.Name, OfficialRating = timer.OfficialRating, OriginalAirDate = timer.OriginalAirDate, Overview = timer.Overview, ProgramId = timer.ProgramId, SeriesTimerId = timer.SeriesTimerId, StartDate = timer.StartDate, Status = RecordingStatus.InProgress, TimerId = timer.Id }; if (program != null) { result.Audio = program.Audio; result.ImagePath = program.ImagePath; result.ImageUrl = program.ImageUrl; result.IsHD = program.IsHD; result.IsLive = program.IsLive; result.IsPremiere = program.IsPremiere; result.ShowId = program.ShowId; } return result; }
private async Task RecordStream(TimerInfo timer, DateTime recordingEndDate, ActiveRecordingInfo activeRecordingInfo, CancellationToken cancellationToken) { if (timer == null) { throw new ArgumentNullException("timer"); } ProgramInfo programInfo = null; if (!string.IsNullOrWhiteSpace(timer.ProgramId)) { programInfo = GetProgramInfoFromCache(timer.ChannelId, timer.ProgramId); } if (programInfo == null) { _logger.Info("Unable to find program with Id {0}. Will search using start date", timer.ProgramId); programInfo = GetProgramInfoFromCache(timer.ChannelId, timer.StartDate); } if (programInfo != null) { RecordingHelper.CopyProgramInfoToTimerInfo(programInfo, timer); activeRecordingInfo.Program = programInfo; } string seriesPath = null; var recordPath = GetRecordingPath(timer, out seriesPath); var recordingStatus = RecordingStatus.New; string liveStreamId = null; OnRecordingStatusChanged(); try { var allMediaSources = await GetChannelStreamMediaSources(timer.ChannelId, CancellationToken.None).ConfigureAwait(false); var liveStreamInfo = await GetChannelStreamInternal(timer.ChannelId, allMediaSources[0].Id, CancellationToken.None) .ConfigureAwait(false); var mediaStreamInfo = liveStreamInfo.Item2; liveStreamId = mediaStreamInfo.Id; // HDHR doesn't seem to release the tuner right away after first probing with ffmpeg //await Task.Delay(3000, cancellationToken).ConfigureAwait(false); var recorder = await GetRecorder().ConfigureAwait(false); recordPath = recorder.GetOutputPath(mediaStreamInfo, recordPath); recordPath = EnsureFileUnique(recordPath, timer.Id); _libraryManager.RegisterIgnoredPath(recordPath); _libraryMonitor.ReportFileSystemChangeBeginning(recordPath); _fileSystem.CreateDirectory(Path.GetDirectoryName(recordPath)); activeRecordingInfo.Path = recordPath; var duration = recordingEndDate - DateTime.UtcNow; _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"); Action onStarted = () => { timer.Status = RecordingStatus.InProgress; _timerProvider.AddOrUpdate(timer, false); SaveNfo(timer, recordPath, seriesPath); EnforceKeepUpTo(timer); }; await recorder.Record(mediaStreamInfo, recordPath, duration, onStarted, cancellationToken) .ConfigureAwait(false); recordingStatus = RecordingStatus.Completed; _logger.Info("Recording completed: {0}", recordPath); } catch (OperationCanceledException) { _logger.Info("Recording stopped: {0}", recordPath); recordingStatus = RecordingStatus.Completed; } catch (Exception ex) { _logger.ErrorException("Error recording to {0}", ex, recordPath); recordingStatus = RecordingStatus.Error; } if (!string.IsNullOrWhiteSpace(liveStreamId)) { try { await CloseLiveStream(liveStreamId, CancellationToken.None).ConfigureAwait(false); } catch (Exception ex) { _logger.ErrorException("Error closing live stream", ex); } } _libraryManager.UnRegisterIgnoredPath(recordPath); _libraryMonitor.ReportFileSystemChangeComplete(recordPath, true); ActiveRecordingInfo removed; _activeRecordings.TryRemove(timer.Id, out removed); if (recordingStatus != RecordingStatus.Completed && DateTime.UtcNow < timer.EndDate) { const int retryIntervalSeconds = 60; _logger.Info("Retrying recording in {0} seconds.", retryIntervalSeconds); timer.Status = RecordingStatus.New; timer.StartDate = DateTime.UtcNow.AddSeconds(retryIntervalSeconds); _timerProvider.AddOrUpdate(timer); } else if (File.Exists(recordPath)) { timer.RecordingPath = recordPath; timer.Status = RecordingStatus.Completed; _timerProvider.AddOrUpdate(timer, false); OnSuccessfulRecording(timer, recordPath); } else { _timerProvider.Delete(timer); } OnRecordingStatusChanged(); }