Media.cs 41.7 KB
Newer Older
Martin Finkel's avatar
Martin Finkel committed
1 2 3 4 5 6 7 8
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using System.Security;

9
namespace LibVLCSharp.Shared
Martin Finkel's avatar
Martin Finkel committed
10
{
11
    public class Media : Internal
Martin Finkel's avatar
Martin Finkel committed
12
    {
13 14
        static readonly ConcurrentDictionary<IntPtr, StreamData> DicStreams = new ConcurrentDictionary<IntPtr, StreamData>();
        static int _streamIndex;
Martin Finkel's avatar
Martin Finkel committed
15

16
        internal struct Native
Martin Finkel's avatar
Martin Finkel committed
17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58
        {
            [SuppressUnmanagedCodeSecurity]
            [DllImport("libvlc", CallingConvention = CallingConvention.Cdecl,
                EntryPoint = "libvlc_media_new_location")]
            internal static extern IntPtr LibVLCMediaNewLocation(IntPtr instance, [MarshalAs(UnmanagedType.LPStr)] string mrl);

            [SuppressUnmanagedCodeSecurity]
            [DllImport("libvlc", CallingConvention = CallingConvention.Cdecl,
                EntryPoint = "libvlc_media_new_path")]
            internal static extern IntPtr LibVLCMediaNewPath(IntPtr instance, [MarshalAs(UnmanagedType.LPStr)] string path);

            [SuppressUnmanagedCodeSecurity]
            [DllImport("libvlc", CallingConvention = CallingConvention.Cdecl,
                EntryPoint = "libvlc_media_new_as_node")]
            internal static extern IntPtr LibVLCMediaNewAsNode(IntPtr instance, [MarshalAs(UnmanagedType.LPStr)] string name);

            [SuppressUnmanagedCodeSecurity]
            [DllImport("libvlc", CallingConvention = CallingConvention.Cdecl,
                EntryPoint = "libvlc_media_new_fd")]
            internal static extern IntPtr LibVLCMediaNewFd(IntPtr instance, int fd);

            /// <summary>
            /// <para>Decrement the reference count of a media descriptor object. If the</para>
            /// <para>reference count is 0, then libvlc_media_release() will release the</para>
            /// <para>media descriptor object. It will send out an libvlc_MediaFreed event</para>
            /// <para>to all listeners. If the media descriptor object has been released it</para>
            /// <para>should not be used again.</para>
            /// </summary>
            /// <param name="media">the media descriptor</param>
            [SuppressUnmanagedCodeSecurity]
            [DllImport("libvlc", CallingConvention = CallingConvention.Cdecl,
                EntryPoint = "libvlc_media_release")]
            internal static extern void LibVLCMediaRelease(IntPtr media);

            [SuppressUnmanagedCodeSecurity]
            [DllImport("libvlc", CallingConvention = CallingConvention.Cdecl,
                EntryPoint = "libvlc_media_list_media")]
            internal static extern IntPtr LibVLCMediaListMedia(IntPtr mediaList);

            [SuppressUnmanagedCodeSecurity]
            [DllImport("libvlc", CallingConvention = CallingConvention.Cdecl,
                EntryPoint = "libvlc_media_new_callbacks")]
59
            internal static extern IntPtr LibVLCMediaNewCallbacks(IntPtr instance, IntPtr openCb, IntPtr readCb, IntPtr seekCb, IntPtr closeCb, IntPtr opaque);
Martin Finkel's avatar
Martin Finkel committed
60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199

            [SuppressUnmanagedCodeSecurity]
            [DllImport("libvlc", CallingConvention = CallingConvention.Cdecl,
                EntryPoint = "libvlc_media_add_option")]
            internal static extern void LibVLCMediaAddOption(IntPtr media, [MarshalAs(UnmanagedType.LPStr)] string options);

            [SuppressUnmanagedCodeSecurity]
            [DllImport("libvlc", CallingConvention = CallingConvention.Cdecl,
                EntryPoint = "libvlc_media_add_option_flag")]
            internal static extern void LibVLCMediaAddOptionFlag(IntPtr media, [MarshalAs(UnmanagedType.LPStr)] string options, uint flags);
            
            [SuppressUnmanagedCodeSecurity]
            [DllImport("libvlc", CallingConvention = CallingConvention.Cdecl,
                EntryPoint = "libvlc_media_get_mrl")]
            internal static extern IntPtr LibVLCMediaGetMrl(IntPtr media);

            [SuppressUnmanagedCodeSecurity]
            [DllImport("libvlc", CallingConvention = CallingConvention.Cdecl,
                EntryPoint = "libvlc_media_duplicate")]
            internal static extern IntPtr LibVLCMediaDuplicate(IntPtr media);

            [SuppressUnmanagedCodeSecurity]
            [DllImport("libvlc", CallingConvention = CallingConvention.Cdecl,
                EntryPoint = "libvlc_media_get_meta")]
            internal static extern IntPtr LibVLCMediaGetMeta(IntPtr media, MetadataType metadataType);

            [SuppressUnmanagedCodeSecurity]
            [DllImport("libvlc", CallingConvention = CallingConvention.Cdecl,
                EntryPoint = "libvlc_media_set_meta")]
            internal static extern void LibVLCMediaSetMeta(IntPtr media, MetadataType metadataType, [MarshalAs(UnmanagedType.LPStr)] string value);

            [SuppressUnmanagedCodeSecurity]
            [DllImport("libvlc", CallingConvention = CallingConvention.Cdecl,
                EntryPoint = "libvlc_media_save_meta")]
            internal static extern int LibVLCMediaSaveMeta(IntPtr media);

            [SuppressUnmanagedCodeSecurity]
            [DllImport("libvlc", CallingConvention = CallingConvention.Cdecl,
                EntryPoint = "libvlc_media_get_state")]
            internal static extern VLCState LibVLCMediaGetState(IntPtr media);

            [SuppressUnmanagedCodeSecurity]
            [DllImport("libvlc", CallingConvention = CallingConvention.Cdecl,
                EntryPoint = "libvlc_media_event_manager")]
            internal static extern IntPtr LibVLCMediaEventManager(IntPtr media);

            [SuppressUnmanagedCodeSecurity]
            [DllImport("libvlc", CallingConvention = CallingConvention.Cdecl,
                EntryPoint = "libvlc_media_get_stats")]
            internal static extern int LibVLCMediaGetStats(IntPtr media, out MediaStats statistics);

            [SuppressUnmanagedCodeSecurity]
            [DllImport("libvlc", CallingConvention = CallingConvention.Cdecl,
                EntryPoint = "libvlc_media_get_duration")]
            internal static extern long LibVLCMediaGetDuration(IntPtr media);

            [SuppressUnmanagedCodeSecurity]
            [DllImport("libvlc", CallingConvention = CallingConvention.Cdecl,
                EntryPoint = "libvlc_media_parse")]
            internal static extern void LibVLCMediaParse(IntPtr media);

            [SuppressUnmanagedCodeSecurity]
            [DllImport("libvlc", CallingConvention = CallingConvention.Cdecl,
                EntryPoint = "libvlc_media_parse_async")]
            internal static extern void LibVLCMediaParseAsync(IntPtr media);

            [SuppressUnmanagedCodeSecurity]
            [DllImport("libvlc", CallingConvention = CallingConvention.Cdecl,
                EntryPoint = "libvlc_media_is_parsed")]
            internal static extern int LibVLCMediaIsParsed(IntPtr media);

            [SuppressUnmanagedCodeSecurity]
            [DllImport("libvlc", CallingConvention = CallingConvention.Cdecl,
                EntryPoint = "libvlc_media_parse_with_options")]
            internal static extern int LibVLCMediaParseWithOptions(IntPtr media, MediaParseOptions mediaParseOptions, int timeout);

            [SuppressUnmanagedCodeSecurity]
            [DllImport("libvlc", CallingConvention = CallingConvention.Cdecl,
                EntryPoint = "libvlc_media_get_parsed_status")]
            internal static extern MediaParsedStatus LibVLCMediaGetParsedStatus(IntPtr media);

            [SuppressUnmanagedCodeSecurity]
            [DllImport("libvlc", CallingConvention = CallingConvention.Cdecl,
                EntryPoint = "libvlc_media_parse_stop")]
            internal static extern void LibVLCMediaParseStop(IntPtr media);

            [SuppressUnmanagedCodeSecurity]
            [DllImport("libvlc", CallingConvention = CallingConvention.Cdecl,
                EntryPoint = "libvlc_media_set_user_data")]
            internal static extern void LibVLCMediaSetUserData(IntPtr media, IntPtr userData);

            [SuppressUnmanagedCodeSecurity]
            [DllImport("libvlc", CallingConvention = CallingConvention.Cdecl,
                EntryPoint = "libvlc_media_get_user_data")]
            internal static extern IntPtr LibVLCMediaGetUserData(IntPtr media);

            [SuppressUnmanagedCodeSecurity]
            [DllImport("libvlc", CallingConvention = CallingConvention.Cdecl,
                EntryPoint = "libvlc_media_tracks_get")]
            internal static extern uint LibVLCMediaTracksGet(IntPtr media, ref IntPtr tracksPtr);

            [SuppressUnmanagedCodeSecurity]
            [DllImport("libvlc", CallingConvention = CallingConvention.Cdecl,
                EntryPoint = "libvlc_media_tracks_release")]
            internal static extern void LibVLCMediaTracksRelease(IntPtr tracks, uint count);
            
            [SuppressUnmanagedCodeSecurity]
            [DllImport("libvlc", CallingConvention = CallingConvention.Cdecl,
                EntryPoint = "libvlc_media_subitems")]
            internal static extern IntPtr LibVLCMediaSubitems(IntPtr media);
            
            [SuppressUnmanagedCodeSecurity]
            [DllImport("libvlc", CallingConvention = CallingConvention.Cdecl,
                EntryPoint = "libvlc_media_get_type")]
            internal static extern MediaType LibVLCMediaGetType(IntPtr media);

            [SuppressUnmanagedCodeSecurity]
            [DllImport("libvlc", CallingConvention = CallingConvention.Cdecl,
                EntryPoint = "libvlc_media_slaves_add")]
            internal static extern int LibVLCMediaAddSlaves(IntPtr media, MediaSlaveType slaveType, uint priority, [MarshalAs(UnmanagedType.LPStr)] string uri);

            [SuppressUnmanagedCodeSecurity]
            [DllImport("libvlc", CallingConvention = CallingConvention.Cdecl,
                EntryPoint = "libvlc_media_slaves_clear")]
            internal static extern void LibVLCMediaClearSlaves(IntPtr media);

            [SuppressUnmanagedCodeSecurity]
            [DllImport("libvlc", CallingConvention = CallingConvention.Cdecl,
                EntryPoint = "libvlc_media_slaves_get")]
            internal static extern uint LibVLCMediaGetSlaves(IntPtr media, ref IntPtr slaves);

            [SuppressUnmanagedCodeSecurity]
            [DllImport("libvlc", CallingConvention = CallingConvention.Cdecl,
                EntryPoint = "libvlc_media_slaves_release")]
            internal static extern void LibVLCMediaReleaseSlaves(IntPtr slaves, uint count);

            [SuppressUnmanagedCodeSecurity]
            [DllImport("libvlc", CallingConvention = CallingConvention.Cdecl,
                EntryPoint = "libvlc_media_retain")]
            internal static extern void LibVLCMediaRetain(IntPtr media);
Martin Finkel's avatar
cleanup  
Martin Finkel committed
200 201 202 203 204

            [SuppressUnmanagedCodeSecurity]
            [DllImport("libvlc", CallingConvention = CallingConvention.Cdecl,
                            EntryPoint = "libvlc_media_get_codec_description")]
            internal static extern string LibvlcMediaGetCodecDescription(TrackType type, uint codec);
Martin Finkel's avatar
Martin Finkel committed
205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255
        }

        #region enums
        /// <summary>Meta data types</summary>
        public enum MetadataType
        {
            Title = 0,
            Artist = 1,
            Genre = 2,
            Copyright = 3,
            Album = 4,
            TrackNumber = 5,
            Description = 6,
            Rating = 7,
            Date = 8,
            Setting = 9,
            URL = 10,
            Language = 11,
            NowPlaying = 12,
            Publisher = 13,
            EncodedBy = 14,
            ArtworkURL = 15,
            TrackID = 16,
            TrackTotal = 17,
            Director = 18,
            Season = 19,
            Episode = 20,
            ShowName = 21,
            Actors = 22,
            AlbumArtist = 23,
            DiscNumber = 24,
            DiscTotal = 25
        }

        public enum FromType
        {
            /// <summary>
            /// Create a media for a certain file path.
            /// </summary>
            FromPath,
            /// <summary>
            /// Create a media with a certain given media resource location,
            /// for instance a valid URL.
            /// note To refer to a local file with this function,
            /// the file://... URI syntax <b>must</b> be used (see IETF RFC3986).
            /// We recommend using FromPath instead when dealing with
            ///local files.
            /// </summary>
            FromLocation,
            /// <summary>
            /// Create a media as an empty node with a given name.
256
            /// </summary>
Martin Finkel's avatar
Martin Finkel committed
257 258 259
            AsNode
        }

260
        /// <summary>
261
        /// Parse flags used by libvlc_media_parse_with_options()
Martin Finkel's avatar
cleanup  
Martin Finkel committed
262
        /// </summary>
Martin Finkel's avatar
Martin Finkel committed
263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282
        /// <remarks>libvlc_media_parse_with_options</remarks>
        [Flags]
        public enum MediaParseOptions
        {
            /// <summary>Parse media if it's a local file</summary>
            ParseLocal = 0,
            /// <summary>Parse media even if it's a network file</summary>
            ParseNetwork = 1,
            /// <summary>Fetch meta and covert art using local resources</summary>
            FetchLocal = 2,
            /// <summary>Fetch meta and covert art using network resources</summary>
            FetchNetwork = 4,
            /// <summary>
            /// Interact with the user (via libvlc_dialog_cbs) when preparsing this item
            /// (and not its sub items). Set this flag in order to receive a callback
            /// when the input is asking for credentials.
            /// </summary>
            DoInteract = 8
        }

Martin Finkel's avatar
cleanup  
Martin Finkel committed
283
        /// <summary>
Martin Finkel's avatar
Martin Finkel committed
284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318
        /// Parse status used sent by libvlc_media_parse_with_options() or returned by
        /// libvlc_media_get_parsed_status()
        /// </summary>
        /// <remarks>
        /// libvlc_media_parse_with_options
        /// libvlc_media_get_parsed_status
        /// </remarks>
        public enum MediaParsedStatus
        {
            Skipped = 1,
            Failed = 2,
            Timeout = 3,
            Done = 4
        }

        /// <summary>Media type</summary>
        /// <remarks>libvlc_media_get_type</remarks>
        public enum MediaType
        {
            Unknown = 0,
            File = 1,
            Directory = 2,
            Disc = 3,
            Stream = 4,
            Playlist = 5
        }

        #endregion

        /// <summary>
        /// Media Constructs a libvlc Media instance
        /// </summary>
        /// <param name="instance">A libvlc instance</param>
        /// <param name="mrl">A path, location, or node name, depending on the 3rd parameter</param>
        /// <param name="type">The type of the 2nd argument. \sa{FromType}</param>
319
        public Media(Instance instance, string mrl, FromType type = FromType.FromPath)
320 321 322 323 324
            : base(() => SelectNativeCtor(instance, mrl, type), Native.LibVLCMediaRelease)
        {
        }

        static IntPtr SelectNativeCtor(Instance instance, string mrl, FromType type)
Martin Finkel's avatar
Martin Finkel committed
325
        {
326 327
            if (instance == null) throw new ArgumentNullException(nameof(instance));
            if (string.IsNullOrEmpty(mrl)) throw new ArgumentNullException(nameof(mrl));
Martin Finkel's avatar
Martin Finkel committed
328 329 330 331

            switch (type)
            {
                case FromType.FromLocation:
332
                    return Native.LibVLCMediaNewLocation(instance.NativeReference, mrl);
Martin Finkel's avatar
Martin Finkel committed
333
                case FromType.FromPath:
334
                    return Native.LibVLCMediaNewPath(instance.NativeReference, mrl);
Martin Finkel's avatar
Martin Finkel committed
335
                case FromType.AsNode:
336 337 338
                    return Native.LibVLCMediaNewAsNode(instance.NativeReference, mrl);
                default:
                    return IntPtr.Zero;
Martin Finkel's avatar
Martin Finkel committed
339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360
            }
        }

        /// <summary>
        /// Create a media for an already open file descriptor.
        /// The file descriptor shall be open for reading(or reading and writing).
        ///
        /// Regular file descriptors, pipe read descriptors and character device
        /// descriptors(including TTYs) are supported on all platforms.
        /// Block device descriptors are supported where available.
        /// Directory descriptors are supported on systems that provide fdopendir().
        /// Sockets are supported on all platforms where they are file descriptors,
        /// i.e.all except Windows.
        /// 
        /// \note This library will <b>not</b> automatically close the file descriptor
        /// under any circumstance.Nevertheless, a file descriptor can usually only be
        /// rendered once in a media player.To render it a second time, the file
        /// descriptor should probably be rewound to the beginning with lseek().
        /// </summary>
        /// <param name="instance">A libvlc instance</param>
        /// <param name="fd">open file descriptor</param>
        public Media(Instance instance, int fd)
361
            : base(() => Native.LibVLCMediaNewFd(instance.NativeReference, fd), Native.LibVLCMediaRelease)
Martin Finkel's avatar
Martin Finkel committed
362 363 364 365
        {
        }

        public Media(MediaList mediaList)
Martin Finkel's avatar
cleanup  
Martin Finkel committed
366
            : base(() => Native.LibVLCMediaListMedia(mediaList.NativeReference), Native.LibVLCMediaRelease)
Martin Finkel's avatar
Martin Finkel committed
367 368 369 370 371 372 373 374
        {
        }

        /// <summary>
        /// requires libvlc 3.0 or higher
        /// </summary>
        /// <param name="instance"></param>
        /// <param name="stream"></param>
375
        /// <param name="options"></param>
376
        [LibVLC(3)]
377
        public Media(Instance instance, Stream stream, params string[] options)
378 379 380 381 382 383 384
            : base(() => CtorFromCallbacks(instance, stream), Native.LibVLCMediaRelease)
        {          
            if(options.Any())
                Native.LibVLCMediaAddOption(NativeReference, options.ToString());
        }

        static IntPtr CtorFromCallbacks(Instance instance, Stream stream)
Martin Finkel's avatar
Martin Finkel committed
385
        {
386
            if (instance == null) throw new ArgumentNullException(nameof(instance));
Martin Finkel's avatar
Martin Finkel committed
387 388 389 390 391 392 393
            if (stream == null) throw new ArgumentNullException(nameof(stream));

            var opaque = AddStream(stream);

            if (opaque == IntPtr.Zero)
                throw new InvalidOperationException("Cannot create opaque parameter");

394 395 396 397
            return Native.LibVLCMediaNewCallbacks(instance.NativeReference,
                Marshal.GetFunctionPointerForDelegate(new OpenMedia(CallbackOpenMedia)),
                Marshal.GetFunctionPointerForDelegate(new ReadMedia(CallbackReadMedia)),
                Marshal.GetFunctionPointerForDelegate(new SeekMedia(CallbackSeekMedia)),
398
                Marshal.GetFunctionPointerForDelegate(new CloseMedia(CallbackCloseMedia)),
Martin Finkel's avatar
Martin Finkel committed
399 400 401 402
                opaque);
        }

        public Media(IntPtr mediaPtr)
403
            : base(() => mediaPtr, Native.LibVLCMediaRelease)
Martin Finkel's avatar
Martin Finkel committed
404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424
        {
        }

        /// <summary>Add an option to the media.</summary>
        /// <param name="options">the options (as a string)</param>
        /// <remarks>
        /// <para>This option will be used to determine how the media_player will</para>
        /// <para>read the media. This allows to use VLC's advanced</para>
        /// <para>reading/streaming options on a per-media basis.</para>
        /// <para>The options are listed in 'vlc --long-help' from the command line,</para>
        /// <para>e.g. &quot;-sout-all&quot;. Keep in mind that available options and their semantics</para>
        /// <para>vary across LibVLC versions and builds.</para>
        /// <para>Not all options affects libvlc_media_t objects:</para>
        /// <para>Specifically, due to architectural issues most audio and video options,</para>
        /// <para>such as text renderer options, have no effects on an individual media.</para>
        /// <para>These options must be set through libvlc_new() instead.</para>
        /// </remarks>
        public void AddOption(string options)
        {
            if(string.IsNullOrEmpty(options)) throw new ArgumentNullException(nameof(options));

425
            Native.LibVLCMediaAddOption(NativeReference, options);
Martin Finkel's avatar
Martin Finkel committed
426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444
        }

        /// <summary>Add an option to the media with configurable flags.</summary>
        /// <param name="options">the options (as a string)</param>
        /// <param name="flags">the flags for this option</param>
        /// <remarks>
        /// <para>This option will be used to determine how the media_player will</para>
        /// <para>read the media. This allows to use VLC's advanced</para>
        /// <para>reading/streaming options on a per-media basis.</para>
        /// <para>The options are detailed in vlc --long-help, for instance</para>
        /// <para>&quot;--sout-all&quot;. Note that all options are not usable on medias:</para>
        /// <para>specifically, due to architectural issues, video-related options</para>
        /// <para>such as text renderer options cannot be set on a single media. They</para>
        /// <para>must be set on the whole libvlc instance instead.</para>
        /// </remarks>
        public void AddOptionFlag(string options, uint flags)
        {
            if (string.IsNullOrEmpty(options)) throw new ArgumentNullException(nameof(options));

445
            Native.LibVLCMediaAddOptionFlag(NativeReference, options, flags);
Martin Finkel's avatar
Martin Finkel committed
446 447 448 449 450 451 452 453 454 455
        }

        string _mrl;
        /// <summary>Get the media resource locator (mrl) from a media descriptor object</summary>
        public string Mrl
        {
            get
            {
                if (string.IsNullOrEmpty(_mrl))
                {
456
                    var mrlPtr = Native.LibVLCMediaGetMrl(NativeReference);
Martin Finkel's avatar
Martin Finkel committed
457 458 459 460 461 462 463 464 465
                    _mrl = Utf8StringMarshaler.GetInstance().MarshalNativeToManaged(mrlPtr) as string;
                }
                return _mrl;
            }
        }

        /// <summary>Duplicate a media descriptor object.</summary>
        public Media Duplicate()
        {
466
            var duplicatePtr = Native.LibVLCMediaDuplicate(NativeReference);
Martin Finkel's avatar
Martin Finkel committed
467 468 469 470 471 472 473 474 475 476 477 478
            if(duplicatePtr == IntPtr.Zero) throw new Exception("Failure to duplicate");
            return new Media(duplicatePtr);
        }

        /// <summary>Read the meta of the media.</summary>
        /// <param name="metadataType">the meta to read</param>
        /// <returns>the media's meta</returns>
        /// <remarks>
        /// If the media has not yet been parsed this will return NULL.
        /// </remarks>
        public string Meta(MetadataType metadataType)
        {
479
            var metaPtr = Native.LibVLCMediaGetMeta(NativeReference, metadataType);
Martin Finkel's avatar
Martin Finkel committed
480 481 482 483 484 485 486 487 488 489 490 491 492 493
            if (metaPtr == IntPtr.Zero) return string.Empty;
            return Utf8StringMarshaler.GetInstance().MarshalNativeToManaged(metaPtr) as string;
        }

        /// <summary>
        /// <para>Set the meta of the media (this function will not save the meta, call</para>
        /// <para>libvlc_media_save_meta in order to save the meta)</para>
        /// </summary>
        /// <param name="metadataType">the <see cref="MetadataType"/>  to write</param>
        /// <param name="value">the media's meta</param>
        public void SetMeta(MetadataType metadataType, string value)
        {
            if(string.IsNullOrEmpty(value)) throw new ArgumentNullException(value);

494
            Native.LibVLCMediaSetMeta(NativeReference, metadataType, value);
Martin Finkel's avatar
Martin Finkel committed
495 496 497 498
        }

        /// <summary>Save the meta previously set</summary>
        /// <returns>true if the write operation was successful</returns>
Martin Finkel's avatar
cleanup  
Martin Finkel committed
499
        public bool SaveMeta() => Native.LibVLCMediaSaveMeta(NativeReference) != 0;
Martin Finkel's avatar
Martin Finkel committed
500 501 502 503

        /// <summary>
        /// Get current <see cref="VLCState"/> of media descriptor object.
        /// </summary>
504
        public VLCState State => Native.LibVLCMediaGetState(NativeReference);
Martin Finkel's avatar
Martin Finkel committed
505 506 507 508

        /// <summary>Get the current statistics about the media
        /// structure that contain the statistics about the media
        /// </summary>
Martin Finkel's avatar
cleanup  
Martin Finkel committed
509 510
        public MediaStats Statistics => Native.LibVLCMediaGetStats(NativeReference, out var mediaStats) == 0 
            ? default(MediaStats) : mediaStats;
Martin Finkel's avatar
Martin Finkel committed
511

Martin Finkel's avatar
Martin Finkel committed
512
        MediaEventManager _eventManager;
Martin Finkel's avatar
Martin Finkel committed
513 514 515 516 517
        /// <summary>
        /// <para>Get event manager from media descriptor object.</para>
        /// <para>NOTE: this function doesn't increment reference counting.</para>
        /// </summary>
        /// <returns>event manager object</returns>
Martin Finkel's avatar
Martin Finkel committed
518
        public MediaEventManager EventManager
Martin Finkel's avatar
Martin Finkel committed
519 520 521 522
        {
            get
            {
                if (_eventManager != null) return _eventManager;
523
                var eventManagerPtr = Native.LibVLCMediaEventManager(NativeReference);
Martin Finkel's avatar
Martin Finkel committed
524
                _eventManager = new MediaEventManager(eventManagerPtr);
Martin Finkel's avatar
Martin Finkel committed
525 526 527 528 529 530
                return _eventManager;
            }
        }

        /// <summary>Get duration (in ms) of media descriptor object item.</summary>
        /// <returns>duration of media item or -1 on error</returns>
531
        public long Duration => Native.LibVLCMediaGetDuration(NativeReference);
Martin Finkel's avatar
Martin Finkel committed
532 533 534 535 536 537 538 539 540 541

        /// <summary>Parse a media.
        /// This fetches (local) art, meta data and tracks information.
        /// The method is synchronous.
        /// This function could block indefinitely.
        /// Use libvlc_media_parse_with_options() instead
        /// libvlc_media_parse_with_options
        /// libvlc_media_get_meta
        /// libvlc_media_get_tracks_info
        /// </summary>
Martin Finkel's avatar
cleanup  
Martin Finkel committed
542
        public void Parse() => Native.LibVLCMediaParse(NativeReference);
Martin Finkel's avatar
Martin Finkel committed
543 544 545

        /// <summary>Return true is the media descriptor object is parsed</summary>
        /// <returns>true if media object has been parsed otherwise it returns false</returns>
546
        public bool IsParsed => Native.LibVLCMediaIsParsed(NativeReference) != 0;
Martin Finkel's avatar
Martin Finkel committed
547 548 549 550 551 552 553 554

        /// <summary>Get Parsed status for media descriptor object.</summary>
        /// <returns>a value of the libvlc_media_parsed_status_t enum</returns>
        /// <remarks>
        /// <para>libvlc_MediaParsedChanged</para>
        /// <para>libvlc_media_parsed_status_t</para>
        /// <para>LibVLC 3.0.0 or later</para>
        /// </remarks>
555
        public MediaParsedStatus ParsedStatus => Native.LibVLCMediaGetParsedStatus(NativeReference);
Martin Finkel's avatar
Martin Finkel committed
556 557 558 559 560 561 562 563

        /// <summary>Stop the parsing of the media</summary>
        /// <remarks>
        /// <para>When the media parsing is stopped, the libvlc_MediaParsedChanged event will</para>
        /// <para>be sent with the libvlc_media_parsed_status_timeout status.</para>
        /// <para>libvlc_media_parse_with_options</para>
        /// <para>LibVLC 3.0.0 or later</para>
        /// </remarks>
Martin Finkel's avatar
cleanup  
Martin Finkel committed
564
        public void ParseStop() => Native.LibVLCMediaParseStop(NativeReference);
Martin Finkel's avatar
Martin Finkel committed
565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581

        /// <summary>Get media descriptor's elementary streams description</summary>
        /// <para>address to store an allocated array of Elementary Streams</para>
        /// <para>descriptions (must be freed with libvlc_media_tracks_release</para>
        /// <para>by the caller) [OUT]</para>
        /// <returns>the number of Elementary Streams (zero on error)</returns>
        /// <remarks>
        /// <para>Note, you need to call libvlc_media_parse() or play the media at least once</para>
        /// <para>before calling this function.</para>
        /// <para>Not doing this will result in an empty array.</para>
        /// <para>LibVLC 2.1.0 and later.</para>
        /// </remarks>
        public IEnumerable<MediaTrack> Tracks
        {
            get
            {
                var arrayResultPtr = IntPtr.Zero;
582
                var count = Native.LibVLCMediaTracksGet(NativeReference, ref arrayResultPtr);
Martin Finkel's avatar
Martin Finkel committed
583 584 585 586 587 588 589 590 591 592 593
                if (count == 0 || arrayResultPtr == IntPtr.Zero) return Enumerable.Empty<MediaTrack>();

                var tracks = new List<MediaTrack>();
                for (var i = 0; i < count; i++)
                {
                    var ptr = Marshal.ReadIntPtr(arrayResultPtr, i * IntPtr.Size);
                    var managedStruct = Marshal.PtrToStructure<MediaTrack>(ptr);
                    tracks.Add(managedStruct);

                }

594
                Native.LibVLCMediaTracksRelease(arrayResultPtr, count);
Martin Finkel's avatar
Martin Finkel committed
595 596 597 598 599 600 601 602 603 604 605

                return tracks;
            }
        }

        /// <summary>
        /// <para>Get subitems of media descriptor object. This will increment</para>
        /// <para>the reference count of supplied media descriptor object. Use</para>
        /// <para>libvlc_media_list_release() to decrement the reference counting.</para>
        /// </summary>
        /// <returns>list of media descriptor subitems or NULL</returns>
Martin Finkel's avatar
Martin Finkel committed
606 607
        public MediaList SubItems => new MediaList(Native.LibVLCMediaSubitems(NativeReference));
       
608
        public MediaType Type => Native.LibVLCMediaGetType(NativeReference);
Martin Finkel's avatar
Martin Finkel committed
609 610 611 612 613 614 615 616 617 618 619 620 621 622

        /// <summary>Add a slave to the current media.</summary>
        /// <param name="type">subtitle or audio</param>
        /// <param name="priority">from 0 (low priority) to 4 (high priority)</param>
        /// <param name="uri">Uri of the slave (should contain a valid scheme).</param>
        /// <returns>0 on success, -1 on error.</returns>
        /// <remarks>
        /// <para>A slave is an external input source that may contains an additional subtitle</para>
        /// <para>track (like a .srt) or an additional audio track (like a .ac3).</para>
        /// <para>This function must be called before the media is parsed (via</para>
        /// <para>libvlc_media_parse_with_options()) or before the media is played (via</para>
        /// <para>libvlc_media_player_play())</para>
        /// <para>LibVLC 3.0.0 and later.</para>
        /// </remarks>
Martin Finkel's avatar
cleanup  
Martin Finkel committed
623 624 625 626
        [LibVLC(3)]
        public bool AddSlave(MediaSlaveType type, uint priority, string uri) => 
            Native.LibVLCMediaAddSlaves(NativeReference, type, priority, uri) != 0;

Martin Finkel's avatar
Martin Finkel committed
627 628 629 630 631 632

        /// <summary>
        /// <para>Clear all slaves previously added by libvlc_media_slaves_add() or</para>
        /// <para>internally.</para>
        /// </summary>
        /// <remarks>LibVLC 3.0.0 and later.</remarks>
Martin Finkel's avatar
cleanup  
Martin Finkel committed
633 634
        [LibVLC(3)]
        public void ClearSlaves() => Native.LibVLCMediaClearSlaves(NativeReference);
Martin Finkel's avatar
Martin Finkel committed
635 636 637 638 639 640 641 642 643 644 645 646

        /// <summary>Get a media descriptor's slave list</summary>
        /// <para>address to store an allocated array of slaves (must be</para>
        /// <para>freed with libvlc_media_slaves_release()) [OUT]</para>
        /// <returns>the number of slaves (zero on error)</returns>
        /// <remarks>
        /// <para>The list will contain slaves parsed by VLC or previously added by</para>
        /// <para>libvlc_media_slaves_add(). The typical use case of this function is to save</para>
        /// <para>a list of slave in a database for a later use.</para>
        /// <para>LibVLC 3.0.0 and later.</para>
        /// <para>libvlc_media_slaves_add</para>
        /// </remarks>
Martin Finkel's avatar
cleanup  
Martin Finkel committed
647
        [LibVLC(3)]
Martin Finkel's avatar
Martin Finkel committed
648 649 650 651 652
        public IEnumerable<MediaSlave> Slaves
        {
            get
            {
                var slaveArrayPtr = IntPtr.Zero;
653
                var count = Native.LibVLCMediaGetSlaves(NativeReference, ref slaveArrayPtr);
Martin Finkel's avatar
Martin Finkel committed
654 655 656 657 658 659 660 661 662
                if (count == 0) return Enumerable.Empty<MediaSlave>();

                var slaves = new List<MediaSlave>();
                for (var i = 0; i < count; i++)
                {
                    var ptr = Marshal.ReadIntPtr(slaveArrayPtr, i * IntPtr.Size);
                    var managedStruct = Marshal.PtrToStructure<MediaSlave>(ptr);
                    slaves.Add(managedStruct);
                }
663
                Native.LibVLCMediaReleaseSlaves(slaveArrayPtr, count);
Martin Finkel's avatar
Martin Finkel committed
664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682
                return slaves;
            }
        }

        public override bool Equals(object obj)
        {
            return obj is Media media &&
                   EqualityComparer<IntPtr>.Default.Equals(NativeReference, media.NativeReference);
        }

        internal class StreamData
        {
            public IntPtr Handle { get; set; }
            public Stream Stream { get; set; }
            public byte[] Buffer { get; set; }
        }

        #region private

683
        static int CallbackOpenMedia(IntPtr opaque, ref IntPtr data, out ulong size)
Martin Finkel's avatar
Martin Finkel committed
684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714
        {
            data = opaque;

            try
            {
                var streamData = GetStream(opaque);

                try
                {
                    size = (ulong)streamData.Stream.Length;
                }
                catch (Exception)
                {
                    // byte length of the bitstream or UINT64_MAX if unknown
                    size = ulong.MaxValue;
                }

                if (streamData.Stream.CanSeek)
                {
                    streamData.Stream.Seek(0L, SeekOrigin.Begin);
                }

                return 0;
            }
            catch (Exception)
            {
                size = 0UL;
                return -1;
            }
        }

715
        static int CallbackReadMedia(IntPtr opaque, IntPtr buf, uint len)
Martin Finkel's avatar
Martin Finkel committed
716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736
        {
            try
            {
                var streamData = GetStream(opaque);
                int read;

                lock (streamData)
                {
                    var canRead = Math.Min((int)len, streamData.Buffer.Length);
                    read = streamData.Stream.Read(streamData.Buffer, 0, canRead);
                    Marshal.Copy(streamData.Buffer, 0, buf, read);
                }

                return read;
            }
            catch (Exception)
            {
                return -1;
            }
        }

737
        static int CallbackSeekMedia(IntPtr opaque, UInt64 offset)
Martin Finkel's avatar
Martin Finkel committed
738 739 740 741 742 743 744 745 746 747 748 749 750
        {
            try
            {
                var streamData = GetStream(opaque);
                streamData.Stream.Seek((long)offset, SeekOrigin.Begin);
                return 0;
            }
            catch (Exception)
            {
                return -1;
            }
        }

751
        static void CallbackCloseMedia(IntPtr opaque)
Martin Finkel's avatar
Martin Finkel committed
752 753 754 755 756 757 758 759 760 761 762
        {
            try
            {
                RemoveStream(opaque);
            }
            catch (Exception)
            {
                // ignored
            }
        }

763
        static IntPtr AddStream(Stream stream)
Martin Finkel's avatar
Martin Finkel committed
764 765 766 767 768 769 770 771
        {
            if (stream == null)
            {
                throw new ArgumentNullException(nameof(stream));
            }

            IntPtr handle;

772
            lock (DicStreams)
Martin Finkel's avatar
Martin Finkel committed
773
            {
774 775
                _streamIndex++;
            
Martin Finkel's avatar
Martin Finkel committed
776

777 778 779 780 781 782 783 784
                handle = new IntPtr(_streamIndex);
                DicStreams[handle] = new StreamData
                {
                    Buffer = new byte[0x4000],
                    Handle = handle,
                    Stream = stream
                };
            }
Martin Finkel's avatar
Martin Finkel committed
785 786 787
            return handle;
        }

788
        static StreamData GetStream(IntPtr handle)
Martin Finkel's avatar
Martin Finkel committed
789 790 791 792
        {
            return !DicStreams.TryGetValue(handle, out var result) ? null : result;
        }

793
        static void RemoveStream(IntPtr handle)
Martin Finkel's avatar
Martin Finkel committed
794 795 796 797 798 799 800
        { 
            DicStreams.TryRemove(handle, out var result);
        }

        void Retain()
        {
            if(NativeReference != IntPtr.Zero)
801
                Native.LibVLCMediaRetain(NativeReference);
Martin Finkel's avatar
Martin Finkel committed
802 803 804 805 806
        }

        #endregion
    }

807 808
    #region Callbacks

Martin Finkel's avatar
Martin Finkel committed
809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864
    /// <summary>
    /// <para>It consists of a media location and various optional meta data.</para>
    /// <para>@{</para>
    /// <para></para>
    /// <para>LibVLC media item/descriptor external API</para>
    /// </summary>
    /// <summary>Callback prototype to open a custom bitstream input media.</summary>
    /// <param name="opaque">private pointer as passed to libvlc_media_new_callbacks()</param>
    /// <param name="data">storage space for a private data pointer [OUT]</param>
    /// <param name="size">byte length of the bitstream or UINT64_MAX if unknown [OUT]</param>
    /// <returns>
    /// <para>0 on success, non-zero on error. In case of failure, the other</para>
    /// <para>callbacks will not be invoked and any value stored in *datap and *sizep is</para>
    /// <para>discarded.</para>
    /// </returns>
    /// <remarks>
    /// <para>The same media item can be opened multiple times. Each time, this callback</para>
    /// <para>is invoked. It should allocate and initialize any instance-specific</para>
    /// <para>resources, then store them in *datap. The instance resources can be freed</para>
    /// <para>in the</para>
    /// <para>For convenience, *datap is initially NULL and *sizep is initially 0.</para>
    /// </remarks>
    [SuppressUnmanagedCodeSecurity, UnmanagedFunctionPointer(CallingConvention.Cdecl)]
    public delegate int OpenMedia(IntPtr opaque, ref IntPtr data, out ulong size);

    /// <summary>Callback prototype to read data from a custom bitstream input media.</summary>
    /// <param name="opaque">private pointer as set by the</param>
    /// <param name="buf">start address of the buffer to read data into</param>
    /// <param name="len">bytes length of the buffer</param>
    /// <returns>
    /// <para>strictly positive number of bytes read, 0 on end-of-stream,</para>
    /// <para>or -1 on non-recoverable error</para>
    /// </returns>
    /// <remarks>
    /// <para>callback</para>
    /// <para>If no data is immediately available, then the callback should sleep.</para>
    /// <para>The application is responsible for avoiding deadlock situations.</para>
    /// <para>In particular, the callback should return an error if playback is stopped;</para>
    /// <para>if it does not return, then libvlc_media_player_stop() will never return.</para>
    /// </remarks>
    [SuppressUnmanagedCodeSecurity, UnmanagedFunctionPointer(CallingConvention.Cdecl)]
    public delegate int ReadMedia(IntPtr opaque, IntPtr buf, uint len);

    /// <summary>Callback prototype to seek a custom bitstream input media.</summary>
    /// <param name="opaque">private pointer as set by the</param>
    /// <param name="offset">absolute byte offset to seek to</param>
    /// <returns>0 on success, -1 on error.</returns>
    /// <remarks>callback</remarks>
    [SuppressUnmanagedCodeSecurity, UnmanagedFunctionPointer(CallingConvention.Cdecl)]
    public delegate int SeekMedia(IntPtr opaque, ulong offset);

    /// <summary>Callback prototype to close a custom bitstream input media.</summary>
    /// <param name="opaque">private pointer as set by the</param>
    /// <remarks>callback</remarks>
    [SuppressUnmanagedCodeSecurity, UnmanagedFunctionPointer(CallingConvention.Cdecl)]
    public delegate void CloseMedia(IntPtr opaque);
865

866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921
    #endregion

    #region Structs

    [StructLayout(LayoutKind.Sequential)]
    public struct MediaStats
    {
        /* Input */
        public int ReadBytes;
        public float InputBitrate;

        /* Demux */
        public int DemuxReadBytes;
        public float DemuxBitrate;
        public int DemuxCorrupted;
        public int DemuxDiscontinuity;

        /* Decoders */
        public int DecodedVideo;
        public int DecodedAudio;

        /* Video Output */
        public int DisplayedPictures;
        public int LostPictures;

        /* Audio output */
        public int PlayedAudioBuffers;
        public int LostAudioBuffers;

        /* Stream output */
        public int SentPackets;
        public int SentBytes;
        public float SendBitrate;
    }

    public struct MediaTrack
    {
        public uint Codec;
        public uint OriginalFourcc;
        public int Id;
        public TrackType TrackType;
        public int Profile;
        public int Level;
        public MediaTrackData Data;
        public uint Bitrate;
        public IntPtr Language;
        public IntPtr Description;
    }

    public struct MediaTrackData
    {
        public AudioTrack Audio;
        public VideoTrack Video;
        public SubtitleTrack Subtitle;
    }

922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970 971 972 973 974 975 976 977 978 979 980 981 982 983 984 985 986 987 988 989 990 991 992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037
    /// <summary>Note the order of libvlc_state_t enum must match exactly the order of</summary>
    /// <remarks>
    /// <para>mediacontrol_PlayerStatus,</para>
    /// <para>input_state_e enums,</para>
    /// <para>and VideoLAN.LibVLCSharp.State (at bindings/cil/src/media.cs).</para>
    /// <para>Expected states by web plugins are:</para>
    /// <para>IDLE/CLOSE=0, OPENING=1, PLAYING=3, PAUSED=4,</para>
    /// <para>STOPPING=5, ENDED=6, ERROR=7</para>
    /// </remarks>
    public enum VLCState
    {
        NothingSpecial = 0,
        Opening = 1,
        Buffering = 2,
        Playing = 3,
        Paused = 4,
        Stopped = 5,
        Ended = 6,
        Error = 7
    }

    public enum TrackType
    {
        Unknown = -1,
        Audio = 0,
        Video = 1,
        Text = 2
    }

    public struct AudioTrack
    {
        public uint Channels;
        public uint Rate;
    }

    public struct VideoTrack
    {
        public uint Height;

        public uint Width;

        public uint SarNum;

        public uint SarDen;

        public uint FrameRateNum;

        public uint FrameRateDen;

        public VideoOrientation Orientation;

        public VideoProjection Projection;

        public VideoViewpoint Pose;
    }

    public enum VideoOrientation
    {
        /// <summary>Normal. Top line represents top, left column left.</summary>
        TopLeft = 0,
        /// <summary>Flipped horizontally</summary>
        TopRight = 1,
        /// <summary>Flipped vertically</summary>
        BottomLeft = 2,
        /// <summary>Rotated 180 degrees</summary>
        BottomRight = 3,
        /// <summary>Transposed</summary>
        LeftTop = 4,
        /// <summary>Rotated 90 degrees clockwise (or 270 anti-clockwise)</summary>
        LeftBottom = 5,
        /// <summary>Rotated 90 degrees anti-clockwise</summary>
        RightTop = 6,
        /// <summary>Anti-transposed</summary>
        RightBottom = 7
    }

    [Flags]
    public enum VideoProjection
    {
        Rectangular = 0,
        /// <summary>360 spherical</summary>
        Equirectangular = 1,
        CubemapLayoutStandard = 256
    }

    /// <summary>Viewpoint for video outputs</summary>
    /// <remarks>allocate using libvlc_video_new_viewpoint()</remarks>
    [StructLayout(LayoutKind.Sequential)]
    public struct VideoViewpoint
    {
        public float Yaw;
        public float Pitch;
        public float Roll;
        public float Fov;
    }

    public struct SubtitleTrack
    {
        public IntPtr Encoding;
    }

    /// <summary>Type of a media slave: subtitle or audio.</summary>
    public enum MediaSlaveType
    {
        Subtitle = 0,
        Audio = 1
    }

    /// <summary>A slave of a libvlc_media_t</summary>
    /// <remarks>libvlc_media_slaves_get</remarks>
    public struct MediaSlave
    {
        public IntPtr Uri;
        public MediaSlaveType Type;
        public uint Priority;
    }
1038
    #endregion
Martin Finkel's avatar
Martin Finkel committed
1039
}