diff --git a/FFMpegCore.Test/VideoTest.cs b/FFMpegCore.Test/VideoTest.cs
index ec80fe3..33f51e4 100644
--- a/FFMpegCore.Test/VideoTest.cs
+++ b/FFMpegCore.Test/VideoTest.cs
@@ -12,6 +12,7 @@
using FFMpegCore.Arguments;
using FFMpegCore.Exceptions;
using FFMpegCore.Pipes;
+using System.Threading;
namespace FFMpegCore.Test
{
@@ -551,24 +552,55 @@ public void Video_TranscodeInMemory()
public async Task Video_Cancel_Async()
{
var outputFile = new TemporaryFile("out.mp4");
-
+
var task = FFMpegArguments
- .FromFileInput(TestResources.Mp4Video)
+ .FromFileInput("testsrc2=size=320x240[out0]; sine[out1]", false, args => args
+ .WithCustomArgument("-re")
+ .ForceFormat("lavfi"))
.OutputToFile(outputFile, false, opt => opt
- .Resize(new Size(1000, 1000))
.WithAudioCodec(AudioCodec.Aac)
.WithVideoCodec(VideoCodec.LibX264)
- .WithConstantRateFactor(14)
- .WithSpeedPreset(Speed.VerySlow)
- .Loop(3))
+ .WithSpeedPreset(Speed.VeryFast))
.CancellableThrough(out var cancel)
.ProcessAsynchronously(false);
-
+
await Task.Delay(300);
cancel();
-
+
var result = await task;
+
Assert.IsFalse(result);
}
+
+ [TestMethod, Timeout(10000)]
+ public async Task Video_Cancel_Async_With_Timeout()
+ {
+ var outputFile = new TemporaryFile("out.mp4");
+
+ var task = FFMpegArguments
+ .FromFileInput("testsrc2=size=320x240[out0]; sine[out1]", false, args => args
+ .WithCustomArgument("-re")
+ .ForceFormat("lavfi"))
+ .OutputToFile(outputFile, false, opt => opt
+ .WithAudioCodec(AudioCodec.Aac)
+ .WithVideoCodec(VideoCodec.LibX264)
+ .WithSpeedPreset(Speed.VeryFast))
+ .CancellableThrough(out var cancel, 10000)
+ .ProcessAsynchronously(false);
+
+ await Task.Delay(300);
+ cancel();
+
+ var result = await task;
+
+ var outputInfo = FFProbe.Analyse(outputFile);
+
+ Assert.IsTrue(result);
+ Assert.IsNotNull(outputInfo);
+ Assert.AreEqual(320, outputInfo.PrimaryVideoStream.Width);
+ Assert.AreEqual(240, outputInfo.PrimaryVideoStream.Height);
+ Assert.AreEqual("h264", outputInfo.PrimaryVideoStream.CodecName);
+ Assert.AreEqual("aac", outputInfo.PrimaryAudioStream.CodecName);
+ }
}
}
diff --git a/FFMpegCore/FFMpeg/Arguments/InputDeviceArgument.cs b/FFMpegCore/FFMpeg/Arguments/InputDeviceArgument.cs
new file mode 100644
index 0000000..f276bbb
--- /dev/null
+++ b/FFMpegCore/FFMpeg/Arguments/InputDeviceArgument.cs
@@ -0,0 +1,26 @@
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace FFMpegCore.Arguments
+{
+ ///
+ /// Represents an input device parameter
+ ///
+ public class InputDeviceArgument : IInputArgument
+ {
+ private readonly string Device;
+
+ public InputDeviceArgument(string device)
+ {
+ Device = device;
+ }
+
+ public Task During(CancellationToken cancellationToken = default) => Task.CompletedTask;
+
+ public void Pre() { }
+
+ public void Post() { }
+
+ public string Text => $"-i {Device}";
+ }
+}
diff --git a/FFMpegCore/FFMpeg/FFMpegArgumentProcessor.cs b/FFMpegCore/FFMpeg/FFMpegArgumentProcessor.cs
index 9d0a4ad..75b98f4 100644
--- a/FFMpegCore/FFMpeg/FFMpegArgumentProcessor.cs
+++ b/FFMpegCore/FFMpeg/FFMpegArgumentProcessor.cs
@@ -27,7 +27,7 @@ internal FFMpegArgumentProcessor(FFMpegArguments ffMpegArguments)
public string Arguments => _ffMpegArguments.Text;
- private event EventHandler CancelEvent = null!;
+ private event EventHandler CancelEvent = null!;
public FFMpegArgumentProcessor NotifyOnProgress(Action onPercentageProgress, TimeSpan totalTimeSpan)
{
@@ -45,9 +45,9 @@ public FFMpegArgumentProcessor NotifyOnOutput(Action onOutput)
_onOutput = onOutput;
return this;
}
- public FFMpegArgumentProcessor CancellableThrough(out Action cancel)
+ public FFMpegArgumentProcessor CancellableThrough(out Action cancel, int timeout = 0)
{
- cancel = () => CancelEvent?.Invoke(this, EventArgs.Empty);
+ cancel = () => CancelEvent?.Invoke(this, timeout);
return this;
}
public bool ProcessSynchronously(bool throwOnError = true)
@@ -55,11 +55,15 @@ public bool ProcessSynchronously(bool throwOnError = true)
using var instance = PrepareInstance(out var cancellationTokenSource);
var errorCode = -1;
- void OnCancelEvent(object sender, EventArgs args)
+ void OnCancelEvent(object sender, int timeout)
{
instance.SendInput("q");
- cancellationTokenSource.Cancel();
- instance.Started = false;
+
+ if (!cancellationTokenSource.Token.WaitHandle.WaitOne(timeout, true))
+ {
+ cancellationTokenSource.Cancel();
+ instance.Started = false;
+ }
}
CancelEvent += OnCancelEvent;
instance.Exited += delegate { cancellationTokenSource.Cancel(); };
@@ -102,11 +106,15 @@ public async Task ProcessAsynchronously(bool throwOnError = true)
using var instance = PrepareInstance(out var cancellationTokenSource);
var errorCode = -1;
- void OnCancelEvent(object sender, EventArgs args)
+ void OnCancelEvent(object sender, int timeout)
{
instance.SendInput("q");
- cancellationTokenSource.Cancel();
- instance.Started = false;
+
+ if (!cancellationTokenSource.Token.WaitHandle.WaitOne(timeout, true))
+ {
+ cancellationTokenSource.Cancel();
+ instance.Started = false;
+ }
}
CancelEvent += OnCancelEvent;
diff --git a/FFMpegCore/FFMpeg/FFMpegArguments.cs b/FFMpegCore/FFMpeg/FFMpegArguments.cs
index 37dc36f..64fff3c 100644
--- a/FFMpegCore/FFMpeg/FFMpegArguments.cs
+++ b/FFMpegCore/FFMpeg/FFMpegArguments.cs
@@ -22,6 +22,7 @@ private FFMpegArguments() { }
public static FFMpegArguments FromFileInput(string filePath, bool verifyExists = true, Action? addArguments = null) => new FFMpegArguments().WithInput(new InputArgument(verifyExists, filePath), addArguments);
public static FFMpegArguments FromFileInput(FileInfo fileInfo, Action? addArguments = null) => new FFMpegArguments().WithInput(new InputArgument(fileInfo.FullName, false), addArguments);
public static FFMpegArguments FromUrlInput(Uri uri, Action? addArguments = null) => new FFMpegArguments().WithInput(new InputArgument(uri.AbsoluteUri, false), addArguments);
+ public static FFMpegArguments FromDeviceInput(string device, Action? addArguments = null) => new FFMpegArguments().WithInput(new InputDeviceArgument(device), addArguments);
public static FFMpegArguments FromPipeInput(IPipeSource sourcePipe, Action? addArguments = null) => new FFMpegArguments().WithInput(new InputPipeArgument(sourcePipe), addArguments);