Example #1
0
        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);
            }
        }
Example #2
0
        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);
            }
        }
Example #3
0
        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);
        }
Example #5
0
        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);
            }
        }
Example #6
0
        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;
        }
Example #7
0
        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();
        }