Hosting VLC Player in .NET Winforms – Part 4

EVENTS

Previously, in parts 1, 2 and 3, we’ve shown how to get to the stage where you can basically play and pause a media file using VLC, displaying the video on a panel on your own form. However, we need to do more:

  1. How do you know when playback is complete ? Or an error occurs ? Or when to update the display to show the current media playback position (e.g. in a trackbar)? And so forth.
  2. Some functions – such as for example trying to get the media’s video image size – to work when if you try and call C’s methods straight after starting playback they seem to return invalid values ?
  3. How do you know if the media file opened successfully ?

The answer to this seems to be in hooking into the VLC events, specifically those to do with playback. Hope you are paying attention, this is a little more involved.

It all starts off simple enough; just like creating a session handle, a media handle or a player handle, we have a function to get a a handle to VLC’s event generator:-

events1

(To be confirmed : although I’ve indicated that a handle to a player – hPlayer – is passed, I think various handles can be passed depending upon the type of object for which we are trying to get events).

There are maybe a hundred or so possible events, but for a player only the following seem to be relevant:

events2

We specify which events we are interested in by taking the VLC event handle and using the following functions:

events3

Wait a moment .. whats this “hCallback”? Well, that’s the way we specify what function we want to be called back when an event occurs.

Well, first we need to define a delegate that describes a function we will be implementing in C# land to receive the callbacks:

events4

If we now go back to our form on which we are playing the media, we implement a function that matches the delegate:

events5

We also hook up VLC to call this function for every player related event:

events6

[IMPORTANT: The first line shows hEvent stored locally. This is just for illustration; you must store this at the class level otherwise when it goes out of scope .NET’ll clean it up !]

Note the use of Marshal.GetFunctionPointerForDelegate(), passing in the function we want to be called back and in return we get an IntPtr whichis the “hCallback” we need for the libvlc_event_attach and libvlc_event_detach calls.

Anyhow, you should now have a function that is called each time there is a player-related event.

As an aside : The more experience reader will probably think they are ahead of me here in knowing what the IntPtr “userData” is that you specify when attaching events, which also seems to be a parameter received when your delegate function is called. You are thinking that this is a way of passing your own data into an event ? Oh, you are SO wrong, sucker ! No, the IntPtr passed to your callback is always IntPtr.Zero; instead, look at the IntPtr “stuff” passed in to the callback via the LibvlcEventData structure, Yep, thats where your userData actually appears.One thing you can use the userData field for is to store the hPlayer. Thus, if you were handling multiple players at the same time, you can use the same callback to handle events for all players, identifying the appropriate player using the userData field. (Note that from this handle you can also get at the related media handle and indeed dll session handle shouldyou ever need them).

Before you dive in to intercepting these events and using them, remember nothing is ever as simple as it seems; For example, when you get the “MediaPlayerLengthChanged” event you might be think that the VLC function you can use to read the media length can be called. Bazinga !!! No, we need to look at the events a little more closely.

Opening media and
calling Play()
Opening media and
calling Pause()
Opening and playing
invalid media
MediaPlayerMediaChanged
MediaPlayerOpening
MediaPlayerPausableChanged
MediaPlayerSeekableChanged
MediaPlayerLengthChanged
MediaPlayerPlaying
MediaPlayerLengthChanged
MediaPlayerPositionChanged
MediaPlayerTimeChanged
MediaPlayerPositionChanged
MediaPlayerTimeChanged
MediaPlayerPositionChanged
MediaPlayerTimeChanged
…… etc ……
MediaPlayerMediaChanged MediaPlayerMediaChanged
MediaPlayerOpening
MediaPlayerPausableChanged
MediaPlayerSeekableChanged
MediaPlayerLengthChanged
MediaPlayerPlaying
MediaPlayerLengthChanged
MediaPlayerEndReached

Observations:-

  1. When playing a file there are two MediaPlayerLengthChanged events, one before and one after the MediaPlayerPlaying. After the first MediaPlayerLengthChanged, if you query the player for the media length it’ll be zero ! Only after the second MediaPlayerLengthChanged – that is the MediaPlayerLengthChanged that occurs only once a MediaPlayerPlaying has fired.
    [Refer update below]
  2. When playing, you get regular MediaPlayerPositionChanged – on a test with a 25fps mpeg2 video, this event seemed to fire around every quarter of a second so can be used to e.g. update an position slider control during playback.
  3. If you attempt to open a media file that VLC can’t play then you get a MediaPlayerEndReached. What differentiates this from a normal “playback complete” event is that you don’t previously encounter a “MediaPlayerPositionChanged” event ; you can use this information to indirectly determine that VLC couldn’t read the media.
    [Refer update below]
  4. It can take several seconds for VLC to recognise a media file – especially if it is on a CD or DVD that needs to spin up. Thus, you can update your UI as follows:-
    a) When you get a MediaPlayerMediaChanged event, disable all player related controls and maybe pop-up a dialog to indicate to the user that the media is in the process of being opened.
    b) Ignore all except MediaPlayerEndReached and MediaPlayerPositionChanged events. When you get either of these, you can close your pop-up dialog; the former event means that the media is not recognised, the latter means it is recognised and you and now call VLC’s functions such as those for getting the media image size and actually get a sensible answer.
  5. If you want to get your player to start in the paused state, follow the procedure for running a file but pause as soon as you get a MediaPlayerPositionChanged event. OK, so it doesn’t mean you’ve initialised the player with the first frame but I’m afraid its as good as you’ll be able to achieve :-(
  6. Events such as MediaPlayerPausableChanged and MediaPlayerSeekableChanged fire before MediaPlayerPositionChanged and hence you can not actually get the true pausable/seekable state. So, if you get these events prior to MediaPlayerPositionChanged then cache them but only act on them AFTER you get the MediaPlayerPositionChanged event.

[Update 2015-05-15] It seems later versions of VLC only generate a single length changed event. Therefore, use the first MediaPlayerPositionChanged event as a signal that media opened successfully, or if you get a MediaPlayerReachedEnd event without having seen a MediaPlayerPositionChanged first then it means VLC couldn’t open the media.

Now for an admission; I’m not entirely convinced about what happens; for example attempting to open a piece of media might actually kick off a thread and querying things about the media can only succeed when that thread gets to a certain point. In my test, that point seems to coincide with the MediaPlayerPositionChanged event, however I can not guarentee this. There is no documentation to help us here, this is a purely empirical result.

Well, that’s about it really, all you need to embed VLC on your own winform, how to load Win32 DLL’s in .NET and link up the functions. Have fun and remember, I found parts of this empirically, there may well be better functions available.

 

 

 

 

3 comments

  1. Jacques says:

    Cool article, any code / sample downloads available?

  2. DukeNukem says:

    ‘Fraid not at this time. The test app was a bit of a mess as I was trying to figure it all out – a lot of trial and error with the VLC side of things – and I never progressed to making it a library.

  3. Jack STephenson says:

    Great work buddy!

Leave a Reply