/// <summary> /// Kills the transcoding job. /// </summary> /// <param name="job">The job.</param> private void KillTranscodingJob(TranscodingJob job) { lock (_activeTranscodingJobs) { _activeTranscodingJobs.Remove(job); if (job.KillTimer != null) { job.KillTimer.Dispose(); job.KillTimer = null; } } var process = job.Process; var hasExited = true; try { hasExited = process.HasExited; } catch (Exception ex) { Logger.ErrorException("Error determining if ffmpeg process has exited for {0}", ex, job.Path); } if (!hasExited) { try { Logger.Info("Killing ffmpeg process for {0}", job.Path); process.Kill(); // Need to wait because killing is asynchronous process.WaitForExit(5000); } catch (Win32Exception ex) { Logger.ErrorException("Error killing transcoding job for {0}", ex, job.Path); } catch (InvalidOperationException ex) { Logger.ErrorException("Error killing transcoding job for {0}", ex, job.Path); } catch (NotSupportedException ex) { Logger.ErrorException("Error killing transcoding job for {0}", ex, job.Path); } } // Determine if it exited successfully var hasExitedSuccessfully = false; try { hasExitedSuccessfully = process.ExitCode == 0; } catch (InvalidOperationException) { } catch (NotSupportedException) { } // Dispose the process process.Dispose(); // If it didn't complete successfully cleanup the partial files // Also don't cache output from resume points // Also don't cache video if (!hasExitedSuccessfully || job.StartTimeTicks.HasValue || job.IsVideo) { DeletePartialStreamFiles(job.Path, job.Type, 0, 1500); } }
private async void PingTimer(TranscodingJob job, bool isProgressCheckIn) { if (job.HasExited) { job.StopKillTimer(); return; } var timerDuration = 10000; if (job.Type != TranscodingJobType.Progressive) { timerDuration = 60000; } job.PingTimeout = timerDuration; job.LastPingDate = DateTime.UtcNow; // Don't start the timer for playback checkins with progressive streaming if (job.Type != TranscodingJobType.Progressive || !isProgressCheckIn) { job.StartKillTimer(OnTranscodeKillTimerStopped); } else { job.ChangeKillTimerIfStarted(); } if (!string.IsNullOrWhiteSpace(job.LiveStreamId)) { try { await _mediaSourceManager.PingLiveStream(job.LiveStreamId, CancellationToken.None).ConfigureAwait(false); } catch (Exception ex) { Logger.ErrorException("Error closing live stream", ex); } } }
/// <summary> /// Kills the transcoding job. /// </summary> /// <param name="job">The job.</param> /// <param name="closeLiveStream">if set to <c>true</c> [close live stream].</param> /// <param name="delete">The delete.</param> private async void KillTranscodingJob(TranscodingJob job, bool closeLiveStream, Func<string, bool> delete) { job.DisposeKillTimer(); Logger.Debug("KillTranscodingJob - JobId {0} PlaySessionId {1}. Killing transcoding", job.Id, job.PlaySessionId); lock (_activeTranscodingJobs) { _activeTranscodingJobs.Remove(job); if (!job.CancellationTokenSource.IsCancellationRequested) { job.CancellationTokenSource.Cancel(); } } lock (job.ProcessLock) { if (job.TranscodingThrottler != null) { job.TranscodingThrottler.Stop(); } var process = job.Process; var hasExited = job.HasExited; if (!hasExited) { try { Logger.Info("Stopping ffmpeg process with q command for {0}", job.Path); //process.Kill(); process.StandardInput.WriteLine("q"); // Need to wait because killing is asynchronous if (!process.WaitForExit(5000)) { Logger.Info("Killing ffmpeg process for {0}", job.Path); process.Kill(); } } catch (Exception ex) { Logger.ErrorException("Error killing transcoding job for {0}", ex, job.Path); } } } if (delete(job.Path)) { DeletePartialStreamFiles(job.Path, job.Type, 0, 1500); } if (closeLiveStream && !string.IsNullOrWhiteSpace(job.LiveStreamId)) { try { await _mediaSourceManager.CloseLiveStream(job.LiveStreamId, CancellationToken.None).ConfigureAwait(false); } catch (Exception ex) { Logger.ErrorException("Error closing live stream for {0}", ex, job.Path); } } }
public void OnTranscodeBeginRequest(TranscodingJob job) { job.ActiveRequestCount++; if (string.IsNullOrWhiteSpace(job.PlaySessionId) || job.Type == TranscodingJobType.Progressive) { job.StopKillTimer(); } }
public void OnTranscodeEndRequest(TranscodingJob job) { job.ActiveRequestCount--; //Logger.Debug("OnTranscodeEndRequest job.ActiveRequestCount={0}", job.ActiveRequestCount); if (job.ActiveRequestCount <= 0) { PingTimer(job, false); } }
/// <summary> /// Called when [transcode beginning]. /// </summary> /// <param name="path">The path.</param> /// <param name="playSessionId">The play session identifier.</param> /// <param name="liveStreamId">The live stream identifier.</param> /// <param name="transcodingJobId">The transcoding job identifier.</param> /// <param name="type">The type.</param> /// <param name="process">The process.</param> /// <param name="deviceId">The device id.</param> /// <param name="state">The state.</param> /// <param name="cancellationTokenSource">The cancellation token source.</param> /// <returns>TranscodingJob.</returns> public TranscodingJob OnTranscodeBeginning(string path, string playSessionId, string liveStreamId, string transcodingJobId, TranscodingJobType type, Process process, string deviceId, StreamState state, CancellationTokenSource cancellationTokenSource) { lock (_activeTranscodingJobs) { var job = new TranscodingJob(Logger) { Type = type, Path = path, Process = process, ActiveRequestCount = 1, DeviceId = deviceId, CancellationTokenSource = cancellationTokenSource, Id = transcodingJobId, PlaySessionId = playSessionId, LiveStreamId = liveStreamId }; _activeTranscodingJobs.Add(job); ReportTranscodingProgress(job, state, null, null, null, null, null); return job; } }
public void ReportTranscodingProgress(TranscodingJob job, StreamState state, TimeSpan? transcodingPosition, float? framerate, double? percentComplete, long? bytesTranscoded, int? bitRate) { var ticks = transcodingPosition.HasValue ? transcodingPosition.Value.Ticks : (long?)null; if (job != null) { job.Framerate = framerate; job.CompletionPercentage = percentComplete; job.TranscodingPositionTicks = ticks; job.BytesTranscoded = bytesTranscoded; job.BitRate = bitRate; } var deviceId = state.Request.DeviceId; if (!string.IsNullOrWhiteSpace(deviceId)) { var audioCodec = state.ActualOutputAudioCodec; var videoCodec = state.ActualOutputVideoCodec; _sessionManager.ReportTranscodingInfo(deviceId, new TranscodingInfo { Bitrate = bitRate ?? state.TotalOutputBitrate, AudioCodec = audioCodec, VideoCodec = videoCodec, Container = state.OutputContainer, Framerate = framerate, CompletionPercentage = percentComplete, Width = state.OutputWidth, Height = state.OutputHeight, AudioChannels = state.OutputAudioChannels, IsAudioDirect = string.Equals(state.OutputAudioCodec, "copy", StringComparison.OrdinalIgnoreCase), IsVideoDirect = string.Equals(state.OutputVideoCodec, "copy", StringComparison.OrdinalIgnoreCase) }); } }
/// <summary> /// Kills the transcoding job. /// </summary> /// <param name="job">The job.</param> /// <param name="delete">The delete.</param> private void KillTranscodingJob(TranscodingJob job, Func<string, bool> delete) { lock (_activeTranscodingJobs) { _activeTranscodingJobs.Remove(job); if (!job.CancellationTokenSource.IsCancellationRequested) { job.CancellationTokenSource.Cancel(); } if (job.KillTimer != null) { job.KillTimer.Dispose(); job.KillTimer = null; } } lock (job.ProcessLock) { var process = job.Process; var hasExited = true; try { hasExited = process.HasExited; } catch (Exception ex) { Logger.ErrorException("Error determining if ffmpeg process has exited for {0}", ex, job.Path); } if (!hasExited) { try { Logger.Info("Killing ffmpeg process for {0}", job.Path); //process.Kill(); process.StandardInput.WriteLine("q"); // Need to wait because killing is asynchronous process.WaitForExit(5000); } catch (Exception ex) { Logger.ErrorException("Error killing transcoding job for {0}", ex, job.Path); } } } if (delete(job.Path)) { DeletePartialStreamFiles(job.Path, job.Type, 0, 1500); } }
/// <summary> /// Kills the transcoding job. /// </summary> /// <param name="job">The job.</param> /// <param name="closeLiveStream">if set to <c>true</c> [close live stream].</param> /// <param name="delete">The delete.</param> private async Task KillTranscodingJob(TranscodingJob job, bool closeLiveStream, Func <string, bool> delete) { job.DisposeKillTimer(); _logger.LogDebug("KillTranscodingJob - JobId {0} PlaySessionId {1}. Killing transcoding", job.Id, job.PlaySessionId); lock (_activeTranscodingJobs) { _activeTranscodingJobs.Remove(job); if (!job.CancellationTokenSource.IsCancellationRequested) { job.CancellationTokenSource.Cancel(); } } lock (_transcodingLocks) { _transcodingLocks.Remove(job.Path); } lock (job.ProcessLock) { job.TranscodingThrottler?.Stop().GetAwaiter().GetResult(); var process = job.Process; var hasExited = job.HasExited; if (!hasExited) { try { _logger.LogInformation("Stopping ffmpeg process with q command for {Path}", job.Path); process.StandardInput.WriteLine("q"); // Need to wait because killing is asynchronous if (!process.WaitForExit(5000)) { _logger.LogInformation("Killing ffmpeg process for {Path}", job.Path); process.Kill(); } } catch (InvalidOperationException) { } } } if (delete(job.Path)) { await DeletePartialStreamFiles(job.Path, job.Type, 0, 1500).ConfigureAwait(false); } if (closeLiveStream && !string.IsNullOrWhiteSpace(job.LiveStreamId)) { try { await _mediaSourceManager.CloseLiveStream(job.LiveStreamId).ConfigureAwait(false); } catch (Exception ex) { _logger.LogError(ex, "Error closing live stream for {Path}", job.Path); } } }
/// <summary> /// Kills the transcoding job. /// </summary> /// <param name="job">The job.</param> private void KillTranscodingJob(TranscodingJob job) { lock (_activeTranscodingJobs) { _activeTranscodingJobs.Remove(job); if (!job.CancellationTokenSource.IsCancellationRequested) { job.CancellationTokenSource.Cancel(); } if (job.KillTimer != null) { job.KillTimer.Dispose(); job.KillTimer = null; } } var process = job.Process; var hasExited = true; try { hasExited = process.HasExited; } catch (Exception ex) { Logger.ErrorException("Error determining if ffmpeg process has exited for {0}", ex, job.Path); } if (!hasExited) { try { Logger.Info("Killing ffmpeg process for {0}", job.Path); process.Kill(); // Need to wait because killing is asynchronous process.WaitForExit(5000); } catch (Win32Exception ex) { Logger.ErrorException("Error killing transcoding job for {0}", ex, job.Path); } catch (InvalidOperationException ex) { Logger.ErrorException("Error killing transcoding job for {0}", ex, job.Path); } catch (NotSupportedException ex) { Logger.ErrorException("Error killing transcoding job for {0}", ex, job.Path); } } // Dispose the process process.Dispose(); DeletePartialStreamFiles(job.Path, job.Type, 0, 1500); }
/// <summary> /// Kills the transcoding job. /// </summary> /// <param name="job">The job.</param> /// <param name="closeLiveStream">if set to <c>true</c> [close live stream].</param> /// <param name="delete">The delete.</param> private async void KillTranscodingJob(TranscodingJob job, bool closeLiveStream, Func <string, bool> delete) { job.DisposeKillTimer(); Logger.Debug("KillTranscodingJob - JobId {0} PlaySessionId {1}. Killing transcoding", job.Id, job.PlaySessionId); lock (_activeTranscodingJobs) { _activeTranscodingJobs.Remove(job); if (!job.CancellationTokenSource.IsCancellationRequested) { job.CancellationTokenSource.Cancel(); } } lock (job.ProcessLock) { if (job.TranscodingThrottler != null) { job.TranscodingThrottler.Stop(); } var process = job.Process; var hasExited = job.HasExited; if (!hasExited) { try { Logger.Info("Killing ffmpeg process for {0}", job.Path); //process.Kill(); process.StandardInput.WriteLine("q"); // Need to wait because killing is asynchronous process.WaitForExit(5000); } catch (Exception ex) { Logger.ErrorException("Error killing transcoding job for {0}", ex, job.Path); } } } if (delete(job.Path)) { DeletePartialStreamFiles(job.Path, job.Type, 0, 1500); } if (closeLiveStream && !string.IsNullOrWhiteSpace(job.LiveStreamId)) { try { await _mediaSourceManager.CloseLiveStream(job.LiveStreamId, CancellationToken.None).ConfigureAwait(false); } catch (Exception ex) { Logger.ErrorException("Error closing live stream for {0}", ex, job.Path); } } }
/// <summary> /// Kills the transcoding job. /// </summary> /// <param name="job">The job.</param> private async void KillTranscodingJob(TranscodingJob job) { lock (_activeTranscodingJobs) { _activeTranscodingJobs.Remove(job); if (job.KillTimer != null) { job.KillTimer.Dispose(); job.KillTimer = null; } } var process = job.Process; var hasExited = true; try { hasExited = process.HasExited; } catch (Win32Exception ex) { Logger.ErrorException("Error determining if ffmpeg process has exited for {0}", ex, job.Path); } catch (InvalidOperationException ex) { Logger.ErrorException("Error determining if ffmpeg process has exited for {0}", ex, job.Path); } catch (NotSupportedException ex) { Logger.ErrorException("Error determining if ffmpeg process has exited for {0}", ex, job.Path); } if (!hasExited) { try { Logger.Info("Killing ffmpeg process for {0}", job.Path); process.Kill(); // Need to wait because killing is asynchronous process.WaitForExit(5000); } catch (Win32Exception ex) { Logger.ErrorException("Error killing transcoding job for {0}", ex, job.Path); } catch (InvalidOperationException ex) { Logger.ErrorException("Error killing transcoding job for {0}", ex, job.Path); } catch (NotSupportedException ex) { Logger.ErrorException("Error killing transcoding job for {0}", ex, job.Path); } } // Determine if it exited successfully var hasExitedSuccessfully = false; try { hasExitedSuccessfully = process.ExitCode == 0; } catch (InvalidOperationException) { } catch (NotSupportedException) { } // Dispose the process process.Dispose(); // If it didn't complete successfully cleanup the partial files // Also don't cache output from resume points // Also don't cache video if (!hasExitedSuccessfully || job.StartTimeTicks.HasValue || job.IsVideo) { Logger.Info("Deleting partial stream file(s) {0}", job.Path); await Task.Delay(1000).ConfigureAwait(false); try { if (job.Type == TranscodingJobType.Progressive) { DeleteProgressivePartialStreamFiles(job.Path); } else { DeleteHlsPartialStreamFiles(job.Path); } } catch (IOException ex) { Logger.ErrorException("Error deleting partial stream file(s) {0}", ex, job.Path); } } }
public void OnTranscodeEndRequest(TranscodingJob job) { job.ActiveRequestCount--; if (job.ActiveRequestCount == 0) { // The HLS kill timer is long - 1/2 hr. clients should use the manual kill command when stopping. var timerDuration = job.Type == TranscodingJobType.Progressive ? 1000 : 1800000; if (job.KillTimer == null) { job.KillTimer = new Timer(OnTranscodeKillTimerStopped, job, timerDuration, Timeout.Infinite); } else { job.KillTimer.Change(timerDuration, Timeout.Infinite); } } }
public void OnTranscodeEndRequest(TranscodingJob job) { job.ActiveRequestCount--; if (job.ActiveRequestCount == 0) { if (job.Type == TranscodingJobType.Progressive) { const int timerDuration = 1000; if (job.KillTimer == null) { job.KillTimer = new Timer(OnTranscodeKillTimerStopped, job, timerDuration, Timeout.Infinite); } else { job.KillTimer.Change(timerDuration, Timeout.Infinite); } } } }