[vlc-devel] [PATCH 3.x 1/2] lib: media_player: add stop/set_media async support

Thomas Guillem thomas at gllm.fr
Mon Oct 19 16:07:36 CEST 2020


This commit adds 2 new function into 3.0.12:
 - libvlc_media_player_stop_async()
 - libvlc_media_player_set_media_async()

These 2 new functions won't wait for the input_thread termination. This
will allow LibVLC users to set media and stop them from the mainthread
without blocking. In that case (*_async() function is called), the input
thread will be terminated from a background thread: the
destructor_thread.

libvlc_media_player_release() will wait for this destructor thread
termination. That is why, it is advised to release the media_player at
the end of the program (when UI is destroyed).

This functionality is backport of the async stop functionality of the
new VLC 4.0 player.
---
 include/vlc/libvlc_media_player.h |  28 ++++-
 lib/libvlc.sym                    |   2 +
 lib/media_player.c                | 182 +++++++++++++++++++++++++-----
 lib/media_player_internal.h       |  16 ++-
 test/libvlc/media_player.c        |  48 ++++++++
 5 files changed, 247 insertions(+), 29 deletions(-)

diff --git a/include/vlc/libvlc_media_player.h b/include/vlc/libvlc_media_player.h
index c431c235e9..010463be9c 100644
--- a/include/vlc/libvlc_media_player.h
+++ b/include/vlc/libvlc_media_player.h
@@ -192,6 +192,10 @@ LIBVLC_API libvlc_media_player_t * libvlc_media_player_new_from_media( libvlc_me
  * release the media player object. If the media player object
  * has been released, then it should not be used again.
  *
+ * If the reference count is 0, the media_player will stop the current media
+ * and all medias that are still stopping asynchronously (cf.
+ * libvlc_media_player_stop_async() and libvlc_media_player_set_media_async()).
+ *
  * \param p_mi the Media Player to free
  */
 LIBVLC_API void libvlc_media_player_release( libvlc_media_player_t *p_mi );
@@ -206,7 +210,9 @@ LIBVLC_API void libvlc_media_player_retain( libvlc_media_player_t *p_mi );
 
 /**
  * Set the media that will be used by the media_player. If any,
- * previous md will be released.
+ * previous md will be released synchronously.
+ *
+ * \warning The function will block until the previous media is released.
  *
  * \param p_mi the Media Player
  * \param p_md the Media. Afterwards the p_md can be safely
@@ -215,6 +221,18 @@ LIBVLC_API void libvlc_media_player_retain( libvlc_media_player_t *p_mi );
 LIBVLC_API void libvlc_media_player_set_media( libvlc_media_player_t *p_mi,
                                                    libvlc_media_t *p_md );
 
+/**
+ * Set the media that will be used by the media_player. If any,
+ * previous md will be released asynchronously.
+ *
+ * \param p_mi the Media Player
+ * \param p_md the Media. Afterwards the p_md can be safely
+ *        destroyed.
+ * \version LibVLC 3.0.12 or later
+ */
+LIBVLC_API void libvlc_media_player_set_media_async( libvlc_media_player_t *p_mi,
+                                                     libvlc_media_t *p_md );
+
 /**
  * Get the media used by the media_player.
  *
@@ -274,6 +292,14 @@ LIBVLC_API void libvlc_media_player_pause ( libvlc_media_player_t *p_mi );
  */
 LIBVLC_API void libvlc_media_player_stop ( libvlc_media_player_t *p_mi );
 
+/**
+ * Stop asynchronously (no effect if there is no media)
+ *
+ * \param p_mi the Media Player
+ * \version LibVLC 3.0.12 or later
+ */
+LIBVLC_API void libvlc_media_player_stop_async ( libvlc_media_player_t *p_mi );
+
 /**
  * Set a renderer to the media player
  *
diff --git a/lib/libvlc.sym b/lib/libvlc.sym
index 482d95f6f1..5cdd663b4a 100644
--- a/lib/libvlc.sym
+++ b/lib/libvlc.sym
@@ -192,6 +192,7 @@ libvlc_media_player_set_chapter
 libvlc_media_player_set_equalizer
 libvlc_media_player_set_hwnd
 libvlc_media_player_set_media
+libvlc_media_player_set_media_async
 libvlc_media_player_set_nsobject
 libvlc_media_player_set_position
 libvlc_media_player_set_rate
@@ -201,6 +202,7 @@ libvlc_media_player_set_time
 libvlc_media_player_set_title
 libvlc_media_player_set_xwindow
 libvlc_media_player_stop
+libvlc_media_player_stop_async
 libvlc_media_player_will_play
 libvlc_media_player_navigate
 libvlc_media_player_set_video_title_display
diff --git a/lib/media_player.c b/lib/media_player.c
index a9a22fee15..127cf5e635 100644
--- a/lib/media_player.c
+++ b/lib/media_player.c
@@ -45,6 +45,8 @@
 
 #define ES_INIT (-2) /* -1 is reserved for ES deselect */
 
+static void *destructor_thread(void *data);
+
 static int
 input_seekable_changed( vlc_object_t * p_this, char const * psz_cmd,
                         vlc_value_t oldval, vlc_value_t newval,
@@ -159,14 +161,17 @@ static void media_detach_preparsed_event( libvlc_media_t *p_md )
  * Object lock is NOT held.
  * Input lock is held or instance is being destroyed.
  */
-static void release_input_thread( libvlc_media_player_t *p_mi )
+static struct input_thread_node *
+stop_input_thread( libvlc_media_player_t *p_mi )
 {
     assert( p_mi );
 
-    input_thread_t *p_input_thread = p_mi->input.p_thread;
-    if( !p_input_thread )
-        return;
-    p_mi->input.p_thread = NULL;
+    struct input_thread_node *node = p_mi->input.node;
+    if (node == NULL)
+        return NULL;
+
+    input_thread_t *p_input_thread = node->input;
+    p_mi->input.node = NULL;
 
     media_detach_preparsed_event( p_mi->p_md );
 
@@ -182,7 +187,36 @@ static void release_input_thread( libvlc_media_player_t *p_mi )
 
     /* We owned this one */
     input_Stop( p_input_thread );
-    input_Close( p_input_thread );
+
+    return node;
+}
+
+static void
+release_input_thread(libvlc_media_player_t *mp, bool async)
+{
+    struct input_thread_node *current_node = stop_input_thread(mp);
+    if (current_node != NULL)
+    {
+        if (!async)
+        {
+            input_Close(current_node->input);
+            free(current_node);
+        }
+        else
+        {
+            /* Send this input to the destructor thread will be close it */
+            if (mp->destructor.input_list == NULL)
+                mp->destructor.input_list = current_node;
+            else
+            {
+                struct input_thread_node *node = mp->destructor.input_list;
+                while (node->next != NULL)
+                    node = node->next;
+                node->next = current_node;
+            }
+            vlc_cond_signal(&mp->destructor.wait);
+        }
+    }
 }
 
 /*
@@ -196,7 +230,7 @@ input_thread_t *libvlc_get_input_thread( libvlc_media_player_t *p_mi )
     assert( p_mi );
 
     lock_input(p_mi);
-    p_input_thread = p_mi->input.p_thread;
+    p_input_thread = p_mi->input.node != NULL ? p_mi->input.node->input : NULL;
     if( p_input_thread )
         vlc_object_hold( p_input_thread );
     else
@@ -737,7 +771,7 @@ libvlc_media_player_new( libvlc_instance_t *instance )
     mp->p_md = NULL;
     mp->state = libvlc_NothingSpecial;
     mp->p_libvlc_instance = instance;
-    mp->input.p_thread = NULL;
+    mp->input.node = NULL;
     mp->input.p_renderer = NULL;
     mp->input.p_resource = input_resource_New(VLC_OBJECT(mp));
     if (unlikely(mp->input.p_resource == NULL))
@@ -774,6 +808,17 @@ libvlc_media_player_new( libvlc_instance_t *instance )
     var_AddCallback(mp->obj.libvlc, "snapshot-file", snapshot_was_taken, mp);
 
     libvlc_retain(instance);
+
+    vlc_cond_init(&mp->destructor.wait);
+    mp->destructor.running = true;
+    mp->destructor.input_list = NULL;
+    if (vlc_clone(&mp->destructor.thread, destructor_thread,
+                  mp, VLC_THREAD_PRIORITY_LOW) != 0)
+    {
+        libvlc_media_player_destroy(mp);
+        return NULL;
+    }
+
     return mp;
 }
 
@@ -814,10 +859,16 @@ static void libvlc_media_player_destroy( libvlc_media_player_t *p_mi )
     var_DelCallback( p_mi, "audio-device", audio_device_changed, NULL );
     var_DelCallback( p_mi, "corks", corks_changed, NULL );
 
-    /* No need for lock_input() because no other threads knows us anymore */
-    if( p_mi->input.p_thread )
-        release_input_thread(p_mi);
-    input_resource_Terminate( p_mi->input.p_resource );
+    lock_input( p_mi );
+
+    release_input_thread(p_mi, true);
+
+    /* Stop and wait for the destructor thread */
+    p_mi->destructor.running = false;
+    vlc_cond_signal( &p_mi->destructor.wait );
+    unlock_input( p_mi );
+    vlc_join(p_mi->destructor.thread, NULL);
+
     input_resource_Release( p_mi->input.p_resource );
     if( p_mi->input.p_renderer )
         vlc_renderer_item_release( p_mi->input.p_renderer );
@@ -877,13 +928,13 @@ void libvlc_media_player_retain( libvlc_media_player_t *p_mi )
  *
  * Enter without lock -- function will lock the object.
  **************************************************************************/
-void libvlc_media_player_set_media(
+static void libvlc_media_player_set_media_common(
                             libvlc_media_player_t *p_mi,
-                            libvlc_media_t *p_md )
+                            libvlc_media_t *p_md, bool async )
 {
     lock_input(p_mi);
 
-    release_input_thread( p_mi );
+    release_input_thread( p_mi, async );
 
     lock( p_mi );
     set_state( p_mi, libvlc_NothingSpecial, true );
@@ -912,7 +963,20 @@ void libvlc_media_player_set_media(
     event.type = libvlc_MediaPlayerMediaChanged;
     event.u.media_player_media_changed.new_media = p_md;
     libvlc_event_send( &p_mi->event_manager, &event );
+}
 
+void libvlc_media_player_set_media(
+                            libvlc_media_player_t *p_mi,
+                            libvlc_media_t *p_md )
+{
+    libvlc_media_player_set_media_common( p_mi, p_md, false );
+}
+
+void libvlc_media_player_set_media_async(
+                            libvlc_media_player_t *p_mi,
+                            libvlc_media_t *p_md )
+{
+    libvlc_media_player_set_media_common( p_mi, p_md, true );
 }
 
 /**************************************************************************
@@ -962,7 +1026,8 @@ int libvlc_media_player_play( libvlc_media_player_t *p_mi )
 {
     lock_input( p_mi );
 
-    input_thread_t *p_input_thread = p_mi->input.p_thread;
+    input_thread_t *p_input_thread =
+            p_mi->input.node != NULL ? p_mi->input.node->input : NULL;
     if( p_input_thread )
     {
         /* A thread already exists, send it a play message */
@@ -982,19 +1047,29 @@ int libvlc_media_player_play( libvlc_media_player_t *p_mi )
         return -1;
     }
 
+    struct input_thread_node *node = malloc(sizeof(*node));
+    if (node == NULL)
+    {
+        unlock(p_mi);
+        return -1;
+    }
+
     for( size_t i = 0; i < ARRAY_SIZE( p_mi->selected_es ); ++i )
         p_mi->selected_es[i] = ES_INIT;
 
     media_attach_preparsed_event( p_mi->p_md );
 
-    p_input_thread = input_Create( p_mi, p_mi->p_md->p_input_item, NULL,
-                                   p_mi->input.p_resource,
-                                   p_mi->input.p_renderer );
+    node->next = NULL;
+    node->input = p_input_thread =
+        input_Create( p_mi, p_mi->p_md->p_input_item, NULL,
+                      p_mi->input.p_resource, p_mi->input.p_renderer );
+
     unlock(p_mi);
     if( !p_input_thread )
     {
         unlock_input(p_mi);
         media_detach_preparsed_event( p_mi->p_md );
+        free(node);
         libvlc_printerr( "Not enough memory" );
         return -1;
     }
@@ -1015,10 +1090,11 @@ int libvlc_media_player_play( libvlc_media_player_t *p_mi )
         var_DelCallback( p_input_thread, "can-seek", input_seekable_changed, p_mi );
         input_Close( p_input_thread );
         media_detach_preparsed_event( p_mi->p_md );
+        free(node);
         libvlc_printerr( "Input initialization failure" );
         return -1;
     }
-    p_mi->input.p_thread = p_input_thread;
+    p_mi->input.node = node;
     unlock_input(p_mi);
     return 0;
 }
@@ -1061,14 +1137,43 @@ int libvlc_media_player_is_playing( libvlc_media_player_t *p_mi )
     return libvlc_Playing == state;
 }
 
-/**************************************************************************
- * Stop playing.
- **************************************************************************/
-void libvlc_media_player_stop( libvlc_media_player_t *p_mi )
+static void *
+destructor_thread(void *data)
 {
-    lock_input(p_mi);
-    release_input_thread( p_mi ); /* This will stop the input thread */
+    libvlc_media_player_t *mp = data;
+
+    lock_input(mp);
 
+    while (mp->destructor.running || mp->destructor.input_list != NULL)
+    {
+        vlc_cond_wait(&mp->destructor.wait, &mp->input.lock);
+
+        while (mp->destructor.input_list != NULL)
+        {
+            struct input_thread_node *node = mp->destructor.input_list;
+            mp->destructor.input_list = node->next;
+
+            unlock_input(mp);
+
+            input_Close(node->input);
+
+            lock_input(mp);
+
+            free(node);
+
+            /* No media currently playing, we can terminate all resources */
+            if (mp->input.node == NULL)
+                input_resource_Terminate(mp->input.p_resource);
+        }
+    }
+    unlock_input(mp);
+
+    return NULL;
+}
+
+static void
+set_stopped_state( libvlc_media_player_t *p_mi )
+{
     /* Force to go to stopped state, in case we were in Ended, or Error
      * state. */
     if( p_mi->state != libvlc_Stopped )
@@ -1080,11 +1185,33 @@ void libvlc_media_player_stop( libvlc_media_player_t *p_mi )
         event.type = libvlc_MediaPlayerStopped;
         libvlc_event_send( &p_mi->event_manager, &event );
     }
+}
+
+/**************************************************************************
+ * Stop playing.
+ **************************************************************************/
+void libvlc_media_player_stop( libvlc_media_player_t *p_mi )
+{
+    lock_input(p_mi);
+    release_input_thread( p_mi, false ); /* This will stop the input thread */
+
+    set_stopped_state( p_mi );
 
     input_resource_Terminate( p_mi->input.p_resource );
     unlock_input(p_mi);
 }
 
+void libvlc_media_player_stop_async( libvlc_media_player_t *p_mi )
+{
+    lock_input(p_mi);
+
+    release_input_thread( p_mi, true );
+
+    set_stopped_state( p_mi );
+
+    unlock_input(p_mi);
+}
+
 int libvlc_media_player_set_renderer( libvlc_media_player_t *p_mi,
                                       libvlc_renderer_item_t *p_litem )
 {
@@ -1092,7 +1219,8 @@ int libvlc_media_player_set_renderer( libvlc_media_player_t *p_mi,
         p_litem ? libvlc_renderer_item_to_vlc( p_litem ) : NULL;
 
     lock_input( p_mi );
-    input_thread_t *p_input_thread = p_mi->input.p_thread;
+    input_thread_t *p_input_thread =
+            p_mi->input.node != NULL ? p_mi->input.node->input : NULL;
     if( p_input_thread )
         input_Control( p_input_thread, INPUT_SET_RENDERER, p_item );
 
diff --git a/lib/media_player_internal.h b/lib/media_player_internal.h
index a9acff1766..e3c4dd15d1 100644
--- a/lib/media_player_internal.h
+++ b/lib/media_player_internal.h
@@ -36,6 +36,12 @@
 
 #include "../modules/audio_filter/equalizer_presets.h"
 
+struct input_thread_node
+{
+    input_thread_t *input;
+    struct input_thread_node *next;
+};
+
 struct libvlc_media_player_t
 {
     VLC_COMMON_MEMBERS
@@ -45,12 +51,20 @@ struct libvlc_media_player_t
 
     struct
     {
-        input_thread_t   *p_thread;
+        struct input_thread_node *node;
         input_resource_t *p_resource;
         vlc_renderer_item_t *p_renderer;
         vlc_mutex_t       lock;
     } input;
 
+    struct
+    {
+        vlc_thread_t thread;
+        vlc_cond_t   wait;
+        struct input_thread_node *input_list;
+        bool running;
+    } destructor;
+
     struct libvlc_instance_t * p_libvlc_instance; /* Parent instance */
     libvlc_media_t * p_md; /* current media descriptor */
     libvlc_event_manager_t event_manager;
diff --git a/test/libvlc/media_player.c b/test/libvlc/media_player.c
index f3198b5bcf..87251d7583 100644
--- a/test/libvlc/media_player.c
+++ b/test/libvlc/media_player.c
@@ -150,6 +150,53 @@ static void test_media_player_play_stop(const char** argv, int argc)
     libvlc_release (vlc);
 }
 
+static void test_media_player_async(const char** argv, int argc)
+{
+    libvlc_instance_t *vlc;
+    libvlc_media_t *md;
+    libvlc_media_player_t *mi;
+    const char * file = test_default_sample;
+
+    log ("Testing play and pause of %s\n", file);
+
+    vlc = libvlc_new (argc, argv);
+    assert (vlc != NULL);
+
+    mi = libvlc_media_player_new (vlc);
+    assert (mi != NULL);
+
+    for (unsigned i = 0; i < 100; ++i)
+    {
+        md = libvlc_media_new_path (vlc, file);
+        assert (md != NULL);
+
+        libvlc_media_player_set_media_async (mi, md);
+
+        libvlc_media_release (md);
+
+        libvlc_media_player_play (mi);
+    }
+
+    libvlc_media_player_stop_async (mi);
+
+    for (unsigned i = 0; i < 100; ++i)
+    {
+        md = libvlc_media_new_path (vlc, file);
+        assert (md != NULL);
+
+        libvlc_media_player_set_media (mi, md);
+
+        libvlc_media_release (md);
+
+        libvlc_media_player_play (mi);
+
+        libvlc_media_player_stop_async (mi);
+    }
+
+    libvlc_media_player_release (mi);
+    libvlc_release (vlc);
+}
+
 static void test_media_player_pause_stop(const char** argv, int argc)
 {
     libvlc_instance_t *vlc;
@@ -197,6 +244,7 @@ int main (void)
 
     test_media_player_set_media (test_defaults_args, test_defaults_nargs);
     test_media_player_play_stop (test_defaults_args, test_defaults_nargs);
+    test_media_player_async (test_defaults_args, test_defaults_nargs);
     test_media_player_pause_stop (test_defaults_args, test_defaults_nargs);
 
     return 0;
-- 
2.28.0



More information about the vlc-devel mailing list