Commit 3e0cc194 authored by Romain Vimont's avatar Romain Vimont Committed by 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's avatarThomas Guillem <thomas@gllm.fr>
parent 9bdfb351
/*****************************************************************************
* 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
......@@ -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 * );
......
......@@ -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 \
......
......@@ -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)
{
......
......@@ -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 );
......
......@@ -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
......
......@@ -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
/*****************************************************************************
* 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;
}
/*****************************************************************************
* 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
/*****************************************************************************
* 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)