[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