public ActionResult <StreamResponse> PostStream(StreamRequest streamRequest) { _logger.LogTrace($"{Request.HttpContext.Connection.RemoteIpAddress}: POST {Request.Host}{Request.Path}"); _authHandler.CheckToken(streamRequest.Token); return(_serverManager.GetStream(streamRequest.StreamId, streamRequest.Settings.VideoPresetId, streamRequest.Settings.AudioPresetId)); }
/// <summary> /// Processes the request. /// </summary> /// <param name="request">The request.</param> /// <param name="isHeadRequest">if set to <c>true</c> [is head request].</param> /// <returns>Task.</returns> protected object ProcessRequest(StreamRequest request, bool isHeadRequest) { var cancellationTokenSource = new CancellationTokenSource(); var state = GetState(request, cancellationTokenSource.Token).Result; var responseHeaders = new Dictionary<string, string>(); // Static remote stream if (request.Static && state.InputProtocol == MediaProtocol.Http) { AddDlnaHeaders(state, responseHeaders, true); using (state) { return GetStaticRemoteStreamResult(state, responseHeaders, isHeadRequest, cancellationTokenSource).Result; } } if (request.Static && state.InputProtocol != MediaProtocol.File) { throw new ArgumentException(string.Format("Input protocol {0} cannot be streamed statically.", state.InputProtocol)); } var outputPath = state.OutputFilePath; var outputPathExists = File.Exists(outputPath); var isTranscodeCached = outputPathExists && !ApiEntryPoint.Instance.HasActiveTranscodingJob(outputPath, TranscodingJobType.Progressive); AddDlnaHeaders(state, responseHeaders, request.Static || isTranscodeCached); // Static stream if (request.Static) { var contentType = state.GetMimeType(state.MediaPath); using (state) { var job = string.IsNullOrEmpty(request.TranscodingJobId) ? null : ApiEntryPoint.Instance.GetTranscodingJob(request.TranscodingJobId); var limits = new List<long>(); if (state.InputBitrate.HasValue) { // Bytes per second limits.Add((state.InputBitrate.Value / 8)); } if (state.InputFileSize.HasValue && state.RunTimeTicks.HasValue) { var totalSeconds = TimeSpan.FromTicks(state.RunTimeTicks.Value).TotalSeconds; if (totalSeconds > 1) { var timeBasedLimit = state.InputFileSize.Value / totalSeconds; limits.Add(Convert.ToInt64(timeBasedLimit)); } } // Take the greater of the above to methods, just to be safe var throttleLimit = limits.Count > 0 ? limits.First() : 0; // Pad to play it safe var bytesPerSecond = Convert.ToInt64(1.05 * throttleLimit); // Don't even start evaluating this until at least two minutes have content have been consumed var targetGap = throttleLimit * 120; return ResultFactory.GetStaticFileResult(Request, new StaticFileResultOptions { ResponseHeaders = responseHeaders, ContentType = contentType, IsHeadRequest = isHeadRequest, Path = state.MediaPath, Throttle = request.Throttle, ThrottleLimit = bytesPerSecond, MinThrottlePosition = targetGap, ThrottleCallback = (l1, l2) => ThrottleCallack(l1, l2, bytesPerSecond, job) }); } } // Not static but transcode cache file exists if (isTranscodeCached) { var contentType = state.GetMimeType(outputPath); try { return ResultFactory.GetStaticFileResult(Request, new StaticFileResultOptions { ResponseHeaders = responseHeaders, ContentType = contentType, IsHeadRequest = isHeadRequest, Path = outputPath }); } finally { state.Dispose(); } } // Need to start ffmpeg try { return GetStreamResult(state, responseHeaders, isHeadRequest, cancellationTokenSource).Result; } catch { state.Dispose(); throw; } }
/// <summary> /// Processes the request async. /// </summary> /// <param name="request">The request.</param> /// <returns>Task{System.Object}.</returns> /// <exception cref="ArgumentException"> /// A video bitrate is required /// or /// An audio bitrate is required /// </exception> private async Task<object> ProcessRequestAsync(StreamRequest request) { var state = GetState(request, CancellationToken.None).Result; if (!state.VideoRequest.VideoBitRate.HasValue && (!state.VideoRequest.VideoCodec.HasValue || state.VideoRequest.VideoCodec.Value != VideoCodecs.Copy)) { throw new ArgumentException("A video bitrate is required"); } if (!state.Request.AudioBitRate.HasValue && (!state.Request.AudioCodec.HasValue || state.Request.AudioCodec.Value != AudioCodecs.Copy)) { throw new ArgumentException("An audio bitrate is required"); } var playlist = GetOutputFilePath(state); var isPlaylistNewlyCreated = false; // If the playlist doesn't already exist, startup ffmpeg if (!File.Exists(playlist)) { isPlaylistNewlyCreated = true; await StartFfMpeg(state, playlist).ConfigureAwait(false); } else { ApiEntryPoint.Instance.OnTranscodeBeginRequest(playlist, TranscodingJobType.Hls); } if (isPlaylistNewlyCreated) { await WaitForMinimumSegmentCount(playlist, GetSegmentWait()).ConfigureAwait(false); } int audioBitrate; int videoBitrate; GetPlaylistBitrates(state, out audioBitrate, out videoBitrate); var appendBaselineStream = false; var baselineStreamBitrate = 64000; var hlsVideoRequest = state.VideoRequest as GetHlsVideoStream; if (hlsVideoRequest != null) { appendBaselineStream = hlsVideoRequest.AppendBaselineStream; baselineStreamBitrate = hlsVideoRequest.BaselineStreamAudioBitRate ?? baselineStreamBitrate; } var playlistText = GetMasterPlaylistFileText(playlist, videoBitrate + audioBitrate, appendBaselineStream, baselineStreamBitrate); try { return ResultFactory.GetResult(playlistText, MimeTypes.GetMimeType("playlist.m3u8"), new Dictionary<string, string>()); } finally { ApiEntryPoint.Instance.OnTranscodeEndRequest(playlist, TranscodingJobType.Hls); } }
/// <summary> /// Processes the request. /// </summary> /// <param name="request">The request.</param> /// <returns>System.Object.</returns> protected object ProcessRequest(StreamRequest request) { return ProcessRequestAsync(request).Result; }
/// <summary> /// Processes the request. /// </summary> /// <param name="request">The request.</param> /// <param name="isLive">if set to <c>true</c> [is live].</param> /// <returns>System.Object.</returns> protected async Task<object> ProcessRequest(StreamRequest request, bool isLive) { return await ProcessRequestAsync(request, isLive).ConfigureAwait(false); }
/// <summary> /// Processes the request. /// </summary> /// <param name="request">The request.</param> /// <param name="isHeadRequest">if set to <c>true</c> [is head request].</param> /// <returns>Task.</returns> protected object ProcessRequest(StreamRequest request, bool isHeadRequest) { var cancellationTokenSource = new CancellationTokenSource(); var state = GetState(request, cancellationTokenSource.Token).Result; var responseHeaders = new Dictionary<string, string>(); // Static remote stream if (request.Static && state.InputProtocol == MediaProtocol.Http) { AddDlnaHeaders(state, responseHeaders, true); using (state) { return GetStaticRemoteStreamResult(state, responseHeaders, isHeadRequest, cancellationTokenSource).Result; } } if (request.Static && state.InputProtocol != MediaProtocol.File) { throw new ArgumentException(string.Format("Input protocol {0} cannot be streamed statically.", state.InputProtocol)); } var outputPath = state.OutputFilePath; var outputPathExists = File.Exists(outputPath); var isTranscodeCached = outputPathExists && !ApiEntryPoint.Instance.HasActiveTranscodingJob(outputPath, TranscodingJobType.Progressive); AddDlnaHeaders(state, responseHeaders, request.Static || isTranscodeCached); // Static stream if (request.Static) { var contentType = state.GetMimeType(state.MediaPath); using (state) { return ResultFactory.GetStaticFileResult(Request, new StaticFileResultOptions { ResponseHeaders = responseHeaders, ContentType = contentType, IsHeadRequest = isHeadRequest, Path = state.MediaPath }); } } // Not static but transcode cache file exists if (isTranscodeCached) { var contentType = state.GetMimeType(outputPath); try { return ResultFactory.GetStaticFileResult(Request, new StaticFileResultOptions { ResponseHeaders = responseHeaders, ContentType = contentType, IsHeadRequest = isHeadRequest, Path = outputPath }); } finally { state.Dispose(); } } // Need to start ffmpeg try { return GetStreamResult(state, responseHeaders, isHeadRequest, cancellationTokenSource).Result; } catch { state.Dispose(); throw; } }
private async Task<object> GetMasterPlaylistInternal(StreamRequest request, string method) { var state = await GetState(request, CancellationToken.None).ConfigureAwait(false); if (string.IsNullOrEmpty(request.MediaSourceId)) { throw new ArgumentException("MediaSourceId is required"); } var playlistText = string.Empty; if (string.Equals(method, "GET", StringComparison.OrdinalIgnoreCase)) { var audioBitrate = state.OutputAudioBitrate ?? 0; var videoBitrate = state.OutputVideoBitrate ?? 0; playlistText = GetMasterPlaylistFileText(state, videoBitrate + audioBitrate); } return ResultFactory.GetResult(playlistText, MimeTypes.GetMimeType("playlist.m3u8"), new Dictionary<string, string>()); }
/// <summary> /// Processes the request async. /// </summary> /// <param name="request">The request.</param> /// <param name="isLive">if set to <c>true</c> [is live].</param> /// <returns>Task{System.Object}.</returns> /// <exception cref="ArgumentException">A video bitrate is required /// or /// An audio bitrate is required</exception> private async Task<object> ProcessRequestAsync(StreamRequest request, bool isLive) { var cancellationTokenSource = new CancellationTokenSource(); var state = await GetState(request, cancellationTokenSource.Token).ConfigureAwait(false); if (isLive) { state.Request.StartTimeTicks = null; } var playlist = state.OutputFilePath; if (File.Exists(playlist)) { ApiEntryPoint.Instance.OnTranscodeBeginRequest(playlist, TranscodingJobType.Hls); } else { await ApiEntryPoint.Instance.TranscodingStartLock.WaitAsync(cancellationTokenSource.Token).ConfigureAwait(false); try { if (File.Exists(playlist)) { ApiEntryPoint.Instance.OnTranscodeBeginRequest(playlist, TranscodingJobType.Hls); } else { // If the playlist doesn't already exist, startup ffmpeg try { await StartFfMpeg(state, playlist, cancellationTokenSource).ConfigureAwait(false); } catch { state.Dispose(); throw; } var waitCount = isLive ? 1 : GetSegmentWait(); await WaitForMinimumSegmentCount(playlist, waitCount, cancellationTokenSource.Token).ConfigureAwait(false); } } finally { ApiEntryPoint.Instance.TranscodingStartLock.Release(); } } if (isLive) { //var file = request.PlaylistId + Path.GetExtension(Request.PathInfo); //file = Path.Combine(ServerConfigurationManager.ApplicationPaths.TranscodingTempPath, file); try { return ResultFactory.GetStaticFileResult(Request, playlist, FileShare.ReadWrite); } finally { ApiEntryPoint.Instance.OnTranscodeEndRequest(playlist, TranscodingJobType.Hls); } } var audioBitrate = state.OutputAudioBitrate ?? 0; var videoBitrate = state.OutputVideoBitrate ?? 0; var appendBaselineStream = false; var baselineStreamBitrate = 64000; var hlsVideoRequest = state.VideoRequest as GetHlsVideoStream; if (hlsVideoRequest != null) { appendBaselineStream = hlsVideoRequest.AppendBaselineStream; baselineStreamBitrate = hlsVideoRequest.BaselineStreamAudioBitRate ?? baselineStreamBitrate; } var playlistText = GetMasterPlaylistFileText(playlist, videoBitrate + audioBitrate, appendBaselineStream, baselineStreamBitrate); try { return ResultFactory.GetResult(playlistText, MimeTypes.GetMimeType("playlist.m3u8"), new Dictionary<string, string>()); } finally { ApiEntryPoint.Instance.OnTranscodeEndRequest(playlist, TranscodingJobType.Hls); } }
/// <summary> /// Processes the request. /// </summary> /// <param name="request">The request.</param> /// <param name="isHeadRequest">if set to <c>true</c> [is head request].</param> /// <returns>Task.</returns> protected object ProcessRequest(StreamRequest request, bool isHeadRequest) { var state = GetState(request, CancellationToken.None).Result; var responseHeaders = new Dictionary<string, string>(); if (request.Static && state.IsRemote) { AddDlnaHeaders(state, responseHeaders, true); return GetStaticRemoteStreamResult(state.MediaPath, responseHeaders, isHeadRequest).Result; } var outputPath = GetOutputFilePath(state); var outputPathExists = File.Exists(outputPath); var isStatic = request.Static || (outputPathExists && !ApiEntryPoint.Instance.HasActiveTranscodingJob(outputPath, TranscodingJobType.Progressive)); AddDlnaHeaders(state, responseHeaders, isStatic); if (request.Static) { return ResultFactory.GetStaticFileResult(Request, state.MediaPath, FileShare.Read, responseHeaders, isHeadRequest); } if (outputPathExists && !ApiEntryPoint.Instance.HasActiveTranscodingJob(outputPath, TranscodingJobType.Progressive)) { return ResultFactory.GetStaticFileResult(Request, outputPath, FileShare.Read, responseHeaders, isHeadRequest); } return GetStreamResult(state, responseHeaders, isHeadRequest).Result; }
/// <summary> /// Processes the request. /// </summary> /// <param name="request">The request.</param> /// <param name="isHeadRequest">if set to <c>true</c> [is head request].</param> /// <returns>Task.</returns> protected async Task<object> ProcessRequest(StreamRequest request, bool isHeadRequest) { var cancellationTokenSource = new CancellationTokenSource(); var state = await GetState(request, cancellationTokenSource.Token).ConfigureAwait(false); var responseHeaders = new Dictionary<string, string>(); if (request.Static && state.DirectStreamProvider != null) { AddDlnaHeaders(state, responseHeaders, true); using (state) { var outputHeaders = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase); // TODO: Don't hardcode this outputHeaders["Content-Type"] = MediaBrowser.Model.Net.MimeTypes.GetMimeType("file.ts"); var streamSource = new ProgressiveFileCopier(state.DirectStreamProvider, outputHeaders, null, Logger, CancellationToken.None) { AllowEndOfFile = false }; return ResultFactory.GetAsyncStreamWriter(streamSource); } } // Static remote stream if (request.Static && state.InputProtocol == MediaProtocol.Http) { AddDlnaHeaders(state, responseHeaders, true); using (state) { return await GetStaticRemoteStreamResult(state, responseHeaders, isHeadRequest, cancellationTokenSource).ConfigureAwait(false); } } if (request.Static && state.InputProtocol != MediaProtocol.File) { throw new ArgumentException(string.Format("Input protocol {0} cannot be streamed statically.", state.InputProtocol)); } var outputPath = state.OutputFilePath; var outputPathExists = FileSystem.FileExists(outputPath); var transcodingJob = ApiEntryPoint.Instance.GetTranscodingJob(outputPath, TranscodingJobType.Progressive); var isTranscodeCached = outputPathExists && transcodingJob != null; AddDlnaHeaders(state, responseHeaders, request.Static || isTranscodeCached); // Static stream if (request.Static) { var contentType = state.GetMimeType(state.MediaPath); using (state) { if (state.MediaSource.IsInfiniteStream) { var outputHeaders = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase); outputHeaders["Content-Type"] = contentType; var streamSource = new ProgressiveFileCopier(FileSystem, state.MediaPath, outputHeaders, null, Logger, CancellationToken.None) { AllowEndOfFile = false }; return ResultFactory.GetAsyncStreamWriter(streamSource); } TimeSpan? cacheDuration = null; if (!string.IsNullOrEmpty(request.Tag)) { cacheDuration = TimeSpan.FromDays(365); } return await ResultFactory.GetStaticFileResult(Request, new StaticFileResultOptions { ResponseHeaders = responseHeaders, ContentType = contentType, IsHeadRequest = isHeadRequest, Path = state.MediaPath, CacheDuration = cacheDuration }).ConfigureAwait(false); } } //// Not static but transcode cache file exists //if (isTranscodeCached && state.VideoRequest == null) //{ // var contentType = state.GetMimeType(outputPath); // try // { // if (transcodingJob != null) // { // ApiEntryPoint.Instance.OnTranscodeBeginRequest(transcodingJob); // } // return await ResultFactory.GetStaticFileResult(Request, new StaticFileResultOptions // { // ResponseHeaders = responseHeaders, // ContentType = contentType, // IsHeadRequest = isHeadRequest, // Path = outputPath, // FileShare = FileShare.ReadWrite, // OnComplete = () => // { // if (transcodingJob != null) // { // ApiEntryPoint.Instance.OnTranscodeEndRequest(transcodingJob); // } // } // }).ConfigureAwait(false); // } // finally // { // state.Dispose(); // } //} // Need to start ffmpeg try { return await GetStreamResult(state, responseHeaders, isHeadRequest, cancellationTokenSource).ConfigureAwait(false); } catch { state.Dispose(); throw; } }
public StreamRequestHandler(IContainerManager containerManager, IJobManager jobManager, Request request) : base(containerManager, jobManager, request) { this.request = (StreamRequest)request; }
/// <summary> /// Processes the request. /// </summary> /// <param name="request">The request.</param> /// <param name="isHeadRequest">if set to <c>true</c> [is head request].</param> /// <returns>Task.</returns> protected object ProcessRequest(StreamRequest request, bool isHeadRequest) { var state = GetState(request); if (request.AlbumArt) { return GetAlbumArtResponse(state); } var responseHeaders = new Dictionary<string, string>(); if (request.Static && state.Item.LocationType == LocationType.Remote) { return GetStaticRemoteStreamResult(state.Item, responseHeaders, isHeadRequest).Result; } var outputPath = GetOutputFilePath(state); var outputPathExists = File.Exists(outputPath); //var isStatic = request.Static || // (outputPathExists && !ApiEntryPoint.Instance.HasActiveTranscodingJob(outputPath, TranscodingJobType.Progressive)); //AddDlnaHeaders(state, responseHeaders, isStatic); if (request.Static) { return ResultFactory.GetStaticFileResult(RequestContext, state.Item.Path, FileShare.Read, responseHeaders, isHeadRequest); } if (outputPathExists && !ApiEntryPoint.Instance.HasActiveTranscodingJob(outputPath, TranscodingJobType.Progressive)) { return ResultFactory.GetStaticFileResult(RequestContext, outputPath, FileShare.Read, responseHeaders, isHeadRequest); } return GetStreamResult(state, responseHeaders, isHeadRequest).Result; }
/// <summary> /// Processes the request. /// </summary> /// <param name="request">The request.</param> /// <returns>System.Object.</returns> protected object ProcessRequest(StreamRequest request) { var state = GetState(request, CancellationToken.None).Result; return ProcessRequestAsync(state).Result; }
/// <summary> /// Add a stream to the server /// </summary> internal uint AddStream(IClient client, Request request) { var streamClient = streamServer.Clients.Single (c => c.Guid == client.Guid); // Check for an existing stream for the request var procedure = KRPC.Service.Services.Instance.GetProcedureSignature (request); var arguments = KRPC.Service.Services.Instance.DecodeArguments (procedure, request); foreach (var streamRequest in streamRequests[streamClient]) { if (streamRequest.Procedure == procedure && streamRequest.Arguments.SequenceEqual (arguments)) return streamRequest.Identifier; } // Create a new stream { var streamRequest = new StreamRequest (request); streamRequests [streamClient].Add (streamRequest); return streamRequest.Identifier; } }
/// <summary> /// Processes the request. /// </summary> /// <param name="request">The request.</param> /// <param name="isLive">if set to <c>true</c> [is live].</param> /// <returns>System.Object.</returns> protected object ProcessRequest(StreamRequest request, bool isLive) { return ProcessRequestAsync(request, isLive).Result; }
private async Task<object> GetVariantPlaylistInternal(StreamRequest request, bool isOutputVideo, string name) { var state = await GetState(request, CancellationToken.None).ConfigureAwait(false); 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-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; while (seconds > 0) { var length = seconds >= state.SegmentLength ? state.SegmentLength : seconds; builder.AppendLine("#EXTINF:" + length.ToString(UsCulture) + ","); builder.AppendLine(string.Format("hlsdynamic/{0}/{1}{2}{3}", name, index.ToString(UsCulture), GetSegmentFileExtension(isOutputVideo), queryString)); seconds -= state.SegmentLength; index++; } builder.AppendLine("#EXT-X-ENDLIST"); var playlistText = builder.ToString(); return ResultFactory.GetResult(playlistText, MimeTypes.GetMimeType("playlist.m3u8"), new Dictionary<string, string>()); }
/// <summary> /// Processes the request async. /// </summary> /// <param name="request">The request.</param> /// <param name="isLive">if set to <c>true</c> [is live].</param> /// <returns>Task{System.Object}.</returns> /// <exception cref="ArgumentException">A video bitrate is required /// or /// An audio bitrate is required</exception> private async Task<object> ProcessRequestAsync(StreamRequest request, bool isLive) { var cancellationTokenSource = new CancellationTokenSource(); var state = await GetState(request, cancellationTokenSource.Token).ConfigureAwait(false); if (isLive) { state.Request.StartTimeTicks = null; } TranscodingJob job = null; var playlist = state.OutputFilePath; if (!FileSystem.FileExists(playlist)) { await ApiEntryPoint.Instance.TranscodingStartLock.WaitAsync(cancellationTokenSource.Token).ConfigureAwait(false); try { if (!FileSystem.FileExists(playlist)) { // If the playlist doesn't already exist, startup ffmpeg try { job = await StartFfMpeg(state, playlist, cancellationTokenSource).ConfigureAwait(false); job.IsLiveOutput = isLive; } catch { state.Dispose(); throw; } await WaitForMinimumSegmentCount(playlist, 3, cancellationTokenSource.Token).ConfigureAwait(false); } } finally { ApiEntryPoint.Instance.TranscodingStartLock.Release(); } } if (isLive) { job = job ?? ApiEntryPoint.Instance.OnTranscodeBeginRequest(playlist, TranscodingJobType); if (job != null) { ApiEntryPoint.Instance.OnTranscodeEndRequest(job); } return ResultFactory.GetResult(GetLivePlaylistText(playlist, state.SegmentLength), MimeTypes.GetMimeType("playlist.m3u8"), new Dictionary<string, string>()); } var audioBitrate = state.OutputAudioBitrate ?? 0; var videoBitrate = state.OutputVideoBitrate ?? 0; var appendBaselineStream = false; var baselineStreamBitrate = 64000; var hlsVideoRequest = state.VideoRequest as GetHlsVideoStreamLegacy; if (hlsVideoRequest != null) { appendBaselineStream = hlsVideoRequest.AppendBaselineStream; baselineStreamBitrate = hlsVideoRequest.BaselineStreamAudioBitRate ?? baselineStreamBitrate; } var playlistText = GetMasterPlaylistFileText(playlist, videoBitrate + audioBitrate, appendBaselineStream, baselineStreamBitrate); job = job ?? ApiEntryPoint.Instance.OnTranscodeBeginRequest(playlist, TranscodingJobType); if (job != null) { ApiEntryPoint.Instance.OnTranscodeEndRequest(job); } return ResultFactory.GetResult(playlistText, MimeTypes.GetMimeType("playlist.m3u8"), new Dictionary<string, string>()); }
/// <summary> /// Processes the request. /// </summary> /// <param name="request">The request.</param> /// <returns>System.Object.</returns> protected object ProcessRequest(StreamRequest request) { var state = GetState(request); return ProcessRequestAsync(state).Result; }
private async Task<object> GetDynamicSegment(StreamRequest request, string segmentId) { if ((request.StartTimeTicks ?? 0) > 0) { throw new ArgumentException("StartTimeTicks is not allowed."); } var cancellationTokenSource = new CancellationTokenSource(); var cancellationToken = cancellationTokenSource.Token; var requestedIndex = int.Parse(segmentId, NumberStyles.Integer, UsCulture); var state = await GetState(request, cancellationToken).ConfigureAwait(false); var playlistPath = Path.ChangeExtension(state.OutputFilePath, ".m3u8"); var segmentPath = GetSegmentPath(state, playlistPath, requestedIndex); var segmentExtension = GetSegmentFileExtension(state); TranscodingJob job = null; if (FileSystem.FileExists(segmentPath)) { job = ApiEntryPoint.Instance.OnTranscodeBeginRequest(playlistPath, TranscodingJobType); return await GetSegmentResult(state, playlistPath, segmentPath, requestedIndex, job, cancellationToken).ConfigureAwait(false); } await ApiEntryPoint.Instance.TranscodingStartLock.WaitAsync(cancellationTokenSource.Token).ConfigureAwait(false); var released = false; try { if (FileSystem.FileExists(segmentPath)) { job = ApiEntryPoint.Instance.OnTranscodeBeginRequest(playlistPath, TranscodingJobType); ApiEntryPoint.Instance.TranscodingStartLock.Release(); released = true; return await GetSegmentResult(state, playlistPath, segmentPath, requestedIndex, job, cancellationToken).ConfigureAwait(false); } else { var startTranscoding = false; var currentTranscodingIndex = GetCurrentTranscodingIndex(playlistPath, segmentExtension); var segmentGapRequiringTranscodingChange = 24 / state.SegmentLength; if (currentTranscodingIndex == null) { Logger.Debug("Starting transcoding because currentTranscodingIndex=null"); startTranscoding = true; } else if (requestedIndex < currentTranscodingIndex.Value) { Logger.Debug("Starting transcoding because requestedIndex={0} and currentTranscodingIndex={1}", requestedIndex, currentTranscodingIndex); startTranscoding = true; } else if ((requestedIndex - currentTranscodingIndex.Value) > segmentGapRequiringTranscodingChange) { Logger.Debug("Starting transcoding because segmentGap is {0} and max allowed gap is {1}. requestedIndex={2}", (requestedIndex - currentTranscodingIndex.Value), segmentGapRequiringTranscodingChange, requestedIndex); startTranscoding = true; } if (startTranscoding) { // If the playlist doesn't already exist, startup ffmpeg try { ApiEntryPoint.Instance.KillTranscodingJobs(request.DeviceId, request.PlaySessionId, p => false); if (currentTranscodingIndex.HasValue) { DeleteLastFile(playlistPath, segmentExtension, 0); } request.StartTimeTicks = GetStartPositionTicks(state, requestedIndex); job = await StartFfMpeg(state, playlistPath, cancellationTokenSource).ConfigureAwait(false); } catch { state.Dispose(); throw; } //await WaitForMinimumSegmentCount(playlistPath, 1, cancellationTokenSource.Token).ConfigureAwait(false); } else { job = ApiEntryPoint.Instance.OnTranscodeBeginRequest(playlistPath, TranscodingJobType); if (job.TranscodingThrottler != null) { job.TranscodingThrottler.UnpauseTranscoding(); } } } } finally { if (!released) { ApiEntryPoint.Instance.TranscodingStartLock.Release(); } } //Logger.Info("waiting for {0}", segmentPath); //while (!File.Exists(segmentPath)) //{ // await Task.Delay(50, cancellationToken).ConfigureAwait(false); //} Logger.Info("returning {0}", segmentPath); job = job ?? ApiEntryPoint.Instance.OnTranscodeBeginRequest(playlistPath, TranscodingJobType); return await GetSegmentResult(state, playlistPath, segmentPath, requestedIndex, job, cancellationToken).ConfigureAwait(false); }
/// <summary> /// Processes the request async. /// </summary> /// <param name="request">The request.</param> /// <returns>Task{System.Object}.</returns> /// <exception cref="ArgumentException"> /// A video bitrate is required /// or /// An audio bitrate is required /// </exception> private async Task<object> ProcessRequestAsync(StreamRequest request) { var state = GetState(request, CancellationToken.None).Result; if (!state.VideoRequest.VideoBitRate.HasValue && (string.IsNullOrEmpty(state.VideoRequest.VideoCodec) || !string.Equals(state.VideoRequest.VideoCodec, "copy", StringComparison.OrdinalIgnoreCase))) { state.Dispose(); throw new ArgumentException("A video bitrate is required"); } if (!state.Request.AudioBitRate.HasValue && (string.IsNullOrEmpty(state.Request.AudioCodec) || !string.Equals(state.Request.AudioCodec, "copy", StringComparison.OrdinalIgnoreCase))) { state.Dispose(); throw new ArgumentException("An audio bitrate is required"); } var playlist = GetOutputFilePath(state); if (File.Exists(playlist)) { ApiEntryPoint.Instance.OnTranscodeBeginRequest(playlist, TranscodingJobType.Hls); } else { await FfmpegStartLock.WaitAsync().ConfigureAwait(false); try { if (File.Exists(playlist)) { ApiEntryPoint.Instance.OnTranscodeBeginRequest(playlist, TranscodingJobType.Hls); } else { // If the playlist doesn't already exist, startup ffmpeg try { await StartFfMpeg(state, playlist).ConfigureAwait(false); } catch { state.Dispose(); throw; } } await WaitForMinimumSegmentCount(playlist, GetSegmentWait()).ConfigureAwait(false); } finally { FfmpegStartLock.Release(); } } int audioBitrate; int videoBitrate; GetPlaylistBitrates(state, out audioBitrate, out videoBitrate); var appendBaselineStream = false; var baselineStreamBitrate = 64000; var hlsVideoRequest = state.VideoRequest as GetHlsVideoStream; if (hlsVideoRequest != null) { appendBaselineStream = hlsVideoRequest.AppendBaselineStream; baselineStreamBitrate = hlsVideoRequest.BaselineStreamAudioBitRate ?? baselineStreamBitrate; } var playlistText = GetMasterPlaylistFileText(playlist, videoBitrate + audioBitrate, appendBaselineStream, baselineStreamBitrate); try { return ResultFactory.GetResult(playlistText, MimeTypes.GetMimeType("playlist.m3u8"), new Dictionary<string, string>()); } finally { ApiEntryPoint.Instance.OnTranscodeEndRequest(playlist, TranscodingJobType.Hls); } }
private async Task<object> GetVariantPlaylistInternal(StreamRequest request, bool isOutputVideo, string name) { 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:" + 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 index = 0; foreach (var length in segmentLengths) { builder.AppendLine("#EXTINF:" + length.ToString("0.0000", UsCulture) + ","); builder.AppendLine(string.Format("hls1/{0}/{1}{2}{3}", name, index.ToString(UsCulture), GetSegmentFileExtension(isOutputVideo), queryString)); index++; } builder.AppendLine("#EXT-X-ENDLIST"); var playlistText = builder.ToString(); return ResultFactory.GetResult(playlistText, MimeTypes.GetMimeType("playlist.m3u8"), new Dictionary<string, string>()); }
/// <summary> /// Processes the request. /// </summary> /// <param name="request">The request.</param> /// <param name="isHeadRequest">if set to <c>true</c> [is head request].</param> /// <returns>Task.</returns> protected object ProcessRequest(StreamRequest request, bool isHeadRequest) { var state = GetState(request, CancellationToken.None).Result; var responseHeaders = new Dictionary<string, string>(); // Static remote stream if (request.Static && state.IsRemote) { AddDlnaHeaders(state, responseHeaders, true); try { return GetStaticRemoteStreamResult(state.MediaPath, responseHeaders, isHeadRequest).Result; } finally { state.Dispose(); } } var outputPath = GetOutputFilePath(state); var outputPathExists = File.Exists(outputPath); var isStatic = request.Static || (outputPathExists && !ApiEntryPoint.Instance.HasActiveTranscodingJob(outputPath, TranscodingJobType.Progressive)); AddDlnaHeaders(state, responseHeaders, isStatic); // Static stream if (request.Static) { var contentType = state.GetMimeType(state.MediaPath); try { return ResultFactory.GetStaticFileResult(Request, state.MediaPath, contentType, FileShare.Read, responseHeaders, isHeadRequest); } finally { state.Dispose(); } } // Not static but transcode cache file exists if (outputPathExists && !ApiEntryPoint.Instance.HasActiveTranscodingJob(outputPath, TranscodingJobType.Progressive)) { var contentType = state.GetMimeType(outputPath); try { return ResultFactory.GetStaticFileResult(Request, outputPath, contentType, FileShare.Read, responseHeaders, isHeadRequest); } finally { state.Dispose(); } } // Need to start ffmpeg try { return GetStreamResult(state, responseHeaders, isHeadRequest).Result; } catch { state.Dispose(); throw; } }