diff --git a/CHANGELOG.md b/CHANGELOG.md
index 9009432..bca3431 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,4 +1,5 @@
-# 2.2.2 (prerelease)
+# 3.0.0 (prerelease)
+- BREAKING CHANGE : Created a new WPF control using ImageSource [#365](https://github.com/ZeBobo5/Vlc.DotNet/pull/365) see how to use in wiki!
- FIXED the type of the `NewTitle` property (int -> string) in `VlcMediaPlayerTitleChangedEventArgs` [#364](https://github.com/ZeBobo5/Vlc.DotNet/pull/364)
- FIXED Dispose() in WPF project [#358](https://github.com/ZeBobo5/Vlc.DotNet/pull/358)
- CHANGED name of internal handle class from `SafeUnmanagedMemoryHandle` to `Utf8StringHandle` [#337](https://github.com/ZeBobo5/Vlc.DotNet/pull/337)
diff --git a/README.md b/README.md
index 6d02691..80c5a32 100644
--- a/README.md
+++ b/README.md
@@ -9,6 +9,27 @@ It can work on any .net framework version starting from .net 2.0 and .net standa
On the front-end side, two components are currently available to make it easy to integrate in your apps. One is for WinForms, the other for WPF.
+Migrating WPF control from 2.x
+----------
+
+The WPF control has been rewritten from scratch from 2.x.
+
+The old WPF control was just a wrapper around the WinForms control.
+This led to some issues (Airspace issue...) and lacked some WPF-ish features.
+
+That's why a new control has been written. To be fair, first versions of Vlc.DotNet
+were built with that techique, but back then, there were issues in the .net framework
+causing the memory usage to explode. As of 2018, this issue is resolved.
+
+You have in fact two options:
+- Use the new WPF control. You might notice a performance impact when reading, for example, a 4k @ 60 fps video on a low-end computer. However, you can do whatever you like, just as a normal ImageSource in WPF.
+- Wrap the Vlc.DotNet.WinForms control in a WinFormHost . It offers better performance, but you will experience Airspace issues (see [#296](https://github.com/ZeBobo5/Vlc.DotNet/issues/296)) if you need to write over the video.
+
+The right option to use depends on your needs.
+
+See the discussion [#249](https://github.com/ZeBobo5/Vlc.DotNet/issue/249) and pull request : [#365](https://github.com/ZeBobo5/Vlc.DotNet/pull/365)
+
+
How to use
----------
It all starts with those three steps :
diff --git a/appveyor.yml b/appveyor.yml
index 0e111b7..e02efe5 100644
--- a/appveyor.yml
+++ b/appveyor.yml
@@ -1,5 +1,5 @@
environment:
- version_prefix: 2.2.2
+ version_prefix: 3.0.0
version: $(version_prefix)-{branch}{build}
image: Visual Studio 2017
diff --git a/src/Samples/Vlc.DotNet.Wpf.Samples/MainWindow.xaml.cs b/src/Samples/Vlc.DotNet.Wpf.Samples/MainWindow.xaml.cs
index ff5c7c1..0499fbb 100644
--- a/src/Samples/Vlc.DotNet.Wpf.Samples/MainWindow.xaml.cs
+++ b/src/Samples/Vlc.DotNet.Wpf.Samples/MainWindow.xaml.cs
@@ -29,29 +29,33 @@ public MainWindow()
vlcLibDirectory = new DirectoryInfo(Path.Combine(currentDirectory, @"..\..\..\lib\x64\"));
}
}
-
+
+ protected override void OnClosing(CancelEventArgs e)
+ {
+ this.control?.Dispose();
+ base.OnClosing(e);
+ }
+
private void OnPlayButtonClick(object sender, RoutedEventArgs e)
{
this.control?.Dispose();
this.control = new VlcControl();
- this.control.MediaPlayer.VlcLibDirectory = this.vlcLibDirectory;
- this.control.MediaPlayer.EndInit();
-
this.ControlContainer.Content = this.control;
+ this.control.SourceProvider.CreatePlayer(this.vlcLibDirectory);
// This can also be called before EndInit
- this.control.MediaPlayer.Log += (_, args) =>
+ this.control.SourceProvider.MediaPlayer.Log += (_, args) =>
{
- string message = string.Format("libVlc : {0} {1} @ {2}", args.Level, args.Message, args.Module);
+ string message = $"libVlc : {args.Level} {args.Message} @ {args.Module}";
System.Diagnostics.Debug.WriteLine(message);
};
- control.MediaPlayer.Play(new Uri("http://download.blender.org/peach/bigbuckbunny_movies/big_buck_bunny_480p_surround-fix.avi"));
+
+ control.SourceProvider.MediaPlayer.Play(new Uri("http://download.blender.org/peach/bigbuckbunny_movies/big_buck_bunny_480p_surround-fix.avi"));
//control.MediaPlayer.Play(new FileInfo(@"..\..\..\Vlc.DotNet\Samples\Videos\BBB trailer.mov"));
}
private void OnStopButtonClick(object sender, RoutedEventArgs e)
{
- this.control?.MediaPlayer.Stop(); // This isn't strictly needed if Dispose() is called, but this is a demo...
this.control?.Dispose();
this.control = null;
}
@@ -63,7 +67,7 @@ private void OnForwardButtonClick(object sender, RoutedEventArgs e)
return;
}
- this.control.MediaPlayer.Rate = 2;
+ this.control.SourceProvider.MediaPlayer.Rate = 2;
}
private void GetLength_Click(object sender, RoutedEventArgs e)
@@ -73,7 +77,7 @@ private void GetLength_Click(object sender, RoutedEventArgs e)
return;
}
- GetLength.Content = this.control.MediaPlayer.Length + " ms";
+ GetLength.Content = this.control.SourceProvider.MediaPlayer.Length + " ms";
}
private void GetCurrentTime_Click(object sender, RoutedEventArgs e)
@@ -83,7 +87,7 @@ private void GetCurrentTime_Click(object sender, RoutedEventArgs e)
return;
}
- GetCurrentTime.Content = this.control.MediaPlayer.Time + " ms";
+ GetCurrentTime.Content = this.control.SourceProvider.MediaPlayer.Time + " ms";
}
private void SetCurrentTime_Click(object sender, RoutedEventArgs e)
@@ -93,8 +97,8 @@ private void SetCurrentTime_Click(object sender, RoutedEventArgs e)
return;
}
- this.control.MediaPlayer.Time = 5000;
- SetCurrentTime.Content = this.control.MediaPlayer.Time + " ms";
+ this.control.SourceProvider.MediaPlayer.Time = 5000;
+ SetCurrentTime.Content = this.control.SourceProvider.MediaPlayer.Time + " ms";
}
}
}
diff --git a/src/Vlc.DotNet.Core.Interops/Signatures/libvlc_media_player.h/libvlc_video_format_cb.cs b/src/Vlc.DotNet.Core.Interops/Signatures/libvlc_media_player.h/libvlc_video_format_cb.cs
index 3d9bc6a..bbbaba2 100644
--- a/src/Vlc.DotNet.Core.Interops/Signatures/libvlc_media_player.h/libvlc_video_format_cb.cs
+++ b/src/Vlc.DotNet.Core.Interops/Signatures/libvlc_media_player.h/libvlc_video_format_cb.cs
@@ -43,5 +43,5 @@ namespace Vlc.DotNet.Core.Interops.Signatures
///
[LibVlcFunction("libvlc_video_format_cb")]
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
- public delegate uint VideoFormatCallback(ref IntPtr userData, IntPtr chroma, ref uint width, ref uint height, ref uint pitches, ref uint lines);
+ public delegate uint VideoFormatCallback(out IntPtr userData, IntPtr chroma, ref uint width, ref uint height, ref uint pitches, ref uint lines);
}
diff --git a/src/Vlc.DotNet.Core.Interops/Signatures/libvlc_media_player.h/libvlc_video_lock_cb.cs b/src/Vlc.DotNet.Core.Interops/Signatures/libvlc_media_player.h/libvlc_video_lock_cb.cs
index 0666937..642c8dd 100644
--- a/src/Vlc.DotNet.Core.Interops/Signatures/libvlc_media_player.h/libvlc_video_lock_cb.cs
+++ b/src/Vlc.DotNet.Core.Interops/Signatures/libvlc_media_player.h/libvlc_video_lock_cb.cs
@@ -10,8 +10,9 @@ namespace Vlc.DotNet.Core.Interops.Signatures
/// Private pointer as passed to .
///
///
- /// start address of the pixel planes (LibVLC allocates the array
- /// of void pointers, this callback must initialize the array)
+ /// The pointer to the array of pointers to the pixel planes (LibVLC allocates the array
+ /// of pointers, this callback must initialize the array)
+ /// If you only need the first plane, then you have to initialize the address pointed by planes
///
///
/// Whenever a new video frame needs to be decoded, the lock callback is
@@ -21,5 +22,5 @@ namespace Vlc.DotNet.Core.Interops.Signatures
///
[LibVlcFunction("libvlc_video_lock_cb")]
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
- public delegate IntPtr LockVideoCallback(IntPtr userData, ref IntPtr planes);
+ public delegate IntPtr LockVideoCallback(IntPtr userData, IntPtr planes);
}
diff --git a/src/Vlc.DotNet.Core.Interops/Signatures/libvlc_media_player.h/libvlc_video_unlock_cb.cs b/src/Vlc.DotNet.Core.Interops/Signatures/libvlc_media_player.h/libvlc_video_unlock_cb.cs
index 1ff9b1b..f987d0d 100644
--- a/src/Vlc.DotNet.Core.Interops/Signatures/libvlc_media_player.h/libvlc_video_unlock_cb.cs
+++ b/src/Vlc.DotNet.Core.Interops/Signatures/libvlc_media_player.h/libvlc_video_unlock_cb.cs
@@ -23,8 +23,10 @@ namespace Vlc.DotNet.Core.Interops.Signatures
///
/// pixel planes as defined by the
/// callback (this parameter is only for convenience)
+ ///
+ /// Its size is always PICTURE_PLANE_MAX, which is 5 in my experience. Only the number of planes required by chroma are usable, which is 1 for RV32.
///
[LibVlcFunction("libvlc_video_lock_cb")]
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
- public delegate void UnlockVideoCallback(IntPtr userData, IntPtr picture, IntPtr planes);
+ public delegate void UnlockVideoCallback(IntPtr userData, IntPtr picture, [MarshalAs(UnmanagedType.LPArray, SizeConst = 5)]IntPtr[] planes);
}
diff --git a/src/Vlc.DotNet.Core.Interops/VlcManager.SetVideoCallbacks.cs b/src/Vlc.DotNet.Core.Interops/VlcManager.SetVideoCallbacks.cs
new file mode 100644
index 0000000..628486c
--- /dev/null
+++ b/src/Vlc.DotNet.Core.Interops/VlcManager.SetVideoCallbacks.cs
@@ -0,0 +1,24 @@
+using System;
+using Vlc.DotNet.Core.Interops.Signatures;
+
+namespace Vlc.DotNet.Core.Interops
+{
+ public sealed partial class VlcManager
+ {
+ private LockVideoCallback _lockVideoCallbackReference;
+ private UnlockVideoCallback _unlockVideoCallbackReference;
+ private DisplayVideoCallback _displayVideoCallbackReference;
+
+ public void SetVideoCallbacks(VlcMediaPlayerInstance mediaPlayerInstance, LockVideoCallback lockVideoCallback, UnlockVideoCallback unlockVideoCallback, DisplayVideoCallback displayVideoCallback, IntPtr userData)
+ {
+ if (mediaPlayerInstance == IntPtr.Zero)
+ throw new ArgumentException("Media player instance is not initialized.");
+
+ this._lockVideoCallbackReference = lockVideoCallback;
+ this._unlockVideoCallbackReference = unlockVideoCallback;
+ this._displayVideoCallbackReference = displayVideoCallback;
+
+ GetInteropDelegate().Invoke(mediaPlayerInstance, this._lockVideoCallbackReference, this._unlockVideoCallbackReference, this._displayVideoCallbackReference, userData);
+ }
+ }
+}
diff --git a/src/Vlc.DotNet.Core.Interops/VlcManager.SetVideoFormatCallbacks.cs b/src/Vlc.DotNet.Core.Interops/VlcManager.SetVideoFormatCallbacks.cs
new file mode 100644
index 0000000..577ca7b
--- /dev/null
+++ b/src/Vlc.DotNet.Core.Interops/VlcManager.SetVideoFormatCallbacks.cs
@@ -0,0 +1,22 @@
+using System;
+using Vlc.DotNet.Core.Interops.Signatures;
+
+namespace Vlc.DotNet.Core.Interops
+{
+ public sealed partial class VlcManager
+ {
+ private VideoFormatCallback _videoFormatCallbackReference;
+ private CleanupVideoCallback _cleanupCallbackReference;
+
+ public void SetVideoFormatCallbacks(VlcMediaPlayerInstance mediaPlayerInstance, VideoFormatCallback videoFormatCallback, CleanupVideoCallback cleanupCallback)
+ {
+ if (mediaPlayerInstance == IntPtr.Zero)
+ throw new ArgumentException("Media player instance is not initialized.");
+
+ this._videoFormatCallbackReference = videoFormatCallback;
+ this._cleanupCallbackReference = cleanupCallback;
+
+ GetInteropDelegate().Invoke(mediaPlayerInstance, this._videoFormatCallbackReference, this._cleanupCallbackReference);
+ }
+ }
+}
diff --git a/src/Vlc.DotNet.Core.Interops/VlcMediaPlayerInstance.SetCallbacks.cs b/src/Vlc.DotNet.Core.Interops/VlcMediaPlayerInstance.SetCallbacks.cs
deleted file mode 100644
index 10b9daf..0000000
--- a/src/Vlc.DotNet.Core.Interops/VlcMediaPlayerInstance.SetCallbacks.cs
+++ /dev/null
@@ -1,13 +0,0 @@
-using System;
-using Vlc.DotNet.Core.Interops.Signatures;
-
-namespace Vlc.DotNet.Core.Interops
-{
- public sealed partial class VlcMediaPlayerInstance
- {
- public void SetVideoCallbacks(LockVideoCallback lockVideoCallback, UnlockVideoCallback unlockVideoCallback, DisplayVideoCallback displayVideoCallback, IntPtr userData)
- {
- myManager.GetInteropDelegate().Invoke(this.Pointer, lockVideoCallback, unlockVideoCallback, displayVideoCallback, userData);
- }
- }
-}
diff --git a/src/Vlc.DotNet.Core.Interops/VlcMediaPlayerInstance.SetFormatCallbacks.cs b/src/Vlc.DotNet.Core.Interops/VlcMediaPlayerInstance.SetFormatCallbacks.cs
deleted file mode 100644
index 0e67e90..0000000
--- a/src/Vlc.DotNet.Core.Interops/VlcMediaPlayerInstance.SetFormatCallbacks.cs
+++ /dev/null
@@ -1,12 +0,0 @@
-using Vlc.DotNet.Core.Interops.Signatures;
-
-namespace Vlc.DotNet.Core.Interops
-{
- public sealed partial class VlcMediaPlayerInstance
- {
- public void SetVideoFormatCallbacks(VideoFormatCallback videoFormatCallback, CleanupVideoCallback cleanupCallback)
- {
- myManager.GetInteropDelegate().Invoke(this.Pointer, videoFormatCallback, cleanupCallback);
- }
- }
-}
diff --git a/src/Vlc.DotNet.Core.Interops/VlcMediaPlayerInstance.cs b/src/Vlc.DotNet.Core.Interops/VlcMediaPlayerInstance.cs
index 865ddcc..f3892f2 100644
--- a/src/Vlc.DotNet.Core.Interops/VlcMediaPlayerInstance.cs
+++ b/src/Vlc.DotNet.Core.Interops/VlcMediaPlayerInstance.cs
@@ -3,7 +3,7 @@
namespace Vlc.DotNet.Core.Interops
{
- public sealed partial class VlcMediaPlayerInstance : InteropObjectInstance
+ public sealed class VlcMediaPlayerInstance : InteropObjectInstance
{
private readonly VlcManager myManager;
diff --git a/src/Vlc.DotNet.Core/VlcMediaPlayer/VlcMediaPlayer.cs b/src/Vlc.DotNet.Core/VlcMediaPlayer/VlcMediaPlayer.cs
index 3e73b1a..5bc0627 100644
--- a/src/Vlc.DotNet.Core/VlcMediaPlayer/VlcMediaPlayer.cs
+++ b/src/Vlc.DotNet.Core/VlcMediaPlayer/VlcMediaPlayer.cs
@@ -1,7 +1,6 @@
using System;
using System.Collections.Generic;
using System.IO;
-using System.Reflection;
using System.Runtime.InteropServices;
using Vlc.DotNet.Core.Interops;
using Vlc.DotNet.Core.Interops.Signatures;
@@ -143,6 +142,50 @@ public void Play()
Manager.Play(myMediaPlayerInstance);
}
+ ///
+ /// Overload, provided for convenience that calls before
+ ///
+ /// The file to play
+ /// The options to be given
+ public void Play(FileInfo file, params string[] options)
+ {
+ this.SetMedia(file, options);
+ this.Play();
+ }
+
+ ///
+ /// Overload, provided for convenience that calls before
+ ///
+ /// The uri to play
+ /// The options to be given
+ public void Play(Uri uri, params string[] options)
+ {
+ this.SetMedia(uri, options);
+ this.Play();
+ }
+
+ ///
+ /// Overload, provided for convenience that calls before
+ ///
+ /// The mrl to play
+ /// The options to be given
+ public void Play(string mrl, params string[] options)
+ {
+ this.SetMedia(mrl, options);
+ this.Play();
+ }
+
+ ///
+ /// Overload, provided for convenience that calls before
+ ///
+ /// The stream to play
+ /// The options to be given
+ public void Play(Stream stream, params string[] options)
+ {
+ this.SetMedia(stream, options);
+ this.Play();
+ }
+
public void Pause()
{
Manager.Pause(myMediaPlayerInstance);
diff --git a/src/Vlc.DotNet.Core/VlcMediaPlayer/VlcMediaPlayerVideo.cs b/src/Vlc.DotNet.Core/VlcMediaPlayer/VlcMediaPlayerVideo.cs
new file mode 100644
index 0000000..edd1efe
--- /dev/null
+++ b/src/Vlc.DotNet.Core/VlcMediaPlayer/VlcMediaPlayerVideo.cs
@@ -0,0 +1,56 @@
+using Vlc.DotNet.Core.Interops.Signatures;
+
+namespace Vlc.DotNet.Core
+{
+ using System;
+
+ public sealed partial class VlcMediaPlayer
+ {
+ ///
+ /// Sets the video callbacks to render decoded video to a custom area in memory.
+ /// The media player will hold a reference on the IVideoCallbacks parameter
+ ///
+ ///
+ /// Rendering video into custom memory buffers is considerably less efficient than rendering in a custom window as normal.
+ /// See libvlc_video_set_callbacks for detailed explanations
+ ///
+ ///
+ /// Callback to lock video memory (must not be NULL)
+ ///
+ ///
+ /// Callback to unlock video memory (or NULL if not needed)
+ ///
+ ///
+ /// Callback to display video (or NULL if not needed)
+ ///
+ ///
+ /// Private pointer for the three callbacks (as first parameter).
+ /// This parameter will be overriden if is used
+ ///
+ public void SetVideoCallbacks(LockVideoCallback lockVideo, UnlockVideoCallback unlockVideo, DisplayVideoCallback display, IntPtr userData)
+ {
+ if (lockVideo == null)
+ {
+ throw new ArgumentNullException(nameof(lockVideo));
+ }
+
+ this.Manager.SetVideoCallbacks(this.myMediaPlayerInstance, lockVideo, unlockVideo, display, userData);
+ }
+
+ ///
+ /// Set decoded video chroma and dimensions. This only works in combination with
+ ///
+ ///
+ /// Callback to select the video format (cannot be NULL)
+ /// Callback to release any allocated resources (or NULL)
+ public void SetVideoFormatCallbacks(VideoFormatCallback videoFormat, CleanupVideoCallback cleanup)
+ {
+ if (videoFormat == null)
+ {
+ throw new ArgumentNullException(nameof(videoFormat));
+ }
+
+ this.Manager.SetVideoFormatCallbacks(this.myMediaPlayerInstance, videoFormat, cleanup);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Vlc.DotNet.Forms/VlcControl.cs b/src/Vlc.DotNet.Forms/VlcControl.cs
index 507a9e8..a4db14c 100644
--- a/src/Vlc.DotNet.Forms/VlcControl.cs
+++ b/src/Vlc.DotNet.Forms/VlcControl.cs
@@ -146,60 +146,32 @@ public DirectoryInfo OnVlcLibDirectoryNeeded()
public void Play()
{
- //EndInit();
- if (myVlcMediaPlayer != null)
- {
- myVlcMediaPlayer.Play();
- }
+ myVlcMediaPlayer?.Play();
}
public void Play(FileInfo file, params string[] options)
{
- //EndInit();
- if (myVlcMediaPlayer != null)
- {
- myVlcMediaPlayer.SetMedia(file, options);
- Play();
- }
+ myVlcMediaPlayer?.Play(file, options);
}
public void Play(Uri uri, params string[] options)
{
- //EndInit();
- if (myVlcMediaPlayer != null)
- {
- myVlcMediaPlayer.SetMedia(uri, options);
- Play();
- }
+ myVlcMediaPlayer?.Play(uri, options);
}
public void Play(string mrl, params string[] options)
{
- //EndInit();
- if (myVlcMediaPlayer != null)
- {
- myVlcMediaPlayer.SetMedia(mrl, options);
- Play();
- }
+ myVlcMediaPlayer?.Play(mrl, options);
}
public void Play(Stream stream, params string[] options)
{
- //EndInit();
- if (myVlcMediaPlayer != null)
- {
- myVlcMediaPlayer.SetMedia(stream, options);
- Play();
- }
+ myVlcMediaPlayer?.Play(stream, options);
}
public void Pause()
{
- //EndInit();
- if (myVlcMediaPlayer != null)
- {
- myVlcMediaPlayer.Pause();
- }
+ myVlcMediaPlayer?.Pause();
}
public void Stop()
diff --git a/src/Vlc.DotNet.Wpf/Vlc.DotNet.Wpf.csproj b/src/Vlc.DotNet.Wpf/Vlc.DotNet.Wpf.csproj
index 300383c..6fd12be 100644
--- a/src/Vlc.DotNet.Wpf/Vlc.DotNet.Wpf.csproj
+++ b/src/Vlc.DotNet.Wpf/Vlc.DotNet.Wpf.csproj
@@ -9,20 +9,18 @@
-
- Vlc.DotNet.pfx.snk
-
-
-
-
+
+
+
+
+
-
diff --git a/src/Vlc.DotNet.Wpf/VlcControl.cs b/src/Vlc.DotNet.Wpf/VlcControl.cs
index 467a5a2..fbe3bd0 100644
--- a/src/Vlc.DotNet.Wpf/VlcControl.cs
+++ b/src/Vlc.DotNet.Wpf/VlcControl.cs
@@ -1,26 +1,55 @@
-using System.Windows.Forms.Integration;
+using System;
+using System.Windows.Data;
+using System.Windows.Media;
namespace Vlc.DotNet.Wpf
{
- public class VlcControl : WindowsFormsHost
+ using System.Windows.Controls;
+
+ ///
+ /// The Wpf component that allows to display a video in a Wpf way
+ ///
+ public class VlcControl: UserControl, IDisposable
{
- public Forms.VlcControl MediaPlayer { get; }
+ ///
+ /// The Viewbox that contains the video image
+ ///
+ private Viewbox viewBox;
+
+ ///
+ /// The image that displays the video
+ ///
+ private readonly Image videoContent = new Image { ClipToBounds = true };
+ ///
+ /// The class that provides video source
+ ///
+ private readonly VlcVideoSourceProvider sourceProvider;
+
+ public VlcVideoSourceProvider SourceProvider => sourceProvider;
+
+ ///
+ /// The constructor
+ ///
public VlcControl()
{
- MediaPlayer = new Forms.VlcControl();
- this.Child = MediaPlayer;
+ sourceProvider = new VlcVideoSourceProvider(this.Dispatcher);
+ this.viewBox = new Viewbox
+ {
+ Child = this.videoContent,
+ Stretch = Stretch.Uniform
+ };
+
+ this.Content = this.viewBox;
+ this.Background = Brushes.Black;
+ // Binds the VideoSource to the Image.Source property
+ this.videoContent.SetBinding(Image.SourceProperty, new Binding(nameof(VlcVideoSourceProvider.VideoSource)) { Source = sourceProvider });
}
- protected override void Dispose(bool disposing)
+ ///
+ public void Dispose()
{
- if (disposing)
- {
- this.Child = null;
- this.MediaPlayer.Dispose();
- }
-
- base.Dispose(disposing);
+ sourceProvider.Dispose();
}
}
}
\ No newline at end of file
diff --git a/src/Vlc.DotNet.Wpf/VlcLibDirectoryNeededEventArgs.cs b/src/Vlc.DotNet.Wpf/VlcLibDirectoryNeededEventArgs.cs
deleted file mode 100644
index e99987a..0000000
--- a/src/Vlc.DotNet.Wpf/VlcLibDirectoryNeededEventArgs.cs
+++ /dev/null
@@ -1,15 +0,0 @@
-using System;
-using System.IO;
-
-namespace Vlc.DotNet.Wpf
-{
- public sealed class VlcLibDirectoryNeededEventArgs : EventArgs
- {
- public DirectoryInfo VlcLibDirectory { get; set; }
-
- public VlcLibDirectoryNeededEventArgs()
- {
-
- }
- }
-}
\ No newline at end of file
diff --git a/src/Vlc.DotNet.Wpf/VlcVideoSourceProvider.cs b/src/Vlc.DotNet.Wpf/VlcVideoSourceProvider.cs
new file mode 100644
index 0000000..1d5bcd2
--- /dev/null
+++ b/src/Vlc.DotNet.Wpf/VlcVideoSourceProvider.cs
@@ -0,0 +1,267 @@
+namespace Vlc.DotNet.Wpf
+{
+ using System;
+ using System.IO;
+#if NET45
+ using System.IO.MemoryMappedFiles;
+#endif
+ using System.Runtime.InteropServices;
+ using System.Windows;
+ using System.Windows.Media;
+ using Vlc.DotNet.Core;
+ using System.Windows.Interop;
+ using System.ComponentModel;
+ using System.Windows.Threading;
+
+ ///
+ /// The class that can provide a Wpf Image Source to display the video.
+ ///
+ public class VlcVideoSourceProvider : INotifyPropertyChanged, IDisposable
+ {
+#if NET45
+ ///
+ /// The memory mapped file that contains the picture data
+ ///
+ private MemoryMappedFile memoryMappedFile;
+
+ ///
+ /// The view that contains the pointer to the buffer that contains the picture data
+ ///
+ private MemoryMappedViewAccessor memoryMappedView;
+#else
+ ///
+ /// The memory mapped file handle that contains the picture data
+ ///
+ private IntPtr memoryMappedFile;
+
+ ///
+ /// The pointer to the buffer that contains the picture data
+ ///
+ private IntPtr memoryMappedView;
+#endif
+
+ private ImageSource videoSource;
+
+ private Dispatcher dispatcher;
+
+ public VlcVideoSourceProvider(Dispatcher dispatcher)
+ {
+ this.dispatcher = dispatcher;
+ }
+
+ ///
+ /// The Image source that represents the video.
+ ///
+ public ImageSource VideoSource
+ {
+ get
+ {
+ return this.videoSource;
+ }
+
+ private set
+ {
+ if (!Object.ReferenceEquals(this.videoSource, value))
+ {
+ this.videoSource = value;
+ this.OnPropertyChanged(nameof(VideoSource));
+ }
+ }
+ }
+
+ ///
+ /// The media player instance. You must call before using this.
+ ///
+ public VlcMediaPlayer MediaPlayer { get; private set; }
+
+ ///
+ /// Creates the player. This method must be called before using
+ ///
+ /// The directory where to find the vlc library
+ public void CreatePlayer(DirectoryInfo vlcLibDirectory)
+ {
+ var directoryInfo = vlcLibDirectory ?? throw new ArgumentNullException(nameof(vlcLibDirectory));
+
+ this.MediaPlayer = new VlcMediaPlayer(directoryInfo);
+
+ this.MediaPlayer.SetVideoFormatCallbacks(this.VideoFormat, this.CleanupVideo);
+ this.MediaPlayer.SetVideoCallbacks(LockVideo, null, DisplayVideo, IntPtr.Zero);
+ }
+
+ ///
+ /// Aligns dimension to the next multiple of mod
+ ///
+ /// The dimension to be aligned
+ /// The modulus
+ /// The aligned dimension
+ private uint GetAlignedDimension(uint dimension, uint mod)
+ {
+ var modResult = dimension % mod;
+ if (modResult == 0)
+ {
+ return dimension;
+ }
+
+ return dimension + mod - (dimension % mod);
+ }
+
+#region Vlc video callbacks
+ ///
+ /// Called by vlc when the video format is needed. This method allocats the picture buffers for vlc and tells it to set the chroma to RV32
+ ///
+ /// The user data that will be given to the callback. It contains the pointer to the buffer
+ /// The chroma
+ /// The visible width
+ /// The visible height
+ /// The buffer width
+ /// The buffer height
+ /// The number of buffers allocated
+ private uint VideoFormat(out IntPtr userdata, IntPtr chroma, ref uint width, ref uint height, ref uint pitches, ref uint lines)
+ {
+ var pixelFormat = PixelFormats.Bgr32;
+ Marshal.WriteByte(chroma, (byte)'R');
+ Marshal.WriteByte(chroma, 1, (byte)'V');
+ Marshal.WriteByte(chroma, 2, (byte)'3');
+ Marshal.WriteByte(chroma, 3, (byte)'2');
+
+ pitches = this.GetAlignedDimension((uint)(width*pixelFormat.BitsPerPixel) / 8, 32);
+ lines = this.GetAlignedDimension(height, 32);
+
+ var size = pitches * lines;
+#if NET45
+ this.memoryMappedFile = MemoryMappedFile.CreateNew(null, size);
+ var handle = this.memoryMappedFile.SafeMemoryMappedFileHandle.DangerousGetHandle();
+#else
+ this.memoryMappedFile = Win32Interop.CreateFileMapping(new IntPtr(-1), IntPtr.Zero,
+ Win32Interop.PageAccess.ReadWrite, 0, (int)size, null);
+ var handle = this.memoryMappedFile;
+#endif
+ var args = new
+ {
+ width = width,
+ height = height,
+ pixelFormat = pixelFormat,
+ pitches = pitches
+ };
+
+ this.dispatcher.Invoke((Action)(() =>
+ {
+ this.VideoSource = (InteropBitmap)Imaging.CreateBitmapSourceFromMemorySection(handle,
+ (int)args.width, (int)args.height, args.pixelFormat, (int)args.pitches, 0);
+ }));
+
+#if NET45
+ this.memoryMappedView = this.memoryMappedFile.CreateViewAccessor();
+ var viewHandle = this.memoryMappedView.SafeMemoryMappedViewHandle.DangerousGetHandle();
+#else
+ this.memoryMappedView = Win32Interop.MapViewOfFile(this.memoryMappedFile, Win32Interop.FileMapAccess.AllAccess, 0, 0, size);
+ var viewHandle = this.memoryMappedView;
+#endif
+ userdata = viewHandle;
+ return 1;
+ }
+
+ ///
+ /// Called by Vlc when it requires a cleanup
+ ///
+ /// The parameter is not used
+ private void CleanupVideo(ref IntPtr userdata)
+ {
+ // This callback may be called by Dispose in the Dispatcher thread, in which case it deadlocks if we call RemoveVideo again in the same thread.
+ if (!disposedValue)
+ {
+ this.dispatcher.Invoke((Action)this.RemoveVideo);
+ }
+ }
+
+ ///
+ /// Called by libvlc when it wants to acquire a buffer where to write
+ ///
+ /// The pointer to the buffer (the out parameter of the callback)
+ /// The pointer to the planes array. Since only one plane has been allocated, the array has only one value to be allocated.
+ /// The pointer that is passed to the other callbacks as a picture identifier, this is not used
+ private IntPtr LockVideo(IntPtr userdata, IntPtr planes)
+ {
+ Marshal.WriteIntPtr(planes, userdata);
+ return userdata;
+ }
+
+ ///
+ /// Called by libvlc when the picture has to be displayed.
+ ///
+ /// The pointer to the buffer (the out parameter of the callback)
+ /// The pointer returned by the callback. This is not used.
+ private void DisplayVideo(IntPtr userdata, IntPtr picture)
+ {
+ // Invalidates the bitmap
+ this.dispatcher.BeginInvoke((Action) (() =>
+ {
+ (this.VideoSource as InteropBitmap)?.Invalidate();
+ }));
+ }
+ #endregion
+
+ ///
+ /// Removes the video (must be called from the Dispatcher thread)
+ ///
+ private void RemoveVideo()
+ {
+ this.VideoSource = null;
+
+#if NET45
+ this.memoryMappedView?.Dispose();
+ this.memoryMappedView = null;
+ this.memoryMappedFile?.Dispose();
+ this.memoryMappedFile = null;
+#else
+ if (this.memoryMappedFile != IntPtr.Zero)
+ {
+ Win32Interop.UnmapViewOfFile(this.memoryMappedView);
+ this.memoryMappedView = IntPtr.Zero;
+ Win32Interop.CloseHandle(this.memoryMappedFile);
+ this.memoryMappedFile = IntPtr.Zero;
+ }
+#endif
+ }
+
+ #region IDisposable Support
+ private bool disposedValue = false;
+
+ ///
+ /// Disposes the control.
+ ///
+ /// The parameter is not used.
+ protected virtual void Dispose(bool disposing)
+ {
+ if (!disposedValue)
+ {
+ disposedValue = true;
+ this.MediaPlayer?.Dispose();
+ this.MediaPlayer = null;
+ this.dispatcher.Invoke((Action)this.RemoveVideo);
+ }
+ }
+
+ ///
+ /// The destructor
+ ///
+ ~VlcVideoSourceProvider() {
+ Dispose(false);
+ }
+
+ ///
+ public void Dispose()
+ {
+ Dispose(true);
+ GC.SuppressFinalize(this);
+ }
+#endregion
+
+ public event PropertyChangedEventHandler PropertyChanged;
+
+ protected virtual void OnPropertyChanged(string propertyName)
+ {
+ PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
+ }
+ }
+}
diff --git a/src/Vlc.DotNet.Wpf/Win32Interop.cs b/src/Vlc.DotNet.Wpf/Win32Interop.cs
new file mode 100644
index 0000000..c22d216
--- /dev/null
+++ b/src/Vlc.DotNet.Wpf/Win32Interop.cs
@@ -0,0 +1,75 @@
+#if !NET45
+namespace Vlc.DotNet.Wpf
+{
+ using System;
+ using System.Runtime.InteropServices;
+
+ public static class Win32Interop
+ {
+ ///
+ /// Creates or opens a named or unnamed file mapping object for a specified file.
+ ///
+ /// A handle to the file from which to create a file mapping object.
+ /// A pointer to a SECURITY_ATTRIBUTES structure that determines whether a returned handle can be inherited by child processes. The lpSecurityDescriptor member of the SECURITY_ATTRIBUTES structure specifies a security descriptor for a new file mapping object.
+ /// Specifies the page protection of the file mapping object. All mapped views of the object must be compatible with this protection.
+ /// The high-order DWORD of the maximum size of the file mapping object.
+ /// The low-order DWORD of the maximum size of the file mapping object.
+ /// The name of the file mapping object.
+ /// The value is a handle to the newly created file mapping object.
+ [DllImport("kernel32", SetLastError = true)]
+ public static extern IntPtr CreateFileMapping(IntPtr hFile, IntPtr lpAttributes, PageAccess flProtect, int dwMaximumSizeLow, int dwMaximumSizeHigh, string lpName);
+
+ ///
+ /// Maps a view of a file mapping into the address space of a calling process.
+ ///
+ /// A handle to a file mapping object. The CreateFileMapping and OpenFileMapping functions return this handle.
+ /// The type of access to a file mapping object, which determines the protection of the pages. This parameter can be one of the following values.
+ /// A high-order DWORD of the file offset where the view begins.
+ /// A low-order DWORD of the file offset where the view is to begin. The combination of the high and low offsets must specify an offset within the file mapping.
+ /// The number of bytes of a file mapping to map to the view. All bytes must be within the maximum size specified by CreateFileMapping. If this parameter is 0 (zero), the mapping extends from the specified offset to the end of the file mapping.
+ /// The value is the starting address of the mapped view.
+ [DllImport("kernel32.dll", SetLastError = true)]
+ public static extern IntPtr MapViewOfFile(IntPtr hFileMappingObject, FileMapAccess dwDesiredAccess, uint dwFileOffsetHigh, uint dwFileOffsetLow, uint dwNumberOfBytesToMap);
+
+ ///
+ /// Unmaps a mapped view of a file from the calling process's address space.
+ ///
+ /// A pointer to the base address of the mapped view of a file that is to be unmapped. This value must be identical to the value returned by a previous call to the MapViewOfFile or MapViewOfFileEx function.
+ /// If the function succeeds, the return value is nonzero.
+ [DllImport("kernel32", SetLastError = true)]
+ public static extern bool UnmapViewOfFile(IntPtr lpBaseAddress);
+
+ ///
+ /// Closes an open object handle.
+ ///
+ /// A valid handle to an open object.
+ /// If the function succeeds, the return value is nonzero.
+ [DllImport("kernel32", SetLastError = true)]
+ public static extern bool CloseHandle(IntPtr handle);
+
+ public enum PageAccess
+ {
+ NoAccess = 0x01,
+ ReadOnly = 0x02,
+ ReadWrite = 0x04,
+ WriteCopy = 0x08,
+ Execute = 0x10,
+ ExecuteRead = 0x20,
+ ExecuteReadWrite = 0x40,
+ ExecuteWriteCopy = 0x80,
+ Guard = 0x100,
+ NoCache = 0x200,
+ WriteCombine = 0x400
+ }
+
+ public enum FileMapAccess : uint
+ {
+ Write = 0x00000002,
+ Read = 0x00000004,
+ AllAccess = 0x000f001f,
+ Copy = 0x00000001,
+ Execute = 0x00000020
+ }
+ }
+}
+#endif
\ No newline at end of file