First, we'll have to locate the file in which libvlc_media_player_play is declared.
Like most of the media_player functions, this one is located in "src/control/media_player.c"
Let's start by analysing what is does :

  • Releasing the input thread (which is the thread that handle the media playback) in case a media was already playing.
  • Creating a new input thread
  • Adding a bunch of rendering related variables (we'll certainly have to go back to variables later)
  • Adding 3 callbacks : input_seekable_changed, input_pausable_changed, input_event_changed


As far as i know, everything that's related to the media playback is managed by the input_thread, so let's take a closer look at the input_CreateThread() function...

This new function is located in "src/input/input.c". It's actually some kind of a wrapper on the "__input_CreateThreadExtended" function, which is located 10lines below. The only thing it does it to create a new thread, and launch it wich the "Run" function, which is located in the same file.

This Run function will call : Init(), MainLoop(), and finally, End(). At this time, I really don't care about End(), so I'll focus on the Init and MainLoop functions, which are, once again, located in the same file.

If I'm not mistaking, VLC worlflow consists of getting an elementary stream from a demuxer (among many other things) The init function creates the elementary streams (ES), creates 2 vlc-variables containing the bit-rate and the sample-rate.
Once this is done, it changes the media state to "Opening", and then call the InputSourceInit function.

This function is in charge of selecting the appropriate demux, or eventually selecting one that has been set by the command line. Selecting the demux is done by calling the demux_New() function. The selected demux is used to get a bunch of informations about the media, such as "can-seek", "can-pause"... and do many other things that I don't really understand at the moment :(
Let's go back to the Init function, and see what is does next...

Basically, it gets information about the file, init some stuff such as subtitles, and then changes the state to "Playing", which is what isn't always working in vlmc... I'm pretty confident that our problem is in this state change...

Even if this in kind of dirty, i'm going to add debug code to see what the hell happens in this function.

I'm going to add debug into the input_ChangeState, and to be sure that our problem isn't event related (and it seems like it, since the playback continues when we display the VLC debug lines...), into the input_SendEventState, which is located in the "src/input/event.c" file.

The "difficult" thing is that the input_SendEventState uses vlc-variables, which is a really nice thing, but something that's hard to apprehend (or at least it was for me). variable value is changed that way :

var_Change( p_input, "state", VLC_VAR_SETVALUE, &val, NULL );


When the "state" var is changed, it triggers a callback... but which one ?! Let's call or friend grep : grep --color -HRn "\"state\"" src/* Which highlight this line in particular : src/input/var.c:87: CALLBACK( "state", StateCallback ), Let's take a closer look... this callback calls "input_ControlPush( p_input, INPUT_CONTROL_SET_STATE, &newval );", provided that the new state is PLAYING_S, or PAUSE_S.

Good news everyone ! The state that was set is indeed PLAYING_S. We can keep going...

input_ControlPush takes us back into input.c.

What's the control inside vlc ?? The VLC playback thread has an array of struct input_control_t, that are executed by the thread, from the MainLoop(). This function will basically push a new control to be executed later... so this code is definitely asynchronous (did you still doubt it ?!)

Ok no worries, let's go to the MainLoop code, and see how this control stack is handled...

MainLoop() is located in input.c (big file ain't it ? :p). At every iteration, it clears the control stack by calling ControlPop(), until the stack is empty. The poped value is handled by the Control() function.

Control function is basically a good old fashioned switch/case, in which we're going to seek for the control we pass. We finally come down to this piece of code :

case INPUT_CONTROL_SET_STATE:
    if( ( val.i_int == PLAYING_S && p_input->p->i_state == PAUSE_S ) ||
        ( val.i_int == PAUSE_S && p_input->p->i_state == PAUSE_S ) )
    {
        ControlUnpause( p_input, i_control_date );
 
        b_force_update = true;
    }
    else if( val.i_int == PAUSE_S && p_input->p->i_state == PLAYING_S /* &&
              p_input->p->b_can_pause */ )
    {
        ControlPause( p_input, i_control_date );
 
        b_force_update = true;
    }
    else if( val.i_int == PAUSE_S && !p_input->p->b_can_pause && 0 )
    {
        b_force_update = true;
 
        /* Correct "state" value */
        input_ChangeState( p_input, p_input->p->i_state );
    }
    else if( val.i_int != PLAYING_S && val.i_int != PAUSE_S )
    {
        msg_Err( p_input, "invalid state in INPUT_CONTROL_SET_STATE" );
    }
    break;

This is were we're going to put our second piece of debug. Now let's build'n pray !

As expected, the second debug output isn't displayed... just have to figure out why :) Since this will consist only of putting debug text here and there, I'll skip the details, and only explain the problem :p

In the end, the problem seems to be a race condition inside the libvlc_media_player_play() function code... More precisely :

void libvlc_media_player_play( libvlc_media_player_t *p_mi,
                                 libvlc_exception_t *p_e )
{
    input_thread_t * p_input_thread;
 
    if( (p_input_thread = libvlc_get_input_thread( p_mi, p_e )) )
    {
        //stoping the thread, releasing the old media...
    }
 
    //Some stuff
 
    //Creates the thread, thats spawn onto the Run() function, which will call Init() (which basically send the event)
    p_mi->p_input_thread = input_CreateThread(
            p_mi->p_libvlc_instance->p_libvlc_int, p_mi->p_md->p_input_item );
 
    if( !p_mi->p_input_thread )
    {
        vlc_mutex_unlock( &p_mi->object_lock );
        return;
    }
 
    p_input_thread = p_mi->p_input_thread;
 
    //Another block of stuff
 
    //Setting the callbacks	
    var_AddCallback( p_input_thread, "can-seek", input_seekable_changed, p_mi );
    var_AddCallback( p_input_thread, "can-pause", input_pausable_changed, p_mi );
    var_AddCallback( p_input_thread, "intf-event", input_event_changed, p_mi );
 
    vlc_mutex_unlock( &p_mi->object_lock );
}

In all my tests, the call to var_AddCallback was made *after* the Init function completed, which is baaaaad, since no event can be fired.

Anyway, a mail has been sent to the vlc-devel mailing list in order to get some instructions on how to fix this (I don't feel like fixing it on my own without asking for advice, since it's somehow a big big part of the libvlc, and it touches some critical part of vlc, IE. the input thread), This should be fixed very soon :)

This is a one-shot post, please forgive me if some incoherence appears ;)