diff --git a/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs b/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs index eea5625244..3bd2dd1bfa 100644 --- a/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs +++ b/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs @@ -710,7 +710,34 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV throw new InvalidOperationException("SeriesId for program not found"); } + // If any timers have already been manually created, make sure they don't get cancelled + var existingTimers = (await GetTimersAsync(CancellationToken.None).ConfigureAwait(false)) + .Where(i => + { + if (string.Equals(i.ProgramId, info.ProgramId, StringComparison.OrdinalIgnoreCase) && !string.IsNullOrWhiteSpace(info.ProgramId)) + { + return true; + } + + //if (string.Equals(i.SeriesId, info.SeriesId, StringComparison.OrdinalIgnoreCase) && !string.IsNullOrWhiteSpace(info.SeriesId)) + //{ + // return true; + //} + + return false; + }) + .ToList(); + _seriesTimerProvider.Add(info); + + foreach (var timer in existingTimers) + { + timer.SeriesTimerId = info.Id; + timer.IsManual = true; + + _timerProvider.AddOrUpdate(timer); + } + await UpdateTimersForSeriesTimer(epgData, info, true, false).ConfigureAwait(false); return info.Id; diff --git a/MediaBrowser.Api/Playback/BaseStreamingService.cs b/MediaBrowser.Api/Playback/BaseStreamingService.cs index b3a00cc92c..480508e6ff 100644 --- a/MediaBrowser.Api/Playback/BaseStreamingService.cs +++ b/MediaBrowser.Api/Playback/BaseStreamingService.cs @@ -289,8 +289,10 @@ namespace MediaBrowser.Api.Playback // MUST read both stdout and stderr asynchronously or a deadlock may occurr //process.BeginOutputReadLine(); + state.TranscodingJob = transcodingJob; + // Important - don't await the log task or we won't be able to kill ffmpeg when the user stops playback - var task = Task.Run(() => StartStreamingLog(transcodingJob, state, process.StandardError.BaseStream, state.LogFileStream)); + new JobLogger(Logger).StartStreamingLog(state, process.StandardError.BaseStream, state.LogFileStream); // Wait for the file to exist before proceeeding while (!FileSystem.FileExists(state.WaitForPath ?? outputPath) && !transcodingJob.HasExited) @@ -340,134 +342,6 @@ namespace MediaBrowser.Api.Playback // string.Equals(GetVideoEncoder(state), "libx264", StringComparison.OrdinalIgnoreCase); } - private async Task StartStreamingLog(TranscodingJob transcodingJob, StreamState state, Stream source, Stream target) - { - try - { - using (var reader = new StreamReader(source)) - { - while (!reader.EndOfStream) - { - var line = await reader.ReadLineAsync().ConfigureAwait(false); - - ParseLogLine(line, transcodingJob, state); - - var bytes = Encoding.UTF8.GetBytes(Environment.NewLine + line); - - await target.WriteAsync(bytes, 0, bytes.Length).ConfigureAwait(false); - await target.FlushAsync().ConfigureAwait(false); - } - } - } - catch (ObjectDisposedException) - { - // Don't spam the log. This doesn't seem to throw in windows, but sometimes under linux - } - catch (Exception ex) - { - Logger.ErrorException("Error reading ffmpeg log", ex); - } - } - - private void ParseLogLine(string line, TranscodingJob transcodingJob, StreamState state) - { - float? framerate = null; - double? percent = null; - TimeSpan? transcodingPosition = null; - long? bytesTranscoded = null; - int? bitRate = null; - - var parts = line.Split(' '); - - var totalMs = state.RunTimeTicks.HasValue - ? TimeSpan.FromTicks(state.RunTimeTicks.Value).TotalMilliseconds - : 0; - - var startMs = state.Request.StartTimeTicks.HasValue - ? TimeSpan.FromTicks(state.Request.StartTimeTicks.Value).TotalMilliseconds - : 0; - - for (var i = 0; i < parts.Length; i++) - { - var part = parts[i]; - - if (string.Equals(part, "fps=", StringComparison.OrdinalIgnoreCase) && - (i + 1 < parts.Length)) - { - var rate = parts[i + 1]; - float val; - - if (float.TryParse(rate, NumberStyles.Any, UsCulture, out val)) - { - framerate = val; - } - } - else if (state.RunTimeTicks.HasValue && - part.StartsWith("time=", StringComparison.OrdinalIgnoreCase)) - { - var time = part.Split(new[] { '=' }, 2).Last(); - TimeSpan val; - - if (TimeSpan.TryParse(time, UsCulture, out val)) - { - var currentMs = startMs + val.TotalMilliseconds; - - var percentVal = currentMs / totalMs; - percent = 100 * percentVal; - - transcodingPosition = val; - } - } - else if (part.StartsWith("size=", StringComparison.OrdinalIgnoreCase)) - { - var size = part.Split(new[] { '=' }, 2).Last(); - - int? scale = null; - if (size.IndexOf("kb", StringComparison.OrdinalIgnoreCase) != -1) - { - scale = 1024; - size = size.Replace("kb", string.Empty, StringComparison.OrdinalIgnoreCase); - } - - if (scale.HasValue) - { - long val; - - if (long.TryParse(size, NumberStyles.Any, UsCulture, out val)) - { - bytesTranscoded = val * scale.Value; - } - } - } - else if (part.StartsWith("bitrate=", StringComparison.OrdinalIgnoreCase)) - { - var rate = part.Split(new[] { '=' }, 2).Last(); - - int? scale = null; - if (rate.IndexOf("kbits/s", StringComparison.OrdinalIgnoreCase) != -1) - { - scale = 1024; - rate = rate.Replace("kbits/s", string.Empty, StringComparison.OrdinalIgnoreCase); - } - - if (scale.HasValue) - { - float val; - - if (float.TryParse(rate, NumberStyles.Any, UsCulture, out val)) - { - bitRate = (int)Math.Ceiling(val * scale.Value); - } - } - } - } - - if (framerate.HasValue || percent.HasValue) - { - ApiEntryPoint.Instance.ReportTranscodingProgress(transcodingJob, state, transcodingPosition, framerate, percent, bytesTranscoded, bitRate); - } - } - /// /// Processes the exited. /// diff --git a/MediaBrowser.Api/Playback/Hls/BaseHlsService.cs b/MediaBrowser.Api/Playback/Hls/BaseHlsService.cs index d2f14f4b80..53813860a8 100644 --- a/MediaBrowser.Api/Playback/Hls/BaseHlsService.cs +++ b/MediaBrowser.Api/Playback/Hls/BaseHlsService.cs @@ -268,7 +268,7 @@ namespace MediaBrowser.Api.Playback.Hls "hls/" + Path.GetFileNameWithoutExtension(outputPath)); } - var useGenericSegmenter = false; + var useGenericSegmenter = true; if (useGenericSegmenter) { var outputTsArg = Path.Combine(Path.GetDirectoryName(outputPath), Path.GetFileNameWithoutExtension(outputPath)) + "%d" + GetSegmentFileExtension(state.Request); @@ -281,7 +281,9 @@ namespace MediaBrowser.Api.Playback.Hls segmentFormat = "mpegts"; } - return string.Format("{0} {1} -map_metadata -1 -map_chapters -1 -threads {2} {3} {4} {5} -f segment -max_delay 5000000 -avoid_negative_ts disabled -start_at_zero -segment_time {6} {10} -individual_header_trailer 0 -segment_format {11} -segment_list_type m3u8 -segment_start_number {7} -segment_list \"{8}\" -y \"{9}\"", + baseUrlParam = string.Format("\"{0}/\"", "hls/" + Path.GetFileNameWithoutExtension(outputPath)); + + return string.Format("{0} {1} -map_metadata -1 -map_chapters -1 -threads {2} {3} {4} {5} -f segment -max_delay 5000000 -avoid_negative_ts disabled -start_at_zero -segment_time {6} {10} -individual_header_trailer 0 -segment_format {11} -segment_list_entry_prefix {12} -segment_list_type m3u8 -segment_start_number {7} -segment_list \"{8}\" -y \"{9}\"", inputModifier, EncodingHelper.GetInputArgument(state, encodingOptions), threads, @@ -293,7 +295,8 @@ namespace MediaBrowser.Api.Playback.Hls outputPath, outputTsArg, timeDeltaParam, - segmentFormat + segmentFormat, + baseUrlParam ).Trim(); } diff --git a/MediaBrowser.Api/Playback/StreamState.cs b/MediaBrowser.Api/Playback/StreamState.cs index 24ba0606d5..2f5f6227a9 100644 --- a/MediaBrowser.Api/Playback/StreamState.cs +++ b/MediaBrowser.Api/Playback/StreamState.cs @@ -154,6 +154,8 @@ namespace MediaBrowser.Api.Playback DisposeLiveStream(); DisposeLogStream(); DisposeIsoMount(); + + TranscodingJob = null; } private void DisposeLogStream() @@ -476,5 +478,11 @@ namespace MediaBrowser.Api.Playback return true; } } + + public TranscodingJob TranscodingJob; + public override void ReportTranscodingProgress(TimeSpan? transcodingPosition, float? framerate, double? percentComplete, long? bytesTranscoded, int? bitRate) + { + ApiEntryPoint.Instance.ReportTranscodingProgress(TranscodingJob, this, transcodingPosition, framerate, percentComplete, bytesTranscoded, bitRate); + } } } diff --git a/MediaBrowser.Controller/MediaBrowser.Controller.csproj b/MediaBrowser.Controller/MediaBrowser.Controller.csproj index 4ebee531f9..88153868f3 100644 --- a/MediaBrowser.Controller/MediaBrowser.Controller.csproj +++ b/MediaBrowser.Controller/MediaBrowser.Controller.csproj @@ -190,6 +190,7 @@ + diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingJobInfo.cs b/MediaBrowser.Controller/MediaEncoding/EncodingJobInfo.cs index 6f566515e8..f5878864b6 100644 --- a/MediaBrowser.Controller/MediaEncoding/EncodingJobInfo.cs +++ b/MediaBrowser.Controller/MediaEncoding/EncodingJobInfo.cs @@ -12,7 +12,7 @@ using MediaBrowser.Model.MediaInfo; namespace MediaBrowser.Controller.MediaEncoding { // For now, a common base class until the API and MediaEncoding classes are unified - public class EncodingJobInfo + public abstract class EncodingJobInfo { private readonly ILogger _logger; @@ -115,5 +115,7 @@ namespace MediaBrowser.Controller.MediaEncoding IsoMount = null; } } + + public abstract void ReportTranscodingProgress(TimeSpan? transcodingPosition, float? framerate, double? percentComplete, long? bytesTranscoded, int? bitRate); } } diff --git a/MediaBrowser.MediaEncoding/Encoder/JobLogger.cs b/MediaBrowser.Controller/MediaEncoding/JobLogger.cs similarity index 64% rename from MediaBrowser.MediaEncoding/Encoder/JobLogger.cs rename to MediaBrowser.Controller/MediaEncoding/JobLogger.cs index cb6e58f176..03e4f7771f 100644 --- a/MediaBrowser.MediaEncoding/Encoder/JobLogger.cs +++ b/MediaBrowser.Controller/MediaEncoding/JobLogger.cs @@ -6,7 +6,7 @@ using System.IO; using System.Linq; using System.Text; -namespace MediaBrowser.MediaEncoding.Encoder +namespace MediaBrowser.Controller.MediaEncoding { public class JobLogger { @@ -18,7 +18,7 @@ namespace MediaBrowser.MediaEncoding.Encoder _logger = logger; } - public async void StartStreamingLog(EncodingJob transcodingJob, Stream source, Stream target) + public async void StartStreamingLog(EncodingJobInfo state, Stream source, Stream target) { try { @@ -28,35 +28,41 @@ namespace MediaBrowser.MediaEncoding.Encoder { var line = await reader.ReadLineAsync().ConfigureAwait(false); - ParseLogLine(line, transcodingJob); + ParseLogLine(line, state); var bytes = Encoding.UTF8.GetBytes(Environment.NewLine + line); await target.WriteAsync(bytes, 0, bytes.Length).ConfigureAwait(false); + await target.FlushAsync().ConfigureAwait(false); } } } + catch (ObjectDisposedException) + { + // Don't spam the log. This doesn't seem to throw in windows, but sometimes under linux + } catch (Exception ex) { _logger.ErrorException("Error reading ffmpeg log", ex); } } - private void ParseLogLine(string line, EncodingJob transcodingJob) + private void ParseLogLine(string line, EncodingJobInfo state) { float? framerate = null; double? percent = null; TimeSpan? transcodingPosition = null; long? bytesTranscoded = null; + int? bitRate = null; var parts = line.Split(' '); - var totalMs = transcodingJob.RunTimeTicks.HasValue - ? TimeSpan.FromTicks(transcodingJob.RunTimeTicks.Value).TotalMilliseconds + var totalMs = state.RunTimeTicks.HasValue + ? TimeSpan.FromTicks(state.RunTimeTicks.Value).TotalMilliseconds : 0; - var startMs = transcodingJob.Options.StartTimeTicks.HasValue - ? TimeSpan.FromTicks(transcodingJob.Options.StartTimeTicks.Value).TotalMilliseconds + var startMs = state.BaseRequest.StartTimeTicks.HasValue + ? TimeSpan.FromTicks(state.BaseRequest.StartTimeTicks.Value).TotalMilliseconds : 0; for (var i = 0; i < parts.Length; i++) @@ -74,7 +80,7 @@ namespace MediaBrowser.MediaEncoding.Encoder framerate = val; } } - else if (transcodingJob.RunTimeTicks.HasValue && + else if (state.RunTimeTicks.HasValue && part.StartsWith("time=", StringComparison.OrdinalIgnoreCase)) { var time = part.Split(new[] { '=' }, 2).Last(); @@ -111,11 +117,32 @@ namespace MediaBrowser.MediaEncoding.Encoder } } } + else if (part.StartsWith("bitrate=", StringComparison.OrdinalIgnoreCase)) + { + var rate = part.Split(new[] { '=' }, 2).Last(); + + int? scale = null; + if (rate.IndexOf("kbits/s", StringComparison.OrdinalIgnoreCase) != -1) + { + scale = 1024; + rate = rate.Replace("kbits/s", string.Empty, StringComparison.OrdinalIgnoreCase); + } + + if (scale.HasValue) + { + float val; + + if (float.TryParse(rate, NumberStyles.Any, _usCulture, out val)) + { + bitRate = (int)Math.Ceiling(val * scale.Value); + } + } + } } if (framerate.HasValue || percent.HasValue) { - transcodingJob.ReportTranscodingProgress(transcodingPosition, framerate, percent, bytesTranscoded); + state.ReportTranscodingProgress(transcodingPosition, framerate, percent, bytesTranscoded, bitRate); } } } diff --git a/MediaBrowser.MediaEncoding/Encoder/BaseEncoder.cs b/MediaBrowser.MediaEncoding/Encoder/BaseEncoder.cs index ef68fb90a4..b6e695f1bc 100644 --- a/MediaBrowser.MediaEncoding/Encoder/BaseEncoder.cs +++ b/MediaBrowser.MediaEncoding/Encoder/BaseEncoder.cs @@ -242,7 +242,7 @@ namespace MediaBrowser.MediaEncoding.Encoder private void OnTranscodeBeginning(EncodingJob job) { - job.ReportTranscodingProgress(null, null, null, null); + job.ReportTranscodingProgress(null, null, null, null, null); } private void OnTranscodeFailedToStart(string path, EncodingJob job) diff --git a/MediaBrowser.MediaEncoding/Encoder/EncodingJob.cs b/MediaBrowser.MediaEncoding/Encoder/EncodingJob.cs index 449c31acec..883709de81 100644 --- a/MediaBrowser.MediaEncoding/Encoder/EncodingJob.cs +++ b/MediaBrowser.MediaEncoding/Encoder/EncodingJob.cs @@ -377,7 +377,7 @@ namespace MediaBrowser.MediaEncoding.Encoder return count; } - public void ReportTranscodingProgress(TimeSpan? transcodingPosition, float? framerate, double? percentComplete, long? bytesTranscoded) + public override void ReportTranscodingProgress(TimeSpan? transcodingPosition, float? framerate, double? percentComplete, long? bytesTranscoded, int? bitRate) { var ticks = transcodingPosition.HasValue ? transcodingPosition.Value.Ticks : (long?)null; @@ -385,8 +385,8 @@ namespace MediaBrowser.MediaEncoding.Encoder if (!percentComplete.HasValue && ticks.HasValue && RunTimeTicks.HasValue) { - var pct = ticks.Value/RunTimeTicks.Value; - percentComplete = pct*100; + var pct = ticks.Value / RunTimeTicks.Value; + percentComplete = pct * 100; } if (percentComplete.HasValue) diff --git a/MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj b/MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj index 63e789a59d..ee19c4b66b 100644 --- a/MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj +++ b/MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj @@ -55,7 +55,6 @@ - diff --git a/MediaBrowser.Model/Dlna/StreamBuilder.cs b/MediaBrowser.Model/Dlna/StreamBuilder.cs index 1c7aa81e5e..36df67b34e 100644 --- a/MediaBrowser.Model/Dlna/StreamBuilder.cs +++ b/MediaBrowser.Model/Dlna/StreamBuilder.cs @@ -324,7 +324,7 @@ namespace MediaBrowser.Model.Dlna if (directPlayProfile != null) { // While options takes the network and other factors into account. Only applies to direct stream - if (item.SupportsDirectStream && IsAudioEligibleForDirectPlay(item, options.GetMaxBitrate(true)) && options.EnableDirectStream) + if (item.SupportsDirectStream && IsAudioEligibleForDirectPlay(item, options.GetMaxBitrate(true), PlayMethod.DirectStream) && options.EnableDirectStream) { playMethods.Add(PlayMethod.DirectStream); } @@ -332,7 +332,7 @@ namespace MediaBrowser.Model.Dlna // The profile describes what the device supports // If device requirements are satisfied then allow both direct stream and direct play if (item.SupportsDirectPlay && - IsAudioEligibleForDirectPlay(item, GetBitrateForDirectPlayCheck(item, options, true)) && options.EnableDirectPlay) + IsAudioEligibleForDirectPlay(item, GetBitrateForDirectPlayCheck(item, options, true), PlayMethod.DirectPlay) && options.EnableDirectPlay) { playMethods.Add(PlayMethod.DirectPlay); } @@ -905,7 +905,7 @@ namespace MediaBrowser.Model.Dlna } } - return IsAudioEligibleForDirectPlay(item, maxBitrate); + return IsAudioEligibleForDirectPlay(item, maxBitrate, playMethod); } public static SubtitleProfile GetSubtitleProfile(MediaStream subtitleStream, SubtitleProfile[] subtitleProfiles, PlayMethod playMethod, string transcodingSubProtocol, string transcodingContainer) @@ -1035,7 +1035,7 @@ namespace MediaBrowser.Model.Dlna return null; } - private bool IsAudioEligibleForDirectPlay(MediaSourceInfo item, long? maxBitrate) + private bool IsAudioEligibleForDirectPlay(MediaSourceInfo item, long? maxBitrate, PlayMethod playMethod) { // Don't restrict by bitrate if coming from an external domain if (item.IsRemote) @@ -1045,19 +1045,19 @@ namespace MediaBrowser.Model.Dlna if (!maxBitrate.HasValue) { - _logger.Info("Cannot direct play due to unknown supported bitrate"); + _logger.Info("Cannot "+ playMethod + " due to unknown supported bitrate"); return false; } if (!item.Bitrate.HasValue) { - _logger.Info("Cannot direct play due to unknown content bitrate"); + _logger.Info("Cannot " + playMethod + " due to unknown content bitrate"); return false; } if (item.Bitrate.Value > maxBitrate.Value) { - _logger.Info("Bitrate exceeds DirectPlay limit: media bitrate: {0}, max bitrate: {1}", item.Bitrate.Value.ToString(CultureInfo.InvariantCulture), maxBitrate.Value.ToString(CultureInfo.InvariantCulture)); + _logger.Info("Bitrate exceeds " + playMethod + " limit: media bitrate: {0}, max bitrate: {1}", item.Bitrate.Value.ToString(CultureInfo.InvariantCulture), maxBitrate.Value.ToString(CultureInfo.InvariantCulture)); return false; } diff --git a/MediaBrowser.Providers/Manager/ImageSaver.cs b/MediaBrowser.Providers/Manager/ImageSaver.cs index 75c4b46b25..7c797133f4 100644 --- a/MediaBrowser.Providers/Manager/ImageSaver.cs +++ b/MediaBrowser.Providers/Manager/ImageSaver.cs @@ -172,18 +172,11 @@ namespace MediaBrowser.Providers.Manager try { - var currentFile = _fileSystem.GetFileInfo(currentPath); - - // This will fail if the file is hidden - if (currentFile.Exists) - { - if (currentFile.IsHidden) - { - _fileSystem.SetHidden(currentFile.FullName, false); - } - - _fileSystem.DeleteFile(currentFile.FullName); - } + _fileSystem.DeleteFile(currentPath); + } + catch (FileNotFoundException) + { + } finally { diff --git a/MediaBrowser.Providers/Manager/ItemImageProvider.cs b/MediaBrowser.Providers/Manager/ItemImageProvider.cs index 9dff243c1e..31cdc164c6 100644 --- a/MediaBrowser.Providers/Manager/ItemImageProvider.cs +++ b/MediaBrowser.Providers/Manager/ItemImageProvider.cs @@ -373,20 +373,15 @@ namespace MediaBrowser.Providers.Manager continue; } - // Delete the source file - var currentFile = _fileSystem.GetFileInfo(image.Path); - - // Deletion will fail if the file is hidden so remove the attribute first - if (currentFile.Exists) + try { - if (currentFile.IsHidden) - { - _fileSystem.SetHidden(currentFile.FullName, false); - } - - _fileSystem.DeleteFile(currentFile.FullName); + _fileSystem.DeleteFile(image.Path); deleted = true; } + catch (FileNotFoundException) + { + + } } foreach (var image in deletedImages)