/// <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;
 }
Example #2
0
 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;
 }
Example #4
0
        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));
        }
Example #5
0
        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);
            }
        }
Example #6
0
        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;
        }
Example #8
0
 public ProgressiveFileCopier(IFileSystem fileSystem, TranscodingJob job)
 {
     _fileSystem = fileSystem;
     _job        = job;
 }
Example #9
0
 private void StartThrottler(StreamState state, TranscodingJob transcodingJob)
 {
     if (EnableThrottling(state))
     {
         transcodingJob.TranscodingThrottler = state.TranscodingThrottler = new TranscodingThrottler(transcodingJob, Logger, ServerConfigurationManager);
         state.TranscodingThrottler.Start();
     }
 }
Example #10
0
        /// <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;
        }
Example #14
0
        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);
            }
        }
 public TranscodingThrottler(TranscodingJob job, ILogger logger, IConfigurationManager config)
 {
     _job = job;
     _logger = logger;
     _config = config;
 }
Example #16
0
 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();
     }
 }
Example #17
0
        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);
        }
Example #19
0
 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;
 }
Example #20
0
        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);
        }
Example #21
0
 public ProgressiveFileCopier(IDirectStreamProvider directStreamProvider, Dictionary <string, string> outputHeaders, TranscodingJob job, ILogger logger, CancellationToken cancellationToken)
 {
     _directStreamProvider = directStreamProvider;
     _outputHeaders        = outputHeaders;
     _job               = job;
     _logger            = logger;
     _cancellationToken = cancellationToken;
 }
Example #22
0
        /// <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);
        }
Example #23
0
 public ProgressiveFileCopier(IFileSystem fileSystem, TranscodingJob job, ILogger logger)
 {
     _fileSystem = fileSystem;
     _job        = job;
     _logger     = logger;
 }
Example #24
0
        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);
        }
Example #25
0
        /// <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);
            //}
        }
Example #26
0
 /// <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(IFileSystem fileSystem, TranscodingJob job)
 {
     _fileSystem = fileSystem;
     _job = job;
 }
Example #28
0
 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();
         }
     }
 }
Example #29
0
        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);
                    }
                }
            }));
        }
Example #30
0
        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);
            }
        }
Example #31
0
 public ProgressiveFileCopier(IFileSystem fileSystem, TranscodingJob job, ILogger logger)
 {
     _fileSystem = fileSystem;
     _job = job;
     _logger = logger;
 }
Example #32
0
        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));
        }
Example #33
0
        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));
        }
Example #34
0
        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));
        }
Example #35
0
        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));
        }
Example #36
0
        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));
        }
Example #37
0
        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);
                    }
                }
            }));
        }
Example #38
0
        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);
        }
Example #39
0
        /// <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);
            }
        }
Example #42
0
        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);
                    }
                }
            });
        }
Example #43
0
 public TranscodingThrottler(TranscodingJob job, ILogger logger, IProcessManager processManager)
 {
     _job            = job;
     _logger         = logger;
     _processManager = processManager;
 }