private IEnumerable <TimerInfo> GetTimersForSeries(SeriesTimerInfo seriesTimer, IEnumerable <ProgramInfo> allPrograms, IReadOnlyList <RecordingInfo> currentRecordings) { if (seriesTimer == null) { throw new ArgumentNullException("seriesTimer"); } if (allPrograms == null) { throw new ArgumentNullException("allPrograms"); } if (currentRecordings == null) { throw new ArgumentNullException("currentRecordings"); } // Exclude programs that have already ended allPrograms = allPrograms.Where(i => i.EndDate > DateTime.UtcNow && i.StartDate > DateTime.UtcNow); allPrograms = GetProgramsForSeries(seriesTimer, allPrograms); var recordingShowIds = currentRecordings.Select(i => i.ProgramId).Where(i => !string.IsNullOrWhiteSpace(i)).ToList(); allPrograms = allPrograms.Where(i => !recordingShowIds.Contains(i.Id, StringComparer.OrdinalIgnoreCase)); return(allPrograms.Select(i => RecordingHelper.CreateTimer(i, seriesTimer))); }
public override void Update(TimerInfo item) { base.Update(item); Timer timer; if (_timers.TryGetValue(item.Id, out timer)) { var timespan = RecordingHelper.GetStartTime(item) - DateTime.UtcNow; timer.Change(timespan, TimeSpan.Zero); } else { AddTimer(item); } }
private void AddTimer(TimerInfo item) { var startDate = RecordingHelper.GetStartTime(item); var now = DateTime.UtcNow; if (startDate < now) { EventHelper.FireEventIfNotNull(TimerFired, this, new GenericEventArgs <TimerInfo> { Argument = item }, Logger); return; } var timerLength = startDate - now; StartTimer(item, timerLength); }
private void ScheduleWake(TimerInfo info) { var startDate = RecordingHelper.GetStartTime(info).AddMinutes(-5); try { _powerManagement.ScheduleWake(startDate); _logger.Info("Scheduled system wake timer at {0} (UTC)", startDate); } catch (NotImplementedException) { } catch (Exception ex) { _logger.ErrorException("Error scheduling wake timer", ex); } }
private void AddOrUpdateSystemTimer(TimerInfo item) { StopTimer(item); if (!ShouldStartTimer(item)) { return; } var startDate = RecordingHelper.GetStartTime(item); var now = DateTime.UtcNow; if (startDate < now) { EventHelper.FireEventIfNotNull(TimerFired, this, new GenericEventArgs <TimerInfo> { Argument = item }, Logger); return; } var dueTime = startDate - now; StartTimer(item, dueTime); }
private async Task RecordStream(TimerInfo timer, DateTime recordingEndDate, CancellationToken cancellationToken) { if (timer == null) { throw new ArgumentNullException("timer"); } if (string.IsNullOrWhiteSpace(timer.ProgramId)) { throw new InvalidOperationException("timer.ProgramId is null. Cannot record."); } var info = GetProgramInfoFromCache(timer.ChannelId, timer.ProgramId); 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); _fileSystem.CreateDirectory(Path.GetDirectoryName(recordPath)); 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); 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; HttpRequestOptions httpRequestOptions = new HttpRequestOptions() { Url = mediaStreamInfo.Path }; recording.Path = recordPath; recording.Status = RecordingStatus.InProgress; recording.DateLastUpdated = DateTime.UtcNow; _recordingProvider.AddOrUpdate(recording); _logger.Info("Beginning recording."); httpRequestOptions.BufferContent = false; var durationToken = new CancellationTokenSource(duration); var linkedToken = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, durationToken.Token).Token; httpRequestOptions.CancellationToken = linkedToken; _logger.Info("Writing file to path: " + recordPath); using (var response = await _httpClient.SendAsync(httpRequestOptions, "GET")) { using (var output = _fileSystem.GetFileStream(recordPath, FileMode.Create, FileAccess.Write, FileShare.Read)) { result.Item2.Release(); isResourceOpen = false; await response.Content.CopyToAsync(output, StreamDefaults.DefaultCopyToBufferSize, linkedToken); } } recording.Status = RecordingStatus.Completed; _logger.Info("Recording completed"); } finally { if (isResourceOpen) { result.Item2.Release(); } } } catch (OperationCanceledException) { _logger.Info("Recording stopped"); recording.Status = RecordingStatus.Completed; } catch (Exception ex) { _logger.ErrorException("Error recording", ex); recording.Status = RecordingStatus.Error; } finally { CancellationTokenSource 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 async Task RecordStream(TimerInfo timer, CancellationToken cancellationToken) { if (timer == null) { throw new ArgumentNullException("timer"); } var mediaStreamInfo = await GetChannelStream(timer.ChannelId, null, CancellationToken.None); var duration = (timer.EndDate - DateTime.UtcNow).Add(TimeSpan.FromSeconds(timer.PostPaddingSeconds)); HttpRequestOptions httpRequestOptions = new HttpRequestOptions() { Url = mediaStreamInfo.Path }; var info = GetProgramInfoFromCache(timer.ChannelId, timer.ProgramId); var recordPath = RecordingPath; if (info.IsMovie) { recordPath = Path.Combine(recordPath, "Movies", _fileSystem.GetValidFilename(info.Name)); } else if (info.IsSeries) { recordPath = Path.Combine(recordPath, "Series", _fileSystem.GetValidFilename(info.Name)); } else if (info.IsKids) { recordPath = Path.Combine(recordPath, "Kids", _fileSystem.GetValidFilename(info.Name)); } else if (info.IsSports) { recordPath = Path.Combine(recordPath, "Sports", _fileSystem.GetValidFilename(info.Name)); } else { recordPath = Path.Combine(recordPath, "Other", _fileSystem.GetValidFilename(info.Name)); } var recordingFileName = _fileSystem.GetValidFilename(RecordingHelper.GetRecordingName(timer, info)) + ".ts"; recordPath = Path.Combine(recordPath, recordingFileName); Directory.CreateDirectory(Path.GetDirectoryName(recordPath)); var recording = _recordingProvider.GetAll().FirstOrDefault(x => string.Equals(x.ProgramId, info.Id, StringComparison.OrdinalIgnoreCase)); if (recording == null) { recording = new RecordingInfo { ChannelId = info.ChannelId, Id = Guid.NewGuid().ToString("N"), 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, HasImage = info.HasImage, 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.Add(recording); } recording.Path = recordPath; recording.Status = RecordingStatus.InProgress; recording.DateLastUpdated = DateTime.UtcNow; _recordingProvider.Update(recording); _logger.Info("Beginning recording."); try { httpRequestOptions.BufferContent = false; var durationToken = new CancellationTokenSource(duration); var linkedToken = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, durationToken.Token).Token; httpRequestOptions.CancellationToken = linkedToken; _logger.Info("Writing file to path: " + recordPath); using (var response = await _httpClient.SendAsync(httpRequestOptions, "GET")) { using (var output = File.Open(recordPath, FileMode.Create, FileAccess.Write, FileShare.Read)) { await response.Content.CopyToAsync(output, StreamDefaults.DefaultCopyToBufferSize, linkedToken); } } recording.Status = RecordingStatus.Completed; _logger.Info("Recording completed"); } catch (OperationCanceledException) { _logger.Info("Recording stopped"); recording.Status = RecordingStatus.Completed; } catch (Exception ex) { _logger.ErrorException("Error recording", ex); recording.Status = RecordingStatus.Error; } finally { CancellationTokenSource removed; _activeRecordings.TryRemove(timer.Id, out removed); } recording.DateLastUpdated = DateTime.UtcNow; _recordingProvider.Update(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 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); } }