[vlc-commits] media source: introduce media source/tree API

Romain Vimont git at videolan.org
Mon Nov 26 11:56:41 CET 2018


vlc | branch: master | Romain Vimont <rom1v at videolabs.io> | Thu May 17 11:37:56 2018 +0200| [3e0cc1942a963693cf97c99a5ab1e9c6171fe6b1] | committer: Thomas Guillem

media source: introduce media source/tree API

Add an API to manage "services discovery" easily from UI clients.

A "media source provider" allows to retrieve media sources (each
associated to a services discovery module).

A media source holds a media tree, containing both the media detected by
the services discovery module and the media detected by preparsing.
Clients may listen to the tree to be notified of changes.

A client may retrieve a media source at any time and listen to its
media tree, even if it is already populated, without race condition.

Signed-off-by: Thomas Guillem <thomas at gllm.fr>

> http://git.videolan.org/gitweb.cgi/vlc.git/?a=commit;h=3e0cc1942a963693cf97c99a5ab1e9c6171fe6b1
---

 include/vlc_media_source.h       | 239 ++++++++++++++++++++++++++++
 include/vlc_services_discovery.h |   2 +
 src/Makefile.am                  |  13 +-
 src/input/services_discovery.c   |   1 +
 src/libvlc.c                     |   9 ++
 src/libvlc.h                     |   2 +
 src/libvlccore.sym               |  10 ++
 src/media_source/media_source.c  | 274 ++++++++++++++++++++++++++++++++
 src/media_source/media_source.h  |  33 ++++
 src/media_source/media_tree.c    | 326 +++++++++++++++++++++++++++++++++++++++
 src/media_source/media_tree.h    |  42 +++++
 src/media_source/test.c          | 316 +++++++++++++++++++++++++++++++++++++
 12 files changed, 1266 insertions(+), 1 deletion(-)

diff --git a/include/vlc_media_source.h b/include/vlc_media_source.h
new file mode 100644
index 0000000000..ac56124d2f
--- /dev/null
+++ b/include/vlc_media_source.h
@@ -0,0 +1,239 @@
+/*****************************************************************************
+ * vlc_media_source.h
+ *****************************************************************************
+ * Copyright (C) 2018 VLC authors and VideoLAN
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2.1 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
+ *****************************************************************************/
+
+#ifndef VLC_MEDIA_SOURCE_H
+#define VLC_MEDIA_SOURCE_H
+
+#include <vlc_common.h>
+#include <vlc_input_item.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * \defgroup media_source Media source
+ * \ingroup input
+ * @{
+ */
+
+/**
+ * Media source API aims to manage "services discovery" easily from UI clients.
+ *
+ * A "media source provider", associated to the libvlc instance, allows to
+ * retrieve media sources (each associated to a services discovery module).
+ *
+ * Requesting a services discovery that is not open will automatically open it.
+ * If several "clients" request the same media source (i.e. by requesting the
+ * same name), they will receive the same (refcounted) media source instance.
+ * As soon as a media source is released by all its clients, the associated
+ * services discovery is closed.
+ *
+ * Each media source holds a media tree, used to store both the media
+ * detected by the services discovery and the media detected by preparsing.
+ * Clients may listen to the tree to be notified of changes.
+ *
+ * To preparse a media belonging to a media tree, use vlc_media_tree_Preparse().
+ * If subitems are detected during the preparsing, the media tree is updated
+ * accordingly.
+ */
+
+/**
+ * Media tree.
+ *
+ * Nodes must be traversed with locked held (vlc_media_tree_Lock()).
+ */
+typedef struct vlc_media_tree {
+    input_item_node_t root;
+} vlc_media_tree_t;
+
+/**
+ * Callbacks to receive media tree events.
+ */
+struct vlc_media_tree_callbacks
+{
+    /**
+     * Called when the whole content of a subtree has changed.
+     *
+     * \param playlist the playlist
+     * \param node     the node having its children reset (may be root)
+     * \param userdata userdata provided to AddListener()
+     */
+    void
+    (*on_children_reset)(vlc_media_tree_t *tree, input_item_node_t *node,
+                         void *userdata);
+
+    /**
+     * Called when children has been added to a node.
+     *
+     * The children may themselves contain children, which will not be notified
+     * separately.
+     *
+     * \param playlist the playlist
+     * \param node     the node having children added
+     * \param children the children added
+     * \param count    the number of children added
+     * \param userdata userdata provided to AddListener()
+     */
+    void
+    (*on_children_added)(vlc_media_tree_t *tree, input_item_node_t *node,
+                         input_item_node_t *const children[], size_t count,
+                         void *userdata);
+
+    /**
+     * Called when children has been removed from a node.
+     *
+     * \param playlist the playlist
+     * \param node     the node having children removed
+     * \param children the children removed
+     * \param count    the number of children removed
+     * \param userdata userdata provided to AddListener()
+     */
+    void
+    (*on_children_removed)(vlc_media_tree_t *tree, input_item_node_t *node,
+                           input_item_node_t *const children[], size_t count,
+                           void *userdata);
+};
+
+/**
+ * Listener for media tree events.
+ */
+typedef struct vlc_media_tree_listener_id vlc_media_tree_listener_id;
+
+/**
+ * Add a listener. The lock must NOT be held.
+ *
+ * \param tree                 the media tree, unlocked
+ * \param cbs                  the callbacks (must be valid until the listener
+ *                             is removed)
+ * \param userdata             userdata provided as a parameter in callbacks
+ * \param notify_current_state true to notify the current state immediately via
+ *                             callbacks
+ */
+VLC_API vlc_media_tree_listener_id *
+vlc_media_tree_AddListener(vlc_media_tree_t *tree,
+                           const struct vlc_media_tree_callbacks *cbs,
+                           void *userdata, bool notify_current_state);
+
+/**
+ * Remove a listener. The lock must NOT be held.
+ *
+ * \param tree     the media tree, unlocked
+ * \param listener the listener identifier returned by
+ *                 vlc_media_tree_AddListener()
+ */
+VLC_API void
+vlc_media_tree_RemoveListener(vlc_media_tree_t *tree,
+                              vlc_media_tree_listener_id *listener);
+
+/**
+ * Lock the media tree (non-recursive).
+ */
+VLC_API void
+vlc_media_tree_Lock(vlc_media_tree_t *);
+
+/**
+ * Unlock the media tree.
+ */
+VLC_API void
+vlc_media_tree_Unlock(vlc_media_tree_t *);
+
+/**
+ * Find the node containing the requested input item (and its parent).
+ *
+ * \param tree the media tree, locked
+ * \param result point to the matching node if the function returns true [OUT]
+ * \param result_parent if not NULL, point to the matching node parent
+ *                      if the function returns true [OUT]
+ *
+ * \retval true if item was found
+ * \retval false if item was not found
+ */
+VLC_API bool
+vlc_media_tree_Find(vlc_media_tree_t *tree, const input_item_t *media,
+                    input_item_node_t **result,
+                    input_item_node_t **result_parent);
+
+/**
+ * Preparse a media, and expand it in the media tree on subitems added.
+ *
+ * \param tree   the media tree (not necessarily locked)
+ * \param libvlc the libvlc instance
+ * \param media  the media to preparse
+ */
+VLC_API void
+vlc_media_tree_Preparse(vlc_media_tree_t *tree, libvlc_int_t *libvlc,
+                        input_item_t *media);
+
+/**
+ * Media source.
+ *
+ * A media source is associated to a "service discovery". It stores the
+ * detected media in a media tree.
+ */
+typedef struct vlc_media_source_t
+{
+    vlc_media_tree_t *tree;
+    const char *description;
+} vlc_media_source_t;
+
+/**
+ * Increase the media source reference count.
+ */
+VLC_API void
+vlc_media_source_Hold(vlc_media_source_t *);
+
+/**
+ * Decrease the media source reference count.
+ *
+ * Destroy the media source and close the associated "service discovery" if it
+ * reaches 0.
+ */
+VLC_API void
+vlc_media_source_Release(vlc_media_source_t *);
+
+/**
+ * Media source provider (opaque pointer), used to get media sources.
+ */
+typedef struct vlc_media_source_provider_t vlc_media_source_provider_t;
+
+/**
+ * Return the media source provider associated to the libvlc instance.
+ */
+VLC_API vlc_media_source_provider_t *
+vlc_media_source_provider_Get(libvlc_int_t *);
+
+/**
+ * Return the media source identified by psz_name.
+ *
+ * The resulting media source must be released by vlc_media_source_Release().
+ */
+VLC_API vlc_media_source_t *
+vlc_media_source_provider_GetMediaSource(vlc_media_source_provider_t *,
+                                         const char *name);
+
+/** @} */
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
+
diff --git a/include/vlc_services_discovery.h b/include/vlc_services_discovery.h
index 1f5305b788..e117a1c0a5 100644
--- a/include/vlc_services_discovery.h
+++ b/include/vlc_services_discovery.h
@@ -149,6 +149,8 @@ VLC_API char ** vlc_sd_GetNames( vlc_object_t *, char ***, int ** ) VLC_USED;
 VLC_API services_discovery_t *vlc_sd_Create(vlc_object_t *parent,
     const char *chain, const struct services_discovery_owner_t *owner)
 VLC_USED;
+#define vlc_sd_Create( obj, a, b ) \
+        vlc_sd_Create( VLC_OBJECT( obj ), a, b )
 
 VLC_API void vlc_sd_Destroy( services_discovery_t * );
 
diff --git a/src/Makefile.am b/src/Makefile.am
index d6bf427179..aec931cdd4 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -63,6 +63,7 @@ pluginsinclude_HEADERS = \
 	../include/vlc_keystore.h \
 	../include/vlc_list.h \
 	../include/vlc_md5.h \
+	../include/vlc_media_source.h \
 	../include/vlc_messages.h \
 	../include/vlc_meta.h \
 	../include/vlc_meta_fetcher.h \
@@ -207,6 +208,10 @@ libvlccore_la_SOURCES = \
 	config/getopt.c \
 	config/vlc_getopt.h \
 	extras/libc.c \
+	media_source/media_source.c \
+	media_source/media_source.h \
+	media_source/media_tree.c \
+	media_source/media_tree.h \
 	modules/modules.h \
 	modules/modules.c \
 	modules/bank.c \
@@ -561,7 +566,8 @@ check_PROGRAMS = \
 	test_vector \
 	test_shared_data_ptr \
 	test_playlist \
-	test_randomizer
+	test_randomizer \
+	test_media_source
 
 TESTS = $(check_PROGRAMS) check_symbols
 
@@ -601,6 +607,11 @@ test_playlist_SOURCES = playlist/test.c \
 test_playlist_CFLAGS = -DTEST_PLAYLIST
 test_randomizer_SOURCES = playlist/randomizer.c
 test_randomizer_CFLAGS = -DTEST_RANDOMIZER
+test_media_source_LDADD = $(LDADD) $(LIBS_libvlccore)
+test_media_source_CFLAGS = -DTEST_MEDIA_SOURCE
+test_media_source_SOURCES = media_source/test.c \
+	media_source/media_source.c \
+	media_source/media_tree.c
 
 AM_LDFLAGS = -no-install
 LDADD = libvlccore.la \
diff --git a/src/input/services_discovery.c b/src/input/services_discovery.c
index 12a029ef6e..7d43e4ee7a 100644
--- a/src/input/services_discovery.c
+++ b/src/input/services_discovery.c
@@ -103,6 +103,7 @@ char **vlc_sd_GetNames (vlc_object_t *obj, char ***pppsz_longnames, int **pp_cat
  * That's how the playlist get's Service Discovery information
  */
 
+#undef vlc_sd_Create
 services_discovery_t *vlc_sd_Create(vlc_object_t *parent, const char *cfg,
     const struct services_discovery_owner_t *restrict owner)
 {
diff --git a/src/libvlc.c b/src/libvlc.c
index 93ce67a00d..166bf96b94 100644
--- a/src/libvlc.c
+++ b/src/libvlc.c
@@ -43,6 +43,7 @@
 #include "modules/modules.h"
 #include "config/configuration.h"
 #include "preparser/preparser.h"
+#include "media_source/media_source.h"
 
 #include <stdio.h>                                              /* sprintf() */
 #include <string.h>
@@ -98,6 +99,7 @@ libvlc_int_t * libvlc_InternalCreate( void )
     priv->playlist = NULL;
     priv->main_playlist = NULL;
     priv->p_vlm = NULL;
+    priv->media_source_provider = NULL;
 
     vlc_ExitInit( &priv->exit );
 
@@ -291,6 +293,10 @@ int libvlc_InternalInit( libvlc_int_t *p_libvlc, int i_argc,
     if( !priv->parser )
         goto error;
 
+    priv->media_source_provider = vlc_media_source_provider_New( VLC_OBJECT( p_libvlc ) );
+    if( !priv->media_source_provider )
+        goto error;
+
     /* variables for signalling creation of new files */
     var_Create( p_libvlc, "snapshot-file", VLC_VAR_STRING );
     var_Create( p_libvlc, "record-file", VLC_VAR_STRING );
@@ -436,6 +442,9 @@ void libvlc_InternalCleanup( libvlc_int_t *p_libvlc )
     if ( priv->p_media_library )
         libvlc_MlRelease( priv->p_media_library );
 
+    if( priv->media_source_provider )
+        vlc_media_source_provider_Delete( priv->media_source_provider );
+
     libvlc_InternalDialogClean( p_libvlc );
     libvlc_InternalKeystoreClean( p_libvlc );
 
diff --git a/src/libvlc.h b/src/libvlc.h
index 7fc6934406..a0adeeb26a 100644
--- a/src/libvlc.h
+++ b/src/libvlc.h
@@ -184,6 +184,7 @@ typedef struct vlc_dialog_provider vlc_dialog_provider;
 typedef struct vlc_keystore vlc_keystore;
 typedef struct vlc_actions_t vlc_actions_t;
 typedef struct vlc_playlist vlc_playlist_t;
+typedef struct vlc_media_source_provider_t vlc_media_source_provider_t;
 
 typedef struct libvlc_priv_t
 {
@@ -197,6 +198,7 @@ typedef struct libvlc_priv_t
     struct playlist_t *playlist; ///< Playlist for interfaces
     vlc_playlist_t *main_playlist;
     struct input_preparser_t *parser; ///< Input item meta data handler
+    vlc_media_source_provider_t *media_source_provider;
     vlc_actions_t *actions; ///< Hotkeys handler
     struct vlc_medialibrary_t *p_media_library; ///< Media library instance
     struct vlc_thumbnailer_t *p_thumbnailer; ///< Lazily instantiated media thumbnailer
diff --git a/src/libvlccore.sym b/src/libvlccore.sym
index 0b00e960cc..9c4a7eab49 100644
--- a/src/libvlccore.sym
+++ b/src/libvlccore.sym
@@ -946,3 +946,13 @@ vlc_playlist_Pause
 vlc_playlist_Resume
 vlc_playlist_Preparse
 vlc_intf_GetMainPlaylist
+vlc_media_source_Hold
+vlc_media_source_Release
+vlc_media_source_provider_Get
+vlc_media_source_provider_GetMediaSource
+vlc_media_tree_AddListener
+vlc_media_tree_RemoveListener
+vlc_media_tree_Lock
+vlc_media_tree_Unlock
+vlc_media_tree_Find
+vlc_media_tree_Preparse
diff --git a/src/media_source/media_source.c b/src/media_source/media_source.c
new file mode 100644
index 0000000000..6f952cd171
--- /dev/null
+++ b/src/media_source/media_source.c
@@ -0,0 +1,274 @@
+/*****************************************************************************
+ * media_source.c
+ *****************************************************************************
+ * Copyright (C) 2018 VLC authors and VideoLAN
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2.1 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
+ *****************************************************************************/
+
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif
+
+#include "media_source.h"
+
+#include <assert.h>
+#include <vlc_atomic.h>
+#include <vlc_playlist.h>
+#include <vlc_services_discovery.h>
+#include "libvlc.h"
+#include "media_tree.h"
+
+#ifdef TEST_MEDIA_SOURCE
+#define vlc_module_name "test"
+#endif /* TEST_MEDIA_SOURCE */
+
+typedef struct
+{
+    vlc_media_source_t public_data;
+
+    services_discovery_t *sd;
+    vlc_atomic_rc_t rc;
+    vlc_media_source_provider_t *owner;
+    struct vlc_list node;
+    char name[];
+} media_source_private_t;
+
+#define ms_priv(ms) container_of(ms, media_source_private_t, public_data)
+
+struct vlc_media_source_provider_t
+{
+    struct vlc_common_members obj;
+    vlc_mutex_t lock;
+    struct vlc_list media_sources;
+};
+
+/* A new item has been added to a certain services discovery */
+static void
+services_discovery_item_added(services_discovery_t *sd,
+                              input_item_t *parent, input_item_t *media,
+                              const char *cat)
+{
+    assert(!parent || !cat);
+    VLC_UNUSED(cat);
+
+    vlc_media_source_t *ms = sd->owner.sys;
+    vlc_media_tree_t *tree = ms->tree;
+
+    msg_Dbg(sd, "adding: %s", media->psz_name ? media->psz_name : "(null)");
+
+    vlc_media_tree_Lock(tree);
+
+    input_item_node_t *parent_node;
+    if (parent)
+        vlc_media_tree_Find(tree, parent, &parent_node, NULL);
+    else
+        parent_node = &tree->root;
+
+    bool added = vlc_media_tree_Add(tree, parent_node, media) != NULL;
+    if (unlikely(!added))
+        msg_Err(sd, "could not allocate media tree node");
+
+    vlc_media_tree_Unlock(tree);
+}
+
+static void
+services_discovery_item_removed(services_discovery_t *sd, input_item_t *media)
+{
+    vlc_media_source_t *ms = sd->owner.sys;
+    vlc_media_tree_t *tree = ms->tree;
+
+    msg_Dbg(sd, "removing: %s", media->psz_name ? media->psz_name : "(null)");
+
+    vlc_media_tree_Lock(tree);
+    bool removed = vlc_media_tree_Remove(tree, media);
+    vlc_media_tree_Unlock(tree);
+
+    if (unlikely(!removed))
+    {
+        msg_Err(sd, "removing item not added"); /* SD plugin bug */
+        return;
+    }
+}
+
+static const struct services_discovery_callbacks sd_cbs = {
+    .item_added = services_discovery_item_added,
+    .item_removed = services_discovery_item_removed,
+};
+
+static vlc_media_source_t *
+vlc_media_source_New(vlc_media_source_provider_t *provider, const char *name)
+{
+    media_source_private_t *priv = malloc(sizeof(*priv) + strlen(name) + 1);
+    if (unlikely(!priv))
+        return NULL;
+
+    vlc_atomic_rc_init(&priv->rc);
+
+    vlc_media_source_t *ms = &priv->public_data;
+
+    /* vlc_sd_Create() may call services_discovery_item_added(), which will read
+     * its tree, so it must be initialized first */
+    ms->tree = vlc_media_tree_New();
+    if (unlikely(!ms->tree))
+    {
+        free(priv);
+        return NULL;
+    }
+
+    strcpy(priv->name, name);
+
+    struct services_discovery_owner_t owner = {
+        .cbs = &sd_cbs,
+        .sys = ms,
+    };
+
+    priv->sd = vlc_sd_Create(provider, name, &owner);
+    if (unlikely(!priv->sd))
+    {
+        vlc_media_tree_Release(ms->tree);
+        free(priv);
+        return NULL;
+    }
+
+    /* sd->description is set during vlc_sd_Create() */
+    ms->description = priv->sd->description;
+
+    priv->owner = provider;
+
+    return ms;
+}
+
+static void
+vlc_media_source_provider_Remove(vlc_media_source_provider_t *provider,
+                                 vlc_media_source_t *ms)
+{
+    vlc_mutex_lock(&provider->lock);
+    vlc_list_remove(&ms_priv(ms)->node);
+    vlc_mutex_unlock(&provider->lock);
+}
+
+static void
+vlc_media_source_Delete(vlc_media_source_t *ms)
+{
+    media_source_private_t *priv = ms_priv(ms);
+    vlc_media_source_provider_Remove(priv->owner, ms);
+    vlc_sd_Destroy(priv->sd);
+    vlc_media_tree_Release(ms->tree);
+    free(priv);
+}
+
+void
+vlc_media_source_Hold(vlc_media_source_t *ms)
+{
+    media_source_private_t *priv = ms_priv(ms);
+    vlc_atomic_rc_inc(&priv->rc);
+}
+
+void
+vlc_media_source_Release(vlc_media_source_t *ms)
+{
+    media_source_private_t *priv = ms_priv(ms);
+    if (vlc_atomic_rc_dec(&priv->rc))
+        vlc_media_source_Delete(ms);
+}
+
+static vlc_media_source_t *
+vlc_media_source_provider_Find(vlc_media_source_provider_t *provider,
+                               const char *name)
+{
+    vlc_mutex_assert(&provider->lock);
+    media_source_private_t *entry;
+    vlc_list_foreach(entry, &provider->media_sources, node)
+        if (!strcmp(name, entry->name))
+            return &entry->public_data;
+    return NULL;
+}
+
+vlc_media_source_provider_t *
+vlc_media_source_provider_Get(libvlc_int_t *libvlc)
+{
+    return libvlc_priv(libvlc)->media_source_provider;
+}
+
+static void *
+CreateObject(vlc_object_t *parent, size_t length, const char *typename)
+{
+#ifdef TEST_MEDIA_SOURCE
+    VLC_UNUSED(parent);
+    VLC_UNUSED(typename);
+    return malloc(length);
+#else
+    return vlc_custom_create(parent, length, typename);
+#endif
+}
+
+static void
+ReleaseObject(void *obj)
+{
+#ifdef TEST_MEDIA_SOURCE
+    free(obj);
+#else
+    vlc_object_release((vlc_object_t *) obj);
+#endif
+}
+
+#undef vlc_media_source_provider_New
+vlc_media_source_provider_t *
+vlc_media_source_provider_New(vlc_object_t *parent)
+{
+    vlc_media_source_provider_t *provider =
+            CreateObject(parent, sizeof(*provider), "media-source-provider");
+    if (unlikely(!provider))
+        return NULL;
+
+    vlc_mutex_init(&provider->lock);
+    vlc_list_init(&provider->media_sources);
+    return provider;
+}
+
+void
+vlc_media_source_provider_Delete(vlc_media_source_provider_t *provider)
+{
+    vlc_mutex_destroy(&provider->lock);
+    ReleaseObject(provider);
+}
+
+static vlc_media_source_t *
+vlc_media_source_provider_Add(vlc_media_source_provider_t *provider,
+                              const char *name)
+{
+    vlc_mutex_assert(&provider->lock);
+
+    vlc_media_source_t *ms = vlc_media_source_New(provider, name);
+    if (unlikely(!ms))
+        return NULL;
+
+    vlc_list_append(&ms_priv(ms)->node, &provider->media_sources);
+    return ms;
+}
+
+vlc_media_source_t *
+vlc_media_source_provider_GetMediaSource(vlc_media_source_provider_t *provider,
+                                         const char *name)
+{
+    vlc_mutex_lock(&provider->lock);
+    vlc_media_source_t *ms = vlc_media_source_provider_Find(provider, name);
+    if (!ms)
+        ms = vlc_media_source_provider_Add(provider, name);
+    vlc_mutex_unlock(&provider->lock);
+
+    return ms;
+}
diff --git a/src/media_source/media_source.h b/src/media_source/media_source.h
new file mode 100644
index 0000000000..bf187c4b38
--- /dev/null
+++ b/src/media_source/media_source.h
@@ -0,0 +1,33 @@
+/*****************************************************************************
+ * media_source.h
+ *****************************************************************************
+ * Copyright (C) 2018 VLC authors and VideoLAN
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2.1 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
+ *****************************************************************************/
+
+#ifndef MEDIA_SOURCE_H
+#define MEDIA_SOURCE_H
+
+#include <vlc_media_source.h>
+
+vlc_media_source_provider_t *
+vlc_media_source_provider_New(vlc_object_t *parent);
+#define vlc_media_source_provider_New(obj) \
+        vlc_media_source_provider_New(VLC_OBJECT(obj))
+
+void vlc_media_source_provider_Delete(vlc_media_source_provider_t *msp);
+
+#endif
diff --git a/src/media_source/media_tree.c b/src/media_source/media_tree.c
new file mode 100644
index 0000000000..6dd7055bcc
--- /dev/null
+++ b/src/media_source/media_tree.c
@@ -0,0 +1,326 @@
+/*****************************************************************************
+ * media_tree.c
+ *****************************************************************************
+ * Copyright (C) 2018 VLC authors and VideoLAN
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2.1 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
+ *****************************************************************************/
+
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif
+
+#include "media_tree.h"
+
+#include <assert.h>
+#include <vlc_common.h>
+#include <vlc_arrays.h>
+#include <vlc_atomic.h>
+#include <vlc_input_item.h>
+#include <vlc_threads.h>
+#include "libvlc.h"
+
+struct vlc_media_tree_listener_id
+{
+    const struct vlc_media_tree_callbacks *cbs;
+    void *userdata;
+    struct vlc_list node; /**< node of media_tree_private_t.listeners */
+};
+
+typedef struct
+{
+    vlc_media_tree_t public_data;
+
+    struct vlc_list listeners; /**< list of vlc_media_tree_listener_id.node */
+    vlc_mutex_t lock;
+    vlc_atomic_rc_t rc;
+} media_tree_private_t;
+
+#define mt_priv(mt) container_of(mt, media_tree_private_t, public_data)
+
+vlc_media_tree_t *
+vlc_media_tree_New(void)
+{
+    media_tree_private_t *priv = malloc(sizeof(*priv));
+    if (unlikely(!priv))
+        return NULL;
+
+    vlc_mutex_init(&priv->lock);
+    vlc_atomic_rc_init(&priv->rc);
+    vlc_list_init(&priv->listeners);
+
+    vlc_media_tree_t *tree = &priv->public_data;
+    input_item_node_t *root = &tree->root;
+    root->p_item = NULL;
+    TAB_INIT(root->i_children, root->pp_children);
+
+    return tree;
+}
+
+static inline void
+vlc_media_tree_AssertLocked(vlc_media_tree_t *tree)
+{
+    media_tree_private_t *priv = mt_priv(tree);
+    vlc_mutex_assert(&priv->lock);
+}
+
+#define vlc_media_tree_listener_foreach(listener, tree) \
+    vlc_list_foreach(listener, &mt_priv(tree)->listeners, node)
+
+#define vlc_media_tree_NotifyListener(tree, listener, event, ...) \
+do { \
+    if (listener->cbs->event) \
+        listener->cbs->event(tree, ##__VA_ARGS__, listener->userdata); \
+} while(0)
+
+#define vlc_media_tree_Notify(tree, event, ...) \
+do { \
+    vlc_media_tree_AssertLocked(tree); \
+    vlc_media_tree_listener_id *listener; \
+    vlc_media_tree_listener_foreach(listener, tree) \
+        vlc_media_tree_NotifyListener(tree, listener, event, ##__VA_ARGS__); \
+} while (0)
+
+static bool
+vlc_media_tree_FindNodeByMedia(input_item_node_t *parent,
+                               const input_item_t *media,
+                               input_item_node_t **result,
+                               input_item_node_t **result_parent)
+{
+    for (int i = 0; i < parent->i_children; ++i)
+    {
+        input_item_node_t *child = parent->pp_children[i];
+        if (child->p_item == media)
+        {
+            *result = child;
+            if (result_parent)
+                *result_parent = parent;
+            return true;
+        }
+
+        if (vlc_media_tree_FindNodeByMedia(child, media, result, result_parent))
+            return true;
+    }
+
+    return false;
+}
+
+static input_item_node_t *
+vlc_media_tree_AddChild(input_item_node_t *parent, input_item_t *media);
+
+static void
+vlc_media_tree_AddSubtree(input_item_node_t *to, input_item_node_t *from)
+{
+    for (int i = 0; i < from->i_children; ++i)
+    {
+        input_item_node_t *child = from->pp_children[i];
+        input_item_node_t *node = vlc_media_tree_AddChild(to, child->p_item);
+        if (unlikely(!node))
+            break; /* what could we do? */
+
+        vlc_media_tree_AddSubtree(node, child);
+    }
+}
+
+static void
+media_subtree_changed(input_item_t *media, input_item_node_t *node,
+                      void *userdata)
+{
+    vlc_media_tree_t *tree = userdata;
+
+    vlc_media_tree_Lock(tree);
+    input_item_node_t *subtree_root;
+    /* TODO retrieve the node without traversing the tree */
+    bool found = vlc_media_tree_FindNodeByMedia(&tree->root, media,
+                                                &subtree_root, NULL);
+    if (!found) {
+        /* the node probably failed to be allocated */
+        vlc_media_tree_Unlock(tree);
+        return;
+    }
+
+    vlc_media_tree_AddSubtree(subtree_root, node);
+    vlc_media_tree_Notify(tree, on_children_reset, subtree_root);
+    vlc_media_tree_Unlock(tree);
+}
+
+static void
+vlc_media_tree_DestroyRootNode(vlc_media_tree_t *tree)
+{
+    input_item_node_t *root = &tree->root;
+    for (int i = 0; i < root->i_children; ++i)
+        input_item_node_Delete(root->pp_children[i]);
+
+    free(root->pp_children);
+}
+
+static void
+vlc_media_tree_Delete(vlc_media_tree_t *tree)
+{
+    media_tree_private_t *priv = mt_priv(tree);
+    vlc_media_tree_listener_id *listener;
+    vlc_list_foreach(listener, &priv->listeners, node)
+        free(listener);
+    vlc_list_init(&priv->listeners); /* reset */
+    vlc_media_tree_DestroyRootNode(tree);
+    vlc_mutex_destroy(&priv->lock);
+    free(tree);
+}
+
+void
+vlc_media_tree_Hold(vlc_media_tree_t *tree)
+{
+    media_tree_private_t *priv = mt_priv(tree);
+    vlc_atomic_rc_inc(&priv->rc);
+}
+
+void
+vlc_media_tree_Release(vlc_media_tree_t *tree)
+{
+    media_tree_private_t *priv = mt_priv(tree);
+    if (vlc_atomic_rc_dec(&priv->rc))
+        vlc_media_tree_Delete(tree);
+}
+
+void
+vlc_media_tree_Lock(vlc_media_tree_t *tree)
+{
+    media_tree_private_t *priv = mt_priv(tree);
+    vlc_mutex_lock(&priv->lock);
+}
+
+void
+vlc_media_tree_Unlock(vlc_media_tree_t *tree)
+{
+    media_tree_private_t *priv = mt_priv(tree);
+    vlc_mutex_unlock(&priv->lock);
+}
+
+static input_item_node_t *
+vlc_media_tree_AddChild(input_item_node_t *parent, input_item_t *media)
+{
+    input_item_node_t *node = input_item_node_Create(media);
+    if (unlikely(!node))
+        return NULL;
+
+    input_item_node_AppendNode(parent, node);
+
+    return node;
+}
+
+static void
+vlc_media_tree_NotifyCurrentState(vlc_media_tree_t *tree,
+                                  vlc_media_tree_listener_id *listener)
+{
+    vlc_media_tree_NotifyListener(tree, listener, on_children_reset,
+                                  &tree->root);
+}
+
+vlc_media_tree_listener_id *
+vlc_media_tree_AddListener(vlc_media_tree_t *tree,
+                           const struct vlc_media_tree_callbacks *cbs,
+                           void *userdata, bool notify_current_state)
+{
+    vlc_media_tree_listener_id *listener = malloc(sizeof(*listener));
+    if (unlikely(!listener))
+        return NULL;
+    listener->cbs = cbs;
+    listener->userdata = userdata;
+
+    media_tree_private_t *priv = mt_priv(tree);
+    vlc_media_tree_Lock(tree);
+
+    vlc_list_append(&listener->node, &priv->listeners);
+
+    if (notify_current_state)
+        vlc_media_tree_NotifyCurrentState(tree, listener);
+
+    vlc_media_tree_Unlock(tree);
+    return listener;
+}
+
+void
+vlc_media_tree_RemoveListener(vlc_media_tree_t *tree,
+                              vlc_media_tree_listener_id *listener)
+{
+    vlc_media_tree_Lock(tree);
+    vlc_list_remove(&listener->node);
+    vlc_media_tree_Unlock(tree);
+
+    free(listener);
+}
+
+input_item_node_t *
+vlc_media_tree_Add(vlc_media_tree_t *tree, input_item_node_t *parent,
+                   input_item_t *media)
+{
+    vlc_media_tree_AssertLocked(tree);
+
+    input_item_node_t *node = vlc_media_tree_AddChild(parent, media);
+    if (unlikely(!node))
+        return NULL;
+
+    vlc_media_tree_Notify(tree, on_children_added, parent, &node, 1);
+
+    return node;
+}
+
+bool
+vlc_media_tree_Find(vlc_media_tree_t *tree, const input_item_t *media,
+                    input_item_node_t **result,
+                    input_item_node_t **result_parent)
+{
+    vlc_media_tree_AssertLocked(tree);
+
+    /* quick & dirty depth-first O(n) implementation, with n the number of nodes
+     * in the tree */
+    return vlc_media_tree_FindNodeByMedia(&tree->root, media, result,
+                                          result_parent);
+}
+
+bool
+vlc_media_tree_Remove(vlc_media_tree_t *tree, input_item_t *media)
+{
+    vlc_media_tree_AssertLocked(tree);
+
+    input_item_node_t *node;
+    input_item_node_t *parent;
+    if (!vlc_media_tree_FindNodeByMedia(&tree->root, media, &node, &parent))
+        return false;
+
+    input_item_node_RemoveNode(parent, node);
+    vlc_media_tree_Notify(tree, on_children_removed, parent, &node, 1);
+    input_item_node_Delete(node);
+    return true;
+}
+
+static const input_preparser_callbacks_t input_preparser_callbacks = {
+    .on_subtree_added = media_subtree_changed,
+};
+
+void
+vlc_media_tree_Preparse(vlc_media_tree_t *tree, libvlc_int_t *libvlc,
+                        input_item_t *media)
+{
+#ifdef TEST_MEDIA_SOURCE
+    VLC_UNUSED(tree);
+    VLC_UNUSED(libvlc);
+    VLC_UNUSED(media);
+    VLC_UNUSED(input_preparser_callbacks);
+#else
+    vlc_MetadataRequest(libvlc, media, META_REQUEST_OPTION_NONE,
+                        &input_preparser_callbacks, tree, -1, NULL);
+#endif
+}
diff --git a/src/media_source/media_tree.h b/src/media_source/media_tree.h
new file mode 100644
index 0000000000..4614e2b824
--- /dev/null
+++ b/src/media_source/media_tree.h
@@ -0,0 +1,42 @@
+/*****************************************************************************
+ * media_tree.h
+ *****************************************************************************
+ * Copyright (C) 2018 VLC authors and VideoLAN
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2.1 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
+ *****************************************************************************/
+
+#ifndef MEDIA_TREE_H
+#define MEDIA_TREE_H
+
+#include <vlc_media_source.h>
+
+vlc_media_tree_t *
+vlc_media_tree_New(void);
+
+void
+vlc_media_tree_Hold(vlc_media_tree_t *tree);
+
+void
+vlc_media_tree_Release(vlc_media_tree_t *tree);
+
+input_item_node_t *
+vlc_media_tree_Add(vlc_media_tree_t *tree, input_item_node_t *parent,
+                   input_item_t *media);
+
+bool
+vlc_media_tree_Remove(vlc_media_tree_t *tree, input_item_t *media);
+
+#endif
diff --git a/src/media_source/test.c b/src/media_source/test.c
new file mode 100644
index 0000000000..24ee580b36
--- /dev/null
+++ b/src/media_source/test.c
@@ -0,0 +1,316 @@
+/*****************************************************************************
+ * media_source/test.c
+ *****************************************************************************
+ * Copyright (C) 2018 VLC authors and VideoLAN
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2.1 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
+ *****************************************************************************/
+
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif
+
+#include <assert.h>
+#include <vlc_common.h>
+#include <vlc_vector.h>
+#include "media_source.h"
+#include "media_tree.h"
+
+static void
+test_media_tree(void)
+{
+    vlc_media_tree_t *tree = vlc_media_tree_New();
+    vlc_media_tree_Lock(tree);
+
+    assert(!tree->root.p_item);
+    assert(tree->root.i_children == 0);
+
+    input_item_t *media = input_item_New("vlc://item", "aaa");
+    assert(media);
+    input_item_node_t *node = vlc_media_tree_Add(tree, &tree->root, media);
+    assert(node);
+    input_item_Release(media); /* there is still 1 ref after that */
+
+    assert(tree->root.i_children == 1);
+    assert(tree->root.pp_children[0] == node);
+    assert(node->p_item == media);
+    assert(node->i_children == 0);
+
+    input_item_t *media2 = input_item_New("vlc://child", "bbb");
+    assert(media2);
+    input_item_node_t *node2 = vlc_media_tree_Add(tree, node, media2);
+    assert(node2);
+    input_item_Release(media2);
+
+    assert(node->i_children == 1);
+    assert(node->pp_children[0] == node2);
+    assert(node2->p_item == media2);
+    assert(node2->i_children == 0);
+
+    input_item_t *media3 = input_item_New("vlc://child2", "ccc");
+    assert(media3);
+    input_item_node_t *node3 = vlc_media_tree_Add(tree, node, media3);
+    assert(node3);
+    input_item_Release(media3);
+
+    assert(node->i_children == 2);
+    assert(node->pp_children[0] == node2);
+    assert(node->pp_children[1] == node3);
+    assert(node3->p_item == media3);
+    assert(node3->i_children == 0);
+
+    bool removed = vlc_media_tree_Remove(tree, media2);
+    assert(removed);
+    assert(node->i_children == 1);
+    assert(node->pp_children[0] == node3);
+
+    vlc_media_tree_Unlock(tree);
+    vlc_media_tree_Release(tree);
+}
+
+struct children_reset_report
+{
+    input_item_node_t *node;
+};
+
+struct children_added_report
+{
+    input_item_node_t *node;
+    input_item_t *first_media;
+    size_t count;
+};
+
+struct children_removed_report
+{
+    input_item_node_t *node;
+    input_item_t *first_media;
+    size_t count;
+};
+
+struct callback_ctx
+{
+    struct VLC_VECTOR(struct children_reset_report) vec_children_reset;
+    struct VLC_VECTOR(struct children_added_report) vec_children_added;
+    struct VLC_VECTOR(struct children_removed_report) vec_children_removed;
+};
+
+#define CALLBACK_CTX_INITIALIZER \
+{ \
+    VLC_VECTOR_INITIALIZER, \
+    VLC_VECTOR_INITIALIZER, \
+    VLC_VECTOR_INITIALIZER, \
+}
+
+static inline void
+callback_ctx_destroy_reports(struct callback_ctx *ctx)
+{
+    for (size_t i = 0; i < ctx->vec_children_added.size; ++i)
+        input_item_Release(ctx->vec_children_added.data[i].first_media);
+    for (size_t i = 0; i < ctx->vec_children_removed.size; ++i)
+        input_item_Release(ctx->vec_children_removed.data[i].first_media);
+}
+
+static inline void
+callback_ctx_reset(struct callback_ctx *ctx)
+{
+    callback_ctx_destroy_reports(ctx);
+    vlc_vector_clear(&ctx->vec_children_reset);
+    vlc_vector_clear(&ctx->vec_children_added);
+    vlc_vector_clear(&ctx->vec_children_removed);
+}
+
+static inline void
+callback_ctx_destroy(struct callback_ctx *ctx)
+{
+    callback_ctx_destroy_reports(ctx);
+    vlc_vector_destroy(&ctx->vec_children_reset);
+    vlc_vector_destroy(&ctx->vec_children_added);
+    vlc_vector_destroy(&ctx->vec_children_removed);
+}
+
+static void
+on_children_reset(vlc_media_tree_t *tree, input_item_node_t *node,
+                  void *userdata)
+{
+    VLC_UNUSED(tree);
+
+    struct callback_ctx *ctx = userdata;
+
+    struct children_reset_report report;
+    report.node = node;
+    bool ok = vlc_vector_push(&ctx->vec_children_reset, report);
+    assert(ok);
+}
+
+static void
+on_children_added(vlc_media_tree_t *tree, input_item_node_t *node,
+                  input_item_node_t *const children[], size_t count,
+                  void *userdata)
+{
+    VLC_UNUSED(tree);
+
+    struct callback_ctx *ctx = userdata;
+
+    struct children_added_report report;
+    report.node = node;
+    report.first_media = input_item_Hold(children[0]->p_item);
+    report.count = count;
+    bool ok = vlc_vector_push(&ctx->vec_children_added, report);
+    assert(ok);
+}
+
+static void
+on_children_removed(vlc_media_tree_t *tree, input_item_node_t *node,
+                    input_item_node_t *const children[], size_t count,
+                    void *userdata)
+{
+    VLC_UNUSED(tree);
+
+    struct callback_ctx *ctx = userdata;
+
+    struct children_removed_report report;
+    report.node = node;
+    report.first_media = input_item_Hold(children[0]->p_item);
+    report.count = count;
+    bool ok = vlc_vector_push(&ctx->vec_children_removed, report);
+    assert(ok);
+}
+
+static void test_media_tree_callbacks(void)
+{
+    struct vlc_media_tree_callbacks cbs = {
+        .on_children_reset = on_children_reset,
+        .on_children_added = on_children_added,
+        .on_children_removed = on_children_removed,
+    };
+
+    vlc_media_tree_t *tree = vlc_media_tree_New();
+    struct callback_ctx ctx = CALLBACK_CTX_INITIALIZER;
+    vlc_media_tree_listener_id *listener =
+            vlc_media_tree_AddListener(tree, &cbs, &ctx, false);
+    assert(listener);
+
+    vlc_media_tree_Lock(tree);
+
+    input_item_t *media = input_item_New("vlc://item", "aaa");
+    assert(media);
+    input_item_node_t *node = vlc_media_tree_Add(tree, &tree->root, media);
+    assert(node);
+    input_item_Release(media); /* there is still 1 ref after that */
+
+    assert(ctx.vec_children_reset.size == 0);
+    assert(ctx.vec_children_added.size == 1);
+    assert(ctx.vec_children_added.data[0].node == &tree->root);
+    assert(ctx.vec_children_added.data[0].first_media == media);
+    assert(ctx.vec_children_removed.size == 0);
+
+    callback_ctx_reset(&ctx);
+
+    input_item_t *media2 = input_item_New("vlc://child", "bbb");
+    assert(media2);
+    input_item_node_t *node2 = vlc_media_tree_Add(tree, node, media2);
+    assert(node2);
+    input_item_Release(media2);
+
+    assert(ctx.vec_children_reset.size == 0);
+    assert(ctx.vec_children_added.size == 1);
+    assert(ctx.vec_children_added.data[0].node == node);
+    assert(ctx.vec_children_added.data[0].first_media == media2);
+    assert(ctx.vec_children_removed.size == 0);
+
+    callback_ctx_reset(&ctx);
+
+    input_item_t *media3 = input_item_New("vlc://child2", "ccc");
+    assert(media3);
+    input_item_node_t *node3 = vlc_media_tree_Add(tree, node, media3);
+    assert(node3);
+    input_item_Release(media3);
+
+    assert(ctx.vec_children_reset.size == 0);
+    assert(ctx.vec_children_added.size == 1);
+    assert(ctx.vec_children_added.data[0].node == node);
+    assert(ctx.vec_children_added.data[0].first_media == media3);
+    assert(ctx.vec_children_removed.size == 0);
+
+    callback_ctx_reset(&ctx);
+
+    bool removed = vlc_media_tree_Remove(tree, media2);
+    assert(removed);
+    assert(node->i_children == 1);
+    assert(node->pp_children[0] == node3);
+
+    assert(ctx.vec_children_reset.size == 0);
+    assert(ctx.vec_children_added.size == 0);
+    assert(ctx.vec_children_removed.size == 1);
+    assert(ctx.vec_children_removed.data[0].node == node);
+    assert(ctx.vec_children_removed.data[0].first_media == media2);
+
+    vlc_media_tree_Unlock(tree);
+
+    vlc_media_tree_RemoveListener(tree, listener);
+    callback_ctx_destroy(&ctx);
+
+    vlc_media_tree_Release(tree);
+}
+
+static void
+test_media_tree_callbacks_on_add_listener(void)
+{
+    struct vlc_media_tree_callbacks cbs = {
+        .on_children_reset = on_children_reset,
+    };
+
+
+    vlc_media_tree_t *tree = vlc_media_tree_New();
+
+    vlc_media_tree_Lock(tree);
+
+    input_item_t *media = input_item_New("vlc://item", "aaa");
+    assert(media);
+    input_item_node_t *node = vlc_media_tree_Add(tree, &tree->root, media);
+    assert(node);
+    input_item_Release(media);
+
+    input_item_t *media2 = input_item_New("vlc://child", "bbb");
+    assert(media2);
+    input_item_node_t *node2 = vlc_media_tree_Add(tree, node, media2);
+    assert(node2);
+    input_item_Release(media2);
+
+    vlc_media_tree_Unlock(tree);
+
+    struct callback_ctx ctx = CALLBACK_CTX_INITIALIZER;
+    vlc_media_tree_listener_id *listener =
+            vlc_media_tree_AddListener(tree, &cbs, &ctx, true);
+    assert(listener);
+
+    assert(ctx.vec_children_reset.size == 1);
+    assert(ctx.vec_children_reset.data[0].node == &tree->root);
+    assert(ctx.vec_children_reset.data[0].node->i_children == 1);
+    assert(ctx.vec_children_reset.data[0].node->pp_children[0] == node);
+
+    vlc_media_tree_RemoveListener(tree, listener);
+    callback_ctx_destroy(&ctx);
+
+    vlc_media_tree_Release(tree);
+}
+
+int main(void)
+{
+    test_media_tree();
+    test_media_tree_callbacks();
+    test_media_tree_callbacks_on_add_listener();
+    return 0;
+}




More information about the vlc-commits mailing list