aboutsummaryrefslogtreecommitdiff
path: root/MediaBrowser.Api
diff options
context:
space:
mode:
authorLuke Pulverenti <luke.pulverenti@gmail.com>2014-06-20 00:50:30 -0400
committerLuke Pulverenti <luke.pulverenti@gmail.com>2014-06-20 00:50:30 -0400
commit439839378364466ae7795c99b67a123bd1008945 (patch)
tree0923656b58425f72df354eb71fd69109eddbe8c8 /MediaBrowser.Api
parente666fee20dd4a8343a262b9225023996b54b1004 (diff)
close ffmpeg more gracefully
Diffstat (limited to 'MediaBrowser.Api')
-rw-r--r--MediaBrowser.Api/ApiEntryPoint.cs76
-rw-r--r--MediaBrowser.Api/Playback/BaseStreamingService.cs17
-rw-r--r--MediaBrowser.Api/Playback/Hls/BaseHlsService.cs4
-rw-r--r--MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs76
-rw-r--r--MediaBrowser.Api/Playback/Hls/VideoHlsService.cs5
5 files changed, 115 insertions, 63 deletions
diff --git a/MediaBrowser.Api/ApiEntryPoint.cs b/MediaBrowser.Api/ApiEntryPoint.cs
index f711807541..3e9a0926be 100644
--- a/MediaBrowser.Api/ApiEntryPoint.cs
+++ b/MediaBrowser.Api/ApiEntryPoint.cs
@@ -3,15 +3,14 @@ using MediaBrowser.Controller;
using MediaBrowser.Controller.Plugins;
using MediaBrowser.Controller.Session;
using MediaBrowser.Model.Logging;
+using MediaBrowser.Model.Session;
using System;
using System.Collections.Generic;
-using System.ComponentModel;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
-using MediaBrowser.Model.Session;
namespace MediaBrowser.Api
{
@@ -100,7 +99,7 @@ namespace MediaBrowser.Api
{
var jobCount = _activeTranscodingJobs.Count;
- Parallel.ForEach(_activeTranscodingJobs.ToList(), KillTranscodingJob);
+ Parallel.ForEach(_activeTranscodingJobs.ToList(), j => KillTranscodingJob(j, true));
// Try to allow for some time to kill the ffmpeg processes and delete the partial stream files
if (jobCount > 0)
@@ -291,16 +290,16 @@ namespace MediaBrowser.Api
{
var job = (TranscodingJob)state;
- KillTranscodingJob(job);
+ KillTranscodingJob(job, true);
}
/// <summary>
/// Kills the single transcoding job.
/// </summary>
/// <param name="deviceId">The device id.</param>
- /// <param name="isVideo">if set to <c>true</c> [is video].</param>
+ /// <param name="deleteFiles">if set to <c>true</c> [delete files].</param>
/// <exception cref="System.ArgumentNullException">sourcePath</exception>
- internal void KillTranscodingJobs(string deviceId, bool isVideo)
+ internal void KillTranscodingJobs(string deviceId, bool deleteFiles)
{
if (string.IsNullOrEmpty(deviceId))
{
@@ -318,7 +317,7 @@ namespace MediaBrowser.Api
foreach (var job in jobs)
{
- KillTranscodingJob(job);
+ KillTranscodingJob(job, deleteFiles);
}
}
@@ -326,7 +325,8 @@ namespace MediaBrowser.Api
/// Kills the transcoding job.
/// </summary>
/// <param name="job">The job.</param>
- private void KillTranscodingJob(TranscodingJob job)
+ /// <param name="deleteFiles">if set to <c>true</c> [delete files].</param>
+ private void KillTranscodingJob(TranscodingJob job, bool deleteFiles)
{
lock (_activeTranscodingJobs)
{
@@ -344,48 +344,44 @@ namespace MediaBrowser.Api
}
}
- var process = job.Process;
-
- var hasExited = true;
-
- try
+ lock (job.ProcessLock)
{
- 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);
+ var process = job.Process;
- process.Kill();
+ var hasExited = true;
- // Need to wait because killing is asynchronous
- process.WaitForExit(5000);
- }
- catch (Win32Exception ex)
+ try
{
- Logger.ErrorException("Error killing transcoding job for {0}", ex, job.Path);
+ hasExited = process.HasExited;
}
- catch (InvalidOperationException ex)
+ catch (Exception ex)
{
- Logger.ErrorException("Error killing transcoding job for {0}", ex, job.Path);
+ Logger.ErrorException("Error determining if ffmpeg process has exited for {0}", ex, job.Path);
}
- catch (NotSupportedException ex)
+
+ if (!hasExited)
{
- Logger.ErrorException("Error killing transcoding job for {0}", ex, job.Path);
+ 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);
+ }
}
}
- // Dispose the process
- process.Dispose();
-
- DeletePartialStreamFiles(job.Path, job.Type, 0, 1500);
+ if (deleteFiles)
+ {
+ DeletePartialStreamFiles(job.Path, job.Type, 0, 1500);
+ }
}
private async void DeletePartialStreamFiles(string path, TranscodingJobType jobType, int retryCount, int delayMs)
@@ -494,6 +490,8 @@ namespace MediaBrowser.Api
public string DeviceId { get; set; }
public CancellationTokenSource CancellationTokenSource { get; set; }
+
+ public object ProcessLock = new object();
}
/// <summary>
diff --git a/MediaBrowser.Api/Playback/BaseStreamingService.cs b/MediaBrowser.Api/Playback/BaseStreamingService.cs
index d2369c4101..0ecc5d9d19 100644
--- a/MediaBrowser.Api/Playback/BaseStreamingService.cs
+++ b/MediaBrowser.Api/Playback/BaseStreamingService.cs
@@ -816,6 +816,7 @@ namespace MediaBrowser.Api.Playback
// Must consume both stdout and stderr or deadlocks may occur
RedirectStandardOutput = true,
RedirectStandardError = true,
+ RedirectStandardInput = true,
FileName = MediaEncoder.EncoderPath,
WorkingDirectory = Path.GetDirectoryName(MediaEncoder.EncoderPath),
@@ -1073,8 +1074,9 @@ namespace MediaBrowser.Api.Playback
/// </summary>
/// <param name="process">The process.</param>
/// <param name="state">The state.</param>
- protected void OnFfMpegProcessExited(Process process, StreamState state)
+ private void OnFfMpegProcessExited(Process process, StreamState state)
{
+ Logger.Debug("Disposing stream resources");
state.Dispose();
try
@@ -1083,8 +1085,19 @@ namespace MediaBrowser.Api.Playback
}
catch
{
- Logger.Info("FFMpeg exited with an error.");
+ 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);
+ //}
}
protected double? GetFramerateParam(StreamState state)
diff --git a/MediaBrowser.Api/Playback/Hls/BaseHlsService.cs b/MediaBrowser.Api/Playback/Hls/BaseHlsService.cs
index 5de0709fd2..39163a1037 100644
--- a/MediaBrowser.Api/Playback/Hls/BaseHlsService.cs
+++ b/MediaBrowser.Api/Playback/Hls/BaseHlsService.cs
@@ -83,7 +83,7 @@ namespace MediaBrowser.Api.Playback.Hls
{
var cancellationTokenSource = new CancellationTokenSource();
- var state = GetState(request, cancellationTokenSource.Token).Result;
+ var state = await GetState(request, cancellationTokenSource.Token).ConfigureAwait(false);
var playlist = state.OutputFilePath;
@@ -154,7 +154,7 @@ namespace MediaBrowser.Api.Playback.Hls
/// <returns>System.Int32.</returns>
protected int GetSegmentWait()
{
- var minimumSegmentCount = 3;
+ var minimumSegmentCount = 2;
var quality = GetQualitySetting();
if (quality == EncodingQuality.HighSpeed || quality == EncodingQuality.HighQuality)
diff --git a/MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs b/MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs
index 13e858aa53..bb547bbffd 100644
--- a/MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs
+++ b/MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs
@@ -1,13 +1,10 @@
using MediaBrowser.Common.IO;
-using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Channels;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Dlna;
-using MediaBrowser.Controller.Dto;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.LiveTv;
using MediaBrowser.Controller.MediaEncoding;
-using MediaBrowser.Controller.Persistence;
using MediaBrowser.Model.IO;
using ServiceStack;
using System;
@@ -17,7 +14,6 @@ using System.IO;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
-using MimeTypes = ServiceStack.MimeTypes;
namespace MediaBrowser.Api.Playback.Hls
{
@@ -83,29 +79,75 @@ namespace MediaBrowser.Api.Playback.Hls
return GetDynamicSegment(request, true).Result;
}
+ private static readonly SemaphoreSlim FfmpegStartLock = new SemaphoreSlim(1, 1);
private async Task<object> GetDynamicSegment(GetDynamicHlsVideoSegment request, bool isMain)
{
+ var cancellationTokenSource = new CancellationTokenSource();
+ var cancellationToken = cancellationTokenSource.Token;
+
var index = int.Parse(request.SegmentId, NumberStyles.Integer, UsCulture);
- var state = await GetState(request, CancellationToken.None).ConfigureAwait(false);
+ var state = await GetState(request, cancellationToken).ConfigureAwait(false);
var playlistPath = Path.ChangeExtension(state.OutputFilePath, ".m3u8");
- var path = GetSegmentPath(playlistPath, index);
+ var segmentPath = GetSegmentPath(playlistPath, index);
- if (File.Exists(path))
+ if (File.Exists(segmentPath))
{
- return GetSegementResult(path);
+ ApiEntryPoint.Instance.OnTranscodeBeginRequest(playlistPath, TranscodingJobType.Hls);
+ return GetSegementResult(segmentPath);
}
- if (!File.Exists(playlistPath))
+ await FfmpegStartLock.WaitAsync(cancellationTokenSource.Token).ConfigureAwait(false);
+ try
+ {
+ if (File.Exists(segmentPath))
+ {
+ ApiEntryPoint.Instance.OnTranscodeBeginRequest(playlistPath, TranscodingJobType.Hls);
+ return GetSegementResult(segmentPath);
+ }
+ else
+ {
+ if (index == 0)
+ {
+ // If the playlist doesn't already exist, startup ffmpeg
+ try
+ {
+ ApiEntryPoint.Instance.KillTranscodingJobs(state.Request.DeviceId, false);
+
+ await StartFfMpeg(state, playlistPath, cancellationTokenSource).ConfigureAwait(false);
+ }
+ catch
+ {
+ state.Dispose();
+ throw;
+ }
+
+ await WaitForMinimumSegmentCount(playlistPath, 2, cancellationTokenSource.Token).ConfigureAwait(false);
+ }
+ }
+ }
+ finally
{
- await StartFfMpeg(state, playlistPath, new CancellationTokenSource()).ConfigureAwait(false);
+ FfmpegStartLock.Release();
+ }
- await WaitForMinimumSegmentCount(playlistPath, GetSegmentWait(), CancellationToken.None).ConfigureAwait(false);
+ Logger.Info("waiting for {0}", segmentPath);
+ while (!File.Exists(segmentPath))
+ {
+ await Task.Delay(50, cancellationToken).ConfigureAwait(false);
}
- return GetSegementResult(path);
+ Logger.Info("returning {0}", segmentPath);
+ return GetSegementResult(segmentPath);
+ }
+
+ protected override int GetStartNumber(StreamState state)
+ {
+ var request = (GetDynamicHlsVideoSegment) state.Request;
+
+ return int.Parse(request.SegmentId, NumberStyles.Integer, UsCulture);
}
private string GetSegmentPath(string playlist, int index)
@@ -120,7 +162,7 @@ namespace MediaBrowser.Api.Playback.Hls
private object GetSegementResult(string path)
{
// TODO: Handle if it's currently being written to
- return ResultFactory.GetStaticFileResult(Request, path);
+ return ResultFactory.GetStaticFileResult(Request, path, FileShare.ReadWrite);
}
private async Task<object> GetAsync(GetMasterHlsVideoStream request)
@@ -143,7 +185,7 @@ namespace MediaBrowser.Api.Playback.Hls
var playlistText = GetMasterPlaylistFileText(videoBitrate + audioBitrate, appendBaselineStream, baselineStreamBitrate);
- return ResultFactory.GetResult(playlistText, MimeTypes.GetMimeType("playlist.m3u8"), new Dictionary<string, string>());
+ return ResultFactory.GetResult(playlistText, Common.Net.MimeTypes.GetMimeType("playlist.m3u8"), new Dictionary<string, string>());
}
private string GetMasterPlaylistFileText(int bitrate, bool includeBaselineStream, int baselineStreamBitrate)
@@ -226,7 +268,7 @@ namespace MediaBrowser.Api.Playback.Hls
var playlistText = builder.ToString();
- return ResultFactory.GetResult(playlistText, MimeTypes.GetMimeType("playlist.m3u8"), new Dictionary<string, string>());
+ return ResultFactory.GetResult(playlistText, Common.Net.MimeTypes.GetMimeType("playlist.m3u8"), new Dictionary<string, string>());
}
protected override string GetAudioArguments(StreamState state)
@@ -274,7 +316,9 @@ namespace MediaBrowser.Api.Playback.Hls
return IsH264(state.VideoStream) ? "-codec:v:0 copy -bsf h264_mp4toannexb" : "-codec:v:0 copy";
}
- const string keyFrameArg = " -force_key_frames expr:if(isnan(prev_forced_t),gte(t,.1),gte(t,prev_forced_t+5))";
+ var keyFrameArg = state.ReadInputAtNativeFramerate ?
+ " -force_key_frames expr:if(isnan(prev_forced_t),gte(t,.1),gte(t,prev_forced_t+1))" :
+ " -force_key_frames expr:if(isnan(prev_forced_t),gte(t,.1),gte(t,prev_forced_t+5))";
var hasGraphicalSubs = state.SubtitleStream != null && !state.SubtitleStream.IsTextSubtitleStream;
diff --git a/MediaBrowser.Api/Playback/Hls/VideoHlsService.cs b/MediaBrowser.Api/Playback/Hls/VideoHlsService.cs
index 337cd88f25..d65d1030ca 100644
--- a/MediaBrowser.Api/Playback/Hls/VideoHlsService.cs
+++ b/MediaBrowser.Api/Playback/Hls/VideoHlsService.cs
@@ -1,19 +1,16 @@
-using System.Threading;
using MediaBrowser.Common.IO;
-using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Channels;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Dlna;
-using MediaBrowser.Controller.Dto;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.LiveTv;
using MediaBrowser.Controller.MediaEncoding;
-using MediaBrowser.Controller.Persistence;
using MediaBrowser.Model.IO;
using ServiceStack;
using System;
using System.IO;
using System.Linq;
+using System.Threading;
using System.Threading.Tasks;
namespace MediaBrowser.Api.Playback.Hls