/// <summary> /// Writes to async. /// </summary> /// <param name="responseStream">The response stream.</param> /// <returns>Task.</returns> private void WriteToInternal(Stream responseStream) { try { var task = new ProgressiveFileCopier(_fileSystem, _job, Logger).StreamFile(Path, responseStream); Task.WaitAll(task); } catch (IOException) { // These error are always the same so don't dump the whole stack trace Logger.Error("Error streaming media. The client has most likely disconnected or transcoding has failed."); throw; } catch (Exception ex) { Logger.ErrorException("Error streaming media. The client has most likely disconnected or transcoding has failed.", ex); throw; } finally { if (_job != null) { ApiEntryPoint.Instance.OnTranscodeEndRequest(_job); } } }
/// <summary> /// Gets the stream result. /// </summary> /// <param name="state">The state.</param> /// <param name="responseHeaders">The response headers.</param> /// <param name="isHeadRequest">if set to <c>true</c> [is head request].</param> /// <param name="cancellationTokenSource">The cancellation token source.</param> /// <returns>Task{System.Object}.</returns> private async Task <object> GetStreamResult(StreamState state, IDictionary <string, string> responseHeaders, bool isHeadRequest, CancellationTokenSource cancellationTokenSource) { // Use the command line args with a dummy playlist path var outputPath = state.OutputFilePath; responseHeaders["Accept-Ranges"] = "none"; var contentType = state.GetMimeType(outputPath); // TODO: The isHeadRequest is only here because ServiceStack will add Content-Length=0 to the response // What we really want to do is hunt that down and remove that var contentLength = state.EstimateContentLength || isHeadRequest?GetEstimatedContentLength(state) : null; if (contentLength.HasValue) { responseHeaders["Content-Length"] = contentLength.Value.ToString(UsCulture); } // Headers only if (isHeadRequest) { var streamResult = ResultFactory.GetResult(new byte[] { }, contentType, responseHeaders); var hasOptions = streamResult as IHasOptions; if (hasOptions != null) { if (contentLength.HasValue) { hasOptions.Options["Content-Length"] = contentLength.Value.ToString(CultureInfo.InvariantCulture); } else { if (hasOptions.Options.ContainsKey("Content-Length")) { hasOptions.Options.Remove("Content-Length"); } } } return(streamResult); } await ApiEntryPoint.Instance.TranscodingStartLock.WaitAsync(cancellationTokenSource.Token).ConfigureAwait(false); try { TranscodingJob job; if (!FileSystem.FileExists(outputPath)) { job = await StartFfMpeg(state, outputPath, cancellationTokenSource).ConfigureAwait(false); } else { job = ApiEntryPoint.Instance.OnTranscodeBeginRequest(outputPath, TranscodingJobType.Progressive); state.Dispose(); } var outputHeaders = new Dictionary <string, string>(StringComparer.OrdinalIgnoreCase); outputHeaders["Content-Type"] = contentType; // Add the response headers to the result object foreach (var item in responseHeaders) { outputHeaders[item.Key] = item.Value; } var streamSource = new ProgressiveFileCopier(FileSystem, outputPath, outputHeaders, job, Logger, CancellationToken.None); return(ResultFactory.GetAsyncStreamWriter(streamSource)); } finally { ApiEntryPoint.Instance.TranscodingStartLock.Release(); } }
/// <summary> /// Gets the stream result. /// </summary> /// <param name="state">The state.</param> /// <param name="responseHeaders">The response headers.</param> /// <param name="isHeadRequest">if set to <c>true</c> [is head request].</param> /// <param name="cancellationTokenSource">The cancellation token source.</param> /// <returns>Task{System.Object}.</returns> private async Task<object> GetStreamResult(StreamState state, IDictionary<string, string> responseHeaders, bool isHeadRequest, CancellationTokenSource cancellationTokenSource) { // Use the command line args with a dummy playlist path var outputPath = state.OutputFilePath; responseHeaders["Accept-Ranges"] = "none"; var contentType = state.GetMimeType(outputPath); // TODO: The isHeadRequest is only here because ServiceStack will add Content-Length=0 to the response // What we really want to do is hunt that down and remove that var contentLength = state.EstimateContentLength || isHeadRequest ? GetEstimatedContentLength(state) : null; if (contentLength.HasValue) { responseHeaders["Content-Length"] = contentLength.Value.ToString(UsCulture); } // Headers only if (isHeadRequest) { var streamResult = ResultFactory.GetResult(new byte[] { }, contentType, responseHeaders); var hasOptions = streamResult as IHasOptions; if (hasOptions != null) { if (contentLength.HasValue) { hasOptions.Options["Content-Length"] = contentLength.Value.ToString(CultureInfo.InvariantCulture); } else { if (hasOptions.Options.ContainsKey("Content-Length")) { hasOptions.Options.Remove("Content-Length"); } } } return streamResult; } var transcodingLock = ApiEntryPoint.Instance.GetTranscodingLock(outputPath); await transcodingLock.WaitAsync(cancellationTokenSource.Token).ConfigureAwait(false); try { TranscodingJob job; if (!FileSystem.FileExists(outputPath)) { job = await StartFfMpeg(state, outputPath, cancellationTokenSource).ConfigureAwait(false); } else { job = ApiEntryPoint.Instance.OnTranscodeBeginRequest(outputPath, TranscodingJobType.Progressive); state.Dispose(); } var outputHeaders = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase); outputHeaders["Content-Type"] = contentType; // Add the response headers to the result object foreach (var item in responseHeaders) { outputHeaders[item.Key] = item.Value; } var streamSource = new ProgressiveFileCopier(FileSystem, outputPath, outputHeaders, job, Logger, CancellationToken.None); return ResultFactory.GetAsyncStreamWriter(streamSource); } finally { transcodingLock.Release(); } }
/// <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; } }
/// <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; } }