diff --git a/MediaBrowser.Api/Playback/BaseStreamingService.cs b/MediaBrowser.Api/Playback/BaseStreamingService.cs index 33f3210a95..45e0e8b8a2 100644 --- a/MediaBrowser.Api/Playback/BaseStreamingService.cs +++ b/MediaBrowser.Api/Playback/BaseStreamingService.cs @@ -1634,11 +1634,6 @@ namespace MediaBrowser.Api.Playback private void TryStreamCopy(StreamState state, VideoStreamRequest videoRequest) { - if (!EnableStreamCopy) - { - return; - } - if (state.VideoStream != null && CanStreamCopyVideo(videoRequest, state.VideoStream)) { state.OutputVideoCodec = "copy"; @@ -1650,14 +1645,6 @@ namespace MediaBrowser.Api.Playback } } - protected virtual bool EnableStreamCopy - { - get - { - return true; - } - } - private void AttachMediaSourceInfo(StreamState state, MediaSourceInfo mediaSource, VideoStreamRequest videoRequest, @@ -1741,7 +1728,7 @@ namespace MediaBrowser.Api.Playback state.MediaSource = mediaSource; } - private bool CanStreamCopyVideo(VideoStreamRequest request, MediaStream videoStream) + protected virtual bool CanStreamCopyVideo(VideoStreamRequest request, MediaStream videoStream) { if (videoStream.IsInterlaced) { @@ -1889,7 +1876,7 @@ namespace MediaBrowser.Api.Playback return Array.FindIndex(list.ToArray(), t => string.Equals(t, profile, StringComparison.OrdinalIgnoreCase)); } - private bool CanStreamCopyAudio(VideoStreamRequest request, MediaStream audioStream, List supportedAudioCodecs) + protected virtual bool CanStreamCopyAudio(VideoStreamRequest request, MediaStream audioStream, List supportedAudioCodecs) { // Source and target codecs must match if (string.IsNullOrEmpty(audioStream.Codec) || !supportedAudioCodecs.Contains(audioStream.Codec, StringComparer.OrdinalIgnoreCase)) diff --git a/MediaBrowser.Api/Playback/Hls/BaseHlsService.cs b/MediaBrowser.Api/Playback/Hls/BaseHlsService.cs index b2ffeca3db..1a152790aa 100644 --- a/MediaBrowser.Api/Playback/Hls/BaseHlsService.cs +++ b/MediaBrowser.Api/Playback/Hls/BaseHlsService.cs @@ -1,9 +1,11 @@ -using MediaBrowser.Common.IO; +using System.Linq; +using MediaBrowser.Common.IO; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Devices; using MediaBrowser.Controller.Dlna; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.MediaEncoding; +using MediaBrowser.Model.Entities; using MediaBrowser.Model.Extensions; using MediaBrowser.Model.IO; using MediaBrowser.Model.Net; @@ -310,5 +312,33 @@ namespace MediaBrowser.Api.Playback.Hls { return 0; } + + protected override bool CanStreamCopyVideo(VideoStreamRequest request, MediaStream videoStream) + { + if (videoStream.KeyFrames == null || videoStream.KeyFrames.Count == 0) + { + return false; + } + + var previousSegment = 0; + foreach (var frame in videoStream.KeyFrames) + { + var length = frame - previousSegment; + + // Don't allow really long segments because this could result in long download times + if (length > 10000) + { + return false; + } + previousSegment = frame; + } + + return base.CanStreamCopyVideo(request, videoStream); + } + + protected override bool CanStreamCopyAudio(VideoStreamRequest request, MediaStream audioStream, List supportedAudioCodecs) + { + return false; + } } } diff --git a/MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs b/MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs index 27429b8dbe..04979fc3ab 100644 --- a/MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs +++ b/MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs @@ -160,7 +160,6 @@ namespace MediaBrowser.Api.Playback.Hls var playlistPath = Path.ChangeExtension(state.OutputFilePath, ".m3u8"); var segmentPath = GetSegmentPath(state, playlistPath, requestedIndex); - var segmentLength = state.SegmentLength; var segmentExtension = GetSegmentFileExtension(state); @@ -169,7 +168,7 @@ namespace MediaBrowser.Api.Playback.Hls if (File.Exists(segmentPath)) { job = ApiEntryPoint.Instance.OnTranscodeBeginRequest(playlistPath, TranscodingJobType); - return await GetSegmentResult(playlistPath, segmentPath, requestedIndex, segmentLength, job, cancellationToken).ConfigureAwait(false); + return await GetSegmentResult(state, playlistPath, segmentPath, requestedIndex, job, cancellationToken).ConfigureAwait(false); } await ApiEntryPoint.Instance.TranscodingStartLock.WaitAsync(cancellationTokenSource.Token).ConfigureAwait(false); @@ -178,7 +177,7 @@ namespace MediaBrowser.Api.Playback.Hls if (File.Exists(segmentPath)) { job = ApiEntryPoint.Instance.OnTranscodeBeginRequest(playlistPath, TranscodingJobType); - return await GetSegmentResult(playlistPath, segmentPath, requestedIndex, segmentLength, job, cancellationToken).ConfigureAwait(false); + return await GetSegmentResult(state, playlistPath, segmentPath, requestedIndex, job, cancellationToken).ConfigureAwait(false); } else { @@ -214,7 +213,7 @@ namespace MediaBrowser.Api.Playback.Hls DeleteLastFile(playlistPath, segmentExtension, 0); } - request.StartTimeTicks = GetSeekPositionTicks(state, playlistPath, requestedIndex); + request.StartTimeTicks = GetStartPositionTicks(state, requestedIndex); job = await StartFfMpeg(state, playlistPath, cancellationTokenSource).ConfigureAwait(false); } @@ -249,37 +248,76 @@ namespace MediaBrowser.Api.Playback.Hls Logger.Info("returning {0}", segmentPath); job = job ?? ApiEntryPoint.Instance.OnTranscodeBeginRequest(playlistPath, TranscodingJobType); - return await GetSegmentResult(playlistPath, segmentPath, requestedIndex, segmentLength, job, cancellationToken).ConfigureAwait(false); + return await GetSegmentResult(state, playlistPath, segmentPath, requestedIndex, job, cancellationToken).ConfigureAwait(false); } // 256k private const int BufferSize = 262144; - private long GetSeekPositionTicks(StreamState state, string playlist, int requestedIndex) + private long GetStartPositionTicks(StreamState state, int requestedIndex) { double startSeconds = 0; + var lengths = GetSegmentLengths(state); for (var i = 0; i < requestedIndex; i++) { - var segmentPath = GetSegmentPath(state, playlist, i); + startSeconds += lengths[requestedIndex]; + } + + var position = TimeSpan.FromSeconds(startSeconds).Ticks; + return position; + } - //double length; - //if (SegmentLengths.TryGetValue(Path.GetFileName(segmentPath), out length)) - //{ - // Logger.Debug("Found segment length of {0} for index {1}", length, i); - // startSeconds += length; - //} - //else - //{ - // startSeconds += state.SegmentLength; - //} - startSeconds += state.SegmentLength; + private long GetEndPositionTicks(StreamState state, int requestedIndex) + { + double startSeconds = 0; + var lengths = GetSegmentLengths(state); + + for (var i = 0; i <= requestedIndex; i++) + { + startSeconds += lengths[requestedIndex]; } var position = TimeSpan.FromSeconds(startSeconds).Ticks; return position; } + private double[] GetSegmentLengths(StreamState state) + { + var result = new List(); + var encoder = GetVideoEncoder(state); + + if (string.Equals(encoder, "copy", StringComparison.OrdinalIgnoreCase)) + { + var videoStream = state.VideoStream; + if (videoStream.KeyFrames != null && videoStream.KeyFrames.Count > 0) + { + foreach (var frame in videoStream.KeyFrames) + { + var seconds = TimeSpan.FromMilliseconds(frame).TotalSeconds; + seconds -= result.Sum(); + result.Add(seconds); + } + return result.ToArray(); + } + } + + var ticks = state.RunTimeTicks ?? 0; + + var segmentLengthTicks = TimeSpan.FromSeconds(state.SegmentLength).Ticks; + + while (ticks > 0) + { + var length = ticks >= segmentLengthTicks ? segmentLengthTicks : ticks; + + result.Add(TimeSpan.FromTicks(length).TotalSeconds); + + ticks -= length; + } + + return result.ToArray(); + } + public int? GetCurrentTranscodingIndex(string playlist, string segmentExtension) { var job = ApiEntryPoint.Instance.GetTranscodingJob(playlist, TranscodingJobType); @@ -384,17 +422,16 @@ namespace MediaBrowser.Api.Playback.Hls return Path.Combine(folder, filename + index.ToString(UsCulture) + GetSegmentFileExtension(state)); } - private async Task GetSegmentResult(string playlistPath, + private async Task GetSegmentResult(StreamState state, string playlistPath, string segmentPath, int segmentIndex, - int segmentLength, TranscodingJob transcodingJob, CancellationToken cancellationToken) { // If all transcoding has completed, just return immediately if (transcodingJob != null && transcodingJob.HasExited && File.Exists(segmentPath)) { - return GetSegmentResult(segmentPath, segmentIndex, segmentLength, transcodingJob); + return GetSegmentResult(state, segmentPath, segmentIndex, transcodingJob); } var segmentFilename = Path.GetFileName(segmentPath); @@ -414,7 +451,7 @@ namespace MediaBrowser.Api.Playback.Hls { if (File.Exists(segmentPath)) { - return GetSegmentResult(segmentPath, segmentIndex, segmentLength, transcodingJob); + return GetSegmentResult(state, segmentPath, segmentIndex, transcodingJob); } //break; } @@ -465,13 +502,12 @@ namespace MediaBrowser.Api.Playback.Hls //} cancellationToken.ThrowIfCancellationRequested(); - return GetSegmentResult(segmentPath, segmentIndex, segmentLength, transcodingJob); + return GetSegmentResult(state, segmentPath, segmentIndex, transcodingJob); } - private object GetSegmentResult(string segmentPath, int index, int segmentLength, TranscodingJob transcodingJob) + private object GetSegmentResult(StreamState state, string segmentPath, int index, TranscodingJob transcodingJob) { - var segmentEndingSeconds = (1 + index) * segmentLength; - var segmentEndingPositionTicks = TimeSpan.FromSeconds(segmentEndingSeconds).Ticks; + var segmentEndingPositionTicks = GetEndPositionTicks(state, index); return ResultFactory.GetStaticFileResult(Request, new StaticFileResultOptions { @@ -698,26 +734,22 @@ namespace MediaBrowser.Api.Playback.Hls { var state = await GetState(request, CancellationToken.None).ConfigureAwait(false); + var segmentLengths = GetSegmentLengths(state); + var builder = new StringBuilder(); builder.AppendLine("#EXTM3U"); builder.AppendLine("#EXT-X-VERSION:3"); - builder.AppendLine("#EXT-X-TARGETDURATION:" + (state.SegmentLength).ToString(UsCulture)); + builder.AppendLine("#EXT-X-TARGETDURATION:" + Math.Ceiling((segmentLengths.Length > 0 ? segmentLengths.Max() : state.SegmentLength)).ToString(UsCulture)); builder.AppendLine("#EXT-X-MEDIA-SEQUENCE:0"); var queryStringIndex = Request.RawUrl.IndexOf('?'); var queryString = queryStringIndex == -1 ? string.Empty : Request.RawUrl.Substring(queryStringIndex); - var seconds = TimeSpan.FromTicks(state.RunTimeTicks ?? 0).TotalSeconds; - var index = 0; - double segmentLength = state.SegmentLength; - - while (seconds > 0) + foreach (var length in segmentLengths) { - var length = seconds >= state.SegmentLength ? segmentLength : seconds; - builder.AppendLine("#EXTINF:" + length.ToString("0.000000", UsCulture) + ","); builder.AppendLine(string.Format("hlsdynamic/{0}/{1}{2}{3}", @@ -727,7 +759,6 @@ namespace MediaBrowser.Api.Playback.Hls GetSegmentFileExtension(isOutputVideo), queryString)); - seconds -= length; index++; } @@ -850,11 +881,6 @@ namespace MediaBrowser.Api.Playback.Hls args += " -flags +loop-global_header -sc_threshold 0"; } - if (!EnableSplitTranscoding(state)) - { - //args += " -copyts"; - } - return args; } @@ -870,21 +896,6 @@ namespace MediaBrowser.Api.Playback.Hls var toTimeParam = string.Empty; var timestampOffsetParam = string.Empty; - if (EnableSplitTranscoding(state)) - { - var startTime = state.Request.StartTimeTicks ?? 0; - var durationSeconds = ApiEntryPoint.Instance.GetEncodingOptions().ThrottleThresholdInSeconds; - - var endTime = startTime + TimeSpan.FromSeconds(durationSeconds).Ticks; - endTime = Math.Min(endTime, state.RunTimeTicks.Value); - - if (endTime < state.RunTimeTicks.Value) - { - //toTimeParam = " -to " + MediaEncoder.GetTimeParameter(endTime); - toTimeParam = " -t " + MediaEncoder.GetTimeParameter(TimeSpan.FromSeconds(durationSeconds).Ticks); - } - } - if (state.IsOutputVideo && !string.Equals(state.OutputVideoCodec, "copy", StringComparison.OrdinalIgnoreCase) && (state.Request.StartTimeTicks ?? 0) > 0) { timestampOffsetParam = " -output_ts_offset " + MediaEncoder.GetTimeParameter(state.Request.StartTimeTicks ?? 0).ToString(CultureInfo.InvariantCulture); @@ -927,36 +938,7 @@ namespace MediaBrowser.Api.Playback.Hls protected override bool EnableThrottling(StreamState state) { - return !EnableSplitTranscoding(state); - } - - private bool EnableSplitTranscoding(StreamState state) - { - return false; - if (string.Equals(Request.QueryString["EnableSplitTranscoding"], "false", StringComparison.OrdinalIgnoreCase)) - { - return false; - } - - if (string.Equals(state.OutputVideoCodec, "copy", StringComparison.OrdinalIgnoreCase)) - { - return false; - } - - if (string.Equals(state.OutputAudioCodec, "copy", StringComparison.OrdinalIgnoreCase)) - { - return false; - } - - return state.RunTimeTicks.HasValue && state.IsOutputVideo; - } - - protected override bool EnableStreamCopy - { - get - { - return false; - } + return true; } /// diff --git a/MediaBrowser.Dlna/MediaBrowser.Dlna.csproj b/MediaBrowser.Dlna/MediaBrowser.Dlna.csproj index a8cd3257c5..c49cbc6548 100644 --- a/MediaBrowser.Dlna/MediaBrowser.Dlna.csproj +++ b/MediaBrowser.Dlna/MediaBrowser.Dlna.csproj @@ -226,7 +226,9 @@ - + + Designer +