LibVLC.cs 35.7 KB
Newer Older
Martin Finkel's avatar
Martin Finkel committed
1 2
using System;
using System.Collections.Generic;
Martin Finkel's avatar
Martin Finkel committed
3
using System.Linq;
4
using System.Runtime.InteropServices;
Martin Finkel's avatar
Martin Finkel committed
5
using System.Threading;
Martin Finkel's avatar
Martin Finkel committed
6
using System.Threading.Tasks;
7
using LibVLCSharp.Shared.Helpers;
Martin Finkel's avatar
Martin Finkel committed
8
using LibVLCSharp.Shared.Structures;
9

10
namespace LibVLCSharp.Shared
11
{
Martin Finkel's avatar
Martin Finkel committed
12 13 14 15
    /// <summary>
    /// Main LibVLC API object representing a libvlc instance in native code. 
    /// Note: You may create multiple mediaplayers from a single LibVLC instance
    /// </summary>
Martin Finkel's avatar
Martin Finkel committed
16
    public class LibVLC : Internal
17
    {
18 19 20 21 22
        /// <summary>
        /// Determines whether two object instances are equal. 
        /// </summary>
        /// <param name="other">other libvlc instance to compare with</param>
        /// <returns>true if same instance, false otherwise</returns>
Martin Finkel's avatar
Martin Finkel committed
23 24 25 26 27
        protected bool Equals(LibVLC other)
        {
            return NativeReference.Equals(other.NativeReference);
        }

28 29 30 31 32
        /// <summary>
        /// Determines whether two object instances are equal. 
        /// </summary>
        /// <param name="obj">other libvlc instance to compare with</param>
        /// <returns>true if same instance, false otherwise</returns>
33
        public override bool Equals(object? obj)
34
        {
Martin Finkel's avatar
Martin Finkel committed
35 36
            if (ReferenceEquals(null, obj)) return false;
            if (ReferenceEquals(this, obj)) return true;
37
            if (obj.GetType() != GetType()) return false;
Martin Finkel's avatar
Martin Finkel committed
38 39
            return Equals((LibVLC) obj);
        }
Martin Finkel's avatar
Martin Finkel committed
40

41
        LogCallback? _logCallback;
Martin Finkel's avatar
Martin Finkel committed
42 43 44 45 46
        readonly object _logLock = new object();

        /// <summary>
        /// The real log event handlers.
        /// </summary>
47
        EventHandler<LogEventArgs>? _log;
Martin Finkel's avatar
Martin Finkel committed
48

49
#if NETFRAMEWORK || NETSTANDARD
Martin Finkel's avatar
Martin Finkel committed
50
        IntPtr _logFileHandle;
Martin Finkel's avatar
Martin Finkel committed
51
#endif
52 53 54 55
        /// <summary>
        /// Returns the hashcode for this libvlc instance
        /// </summary>
        /// <returns></returns>
Martin Finkel's avatar
Martin Finkel committed
56 57 58 59 60 61 62 63
        public override int GetHashCode()
        {
            return NativeReference.GetHashCode();
        }

        [StructLayout(LayoutKind.Explicit, Size = 0)]
        internal struct Native
        {
64
            [DllImport(Constants.LibraryName, CallingConvention = CallingConvention.Cdecl,
Martin Finkel's avatar
Martin Finkel committed
65 66 67
                EntryPoint = "libvlc_new")]
            internal static extern IntPtr LibVLCNew(int argc, IntPtr[] argv);

68
            [DllImport(Constants.LibraryName, CallingConvention = CallingConvention.Cdecl,
Martin Finkel's avatar
Martin Finkel committed
69 70 71
                EntryPoint = "libvlc_release")]
            internal static extern void LibVLCRelease(IntPtr libVLC);

72
#if NETFRAMEWORK || NETSTANDARD
73
            [DllImport(Constants.LibraryName, CallingConvention = CallingConvention.Cdecl,
Martin Finkel's avatar
Martin Finkel committed
74
                EntryPoint = "libvlc_add_intf")]
Martin Finkel's avatar
Martin Finkel committed
75
            internal static extern int LibVLCAddInterface(IntPtr libVLC, IntPtr name);
76
#endif
77
            [DllImport(Constants.LibraryName, CallingConvention = CallingConvention.Cdecl,
Martin Finkel's avatar
Martin Finkel committed
78 79 80
                EntryPoint = "libvlc_set_exit_handler")]
            internal static extern void LibVLCSetExitHandler(IntPtr libVLC, IntPtr cb, IntPtr opaque);

81
            [DllImport(Constants.LibraryName, CallingConvention = CallingConvention.Cdecl,
Martin Finkel's avatar
Martin Finkel committed
82
                EntryPoint = "libvlc_set_user_agent")]
Martin Finkel's avatar
Martin Finkel committed
83
            internal static extern void LibVLCSetUserAgent(IntPtr libVLC, IntPtr name, IntPtr http);
Martin Finkel's avatar
Martin Finkel committed
84

85
            [DllImport(Constants.LibraryName, CallingConvention = CallingConvention.Cdecl,
Martin Finkel's avatar
Martin Finkel committed
86
                EntryPoint = "libvlc_set_app_id")]
Martin Finkel's avatar
Martin Finkel committed
87
            internal static extern void LibVLCSetAppId(IntPtr libVLC, IntPtr id, IntPtr version, IntPtr icon);
Martin Finkel's avatar
Martin Finkel committed
88

Martin Finkel's avatar
Martin Finkel committed
89 90 91 92
            [DllImport(Constants.LibraryName, CallingConvention = CallingConvention.Cdecl,
                EntryPoint = "libvlc_log_unset")]
            internal static extern void LibVLCLogUnset(IntPtr libVLC);

93
            [DllImport(Constants.LibraryName, CallingConvention = CallingConvention.Cdecl,
Martin Finkel's avatar
Martin Finkel committed
94 95 96
                EntryPoint = "libvlc_log_set_file")]
            internal static extern void LibVLCLogSetFile(IntPtr libVLC, IntPtr stream);

97
            [DllImport(Constants.LibraryName, CallingConvention = CallingConvention.Cdecl,
Martin Finkel's avatar
Martin Finkel committed
98 99 100
                CharSet = CharSet.Ansi, EntryPoint = "libvlc_log_get_context")]
            internal static extern void LibVLCLogGetContext(IntPtr ctx, out IntPtr module, out IntPtr file, out UIntPtr line);

Martin Finkel's avatar
Martin Finkel committed
101 102 103 104
            [DllImport(Constants.LibraryName, CallingConvention = CallingConvention.Cdecl,
                EntryPoint = "libvlc_log_set")]
            internal static extern void LibVLCLogSet(IntPtr libVLC, LogCallback cb, IntPtr data);

105
            [DllImport(Constants.LibraryName, CallingConvention = CallingConvention.Cdecl,
Martin Finkel's avatar
Martin Finkel committed
106 107 108
                EntryPoint = "libvlc_module_description_list_release")]
            internal static extern void LibVLCModuleDescriptionListRelease(IntPtr moduleDescriptionList);

109
            [DllImport(Constants.LibraryName, CallingConvention = CallingConvention.Cdecl,
Martin Finkel's avatar
Martin Finkel committed
110 111 112
                EntryPoint = "libvlc_audio_filter_list_get")]
            internal static extern IntPtr LibVLCAudioFilterListGet(IntPtr libVLC);

113
            [DllImport(Constants.LibraryName, CallingConvention = CallingConvention.Cdecl,
Martin Finkel's avatar
Martin Finkel committed
114 115 116
                EntryPoint = "libvlc_video_filter_list_get")]
            internal static extern IntPtr LibVLCVideoFilterListGet(IntPtr libVLC);

117
            [DllImport(Constants.LibraryName, CallingConvention = CallingConvention.Cdecl,
Martin Finkel's avatar
Martin Finkel committed
118 119 120
                EntryPoint = "libvlc_audio_output_list_get")]
            internal static extern IntPtr LibVLCAudioOutputListGet(IntPtr libVLC);

121
            [DllImport(Constants.LibraryName, CallingConvention = CallingConvention.Cdecl,
Martin Finkel's avatar
Martin Finkel committed
122 123 124
                EntryPoint = "libvlc_audio_output_list_release")]
            internal static extern void LibVLCAudioOutputListRelease(IntPtr list);

125
            [DllImport(Constants.LibraryName, CallingConvention = CallingConvention.Cdecl,
Martin Finkel's avatar
Martin Finkel committed
126
                EntryPoint = "libvlc_audio_output_device_list_get")]
127
            internal static extern IntPtr LibVLCAudioOutputDeviceListGet(IntPtr libVLC, IntPtr aout);
Martin Finkel's avatar
Martin Finkel committed
128

129
            [DllImport(Constants.LibraryName, CallingConvention = CallingConvention.Cdecl,
Martin Finkel's avatar
Martin Finkel committed
130 131 132
                EntryPoint = "libvlc_audio_output_device_list_release")]
            internal static extern void LibVLCAudioOutputDeviceListRelease(IntPtr list);

133
            [DllImport(Constants.LibraryName, CallingConvention = CallingConvention.Cdecl,
Martin Finkel's avatar
Martin Finkel committed
134
                EntryPoint = "libvlc_media_discoverer_list_get")]
Martin Finkel's avatar
Martin Finkel committed
135
            internal static extern UIntPtr LibVLCMediaDiscovererListGet(IntPtr libVLC, MediaDiscovererCategory category, out IntPtr pppServices);
Martin Finkel's avatar
Martin Finkel committed
136

137
            [DllImport(Constants.LibraryName, CallingConvention = CallingConvention.Cdecl,
Martin Finkel's avatar
Martin Finkel committed
138
                EntryPoint = "libvlc_media_discoverer_list_release")]
Martin Finkel's avatar
Martin Finkel committed
139
            internal static extern void LibVLCMediaDiscovererListRelease(IntPtr ppServices, UIntPtr count);
Martin Finkel's avatar
Martin Finkel committed
140

141
            [DllImport(Constants.LibraryName, CallingConvention = CallingConvention.Cdecl,
Martin Finkel's avatar
Martin Finkel committed
142
                EntryPoint = "libvlc_dialog_set_callbacks")]
143
            internal static extern void LibVLCDialogSetCallbacks(IntPtr libVLC, DialogCallbacks callbacks, IntPtr data);
Martin Finkel's avatar
Martin Finkel committed
144

145
            [DllImport(Constants.LibraryName, CallingConvention = CallingConvention.Cdecl,
Martin Finkel's avatar
Martin Finkel committed
146
                EntryPoint = "libvlc_renderer_discoverer_list_get")]
Martin Finkel's avatar
Martin Finkel committed
147
            internal static extern UIntPtr LibVLCRendererDiscovererGetList(IntPtr libVLC, out IntPtr discovererList);
Martin Finkel's avatar
Martin Finkel committed
148

149
            [DllImport(Constants.LibraryName, CallingConvention = CallingConvention.Cdecl,
Martin Finkel's avatar
Martin Finkel committed
150
                EntryPoint = "libvlc_renderer_discoverer_list_release")]
Martin Finkel's avatar
Martin Finkel committed
151
            internal static extern void LibVLCRendererDiscovererReleaseList(IntPtr discovererList, UIntPtr count);
Martin Finkel's avatar
Martin Finkel committed
152

Martin Finkel's avatar
Martin Finkel committed
153 154 155 156
            [DllImport(Constants.LibraryName, CallingConvention = CallingConvention.Cdecl,
                EntryPoint = "libvlc_retain")]
            internal static extern void LibVLCRetain(IntPtr libVLC);

157 158 159
            [DllImport(Constants.LibraryName, CallingConvention = CallingConvention.Cdecl,
                EntryPoint = "libvlc_get_version")]
            internal static extern IntPtr LibVLCVersion();
Martin Finkel's avatar
Martin Finkel committed
160

Martin Finkel's avatar
Martin Finkel committed
161 162 163
            [DllImport(Constants.LibraryName, CallingConvention = CallingConvention.Cdecl,
                EntryPoint = "libvlc_get_changeset")]
            internal static extern IntPtr LibVLCChangeset();
164 165 166 167 168 169 170 171 172 173 174 175 176

            [DllImport(Constants.LibraryName, CallingConvention = CallingConvention.Cdecl,
                EntryPoint = "libvlc_errmsg")]
            internal static extern IntPtr LibVLCErrorMessage();

            [DllImport(Constants.LibraryName, CallingConvention = CallingConvention.Cdecl,
                EntryPoint = "libvlc_clearerr")]
            internal static extern void LibVLCClearError();

            [DllImport(Constants.LibraryName, CallingConvention = CallingConvention.Cdecl,
                EntryPoint = "libvlc_get_compiler")]
            internal static extern IntPtr LibVLCGetCompiler();

Martin Finkel's avatar
Martin Finkel committed
177
#if ANDROID
178
            [DllImport(Constants.LibraryName, CallingConvention = CallingConvention.Cdecl,
Martin Finkel's avatar
Martin Finkel committed
179 180 181 182 183 184
                EntryPoint = "libvlc_media_player_set_android_context")]
            internal static extern void LibVLCMediaPlayerSetAndroidContext(IntPtr mediaPlayer, IntPtr aWindow);
#endif
        }

        /// <summary>
185 186 187 188 189 190
        /// Create and initialize a libvlc instance.
        /// This functions accept a list of &quot;command line&quot; arguments similar to the
        /// main(). These arguments affect the LibVLC instance default configuration.
        /// LibVLC may create threads. Therefore, any thread-unsafe process
        /// initialization must be performed before calling libvlc_new(). In particular
        /// and where applicable:
Martin Finkel's avatar
Martin Finkel committed
191 192 193
        /// <para>- setlocale() and textdomain(),</para>
        /// <para>- setenv(), unsetenv() and putenv(),</para>
        /// <para>- with the X11 display system, XInitThreads()</para>
194
        /// (see also libvlc_media_player_set_xwindow()) and
Martin Finkel's avatar
Martin Finkel committed
195 196
        /// <para>- on Microsoft Windows, SetErrorMode().</para>
        /// <para>- sigprocmask() shall never be invoked; pthread_sigmask() can be used.</para>
197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219
        /// On POSIX systems, the SIGCHLD signalmust notbe ignored, i.e. the
        /// signal handler must set to SIG_DFL or a function pointer, not SIG_IGN.
        /// Also while LibVLC is active, the wait() function shall not be called, and
        /// any call to waitpid() shall use a strictly positive value for the first
        /// parameter (i.e. the PID). Failure to follow those rules may lead to a
        /// deadlock or a busy loop.
        /// Also on POSIX systems, it is recommended that the SIGPIPE signal be blocked,
        /// even if it is not, in principles, necessary, e.g.:
        /// On Microsoft Windows Vista/2008, the process error mode
        /// SEM_FAILCRITICALERRORS flagmustbe set before using LibVLC.
        /// On later versions, that is optional and unnecessary.
        /// Also on Microsoft Windows (Vista and any later version), setting the default
        /// DLL directories to SYSTEM32 exclusively is strongly recommended for
        /// security reasons:
        /// Arguments are meant to be passed from the command line to LibVLC, just like
        /// VLC media player does. The list of valid arguments depends on the LibVLC
        /// version, the operating system and platform, and set of available LibVLC
        /// plugins. Invalid or unsupported arguments will cause the function to fail
        /// (i.e. return NULL). Also, some arguments may alter the behaviour or
        /// otherwise interfere with other LibVLC functions.
        /// There is absolutely no warranty or promise of forward, backward and
        /// cross-platform compatibility with regards to libvlc_new() arguments.
        /// We recommend that you do not use them, other than when debugging.
220 221 222 223
        /// </summary>
        /// <param name="options">list of arguments (should be NULL)</param>
        /// <returns>the libvlc instance or NULL in case of error</returns>
        public LibVLC(params string[] options)
Martin Finkel's avatar
Martin Finkel committed
224
            : base(() => MarshalUtils.CreateWithOptions(PatchOptions(options), Native.LibVLCNew), Native.LibVLCRelease)
Martin Finkel's avatar
Martin Finkel committed
225 226 227
        {
        }

Martin Finkel's avatar
Martin Finkel committed
228 229 230 231 232 233 234 235 236 237 238 239 240 241
        /// <summary>
        /// Make dirty hacks to include necessary defaults on some platforms.
        /// </summary>
        /// <param name="options">The options given by the user</param>
        /// <returns>The patched options</returns>
        static string[] PatchOptions(string[] options)
        {
#if UWP
            return options.Concat(new[] {"--aout=winstore"}).ToArray();
#else
            return options;
#endif
        }

242
        /// <summary>
243
        /// Dispose of this libvlc instance
244 245
        /// </summary>
        /// <param name="disposing"></param>
246
        protected override void Dispose(bool disposing)
Martin Finkel's avatar
Martin Finkel committed
247
        {
248 249 250 251 252 253
            if (IsDisposed || NativeReference == IntPtr.Zero)
                return;

            if (disposing)
            {
                UnsetDialogHandlers();
254
                UnsetLog();
255 256 257
            }

            base.Dispose(disposing);
Martin Finkel's avatar
Martin Finkel committed
258
        }
259 260 261 262 263 264 265

        /// <summary>
        /// Determines whether 2 instances of libvlc are equals
        /// </summary>
        /// <param name="libvlc1">1st instance of libvlc</param>
        /// <param name="libvlc2">2nd instance of libvlc</param>
        /// <returns></returns>
266
        public static bool operator ==(LibVLC? libvlc1, LibVLC? libvlc2)
Martin Finkel's avatar
Martin Finkel committed
267
        {
268
            return libvlc1?.NativeReference == libvlc2?.NativeReference;
Martin Finkel's avatar
Martin Finkel committed
269 270
        }

271 272 273 274 275 276
        /// <summary>
        /// Determines whether 2 instances of libvlc are different
        /// </summary>
        /// <param name="libvlc1">1st instance of libvlc</param>
        /// <param name="libvlc2">2nd instance of libvlc</param>
        /// <returns></returns>
277
        public static bool operator !=(LibVLC? libvlc1, LibVLC? libvlc2)
Martin Finkel's avatar
Martin Finkel committed
278
        {
279
            return libvlc1?.NativeReference != libvlc2?.NativeReference;
Martin Finkel's avatar
Martin Finkel committed
280 281
        }

282
#if NETFRAMEWORK || NETSTANDARD
283 284 285
        /// <summary>
        /// Try to start a user interface for the libvlc instance.
        /// </summary>
286
        /// <param name="name">interface name, or null for default</param>
Martin Finkel's avatar
Martin Finkel committed
287
        /// <returns>True if successful, false otherwise</returns>
288
        public bool AddInterface(string? name)
Martin Finkel's avatar
Martin Finkel committed
289 290 291 292
        {
            var namePtr = name.ToUtf8();
            return MarshalUtils.PerformInteropAndFree(() => Native.LibVLCAddInterface(NativeReference, namePtr) == 0, namePtr);
        }
293
#endif
Martin Finkel's avatar
Martin Finkel committed
294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311
        /// <summary>
        /// <para>Registers a callback for the LibVLC exit event. This is mostly useful if</para>
        /// <para>the VLC playlist and/or at least one interface are started with</para>
        /// <para>libvlc_playlist_play() or libvlc_add_intf() respectively.</para>
        /// <para>Typically, this function will wake up your application main loop (from</para>
        /// <para>another thread).</para>
        /// </summary>
        /// <param name="cb">
        /// <para>callback to invoke when LibVLC wants to exit,</para>
        /// <para>or NULL to disable the exit handler (as by default)</para>
        /// </param>
        /// <param name="opaque">data pointer for the callback</param>
        /// <remarks>
        /// <para>This function should be called before the playlist or interface are</para>
        /// <para>started. Otherwise, there is a small race condition: the exit event could</para>
        /// <para>be raised before the handler is registered.</para>
        /// <para>This function and libvlc_wait() cannot be used at the same time.</para>
        /// </remarks>
312
        public void SetExitHandler(ExitCallback? cb, IntPtr opaque)
Martin Finkel's avatar
Martin Finkel committed
313 314 315 316 317 318 319 320 321 322 323 324
        {
            var cbFunctionPointer = cb == null ? IntPtr.Zero : Marshal.GetFunctionPointerForDelegate(cb);
            Native.LibVLCSetExitHandler(NativeReference, cbFunctionPointer, opaque);
        }

        /// <summary>
        /// <para>Sets the application name. LibVLC passes this as the user agent string</para>
        /// <para>when a protocol requires it.</para>
        /// </summary>
        /// <param name="name">human-readable application name, e.g. &quot;FooBar player 1.2.3&quot;</param>
        /// <param name="http">HTTP User Agent, e.g. &quot;FooBar/1.2.3 Python/2.6.0&quot;</param>
        /// <remarks>LibVLC 1.1.1 or later</remarks>
Martin Finkel's avatar
Martin Finkel committed
325 326 327 328 329 330 331
        public void SetUserAgent(string name, string http)
        {
            var nameUtf8 = name.ToUtf8();
            var httpUtf8 = http.ToUtf8();

            MarshalUtils.PerformInteropAndFree(() => Native.LibVLCSetUserAgent(NativeReference, nameUtf8, httpUtf8), nameUtf8, httpUtf8);
        }
332

Martin Finkel's avatar
Martin Finkel committed
333 334 335 336 337 338 339 340
        /// <summary>
        /// <para>Sets some meta-information about the application.</para>
        /// <para>See also libvlc_set_user_agent().</para>
        /// </summary>
        /// <param name="id">Java-style application identifier, e.g. &quot;com.acme.foobar&quot;</param>
        /// <param name="version">application version numbers, e.g. &quot;1.2.3&quot;</param>
        /// <param name="icon">application icon name, e.g. &quot;foobar&quot;</param>
        /// <remarks>LibVLC 2.1.0 or later.</remarks>
341
        public void SetAppId(string? id, string? version, string? icon)
Martin Finkel's avatar
Martin Finkel committed
342 343 344 345 346 347 348 349
        {
            var idUtf8 = id.ToUtf8();
            var versionUtf8 = version.ToUtf8();
            var iconUtf8 = icon.ToUtf8();

            MarshalUtils.PerformInteropAndFree(() => Native.LibVLCSetAppId(NativeReference, idUtf8, versionUtf8, iconUtf8),
                idUtf8, versionUtf8, iconUtf8);
        }
350

351
#if NETFRAMEWORK || NETSTANDARD
Martin Finkel's avatar
Martin Finkel committed
352
        /// <summary>
Martin Finkel's avatar
Martin Finkel committed
353
        /// Close log file handle
Martin Finkel's avatar
Martin Finkel committed
354
        /// </summary>
355
        /// <returns>true if no file to close or close operation successful, false otherwise</returns>
Martin Finkel's avatar
Martin Finkel committed
356
        public bool CloseLogFile()
Martin Finkel's avatar
Martin Finkel committed
357 358
        {
            if (_logFileHandle == IntPtr.Zero) return true;
359 360

            return MarshalUtils.Close(_logFileHandle);
Martin Finkel's avatar
Martin Finkel committed
361 362 363 364
        }

        /// <summary>Sets up logging to a file.
        /// Watch out: Overwrite contents if file exists!
365
        /// Potentially throws a VLCException if FILE * cannot be obtained
Martin Finkel's avatar
Martin Finkel committed
366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381
        /// </summary>
        /// <para>FILE pointer opened for writing</para>
        /// <para>(the FILE pointer must remain valid until libvlc_log_unset())</para>
        /// <param name="filename">open/create file with Write access. If existing, resets content.</param>
        /// <remarks>LibVLC 2.1.0 or later</remarks>
        public void SetLogFile(string filename)
        {
            if (string.IsNullOrEmpty(filename)) throw new NullReferenceException(nameof(filename));

            _logFileHandle = NativeFilePtr(filename);

            Native.LibVLCLogSetFile(NativeReference, _logFileHandle);
        }

        IntPtr NativeFilePtr(string filename)
        {
382 383
            var result = MarshalUtils.Open(filename, out var filePtr);
            if (!result)
Martin Finkel's avatar
Martin Finkel committed
384
                throw new VLCException("Could not get FILE * for log_set_file");
385
            return filePtr;
Martin Finkel's avatar
Martin Finkel committed
386
        }
387
#endif
388
        GCHandle _libvlcGcHandle;
Martin Finkel's avatar
Martin Finkel committed
389 390 391 392
        void SetLog(LogCallback cb)
        {
            _logCallback = cb ?? throw new ArgumentException(nameof(cb));

393
            _libvlcGcHandle = GCHandle.Alloc(this, GCHandleType.Normal);
394

395
            Native.LibVLCLogSet(NativeReference, cb, GCHandle.ToIntPtr(_libvlcGcHandle));
Martin Finkel's avatar
Martin Finkel committed
396 397 398 399 400 401
        }

        void UnsetLog()
        {
            if (_logCallback == null) return;

402
            if (_libvlcGcHandle.IsAllocated)
403
            {
404
                _libvlcGcHandle.Free();
405 406
            }

Martin Finkel's avatar
Martin Finkel committed
407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422
            _logCallback = null;
            Native.LibVLCLogUnset(NativeReference);
        }

        int _logSubscriberCount = 0;
        /// <summary>
        /// The event that is triggered when a log is emitted from libVLC.
        /// Listening to this event will discard the default logger in libvlc.
        /// </summary>
        public event EventHandler<LogEventArgs> Log
        {
            add
            {
                lock (_logLock)
                {
                    _log += value;
423 424
                    if(_logSubscriberCount == 0)
                        SetLog(OnLogInternal);
Martin Finkel's avatar
Martin Finkel committed
425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445
                    _logSubscriberCount++;
                }
            }

            remove
            {
                lock (_logLock)
                {
                    _log -= value;
                    if (_logSubscriberCount > 0)
                    {
                        _logSubscriberCount--;
                    }
                    if (_logSubscriberCount == 0)
                    {
                        UnsetLog();
                    }
                }
            }
        }

Martin Finkel's avatar
Martin Finkel committed
446 447 448 449 450 451 452 453 454
        /// <summary>Returns a list of audio filters that are available.</summary>
        /// <returns>
        /// <para>a list of module descriptions. It should be freed with libvlc_module_description_list_release().</para>
        /// <para>In case of an error, NULL is returned.</para>
        /// </returns>
        /// <remarks>
        /// <para>libvlc_module_description_t</para>
        /// <para>libvlc_module_description_list_release</para>
        /// </remarks>
Martin Finkel's avatar
Martin Finkel committed
455 456 457 458 459
        public ModuleDescription[] AudioFilters => MarshalUtils.Retrieve(() => Native.LibVLCAudioFilterListGet(NativeReference),
            MarshalUtils.PtrToStructure<ModuleDescriptionStructure>,
            s => s.Build(),
            module => module.Next, 
            Native.LibVLCModuleDescriptionListRelease);
460

Martin Finkel's avatar
Martin Finkel committed
461 462 463 464 465 466 467 468 469
        /// <summary>Returns a list of video filters that are available.</summary>
        /// <returns>
        /// <para>a list of module descriptions. It should be freed with libvlc_module_description_list_release().</para>
        /// <para>In case of an error, NULL is returned.</para>
        /// </returns>
        /// <remarks>
        /// <para>libvlc_module_description_t</para>
        /// <para>libvlc_module_description_list_release</para>
        /// </remarks>
470
        public ModuleDescription[] VideoFilters => MarshalUtils.Retrieve(() => Native.LibVLCVideoFilterListGet(NativeReference),
Martin Finkel's avatar
Martin Finkel committed
471 472 473 474
            MarshalUtils.PtrToStructure<ModuleDescriptionStructure>,
            s => s.Build(),
            module => module.Next, 
            Native.LibVLCModuleDescriptionListRelease);
475

Martin Finkel's avatar
Martin Finkel committed
476 477 478 479 480 481 482
        /// <summary>Gets the list of available audio output modules.</summary>
        /// <returns>list of available audio outputs. It must be freed with</returns>
        /// <remarks>
        /// <para>libvlc_audio_output_list_release</para>
        /// <para>libvlc_audio_output_t .</para>
        /// <para>In case of error, NULL is returned.</para>
        /// </remarks>
483 484
        public AudioOutputDescription[] AudioOutputs => MarshalUtils.Retrieve(() => Native.LibVLCAudioOutputListGet(NativeReference), 
            ptr => MarshalUtils.PtrToStructure<AudioOutputDescriptionStructure>(ptr),
485
            s => s.Build(), 
Martin Finkel's avatar
Martin Finkel committed
486
            s => s.Next, 
487
            Native.LibVLCAudioOutputListRelease);
488

Martin Finkel's avatar
Martin Finkel committed
489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508
        /// <summary>Gets a list of audio output devices for a given audio output module,</summary>
        /// <param name="audioOutputName">
        /// <para>audio output name</para>
        /// <para>(as returned by libvlc_audio_output_list_get())</para>
        /// </param>
        /// <returns>
        /// <para>A NULL-terminated linked list of potential audio output devices.</para>
        /// <para>It must be freed with libvlc_audio_output_device_list_release()</para>
        /// </returns>
        /// <remarks>
        /// <para>libvlc_audio_output_device_set().</para>
        /// <para>Not all audio outputs support this. In particular, an empty (NULL)</para>
        /// <para>list of devices doesnotimply that the specified audio output does</para>
        /// <para>not work.</para>
        /// <para>The list might not be exhaustive.</para>
        /// <para>Some audio output devices in the list might not actually work in</para>
        /// <para>some circumstances. By default, it is recommended to not specify any</para>
        /// <para>explicit audio device.</para>
        /// <para>LibVLC 2.1.0 or later.</para>
        /// </remarks>
509
        public AudioOutputDevice[] AudioOutputDevices(string audioOutputName) => 
Martin Finkel's avatar
Martin Finkel committed
510 511 512
            MarshalUtils.Retrieve(() => 
            {
                var audioOutputNameUtf8 = audioOutputName.ToUtf8();
Martin Finkel's avatar
Martin Finkel committed
513 514
                return MarshalUtils.PerformInteropAndFree(() => 
                    Native.LibVLCAudioOutputDeviceListGet(NativeReference, audioOutputNameUtf8), audioOutputNameUtf8);
Martin Finkel's avatar
Martin Finkel committed
515
            }, 
516 517 518 519
            MarshalUtils.PtrToStructure<AudioOutputDeviceStructure>,
            s => s.Build(), 
            device => device.Next, 
            Native.LibVLCAudioOutputDeviceListRelease);
Martin Finkel's avatar
Martin Finkel committed
520
        
Martin Finkel's avatar
Martin Finkel committed
521
        /// <summary>Get media discoverer services by category</summary>
Martin Finkel's avatar
Martin Finkel committed
522
        /// <param name="discovererCategory">category of services to fetch</param>
Martin Finkel's avatar
Martin Finkel committed
523 524
        /// <returns>the number of media discoverer services (0 on error)</returns>
        /// <remarks>LibVLC 3.0.0 and later.</remarks>
Martin Finkel's avatar
Martin Finkel committed
525
        public MediaDiscovererDescription[] MediaDiscoverers(MediaDiscovererCategory discovererCategory) =>
Martin Finkel's avatar
Martin Finkel committed
526
            MarshalUtils.Retrieve(NativeReference, discovererCategory, 
Martin Finkel's avatar
Martin Finkel committed
527
                (IntPtr nativeRef, MediaDiscovererCategory enumType, out IntPtr array) => Native.LibVLCMediaDiscovererListGet(nativeRef, enumType, out array),
Martin Finkel's avatar
Martin Finkel committed
528 529 530
            MarshalUtils.PtrToStructure<MediaDiscovererDescriptionStructure>,
            m => m.Build(),
            Native.LibVLCMediaDiscovererListRelease);
Martin Finkel's avatar
Martin Finkel committed
531

532
        #region DialogManagement
Martin Finkel's avatar
Martin Finkel committed
533

Martin Finkel's avatar
Martin Finkel committed
534 535 536 537 538 539 540 541 542 543 544
        /// <summary>
        /// Register callbacks in order to handle VLC dialogs. 
        /// LibVLC 3.0.0 and later.
        /// </summary>
        /// <param name="error">Called when an error message needs to be displayed.</param>
        /// <param name="login">Called when a login dialog needs to be displayed.
        /// You can interact with this dialog by calling Dialog.PostLogin() to post an answer or Dialog.Dismiss() to cancel this dialog.</param>
        /// <param name="question">Called when a question dialog needs to be displayed.
        /// You can interact with this dialog by calling Dialog.PostLogin() to post an answer or Dialog.Dismiss() to cancel this dialog.</param>
        /// <param name="displayProgress">Called when a progress dialog needs to be displayed.</param>
        /// <param name="updateProgress">Called when a progress dialog needs to be updated.</param>
Martin Finkel's avatar
Martin Finkel committed
545 546
        public void SetDialogHandlers(DisplayError error, DisplayLogin login, DisplayQuestion question,
            DisplayProgress displayProgress, UpdateProgress updateProgress)
547
        {
548 549 550 551 552 553
            _error = error ?? throw new ArgumentNullException(nameof(error));
            _login = login ?? throw new ArgumentNullException(nameof(login));
            _question = question ?? throw new ArgumentNullException(nameof(question));
            _displayProgress = displayProgress ?? throw new ArgumentNullException(nameof(displayProgress));
            _updateProgress = updateProgress ?? throw new ArgumentNullException(nameof(updateProgress));

Martin Finkel's avatar
repro  
Martin Finkel committed
554 555 556 557 558 559
            //_dialogCbs = new DialogCallbacks((data, title, text) => 
            //{
            //    _error?.Invoke(title, text);
            //}, Login, Question, DisplayProgress, Cancel, UpdateProgress);


560
            _dialogCbs = new DialogCallbacks(Error, Login, Question, DisplayProgress, Cancel, UpdateProgress);
Martin Finkel's avatar
repro  
Martin Finkel committed
561 562
            //   dialogCbsPtr = Marshal.AllocHGlobal(MarshalUtils.SizeOf(_dialogCbs));
            // Marshal.StructureToPtr(_dialogCbs, dialogCbsPtr, true);
563 564 565
            Native.LibVLCDialogSetCallbacks(NativeReference, _dialogCbs, IntPtr.Zero);
        }

Martin Finkel's avatar
repro  
Martin Finkel committed
566
        //IntPtr dialogCbsPtr;
567 568 569 570 571 572 573 574 575 576 577 578 579 580 581
        /// <summary>
        /// Unset dialog callbacks if previously set
        /// </summary>
        public void UnsetDialogHandlers()
        {
            if (DialogHandlersSet)
            {
                _dialogCbs = default;
                Native.LibVLCDialogSetCallbacks(NativeReference, _dialogCbs, IntPtr.Zero);
                _error = null;
                _login = null;
                _question = null;
                _displayProgress = null;
                _updateProgress = null;
            }
582 583
        }

Martin Finkel's avatar
Martin Finkel committed
584 585 586
        /// <summary>
        /// True if dialog handlers are set
        /// </summary>
587 588 589
        public bool DialogHandlersSet => _dialogCbs.DisplayLogin != IntPtr.Zero;

        DialogCallbacks _dialogCbs;
590 591 592 593 594
        static DisplayError? _error;
        static DisplayLogin? _login;
        static DisplayQuestion? _question;
        static DisplayProgress? _displayProgress;
        static UpdateProgress? _updateProgress;
595 596
        static readonly Dictionary<IntPtr, CancellationTokenSource> _cts = new Dictionary<IntPtr, CancellationTokenSource>();

Martin Finkel's avatar
repro  
Martin Finkel committed
597 598
        //[MonoPInvokeCallback(typeof(DisplayErrorCallback))]
        /*static*/ void Error(IntPtr data, string title, string text)
599 600 601
        {
            _error?.Invoke(title, text);
        }
Martin Finkel's avatar
Martin Finkel committed
602

603 604 605 606 607 608 609 610 611 612 613 614 615
        [MonoPInvokeCallback(typeof(DisplayLoginCallback))]
        static void Login(IntPtr data, IntPtr dialogId, string title, string text, string defaultUsername, bool askStore)
        {
            if (_login == null) return;

            var cts = new CancellationTokenSource();
            var dlg = new Dialog(new DialogId(dialogId));
            _cts[dialogId] = cts;
            _login(dlg, title, text, defaultUsername, askStore, cts.Token);
        }
        
        [MonoPInvokeCallback(typeof(DisplayQuestionCallback))]
        static void Question(IntPtr data, IntPtr dialogId, string title, string text, DialogQuestionType type, 
616
            string cancelText, string? firstActionText, string? secondActionText)
617 618 619 620 621 622 623 624 625 626
        {
            if (_question == null) return;

            var cts = new CancellationTokenSource();
            var dlg = new Dialog(new DialogId(dialogId));
            _cts[dialogId] = cts;
            _question(dlg, title, text, type, cancelText, firstActionText, secondActionText, cts.Token);
        }

        [MonoPInvokeCallback(typeof(DisplayProgressCallback))]
627
        static void DisplayProgress(IntPtr data, IntPtr dialogId, string title, string text, bool indeterminate, float position, string? cancelText)
628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657
        {
            if (_displayProgress == null) return;

            var cts = new CancellationTokenSource();
            var dlg = new Dialog(new DialogId(dialogId));
            _cts[dialogId] = cts;
            _displayProgress(dlg, title, text, indeterminate, position, cancelText, cts.Token);
        }

        [MonoPInvokeCallback(typeof(CancelCallback))]
        static void Cancel(IntPtr data, IntPtr dialogId)
        {
            if (_cts.TryGetValue(dialogId, out var token))
            {
                token.Cancel();
                _cts.Remove(dialogId);
            }
        }

        [MonoPInvokeCallback(typeof(UpdateProgressCallback))]
        static void UpdateProgress(IntPtr data, IntPtr dialogId, float position, string text)
        {
            if (_updateProgress == null) return;

            var dlg = new Dialog(new DialogId(dialogId));
            _updateProgress(dlg, position, text);
        }

        #endregion
        
Martin Finkel's avatar
Martin Finkel committed
658 659
        /// <summary>
        /// List of available renderers used to create RendererDiscoverer objects
Martin Finkel's avatar
Martin Finkel committed
660
        /// Note: LibVLC 3.0.0 and later
Martin Finkel's avatar
Martin Finkel committed
661
        /// </summary>       
662 663 664 665 666
        public RendererDescription[] RendererList => MarshalUtils.Retrieve(NativeReference, 
            (IntPtr nativeRef, out IntPtr array) => Native.LibVLCRendererDiscovererGetList(nativeRef, out array),
            MarshalUtils.PtrToStructure<RendererDescriptionStructure>,
            m => m.Build(),
            Native.LibVLCRendererDiscovererReleaseList);
667

Martin Finkel's avatar
Martin Finkel committed
668 669 670
        [MonoPInvokeCallback(typeof(LogCallback))]
        static void OnLogInternal(IntPtr data, LogLevel level, IntPtr ctx, IntPtr format, IntPtr args)
        {
671 672 673
            if (data == IntPtr.Zero)
                return;

674 675
            var gch = GCHandle.FromIntPtr(data);

676
            if (!gch.IsAllocated || !(gch.Target is LibVLC libvlc) || libvlc.IsDisposed)
677
                return;
Martin Finkel's avatar
Martin Finkel committed
678 679 680

            try
            {
681
                var message = MarshalUtils.GetLogMessage(format, args);
682 683

                GetLogContext(ctx, out var module, out var file, out var line);
Martin Finkel's avatar
Martin Finkel committed
684
#if NET40
685
                Task.Factory.StartNew(() => libvlc._log?.Invoke(null, new LogEventArgs(level, message, module, file, line)));
Martin Finkel's avatar
Martin Finkel committed
686
#else
687
                Task.Run(() => libvlc._log?.Invoke(null, new LogEventArgs(level, message, module, file, line)));
Martin Finkel's avatar
Martin Finkel committed
688 689
#endif
            }
690
            // Silently catching OOM exceptions and others as this is not critical if it fails
691
            catch
Martin Finkel's avatar
Martin Finkel committed
692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710
            {
            }
        }

        /// <summary>
        /// Gets log message debug infos.
        ///
        /// This function retrieves self-debug information about a log message:
        /// - the name of the VLC module emitting the message,
        /// - the name of the source code module (i.e.file) and
        /// - the line number within the source code module.
        ///
        /// The returned module name and file name will be NULL if unknown.
        /// The returned line number will similarly be zero if unknown.
        /// </summary>
        /// <param name="logContext">The log message context (as passed to the <see cref="LogCallback"/>)</param>
        /// <param name="module">The module name storage.</param>
        /// <param name="file">The source code file name storage.</param>
        /// <param name="line">The source code file line number storage.</param>
711
        static void GetLogContext(IntPtr logContext, out string? module, out string? file, out uint? line)
Martin Finkel's avatar
Martin Finkel committed
712 713 714 715 716 717 718 719
        {
            Native.LibVLCLogGetContext(logContext, out var modulePtr, out var filePtr, out var linePtr);

            line = linePtr == UIntPtr.Zero ? null : (uint?)linePtr.ToUInt32();
            module = modulePtr.FromUtf8();
            file = filePtr.FromUtf8();
        }

Martin Finkel's avatar
Martin Finkel committed
720
        /// <summary>Increments the native reference counter for this libvlc instance</summary>
Martin Finkel's avatar
Martin Finkel committed
721
        internal void Retain() => Native.LibVLCRetain(NativeReference);
722 723

        /// <summary>The version of the LibVLC engine currently used by LibVLCSharp</summary>
724
        public string? Version => Native.LibVLCVersion().FromUtf8();
Martin Finkel's avatar
Martin Finkel committed
725 726

        /// <summary>The changeset of the LibVLC engine currently used by LibVLCSharp</summary>
727
        public string? Changeset => Native.LibVLCChangeset().FromUtf8();
728 729 730 731 732 733 734

        /// <summary>
        /// A human-readable error message for the last LibVLC error in the calling
        /// thread. The resulting string is valid until another error occurs (at least
        /// until the next LibVLC call). 
        /// <para/> Null if no error.
        /// </summary>
735
        public string? LastLibVLCError => Native.LibVLCErrorMessage().FromUtf8();
736 737 738 739 740 741 742 743 744 745 746 747

        /// <summary>
        /// Clears the LibVLC error status for the current thread. This is optional.
        /// By default, the error status is automatically overridden when a new error
        /// occurs, and destroyed when the thread exits.
        /// </summary>
        public void ClearLibVLCError() => Native.LibVLCClearError();

        /// <summary>
        /// Retrieve the libvlc compiler version.
        /// Example: "gcc version 4.2.3 (Ubuntu 4.2.3-2ubuntu6)"
        /// </summary>
748
        public string? LibVLCCompiler => Native.LibVLCGetCompiler().FromUtf8();
749
    }
Martin Finkel's avatar
Martin Finkel committed
750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766

    /// <summary>Logging messages level.</summary>
    /// <remarks>Future LibVLC versions may define new levels.</remarks>
    public enum LogLevel
    {
        /// <summary>Debug message</summary>
        Debug = 0,
        /// <summary>Important informational message</summary>
        Notice = 2,
        /// <summary>Warning (potential error) message</summary>
        Warning = 3,
        /// <summary>Error message</summary>
        Error = 4
    }

#region Callbacks

Martin Finkel's avatar
Martin Finkel committed
767 768 769 770 771
    /// <summary>
    /// Registers a callback for the LibVLC exit event. 
    /// This is mostly useful if the VLC playlist and/or at least one interface are started with libvlc_playlist_play() 
    /// or AddInterface() respectively. Typically, this function will wake up your application main loop (from another thread).
    /// </summary>
772
    [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
Martin Finkel's avatar
Martin Finkel committed
773
    public delegate void ExitCallback();
Martin Finkel's avatar
Martin Finkel committed
774 775 776 777

    [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
    internal delegate void LogCallback(IntPtr data, LogLevel logLevel, IntPtr logContext, IntPtr format, IntPtr args);

Martin Finkel's avatar
Martin Finkel committed
778
#endregion
Martin Finkel's avatar
Martin Finkel committed
779
}