diff --git a/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/DirectRecorder.cs b/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/DirectRecorder.cs index 9ac96165f..d33b2c51d 100644 --- a/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/DirectRecorder.cs +++ b/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/DirectRecorder.cs @@ -23,7 +23,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV _fileSystem = fileSystem; } - public async Task Record(MediaSourceInfo mediaSource, string targetFile, Action onStarted, CancellationToken cancellationToken) + public async Task Record(MediaSourceInfo mediaSource, string targetFile, TimeSpan duration, Action onStarted, CancellationToken cancellationToken) { var httpRequestOptions = new HttpRequestOptions() { @@ -42,7 +42,10 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV _logger.Info("Copying recording stream to file stream"); - await response.Content.CopyToAsync(output, StreamDefaults.DefaultCopyToBufferSize, cancellationToken).ConfigureAwait(false); + var durationToken = new CancellationTokenSource(duration); + var linkedToken = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, durationToken.Token).Token; + + await response.Content.CopyToAsync(output, StreamDefaults.DefaultCopyToBufferSize, linkedToken).ConfigureAwait(false); } } } diff --git a/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs b/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs index e534d8c67..3ab08274c 100644 --- a/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs +++ b/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs @@ -103,8 +103,8 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV public event EventHandler RecordingStatusChanged; - private readonly ConcurrentDictionary _activeRecordings = - new ConcurrentDictionary(StringComparer.OrdinalIgnoreCase); + private readonly ConcurrentDictionary _activeRecordings = + new ConcurrentDictionary(StringComparer.OrdinalIgnoreCase); public string Name { @@ -268,11 +268,11 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV { _timerProvider.Delete(remove); } - CancellationTokenSource cancellationTokenSource; + ActiveRecordingInfo activeRecordingInfo; - if (_activeRecordings.TryGetValue(timerId, out cancellationTokenSource)) + if (_activeRecordings.TryGetValue(timerId, out activeRecordingInfo)) { - cancellationTokenSource.Cancel(); + activeRecordingInfo.CancellationTokenSource.Cancel(); } } @@ -653,11 +653,15 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV return; } - var cancellationTokenSource = new CancellationTokenSource(); - - if (_activeRecordings.TryAdd(timer.Id, cancellationTokenSource)) + var activeRecordingInfo = new ActiveRecordingInfo { - await RecordStream(timer, recordingEndDate, cancellationTokenSource.Token).ConfigureAwait(false); + CancellationTokenSource = new CancellationTokenSource(), + TimerId = timer.Id + }; + + if (_activeRecordings.TryAdd(timer.Id, activeRecordingInfo)) + { + await RecordStream(timer, recordingEndDate, activeRecordingInfo, activeRecordingInfo.CancellationTokenSource.Token).ConfigureAwait(false); } else { @@ -674,7 +678,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV } } - private async Task RecordStream(TimerInfo timer, DateTime recordingEndDate, CancellationToken cancellationToken) + private async Task RecordStream(TimerInfo timer, DateTime recordingEndDate, ActiveRecordingInfo activeRecordingInfo, CancellationToken cancellationToken) { if (timer == null) { @@ -729,8 +733,6 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV var recordingFileName = _fileSystem.GetValidFilename(RecordingHelper.GetRecordingName(timer, info)).Trim() + ".ts"; recordPath = Path.Combine(recordPath, recordingFileName); - recordPath = EnsureFileUnique(recordPath); - _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)); @@ -788,6 +790,9 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV { recordPath = Path.ChangeExtension(recordPath, ".mp4"); } + recordPath = EnsureFileUnique(recordPath, timer.Id); + _fileSystem.CreateDirectory(Path.GetDirectoryName(recordPath)); + activeRecordingInfo.Path = recordPath; _libraryMonitor.ReportFileSystemChangeBeginning(recordPath); @@ -798,9 +803,6 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV _logger.Info("Beginning recording. Will record for {0} minutes.", duration.TotalMinutes.ToString(CultureInfo.InvariantCulture)); - var durationToken = new CancellationTokenSource(duration); - var linkedToken = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, durationToken.Token).Token; - _logger.Info("Writing file to path: " + recordPath); _logger.Info("Opening recording stream from tuner provider"); @@ -810,10 +812,10 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV isResourceOpen = false; }; - await recorder.Record(mediaStreamInfo, recordPath, onStarted, linkedToken).ConfigureAwait(false); + await recorder.Record(mediaStreamInfo, recordPath, duration, onStarted, cancellationToken).ConfigureAwait(false); recording.Status = RecordingStatus.Completed; - _logger.Info("Recording completed"); + _logger.Info("Recording completed: {0}", recordPath); } finally { @@ -827,17 +829,17 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV } catch (OperationCanceledException) { - _logger.Info("Recording stopped"); + _logger.Info("Recording stopped: {0}", recordPath); recording.Status = RecordingStatus.Completed; } catch (Exception ex) { - _logger.ErrorException("Error recording", ex); + _logger.ErrorException("Error recording to {0}", ex, recordPath); recording.Status = RecordingStatus.Error; } finally { - CancellationTokenSource removed; + ActiveRecordingInfo removed; _activeRecordings.TryRemove(timer.Id, out removed); } @@ -863,12 +865,12 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV } } - private string EnsureFileUnique(string path) + private string EnsureFileUnique(string path, string timerId) { var originalPath = path; var index = 1; - while (_fileSystem.FileExists(path)) + while (FileExists(path, timerId)) { var parent = Path.GetDirectoryName(originalPath); var name = Path.GetFileNameWithoutExtension(originalPath); @@ -881,6 +883,22 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV return path; } + private bool FileExists(string path, string timerId) + { + if (_fileSystem.FileExists(path)) + { + return true; + } + + var hasRecordingAtPath = _activeRecordings.Values.ToList().Any(i => string.Equals(i.Path, path, StringComparison.OrdinalIgnoreCase) && !string.Equals(i.TimerId, timerId, StringComparison.OrdinalIgnoreCase)); + + if (hasRecordingAtPath) + { + return true; + } + return false; + } + private async Task GetRecorder() { if (GetConfiguration().EnableRecordingEncoding) @@ -1064,7 +1082,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV { foreach (var pair in _activeRecordings.ToList()) { - pair.Value.Cancel(); + pair.Value.CancellationTokenSource.Cancel(); } } @@ -1081,5 +1099,12 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV IsRegistered = true }); } + + class ActiveRecordingInfo + { + public string Path { get; set; } + public string TimerId { get; set; } + public CancellationTokenSource CancellationTokenSource { get; set; } + } } } \ No newline at end of file diff --git a/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs b/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs index 8b7bd897b..62c9cd171 100644 --- a/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs +++ b/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs @@ -38,7 +38,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV _json = json; } - public async Task Record(MediaSourceInfo mediaSource, string targetFile, Action onStarted, CancellationToken cancellationToken) + public async Task Record(MediaSourceInfo mediaSource, string targetFile, TimeSpan duration, Action onStarted, CancellationToken cancellationToken) { _targetPath = targetFile; _fileSystem.CreateDirectory(Path.GetDirectoryName(targetFile)); @@ -56,7 +56,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV RedirectStandardInput = true, FileName = _mediaEncoder.EncoderPath, - Arguments = GetCommandLineArgs(mediaSource, targetFile), + Arguments = GetCommandLineArgs(mediaSource, targetFile, duration), WindowStyle = ProcessWindowStyle.Hidden, ErrorDialog = false @@ -100,7 +100,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV } } - private string GetCommandLineArgs(MediaSourceInfo mediaSource, string targetFile) + private string GetCommandLineArgs(MediaSourceInfo mediaSource, string targetFile, TimeSpan duration) { string videoArgs; if (EncodeVideo(mediaSource)) @@ -116,14 +116,14 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV videoArgs = "-codec:v:0 copy"; } - var commandLineArgs = "-fflags +genpts -async 1 -vsync -1 -i \"{0}\" -sn {2} -map_metadata -1 -threads 0 {3} -y \"{1}\""; + var commandLineArgs = "-fflags +genpts -async 1 -vsync -1 -i \"{0}\" -t {4} -sn {2} -map_metadata -1 -threads 0 {3} -y \"{1}\""; if (mediaSource.ReadAtNativeFramerate) { commandLineArgs = "-re " + commandLineArgs; } - commandLineArgs = string.Format(commandLineArgs, mediaSource.Path, targetFile, videoArgs, GetAudioArgs(mediaSource)); + commandLineArgs = string.Format(commandLineArgs, mediaSource.Path, targetFile, videoArgs, GetAudioArgs(mediaSource), _mediaEncoder.GetTimeParameter(duration.Ticks)); return commandLineArgs; } diff --git a/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/IRecorder.cs b/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/IRecorder.cs index 12e73c1f3..268a4f751 100644 --- a/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/IRecorder.cs +++ b/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/IRecorder.cs @@ -7,6 +7,15 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV { public interface IRecorder { - Task Record(MediaSourceInfo mediaSource, string targetFile, Action onStarted, CancellationToken cancellationToken); + /// + /// Records the specified media source. + /// + /// The media source. + /// The target file. + /// The duration. + /// The on started. + /// The cancellation token. + /// Task. + Task Record(MediaSourceInfo mediaSource, string targetFile, TimeSpan duration, Action onStarted, CancellationToken cancellationToken); } } diff --git a/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/RecordingHelper.cs b/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/RecordingHelper.cs index a5c869d45..37e10d925 100644 --- a/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/RecordingHelper.cs +++ b/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/RecordingHelper.cs @@ -56,13 +56,13 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV name += " " + info.OriginalAirDate.Value.ToString("yyyy-MM-dd"); } - if (addHyphen) - { - name += " -"; - } - if (!string.IsNullOrWhiteSpace(info.EpisodeTitle)) { + if (addHyphen) + { + name += " -"; + } + name += " " + info.EpisodeTitle; } }