/// <summary> /// Initializes a new instance of the <see cref="ProgressiveStreamWriter" /> class. /// </summary> /// <param name="path">The path.</param> /// <param name="logger">The logger.</param> /// <param name="fileSystem">The file system.</param> public ProgressiveStreamWriter(string path, ILogger logger, IFileSystem fileSystem, TranscodingJob job) { Path = path; Logger = logger; _fileSystem = fileSystem; _job = job; }
public ProgressiveFileCopier(IDirectStreamProvider directStreamProvider, Dictionary<string, string> outputHeaders, TranscodingJob job, ILogger logger, CancellationToken cancellationToken) { _directStreamProvider = directStreamProvider; _outputHeaders = outputHeaders; _job = job; _logger = logger; _cancellationToken = cancellationToken; }
public ProgressiveFileCopier(IFileSystem fileSystem, string path, Dictionary<string, string> outputHeaders, TranscodingJob job, ILogger logger, CancellationToken cancellationToken) { _fileSystem = fileSystem; _path = path; _outputHeaders = outputHeaders; _job = job; _logger = logger; _cancellationToken = cancellationToken; }
private async Task <object> GetSegmentResult(StreamState state, string playlistPath, string segmentPath, string segmentExtension, int segmentIndex, TranscodingJob transcodingJob, CancellationToken cancellationToken) { var segmentExists = File.Exists(segmentPath); if (segmentExists) { if (transcodingJob != null && transcodingJob.HasExited) { // Transcoding job is over, so assume all existing files are ready Logger.LogDebug("serving up {0} as transcode is over", segmentPath); return(await GetSegmentResult(state, segmentPath, segmentIndex, transcodingJob).ConfigureAwait(false)); } var currentTranscodingIndex = GetCurrentTranscodingIndex(playlistPath, segmentExtension); // If requested segment is less than transcoding position, we can't transcode backwards, so assume it's ready if (segmentIndex < currentTranscodingIndex) { Logger.LogDebug("serving up {0} as transcode index {1} is past requested point {2}", segmentPath, currentTranscodingIndex, segmentIndex); return(await GetSegmentResult(state, segmentPath, segmentIndex, transcodingJob).ConfigureAwait(false)); } } var nextSegmentPath = GetSegmentPath(state, playlistPath, segmentIndex + 1); if (transcodingJob != null) { while (!cancellationToken.IsCancellationRequested && !transcodingJob.HasExited) { // To be considered ready, the segment file has to exist AND // either the transcoding job should be done or next segment should also exist if (segmentExists) { if (transcodingJob.HasExited || File.Exists(nextSegmentPath)) { Logger.LogDebug("serving up {0} as it deemed ready", segmentPath); return(await GetSegmentResult(state, segmentPath, segmentIndex, transcodingJob).ConfigureAwait(false)); } } else { segmentExists = File.Exists(segmentPath); if (segmentExists) { continue; // avoid unnecessary waiting if segment just became available } } await Task.Delay(100, cancellationToken).ConfigureAwait(false); } if (!File.Exists(segmentPath)) { Logger.LogWarning("cannot serve {0} as transcoding quit before we got there", segmentPath); } else { Logger.LogDebug("serving {0} as it's on disk and transcoding stopped", segmentPath); } cancellationToken.ThrowIfCancellationRequested(); } else { Logger.LogWarning("cannot serve {0} as it doesn't exist and no transcode is running", segmentPath); } return(await GetSegmentResult(state, segmentPath, segmentIndex, transcodingJob).ConfigureAwait(false)); }
private async void 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 async Task <object> GetSegmentResult(StreamState state, string playlistPath, string segmentPath, int segmentIndex, TranscodingJob transcodingJob, CancellationToken cancellationToken) { // If all transcoding has completed, just return immediately if (transcodingJob != null && transcodingJob.HasExited && FileSystem.FileExists(segmentPath)) { return(GetSegmentResult(state, segmentPath, segmentIndex, transcodingJob)); } var segmentFilename = Path.GetFileName(segmentPath); while (!cancellationToken.IsCancellationRequested) { try { using (var fileStream = GetPlaylistFileStream(playlistPath)) { using (var reader = new StreamReader(fileStream, Encoding.UTF8, true, BufferSize)) { var text = await reader.ReadToEndAsync().ConfigureAwait(false); // If it appears in the playlist, it's done if (text.IndexOf(segmentFilename, StringComparison.OrdinalIgnoreCase) != -1) { if (FileSystem.FileExists(segmentPath)) { return(GetSegmentResult(state, segmentPath, segmentIndex, transcodingJob)); } //break; } } } } catch (IOException) { // May get an error if the file is locked } await Task.Delay(100, cancellationToken).ConfigureAwait(false); } // if a different file is encoding, it's done //var currentTranscodingIndex = GetCurrentTranscodingIndex(playlistPath); //if (currentTranscodingIndex > segmentIndex) //{ //return GetSegmentResult(segmentPath, segmentIndex); //} //// Wait for the file to stop being written to, then stream it //var length = new FileInfo(segmentPath).Length; //var eofCount = 0; //while (eofCount < 10) //{ // var info = new FileInfo(segmentPath); // if (!info.Exists) // { // break; // } // var newLength = info.Length; // if (newLength == length) // { // eofCount++; // } // else // { // eofCount = 0; // } // length = newLength; // await Task.Delay(100, cancellationToken).ConfigureAwait(false); //} cancellationToken.ThrowIfCancellationRequested(); return(GetSegmentResult(state, segmentPath, segmentIndex, transcodingJob)); }
private long ThrottleCallack(long currentBytesPerSecond, long bytesWritten, long originalBytesPerSecond, TranscodingJob job) { var bytesDownloaded = job.BytesDownloaded ?? 0; var transcodingPositionTicks = job.TranscodingPositionTicks ?? 0; var downloadPositionTicks = job.DownloadPositionTicks ?? 0; var path = job.Path; if (bytesDownloaded > 0 && transcodingPositionTicks > 0) { // Progressive Streaming - byte-based consideration try { var bytesTranscoded = job.BytesTranscoded ?? new FileInfo(path).Length; // Estimate the bytes the transcoder should be ahead double gapFactor = _gapLengthInTicks; gapFactor /= transcodingPositionTicks; var targetGap = bytesTranscoded * gapFactor; var gap = bytesTranscoded - bytesDownloaded; if (gap < targetGap) { //Logger.Debug("Not throttling transcoder gap {0} target gap {1} bytes downloaded {2}", gap, targetGap, bytesDownloaded); return 0; } //Logger.Debug("Throttling transcoder gap {0} target gap {1} bytes downloaded {2}", gap, targetGap, bytesDownloaded); } catch { //Logger.Error("Error getting output size"); } } else if (downloadPositionTicks > 0 && transcodingPositionTicks > 0) { // HLS - time-based consideration var targetGap = _gapLengthInTicks; var gap = transcodingPositionTicks - downloadPositionTicks; if (gap < targetGap) { //Logger.Debug("Not throttling transcoder gap {0} target gap {1}", gap, targetGap); return 0; } //Logger.Debug("Throttling transcoder gap {0} target gap {1}", gap, targetGap); } else { //Logger.Debug("No throttle data for " + path); } return originalBytesPerSecond; }
public ProgressiveFileCopier(IFileSystem fileSystem, TranscodingJob job) { _fileSystem = fileSystem; _job = job; }
private void StartThrottler(StreamState state, TranscodingJob transcodingJob) { if (EnableThrottling(state)) { transcodingJob.TranscodingThrottler = state.TranscodingThrottler = new TranscodingThrottler(transcodingJob, Logger, ServerConfigurationManager); state.TranscodingThrottler.Start(); } }
/// <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 (!File.Exists(playlist)) { await ApiEntryPoint.Instance.TranscodingStartLock.WaitAsync(cancellationTokenSource.Token).ConfigureAwait(false); try { if (!File.Exists(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>())); }
private long ThrottleCallack(long currentBytesPerSecond, long bytesWritten, long originalBytesPerSecond, TranscodingJob job) { var bytesDownloaded = job.BytesDownloaded ?? 0; var transcodingPositionTicks = job.TranscodingPositionTicks ?? 0; var downloadPositionTicks = job.DownloadPositionTicks ?? 0; var path = job.Path; if (bytesDownloaded > 0 && transcodingPositionTicks > 0) { // Progressive Streaming - byte-based consideration try { var bytesTranscoded = job.BytesTranscoded ?? new FileInfo(path).Length; // Estimate the bytes the transcoder should be ahead double gapFactor = _gapLengthInTicks; gapFactor /= transcodingPositionTicks; var targetGap = bytesTranscoded * gapFactor; var gap = bytesTranscoded - bytesDownloaded; if (gap < targetGap) { //Logger.Debug("Not throttling transcoder gap {0} target gap {1} bytes downloaded {2}", gap, targetGap, bytesDownloaded); return(0); } //Logger.Debug("Throttling transcoder gap {0} target gap {1} bytes downloaded {2}", gap, targetGap, bytesDownloaded); } catch { //Logger.Error("Error getting output size"); } } else if (downloadPositionTicks > 0 && transcodingPositionTicks > 0) { // HLS - time-based consideration var targetGap = _gapLengthInTicks; var gap = transcodingPositionTicks - downloadPositionTicks; if (gap < targetGap) { //Logger.Debug("Not throttling transcoder gap {0} target gap {1}", gap, targetGap); return(0); } //Logger.Debug("Throttling transcoder gap {0} target gap {1}", gap, targetGap); } else { //Logger.Debug("No throttle data for " + path); } return(originalBytesPerSecond); }
public TranscodingThrottler(TranscodingJob job, ILogger logger, IConfigurationManager config) { _job = job; _logger = logger; _config = config; }
private bool IsThrottleAllowed(TranscodingJob job, int thresholdSeconds) { var bytesDownloaded = job.BytesDownloaded ?? 0; var transcodingPositionTicks = job.TranscodingPositionTicks ?? 0; var downloadPositionTicks = job.DownloadPositionTicks ?? 0; var path = job.Path; var gapLengthInTicks = TimeSpan.FromSeconds(thresholdSeconds).Ticks; if (downloadPositionTicks > 0 && transcodingPositionTicks > 0) { // HLS - time-based consideration var targetGap = gapLengthInTicks; var gap = transcodingPositionTicks - downloadPositionTicks; if (gap < targetGap) { //_logger.Debug("Not throttling transcoder gap {0} target gap {1}", gap, targetGap); return false; } //_logger.Debug("Throttling transcoder gap {0} target gap {1}", gap, targetGap); return true; } if (bytesDownloaded > 0 && transcodingPositionTicks > 0) { // Progressive Streaming - byte-based consideration try { var bytesTranscoded = job.BytesTranscoded ?? new FileInfo(path).Length; // Estimate the bytes the transcoder should be ahead double gapFactor = gapLengthInTicks; gapFactor /= transcodingPositionTicks; var targetGap = bytesTranscoded * gapFactor; var gap = bytesTranscoded - bytesDownloaded; if (gap < targetGap) { //_logger.Debug("Not throttling transcoder gap {0} target gap {1} bytes downloaded {2}", gap, targetGap, bytesDownloaded); return false; } //_logger.Debug("Throttling transcoder gap {0} target gap {1} bytes downloaded {2}", gap, targetGap, bytesDownloaded); return true; } catch { //_logger.Error("Error getting output size"); return false; } } //_logger.Debug("No throttle data for " + path); return false; }
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); } }
private void StartThrottler(StreamState state, TranscodingJob transcodingJob) { if (EnableThrottling(state) && !string.Equals(state.OutputVideoCodec, "copy", StringComparison.OrdinalIgnoreCase)) { transcodingJob.TranscodingThrottler = state.TranscodingThrottler = new TranscodingThrottler(transcodingJob, Logger, ServerConfigurationManager); state.TranscodingThrottler.Start(); } }
private async Task <object> GetDynamicSegment(VideoStreamRequest 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(playlistPath, requestedIndex); var segmentLength = state.SegmentLength; var segmentExtension = GetSegmentFileExtension(state); TranscodingJob job = null; if (File.Exists(segmentPath)) { job = ApiEntryPoint.Instance.GetTranscodingJob(playlistPath, TranscodingJobType); return(await GetSegmentResult(playlistPath, segmentPath, requestedIndex, segmentLength, job, cancellationToken).ConfigureAwait(false)); } await ApiEntryPoint.Instance.TranscodingStartLock.WaitAsync(cancellationTokenSource.Token).ConfigureAwait(false); try { if (File.Exists(segmentPath)) { job = ApiEntryPoint.Instance.GetTranscodingJob(playlistPath, TranscodingJobType); return(await GetSegmentResult(playlistPath, segmentPath, requestedIndex, segmentLength, job, cancellationToken).ConfigureAwait(false)); } else { var currentTranscodingIndex = GetCurrentTranscodingIndex(playlistPath, segmentExtension); var segmentGapRequiringTranscodingChange = 24 / state.SegmentLength; if (currentTranscodingIndex == null || requestedIndex < currentTranscodingIndex.Value || (requestedIndex - currentTranscodingIndex.Value) > segmentGapRequiringTranscodingChange) { // If the playlist doesn't already exist, startup ffmpeg try { ApiEntryPoint.Instance.KillTranscodingJobs(request.DeviceId, request.StreamId ?? request.ClientTime, p => false); if (currentTranscodingIndex.HasValue) { DeleteLastFile(playlistPath, segmentExtension, 0); } request.StartTimeTicks = GetSeekPositionTicks(state, requestedIndex); job = await StartFfMpeg(state, playlistPath, cancellationTokenSource).ConfigureAwait(false); } catch { state.Dispose(); throw; } await WaitForMinimumSegmentCount(playlistPath, 1, cancellationTokenSource.Token).ConfigureAwait(false); } } } finally { 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.GetTranscodingJob(playlistPath, TranscodingJobType); return(await GetSegmentResult(playlistPath, segmentPath, requestedIndex, segmentLength, job, cancellationToken).ConfigureAwait(false)); }
private async Task<object> GetSegmentResult(string playlistPath, string segmentPath, int segmentIndex, int segmentLength, TranscodingJob transcodingJob, CancellationToken cancellationToken) { // If all transcoding has completed, just return immediately if (!IsTranscoding(playlistPath)) { return GetSegmentResult(segmentPath, segmentIndex, segmentLength, transcodingJob); } var segmentFilename = Path.GetFileName(segmentPath); using (var fileStream = FileSystem.GetFileStream(playlistPath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite, true)) { using (var reader = new StreamReader(fileStream)) { var text = await reader.ReadToEndAsync().ConfigureAwait(false); // If it appears in the playlist, it's done if (text.IndexOf(segmentFilename, StringComparison.OrdinalIgnoreCase) != -1) { return GetSegmentResult(segmentPath, segmentIndex, segmentLength, transcodingJob); } } } // if a different file is encoding, it's done //var currentTranscodingIndex = GetCurrentTranscodingIndex(playlistPath); //if (currentTranscodingIndex > segmentIndex) //{ //return GetSegmentResult(segmentPath, segmentIndex); //} // Wait for the file to stop being written to, then stream it var length = new FileInfo(segmentPath).Length; var eofCount = 0; while (eofCount < 10) { var info = new FileInfo(segmentPath); if (!info.Exists) { break; } var newLength = info.Length; if (newLength == length) { eofCount++; } else { eofCount = 0; } length = newLength; await Task.Delay(100, cancellationToken).ConfigureAwait(false); } return GetSegmentResult(segmentPath, segmentIndex, segmentLength, transcodingJob); }
public ProgressiveFileCopier(IFileSystem fileSystem, string path, Dictionary <string, string> outputHeaders, TranscodingJob job, ILogger logger, CancellationToken cancellationToken) { _fileSystem = fileSystem; _path = path; _outputHeaders = outputHeaders; _job = job; _logger = logger; _cancellationToken = cancellationToken; }
private async Task<object> GetSegmentResult(StreamState state, string playlistPath, string segmentPath, int segmentIndex, TranscodingJob transcodingJob, CancellationToken cancellationToken) { // If all transcoding has completed, just return immediately if (transcodingJob != null && transcodingJob.HasExited && FileSystem.FileExists(segmentPath)) { return GetSegmentResult(state, segmentPath, segmentIndex, transcodingJob); } var segmentFilename = Path.GetFileName(segmentPath); while (!cancellationToken.IsCancellationRequested) { try { using (var fileStream = GetPlaylistFileStream(playlistPath)) { using (var reader = new StreamReader(fileStream, Encoding.UTF8, true, BufferSize)) { var text = await reader.ReadToEndAsync().ConfigureAwait(false); // If it appears in the playlist, it's done if (text.IndexOf(segmentFilename, StringComparison.OrdinalIgnoreCase) != -1) { if (FileSystem.FileExists(segmentPath)) { return GetSegmentResult(state, segmentPath, segmentIndex, transcodingJob); } //break; } } } } catch (IOException) { // May get an error if the file is locked } await Task.Delay(100, cancellationToken).ConfigureAwait(false); } cancellationToken.ThrowIfCancellationRequested(); return GetSegmentResult(state, segmentPath, segmentIndex, transcodingJob); }
public ProgressiveFileCopier(IDirectStreamProvider directStreamProvider, Dictionary <string, string> outputHeaders, TranscodingJob job, ILogger logger, CancellationToken cancellationToken) { _directStreamProvider = directStreamProvider; _outputHeaders = outputHeaders; _job = job; _logger = logger; _cancellationToken = cancellationToken; }
/// <summary> /// Gets the state. /// </summary> /// <param name="request">The request.</param> /// <param name="cancellationToken">The cancellation token.</param> /// <returns>StreamState.</returns> protected async Task <StreamState> GetState(StreamRequest request, CancellationToken cancellationToken) { ParseDlnaHeaders(request); if (!string.IsNullOrWhiteSpace(request.Params)) { ParseParams(request); } var url = Request.PathInfo; if (string.IsNullOrEmpty(request.AudioCodec)) { request.AudioCodec = EncodingHelper.InferAudioCodec(url); } var enableDlnaHeaders = !string.IsNullOrWhiteSpace(request.Params) /*|| * string.Equals(Request.Headers.Get("GetContentFeatures.DLNA.ORG"), "1", StringComparison.OrdinalIgnoreCase)*/; var state = new StreamState(MediaSourceManager, Logger, TranscodingJobType) { Request = request, RequestedUrl = url, UserAgent = Request.UserAgent, EnableDlnaHeaders = enableDlnaHeaders }; var auth = AuthorizationContext.GetAuthorizationInfo(Request); if (!string.IsNullOrWhiteSpace(auth.UserId)) { state.User = UserManager.GetUserById(auth.UserId); } //if ((Request.UserAgent ?? string.Empty).IndexOf("iphone", StringComparison.OrdinalIgnoreCase) != -1 || // (Request.UserAgent ?? string.Empty).IndexOf("ipad", StringComparison.OrdinalIgnoreCase) != -1 || // (Request.UserAgent ?? string.Empty).IndexOf("ipod", StringComparison.OrdinalIgnoreCase) != -1) //{ // state.SegmentLength = 6; //} if (state.VideoRequest != null) { if (!string.IsNullOrWhiteSpace(state.VideoRequest.VideoCodec)) { state.SupportedVideoCodecs = state.VideoRequest.VideoCodec.Split(',').Where(i => !string.IsNullOrWhiteSpace(i)).ToList(); state.VideoRequest.VideoCodec = state.SupportedVideoCodecs.FirstOrDefault(); } } if (!string.IsNullOrWhiteSpace(request.AudioCodec)) { state.SupportedAudioCodecs = request.AudioCodec.Split(',').Where(i => !string.IsNullOrWhiteSpace(i)).ToList(); state.Request.AudioCodec = state.SupportedAudioCodecs.FirstOrDefault(i => MediaEncoder.CanEncodeToAudioCodec(i)) ?? state.SupportedAudioCodecs.FirstOrDefault(); } if (!string.IsNullOrWhiteSpace(request.SubtitleCodec)) { state.SupportedSubtitleCodecs = request.SubtitleCodec.Split(',').Where(i => !string.IsNullOrWhiteSpace(i)).ToList(); state.Request.SubtitleCodec = state.SupportedSubtitleCodecs.FirstOrDefault(i => MediaEncoder.CanEncodeToSubtitleCodec(i)) ?? state.SupportedSubtitleCodecs.FirstOrDefault(); } var item = LibraryManager.GetItemById(request.Id); state.IsInputVideo = string.Equals(item.MediaType, MediaType.Video, StringComparison.OrdinalIgnoreCase); //var primaryImage = item.GetImageInfo(ImageType.Primary, 0) ?? // item.Parents.Select(i => i.GetImageInfo(ImageType.Primary, 0)).FirstOrDefault(i => i != null); //if (primaryImage != null) //{ // state.AlbumCoverPath = primaryImage.Path; //} MediaSourceInfo mediaSource = null; if (string.IsNullOrWhiteSpace(request.LiveStreamId)) { TranscodingJob currentJob = !string.IsNullOrWhiteSpace(request.PlaySessionId) ? ApiEntryPoint.Instance.GetTranscodingJob(request.PlaySessionId) : null; if (currentJob != null) { mediaSource = currentJob.MediaSource; } if (mediaSource == null) { var mediaSources = (await MediaSourceManager.GetPlayackMediaSources(request.Id, null, false, new[] { MediaType.Audio, MediaType.Video }, cancellationToken).ConfigureAwait(false)).ToList(); mediaSource = string.IsNullOrEmpty(request.MediaSourceId) ? mediaSources.First() : mediaSources.FirstOrDefault(i => string.Equals(i.Id, request.MediaSourceId)); if (mediaSource == null && string.Equals(request.Id, request.MediaSourceId, StringComparison.OrdinalIgnoreCase)) { mediaSource = mediaSources.First(); } } } else { var liveStreamInfo = await MediaSourceManager.GetLiveStreamWithDirectStreamProvider(request.LiveStreamId, cancellationToken).ConfigureAwait(false); mediaSource = liveStreamInfo.Item1; state.DirectStreamProvider = liveStreamInfo.Item2; } var videoRequest = request as VideoStreamRequest; EncodingHelper.AttachMediaSourceInfo(state, mediaSource, url); var container = Path.GetExtension(state.RequestedUrl); if (string.IsNullOrEmpty(container)) { container = request.Container; } if (string.IsNullOrEmpty(container)) { container = request.Static ? state.InputContainer : GetOutputFileExtension(state); } state.OutputContainer = (container ?? string.Empty).TrimStart('.'); state.OutputAudioBitrate = EncodingHelper.GetAudioBitrateParam(state.Request, state.AudioStream); state.OutputAudioCodec = state.Request.AudioCodec; state.OutputAudioChannels = EncodingHelper.GetNumAudioChannelsParam(state.Request, state.AudioStream, state.OutputAudioCodec); if (videoRequest != null) { state.OutputVideoCodec = state.VideoRequest.VideoCodec; state.OutputVideoBitrate = EncodingHelper.GetVideoBitrateParamValue(state.VideoRequest, state.VideoStream, state.OutputVideoCodec); if (videoRequest != null) { EncodingHelper.TryStreamCopy(state); } if (state.OutputVideoBitrate.HasValue && !string.Equals(state.OutputVideoCodec, "copy", StringComparison.OrdinalIgnoreCase)) { var resolution = ResolutionNormalizer.Normalize( state.VideoStream == null ? (int?)null : state.VideoStream.BitRate, state.OutputVideoBitrate.Value, state.VideoStream == null ? null : state.VideoStream.Codec, state.OutputVideoCodec, videoRequest.MaxWidth, videoRequest.MaxHeight); videoRequest.MaxWidth = resolution.MaxWidth; videoRequest.MaxHeight = resolution.MaxHeight; } ApplyDeviceProfileSettings(state); } else { ApplyDeviceProfileSettings(state); } var ext = string.IsNullOrWhiteSpace(state.OutputContainer) ? GetOutputFileExtension(state) : ("." + state.OutputContainer); state.OutputFilePath = GetOutputFilePath(state, ext); return(state); }
public ProgressiveFileCopier(IFileSystem fileSystem, TranscodingJob job, ILogger logger) { _fileSystem = fileSystem; _job = job; _logger = logger; }
private async Task<object> GetSegmentResult(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) { return GetSegmentResult(segmentPath, segmentIndex, segmentLength, transcodingJob); } // Wait for the file to stop being written to, then stream it var length = new FileInfo(segmentPath).Length; var eofCount = 0; while (eofCount < 10) { var info = new FileInfo(segmentPath); if (!info.Exists) { break; } var newLength = info.Length; if (newLength == length) { eofCount++; } else { eofCount = 0; } length = newLength; await Task.Delay(100, cancellationToken).ConfigureAwait(false); } return GetSegmentResult(segmentPath, segmentIndex, segmentLength, transcodingJob); }
/// <summary> /// Processes the exited. /// </summary> /// <param name="process">The process.</param> /// <param name="job">The job.</param> /// <param name="state">The state.</param> private void OnFfMpegProcessExited(Process process, TranscodingJob job, StreamState state) { if (job != null) { job.HasExited = true; } Logger.Debug("Disposing stream resources"); state.Dispose(); try { Logger.Info("FFMpeg exited with code {0}", process.ExitCode); } catch { Logger.Error("FFMpeg exited with an error."); } // This causes on exited to be called twice: //try //{ // // Dispose the process // process.Dispose(); //} //catch (Exception ex) //{ // Logger.ErrorException("Error disposing ffmpeg.", ex); //} }
private void StartThrottler(StreamState state, TranscodingJob transcodingJob) { if (EnableThrottling(state) && state.InputProtocol == MediaProtocol.File && state.RunTimeTicks.HasValue && state.VideoType == VideoType.VideoFile && !string.Equals(state.OutputVideoCodec, "copy", StringComparison.OrdinalIgnoreCase)) { if (state.RunTimeTicks.Value >= TimeSpan.FromMinutes(5).Ticks && state.IsInputVideo) { transcodingJob.TranscodingThrottler = state.TranscodingThrottler = new TranscodingThrottler(transcodingJob, Logger, ServerConfigurationManager); state.TranscodingThrottler.Start(); } } }
private Task <object> GetSegmentResult(StreamState state, string segmentPath, int index, TranscodingJob transcodingJob) { var segmentEndingPositionTicks = GetEndPositionTicks(state, index); return(ResultFactory.GetStaticFileResult(Request, new StaticFileResultOptions { Path = segmentPath, FileShare = FileShareMode.ReadWrite, OnComplete = () => { if (transcodingJob != null) { transcodingJob.DownloadPositionTicks = Math.Max(transcodingJob.DownloadPositionTicks ?? segmentEndingPositionTicks, segmentEndingPositionTicks); ApiEntryPoint.Instance.OnTranscodeEndRequest(transcodingJob); } } })); }
private void ParseLogLine(string line, TranscodingJob transcodingJob, StreamState state) { float? framerate = null; double? percent = null; TimeSpan? transcodingPosition = null; long? bytesTranscoded = 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; } } } } if (framerate.HasValue || percent.HasValue) { ApiEntryPoint.Instance.ReportTranscodingProgress(transcodingJob, state, transcodingPosition, framerate, percent, bytesTranscoded); } }
private async Task <object> GetDynamicSegment(VideoStreamRequest request, string segmentId, string representationId) { if ((request.StartTimeTicks ?? 0) > 0) { throw new ArgumentException("StartTimeTicks is not allowed."); } var cancellationTokenSource = new CancellationTokenSource(); var cancellationToken = cancellationTokenSource.Token; var requestedIndex = string.Equals(segmentId, "init", StringComparison.OrdinalIgnoreCase) ? -1 : int.Parse(segmentId, NumberStyles.Integer, UsCulture); var state = await GetState(request, cancellationToken).ConfigureAwait(false); var playlistPath = Path.ChangeExtension(state.OutputFilePath, ".mpd"); var segmentExtension = GetSegmentFileExtension(state); var segmentPath = FindSegment(playlistPath, representationId, segmentExtension, requestedIndex); var segmentLength = state.SegmentLength; TranscodingJob job = null; if (!string.IsNullOrWhiteSpace(segmentPath)) { job = ApiEntryPoint.Instance.GetTranscodingJob(playlistPath, TranscodingJobType); return(await GetSegmentResult(playlistPath, segmentPath, requestedIndex, segmentLength, job, cancellationToken).ConfigureAwait(false)); } await ApiEntryPoint.Instance.TranscodingStartLock.WaitAsync(cancellationTokenSource.Token).ConfigureAwait(false); try { segmentPath = FindSegment(playlistPath, representationId, segmentExtension, requestedIndex); if (!string.IsNullOrWhiteSpace(segmentPath)) { job = ApiEntryPoint.Instance.GetTranscodingJob(playlistPath, TranscodingJobType); return(await GetSegmentResult(playlistPath, segmentPath, requestedIndex, segmentLength, job, cancellationToken).ConfigureAwait(false)); } else { if (string.Equals(representationId, "0", StringComparison.OrdinalIgnoreCase)) { job = ApiEntryPoint.Instance.GetTranscodingJob(playlistPath, TranscodingJobType); var currentTranscodingIndex = GetCurrentTranscodingIndex(playlistPath, segmentExtension); var segmentGapRequiringTranscodingChange = 24 / state.SegmentLength; Logger.Debug("Current transcoding index is {0}. requestedIndex={1}. segmentGapRequiringTranscodingChange={2}", currentTranscodingIndex ?? -2, requestedIndex, segmentGapRequiringTranscodingChange); if (currentTranscodingIndex == null || requestedIndex < currentTranscodingIndex.Value || (requestedIndex - currentTranscodingIndex.Value) > segmentGapRequiringTranscodingChange) { // If the playlist doesn't already exist, startup ffmpeg try { ApiEntryPoint.Instance.KillTranscodingJobs(request.DeviceId, request.PlaySessionId, p => false); if (currentTranscodingIndex.HasValue) { DeleteLastTranscodedFiles(playlistPath, 0); } var positionTicks = GetPositionTicks(state, requestedIndex); request.StartTimeTicks = positionTicks; var startNumber = GetStartNumber(state); var workingDirectory = Path.Combine(Path.GetDirectoryName(playlistPath), (startNumber == -1 ? 0 : startNumber).ToString(CultureInfo.InvariantCulture)); state.WaitForPath = Path.Combine(workingDirectory, Path.GetFileName(playlistPath)); Directory.CreateDirectory(workingDirectory); job = await StartFfMpeg(state, playlistPath, cancellationTokenSource, workingDirectory).ConfigureAwait(false); await WaitForMinimumDashSegmentCount(Path.Combine(workingDirectory, Path.GetFileName(playlistPath)), 1, cancellationTokenSource.Token).ConfigureAwait(false); } catch { state.Dispose(); throw; } } } } } finally { ApiEntryPoint.Instance.TranscodingStartLock.Release(); } while (string.IsNullOrWhiteSpace(segmentPath)) { segmentPath = FindSegment(playlistPath, representationId, segmentExtension, requestedIndex); await Task.Delay(50, cancellationToken).ConfigureAwait(false); } Logger.Info("returning {0}", segmentPath); return(await GetSegmentResult(playlistPath, segmentPath, requestedIndex, segmentLength, job ?? ApiEntryPoint.Instance.GetTranscodingJob(playlistPath, TranscodingJobType), cancellationToken).ConfigureAwait(false)); }
private async Task <object> GetDynamicSegment(VideoStreamRequest request, string segmentId) { if ((request.StartTimeTicks ?? 0) > 0) { throw new ArgumentException("StartTimeTicks is not allowed."); } var cancellationTokenSource = new CancellationTokenSource(); var cancellationToken = cancellationTokenSource.Token; var index = int.Parse(segmentId, NumberStyles.Integer, UsCulture); var state = await GetState(request, cancellationToken).ConfigureAwait(false); var playlistPath = Path.ChangeExtension(state.OutputFilePath, ".m3u8"); var segmentExtension = GetSegmentFileExtension(state); var segmentPath = GetSegmentPath(playlistPath, segmentExtension, index); var segmentLength = state.SegmentLength; TranscodingJob job = null; if (File.Exists(segmentPath)) { return(await GetSegmentResult(playlistPath, segmentPath, index, segmentLength, job, cancellationToken).ConfigureAwait(false)); } await ApiEntryPoint.Instance.TranscodingStartLock.WaitAsync(cancellationTokenSource.Token).ConfigureAwait(false); try { if (File.Exists(segmentPath)) { return(await GetSegmentResult(playlistPath, segmentPath, index, segmentLength, job, cancellationToken).ConfigureAwait(false)); } else { var currentTranscodingIndex = GetCurrentTranscodingIndex(playlistPath, segmentExtension); if (currentTranscodingIndex == null || index < currentTranscodingIndex.Value || (index - currentTranscodingIndex.Value) > 4) { // If the playlist doesn't already exist, startup ffmpeg try { ApiEntryPoint.Instance.KillTranscodingJobs(j => j.Type == TranscodingJobType && string.Equals(j.DeviceId, request.DeviceId, StringComparison.OrdinalIgnoreCase), p => !string.Equals(p, playlistPath, StringComparison.OrdinalIgnoreCase)); if (currentTranscodingIndex.HasValue) { DeleteLastFile(playlistPath, segmentExtension, 0); } var startSeconds = index * state.SegmentLength; request.StartTimeTicks = TimeSpan.FromSeconds(startSeconds).Ticks; job = await StartFfMpeg(state, playlistPath, cancellationTokenSource, Path.GetDirectoryName(playlistPath)).ConfigureAwait(false); } catch { state.Dispose(); throw; } await WaitForMinimumSegmentCount(playlistPath, 2, cancellationTokenSource.Token).ConfigureAwait(false); } } } finally { 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.GetTranscodingJob(playlistPath, TranscodingJobType); return(await GetSegmentResult(playlistPath, segmentPath, index, segmentLength, job, cancellationToken).ConfigureAwait(false)); }
private async Task <object> GetSegmentResult(StreamState state, string playlistPath, string segmentPath, string segmentExtension, int segmentIndex, TranscodingJob transcodingJob, CancellationToken cancellationToken) { var segmentFileExists = FileSystem.FileExists(segmentPath); // If all transcoding has completed, just return immediately if (transcodingJob != null && transcodingJob.HasExited && segmentFileExists) { return(await GetSegmentResult(state, segmentPath, segmentIndex, transcodingJob).ConfigureAwait(false)); } if (segmentFileExists) { var currentTranscodingIndex = GetCurrentTranscodingIndex(playlistPath, segmentExtension); // If requested segment is less than transcoding position, we can't transcode backwards, so assume it's ready if (segmentIndex < currentTranscodingIndex) { return(await GetSegmentResult(state, segmentPath, segmentIndex, transcodingJob).ConfigureAwait(false)); } } var segmentFilename = Path.GetFileName(segmentPath); while (!cancellationToken.IsCancellationRequested) { try { using (var fileStream = GetPlaylistFileStream(playlistPath)) { using (var reader = new StreamReader(fileStream, Encoding.UTF8, true, BufferSize)) { var text = await reader.ReadToEndAsync().ConfigureAwait(false); // If it appears in the playlist, it's done if (text.IndexOf(segmentFilename, StringComparison.OrdinalIgnoreCase) != -1) { if (!segmentFileExists) { segmentFileExists = FileSystem.FileExists(segmentPath); } if (segmentFileExists) { return(await GetSegmentResult(state, segmentPath, segmentIndex, transcodingJob).ConfigureAwait(false)); } //break; } } } } catch (IOException) { // May get an error if the file is locked } await Task.Delay(100, cancellationToken).ConfigureAwait(false); } cancellationToken.ThrowIfCancellationRequested(); return(await GetSegmentResult(state, segmentPath, segmentIndex, transcodingJob).ConfigureAwait(false)); }
private async Task <object> GetSegmentResult(string playlistPath, string segmentPath, int segmentIndex, int segmentLength, TranscodingJob transcodingJob, CancellationToken cancellationToken) { // If all transcoding has completed, just return immediately if (!IsTranscoding(playlistPath)) { return(GetSegmentResult(segmentPath, segmentIndex, segmentLength, transcodingJob)); } var segmentFilename = Path.GetFileName(segmentPath); using (var fileStream = FileSystem.GetFileStream(playlistPath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite, true)) { using (var reader = new StreamReader(fileStream)) { var text = await reader.ReadToEndAsync().ConfigureAwait(false); // If it appears in the playlist, it's done if (text.IndexOf(segmentFilename, StringComparison.OrdinalIgnoreCase) != -1) { return(GetSegmentResult(segmentPath, segmentIndex, segmentLength, transcodingJob)); } } } // if a different file is encoding, it's done //var currentTranscodingIndex = GetCurrentTranscodingIndex(playlistPath); //if (currentTranscodingIndex > segmentIndex) //{ //return GetSegmentResult(segmentPath, segmentIndex); //} // Wait for the file to stop being written to, then stream it var length = new FileInfo(segmentPath).Length; var eofCount = 0; while (eofCount < 10) { var info = new FileInfo(segmentPath); if (!info.Exists) { break; } var newLength = info.Length; if (newLength == length) { eofCount++; } else { eofCount = 0; } length = newLength; await Task.Delay(100, cancellationToken).ConfigureAwait(false); } return(GetSegmentResult(segmentPath, segmentIndex, segmentLength, transcodingJob)); }
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, CultureInfo.InvariantCulture); 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.Request); TranscodingJob job = null; if (File.Exists(segmentPath)) { job = ApiEntryPoint.Instance.OnTranscodeBeginRequest(playlistPath, TranscodingJobType); Logger.LogDebug("returning {0} [it exists, try 1]", segmentPath); return(await GetSegmentResult(state, playlistPath, segmentPath, segmentExtension, requestedIndex, job, cancellationToken).ConfigureAwait(false)); } var transcodingLock = ApiEntryPoint.Instance.GetTranscodingLock(playlistPath); await transcodingLock.WaitAsync(cancellationTokenSource.Token).ConfigureAwait(false); var released = false; var startTranscoding = false; try { if (File.Exists(segmentPath)) { job = ApiEntryPoint.Instance.OnTranscodeBeginRequest(playlistPath, TranscodingJobType); transcodingLock.Release(); released = true; Logger.LogDebug("returning {0} [it exists, try 2]", segmentPath); return(await GetSegmentResult(state, playlistPath, segmentPath, segmentExtension, requestedIndex, job, cancellationToken).ConfigureAwait(false)); } else { var currentTranscodingIndex = GetCurrentTranscodingIndex(playlistPath, segmentExtension); var segmentGapRequiringTranscodingChange = 24 / state.SegmentLength; if (currentTranscodingIndex == null) { Logger.LogDebug("Starting transcoding because currentTranscodingIndex=null"); startTranscoding = true; } else if (requestedIndex < currentTranscodingIndex.Value) { Logger.LogDebug("Starting transcoding because requestedIndex={0} and currentTranscodingIndex={1}", requestedIndex, currentTranscodingIndex); startTranscoding = true; } else if (requestedIndex - currentTranscodingIndex.Value > segmentGapRequiringTranscodingChange) { Logger.LogDebug("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 { await ApiEntryPoint.Instance.KillTranscodingJobs(request.DeviceId, request.PlaySessionId, p => false); if (currentTranscodingIndex.HasValue) { DeleteLastFile(playlistPath, segmentExtension, 0); } request.StartTimeTicks = GetStartPositionTicks(state, requestedIndex); state.WaitForPath = segmentPath; 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) { await job.TranscodingThrottler.UnpauseTranscoding(); } } } } finally { if (!released) { transcodingLock.Release(); } } // Logger.LogInformation("waiting for {0}", segmentPath); // while (!File.Exists(segmentPath)) //{ // await Task.Delay(50, cancellationToken).ConfigureAwait(false); //} Logger.LogDebug("returning {0} [general case]", segmentPath); job ??= ApiEntryPoint.Instance.OnTranscodeBeginRequest(playlistPath, TranscodingJobType); return(await GetSegmentResult(state, playlistPath, segmentPath, segmentExtension, requestedIndex, job, cancellationToken).ConfigureAwait(false)); }
private object GetSegmentResult(string segmentPath, int index, int segmentLength, TranscodingJob transcodingJob) { var segmentEndingSeconds = (1 + index) * segmentLength; var segmentEndingPositionTicks = TimeSpan.FromSeconds(segmentEndingSeconds).Ticks; return(ResultFactory.GetStaticFileResult(Request, new StaticFileResultOptions { Path = segmentPath, FileShare = FileShare.ReadWrite, OnComplete = () => { if (transcodingJob != null) { transcodingJob.DownloadPositionTicks = Math.Max(transcodingJob.DownloadPositionTicks ?? segmentEndingPositionTicks, segmentEndingPositionTicks); } } })); }
private async Task<object> GetSegmentResult(StreamState state, string playlistPath, string segmentPath, int segmentIndex, TranscodingJob transcodingJob, CancellationToken cancellationToken) { // If all transcoding has completed, just return immediately if (transcodingJob != null && transcodingJob.HasExited && FileSystem.FileExists(segmentPath)) { return GetSegmentResult(state, segmentPath, segmentIndex, transcodingJob); } var segmentFilename = Path.GetFileName(segmentPath); while (!cancellationToken.IsCancellationRequested) { try { using (var fileStream = GetPlaylistFileStream(playlistPath)) { using (var reader = new StreamReader(fileStream, Encoding.UTF8, true, BufferSize)) { var text = await reader.ReadToEndAsync().ConfigureAwait(false); // If it appears in the playlist, it's done if (text.IndexOf(segmentFilename, StringComparison.OrdinalIgnoreCase) != -1) { if (FileSystem.FileExists(segmentPath)) { return GetSegmentResult(state, segmentPath, segmentIndex, transcodingJob); } //break; } } } } catch (IOException) { // May get an error if the file is locked } await Task.Delay(100, cancellationToken).ConfigureAwait(false); } // if a different file is encoding, it's done //var currentTranscodingIndex = GetCurrentTranscodingIndex(playlistPath); //if (currentTranscodingIndex > segmentIndex) //{ //return GetSegmentResult(segmentPath, segmentIndex); //} //// Wait for the file to stop being written to, then stream it //var length = new FileInfo(segmentPath).Length; //var eofCount = 0; //while (eofCount < 10) //{ // var info = new FileInfo(segmentPath); // if (!info.Exists) // { // break; // } // var newLength = info.Length; // if (newLength == length) // { // eofCount++; // } // else // { // eofCount = 0; // } // length = newLength; // await Task.Delay(100, cancellationToken).ConfigureAwait(false); //} cancellationToken.ThrowIfCancellationRequested(); return GetSegmentResult(state, segmentPath, segmentIndex, transcodingJob); }
/// <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); TranscodingJob job = null; var playlist = state.OutputFilePath; if (!FileSystem.FileExists(playlist)) { var transcodingLock = ApiEntryPoint.Instance.GetTranscodingLock(playlist); await transcodingLock.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; } var waitForSegments = state.SegmentLength >= 10 ? 2 : 3; await WaitForMinimumSegmentCount(playlist, waitForSegments, cancellationTokenSource.Token).ConfigureAwait(false); } } finally { transcodingLock.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 baselineStreamBitrate = 64000; var playlistText = GetMasterPlaylistFileText(playlist, videoBitrate + audioBitrate, 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>())); }
private object GetSegmentResult(string segmentPath, int index, int segmentLength, TranscodingJob transcodingJob) { var segmentEndingSeconds = (1 + index) * segmentLength; var segmentEndingPositionTicks = TimeSpan.FromSeconds(segmentEndingSeconds).Ticks; return ResultFactory.GetStaticFileResult(Request, new StaticFileResultOptions { Path = segmentPath, FileShare = FileShare.ReadWrite, OnComplete = () => { if (transcodingJob != null) { transcodingJob.DownloadPositionTicks = Math.Max(transcodingJob.DownloadPositionTicks ?? segmentEndingPositionTicks, segmentEndingPositionTicks); } } }); }
private async void 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); } } } catch (Exception ex) { Logger.ErrorException("Error reading ffmpeg log", ex); } }
private object GetSegmentResult(StreamState state, string segmentPath, int index, TranscodingJob transcodingJob) { var segmentEndingPositionTicks = GetEndPositionTicks(state, index); return ResultFactory.GetStaticFileResult(Request, new StaticFileResultOptions { Path = segmentPath, FileShare = FileShare.ReadWrite, OnComplete = () => { if (transcodingJob != null) { transcodingJob.DownloadPositionTicks = Math.Max(transcodingJob.DownloadPositionTicks ?? segmentEndingPositionTicks, segmentEndingPositionTicks); ApiEntryPoint.Instance.OnTranscodeEndRequest(transcodingJob); } } }); }
public TranscodingThrottler(TranscodingJob job, ILogger logger, IProcessManager processManager) { _job = job; _logger = logger; _processManager = processManager; }