[vlc-devel] [V1 PATCH] RFC: add vlc_player_t (input manager)

Thomas Guillem thomas at gllm.fr
Fri Aug 31 17:53:07 CEST 2018


Hello,

First, I decided to name the future input manager/controller vlc_player_t.
For me it is really a player, really like the libvlc one
(libvlc_media_player_t). Note than the libvlc_media_player_t will use this
vlc_player_t in the future.

This commit is an RFC, it's not yet tested (I will add a test), or used.

I discussed a lot with Romain, that is working on the next playlist, to provide
an API that will be easy to use for him.

The main vlc_player_t will be created and owned by the main playlist. Sub
players and playlist can still be created/used from any modules (but I don't
know the real use case for that). The playlist will take care of setting the
current and next medias via vlc_player_SetCurrentMedia() and via the
vlc_player_owner_cbs.get_next_media callback. The API was thought for gapless,
even if it still not implemented.

Any interface or controls modules will be able to retrieve the main player
instance via the main playlist instance. They will be able to register for
playback events (the same than input_thread_t events for now), get the full
state of the player (length, paused, tracks, programs...) and control it.

The lock of the player will be exposed by the API, and each function must be
called while the player is locked. This allows finer controls of the player,
you can now do actions in function of the player state in a non racy way.
Player functions can also be called from player event callbacks.

All controls of the player are asynchronous, even vlc_player_Stop(). I saw too
many hacks in VLC ports for stopping the player, they generally spawn a
detached thread that will stop it. Indeed, input_Close() is blocking, it wait
for the input thread to be terminated. This should be instantaneous for basic
remote accesses that handle vlc_interrupt. But what do we do for remote
accesses that need a teardown of local accesses (yes, it's really easy to block
on a USB drive) ?

My proposal is the destructor struct from player.c. When the user need to stop
a playback, the player will prevent input events forwarding, call input_Stop()
and won't wait for the input thread termination. Instead, when the
INPUT_EVENT_DEAD event will be received, the input_thread_t instance will be
passed to the destructor thread that will join it.

One problem: vlc_player_Delete() will join this destructor thread and then wait
for all input threads termination (but I don't know what else we can do without
leaking).

Still not handled in this patch
 - Test
 - Seeking
 - Get position/time
 - Some state not handling (missing getters)
 - gapless
 - aout/vout handling. Do we expose them or add API to control the
   volume/select the audio device for example.

What do you think about this patch so far ?

Regards,
Thomas
---
 include/vlc_player.h |  588 ++++++++++++++++++++++++
 src/Makefile.am      |    2 +
 src/input/player.c   | 1038 ++++++++++++++++++++++++++++++++++++++++++
 src/libvlccore.sym   |   28 ++
 4 files changed, 1656 insertions(+)
 create mode 100644 include/vlc_player.h
 create mode 100644 src/input/player.c

diff --git a/include/vlc_player.h b/include/vlc_player.h
new file mode 100644
index 0000000000..d23c6870c1
--- /dev/null
+++ b/include/vlc_player.h
@@ -0,0 +1,588 @@
+/*****************************************************************************
+ * vlc_player.h: player interface
+ *****************************************************************************
+ * 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_PLAYER_H
+#define VLC_PLAYER_H 1
+
+#include <vlc_input.h>
+
+/**
+ * \defgroup Player
+ * \ingroup input
+ * @{
+ */
+
+/**
+ * Player opaque vlc_object structure.
+ */
+typedef struct vlc_player_t vlc_player_t;
+
+/**
+ * Player listener opaque structure.
+ *
+ * This opaque structure is returned by vlc_player_AddListener() and can be
+ * used to remove the listener via vlc_player_RemoveListener().
+ */
+struct vlc_player_listener_id;
+
+/**
+ * Player program structure.
+ */
+struct vlc_player_program
+{
+    int id;
+    const char *title;
+    bool selected;
+    bool scrambled;
+};
+
+/**
+ * Player ES track structure.
+ */
+struct vlc_player_track
+{
+    vlc_es_id_t *id;
+    const char *title;
+    const es_format_t *fmt;
+    bool selected;
+};
+
+/**
+ * Callbacks for the owner of the player.
+ *
+ * These callbacks are needed to control the player flow (via the
+ * vlc_playlist_t as a owner for example). It can only be set when creating the
+ * player via vlc_player_New().
+ *
+ * All callbacks are called with the player locked (cf. vlc_player_Lock()).
+ */
+struct vlc_player_owner_cbs
+{
+    /**
+     * Called when a new media is played.
+     *
+     * @param player locked player instance
+     * @param new_media new media currently played
+     * @param data opaque pointer set from vlc_player_New()
+     */
+    void (*on_current_media_changed)(vlc_player_t *player,
+                                     input_item_t *new_media, void *data);
+    /**
+     * Called when the player requires a new media.
+     *
+     * @param player locked player instance
+     * @param data opaque pointer set from vlc_player_New()
+     * @return the next media to play, held by the callee with input_item_Hold()
+     */
+    input_item_t *(*get_next_media)(vlc_player_t *player, void *data);
+};
+
+/**
+ * Callbacks to get the state of the input.
+ *
+ * Can be registered with vlc_player_AddListener().
+ *
+ * All callbacks are called with the player locked (cf. vlc_player_Lock()).
+ */
+struct vlc_player_cbs
+{
+    /**
+     *Called when a new playback event is sent.
+     *
+     * @param player locked player instance
+     * @param media media attached to this event
+     * @param event new playback event
+     * @param data opaque pointer set from vlc_player_AddListener()
+     */
+    void (*on_playback_event)(vlc_player_t *player, input_item_t *media,
+                              const struct vlc_input_event *event, void *data);
+};
+
+/**
+ * Create a new player instance.
+ *
+ * @param parent parent VLC object
+ * @parent owner_cbs callbacks for the owner
+ * @parent owner_cbs_data opaque data for owner callbacks
+ * @return a pointer to a valid player instance or NULL in case of error
+ */
+VLC_API vlc_player_t *
+vlc_player_New(vlc_object_t *parent,
+               const struct vlc_player_owner_cbs *owner_cbs,
+               void *owner_cbs_data);
+
+/**
+ * Delete the player.
+ *
+ * @param player unlocked player instance created by vlc_player_New()
+ */
+VLC_API void
+vlc_player_Delete(vlc_player_t *player);
+
+/**
+ * Lock the player.
+ *
+ * All player functions (except vlc_player_Delete()) need to be called while
+ * the player lock is held.
+ *
+ * @param player unlocked player instance
+ */
+VLC_API void
+vlc_player_Lock(vlc_player_t *player);
+
+/**
+ * Unlock the player
+ *
+ * @param player locked player instance
+ */
+VLC_API void
+vlc_player_Unlock(vlc_player_t *player);
+
+/**
+ * Add a listener callback
+ *
+ * Every registered callbacks need to be removed by the caller with
+ * vlc_player_RemoveListener().
+ *
+ * @param player locked player instance
+ * @param cbs pointer to a static vlc_player_cbs structure
+ * @param cbs_data opaque pointer used by the callbacks
+ * @return a valid listener id, or NULL in case of error
+ */
+VLC_API struct vlc_player_listener_id *
+vlc_player_AddListener(vlc_player_t *player,
+                       const struct vlc_player_cbs *cbs, void *cbs_data);
+
+/**
+ * Remove a listener callback
+ *
+ * @param player locked player instance
+ * @param id listener id returned by vlc_player_AddListener()
+ */
+VLC_API void
+vlc_player_RemoveListener(vlc_player_t *player,
+                          struct vlc_player_listener_id *id);
+
+/**
+ * Set the current media for playback.
+ *
+ * This function replaces the current and next medias (and stop the playback of
+ * these medias if needed). The playback need to be started with
+ * vlc_player_Start().
+ *
+ * @param player locked player instance
+ * @param media new media to player
+ * @return VLC_SUCCESS or a VLC error code
+ */
+VLC_API int
+vlc_player_SetCurrentMedia(vlc_player_t *player, input_item_t *media);
+
+/**
+ * Get the current played media.
+ *
+ * @param player locked player instance
+ * @return a valid media or NULL (if not media are set)
+ */
+VLC_API input_item_t *
+vlc_player_GetCurrentMedia(vlc_player_t *player);
+
+/**
+ * Invalidate the next media.
+ *
+ * This function can be used to invalidate the media returned by the
+ * vlc_player_owner_cbs.get_next_media callback. This can be used when the next
+ * item from a playlist was changed by the user.
+ *
+ * Calling this function will trigger the vlc_player_owner_cbs.get_next_media
+ * callback from the playback thread.
+ *
+ * @param player locked player instance
+ */
+VLC_API void
+vlc_player_InvalidateNextMedia(vlc_player_t *player);
+
+/**
+ * Start the playback of the current media.
+ *
+ * @warning The behaviour is undefined if there is no current media.
+ *
+ * @param player locked player instance
+ * @return VLC_SUCCESS or a VLC error code
+ */
+VLC_API int
+vlc_player_Start(vlc_player_t *player);
+
+/**
+ * Stop the playback of the current media.
+ *
+ * @warning The behaviour is undefined if there is no current media.
+ *
+ * @param player locked player instance
+ * @return VLC_SUCCESS or a VLC error code
+ */
+VLC_API void
+vlc_player_Stop(vlc_player_t *player);
+
+/**
+ * Pause the playback.
+ *
+ * @warning The behaviour is undefined if the player is not started.
+ * @warning The behaviour is undefined if vlc_player_CanPause() is false.
+ *
+ * @param player locked player instance
+ */
+VLC_API void
+vlc_player_Pause(vlc_player_t *player);
+
+/**
+ * Resume the playback from a pause.
+ *
+ * @warning The behaviour is undefined if the player is not started.
+ * @warning The behaviour is undefined if vlc_player_CanPause() is false.
+ *
+ * @param player locked player instance
+ */
+VLC_API void
+vlc_player_Resume(vlc_player_t *player);
+
+/**
+ * Get the started state.
+ *
+ * @param player locked player instance
+ * @return true if the player is started (vlc_player_Start() succeeded and
+ * vlc_player_cbs.on_playback_event didn't send a stopped/dead event).
+ */
+VLC_API bool
+vlc_player_IsStarted(vlc_player_t *player);
+
+/**
+ * Get the paused state.
+ *
+ * Since the vlc_player_Pause() / vlc_player_Resume() are asynchronous, this
+ * function won't reflect the paused state immediately. Wait for the
+ * INPUT_EVENT_STATE event to be notified.
+ *
+ * @param player locked player instance
+ * @return true if the player is paused
+ */
+VLC_API bool
+vlc_player_IsPaused(vlc_player_t *player);
+
+/**
+ * Get the player capabilities
+ *
+ * @warning The behaviour is undefined if there is no current media.
+ *
+ * @param player locked player instance
+ * @return the player capabilities, a bitwise mask of
+ * VLC_INPUT_CAPABILITIES_SEEKABLE, VLC_INPUT_CAPABILITIES_PAUSEABLE,
+ * VLC_INPUT_CAPABILITIES_CHANGE_RATE, VLC_INPUT_CAPABILITIES_REWINDABLE,
+ * VLC_INPUT_CAPABILITIES_RECORDABLE,
+ */
+VLC_API int
+vlc_player_GetCapabilities(vlc_player_t *player);
+
+/**
+ * Get the seek capability (Helper).
+ */
+static inline bool
+vlc_player_CanSeek(vlc_player_t *player)
+{
+    return vlc_player_GetCapabilities(player) & VLC_INPUT_CAPABILITIES_SEEKABLE;
+}
+
+/**
+ * Get the pause capability (Helper).
+ */
+static inline bool
+vlc_player_CanPause(vlc_player_t *player)
+{
+    return vlc_player_GetCapabilities(player) & VLC_INPUT_CAPABILITIES_PAUSEABLE;
+}
+
+/**
+ * Get the change-rate capability (Helper).
+ */
+static inline bool
+vlc_player_CanChangeRate(vlc_player_t *player)
+{
+    return vlc_player_GetCapabilities(player) & VLC_INPUT_CAPABILITIES_CHANGE_RATE;
+}
+
+/**
+ * Get the rewindable capability (Helper).
+ */
+static inline bool
+vlc_player_IsRewindable(vlc_player_t *player)
+{
+    return vlc_player_GetCapabilities(player) & VLC_INPUT_CAPABILITIES_REWINDABLE;
+}
+
+/**
+ * Get the recordable capability (Helper).
+ */
+static inline bool
+vlc_player_IsRecordable(vlc_player_t *player)
+{
+    return vlc_player_GetCapabilities(player) & VLC_INPUT_CAPABILITIES_RECORDABLE;
+}
+
+/**
+ * Get the number of tracks for an ES category.
+ *
+ * @warning The behaviour is undefined if there is no current media.
+ *
+ * @param player locked player instance
+ * @param cat VIDEO_ES, AUDIO_ES or SPU_ES
+ * @return number of tracks (or 0)
+ */
+VLC_API size_t
+vlc_player_GetTrackCount(vlc_player_t *player, enum es_format_category_e cat);
+
+/**
+ * Get the track for an ES caterogy at a specific index.
+ *
+ * @warning The behaviour is undefined if there is no current media.
+ * @warning The behaviour is undefined if the index is not valid.
+ *
+ * @param player locked player instance
+ * @param cat VIDEO_ES, AUDIO_ES or SPU_ES
+ * @param index valid index in the range [0; count[
+ * @return a valid player track (can't be NULL)
+ */
+VLC_API const struct vlc_player_track *
+vlc_player_GetTrackAt(vlc_player_t *player, enum es_format_category_e cat,
+                      size_t index);
+
+/**
+ * Get the video track count (Helper).
+ */
+static inline size_t
+vlc_player_GetVideoTrackCount(vlc_player_t *player)
+{
+    return vlc_player_GetTrackCount(player, VIDEO_ES);
+}
+
+/**
+ * Get the video track at a specific index (Helper).
+ */
+static inline const struct vlc_player_track *
+vlc_player_GetVideoTrackAt(vlc_player_t *player, size_t index)
+{
+    return vlc_player_GetTrackAt(player, VIDEO_ES, index);
+}
+
+/**
+ * Get the audio track count (Helper).
+ */
+static inline size_t
+vlc_player_GetAudioTrackCount(vlc_player_t *player)
+{
+    return vlc_player_GetTrackCount(player, AUDIO_ES);
+}
+
+/**
+ * Get the audio track at a specific index (Helper).
+ */
+static inline const struct vlc_player_track *
+vlc_player_GetAudioTrackAt(vlc_player_t *player, size_t index)
+{
+    return vlc_player_GetTrackAt(player, AUDIO_ES, index);
+}
+
+/**
+ * Get the spu track count (Helper).
+ */
+static inline size_t
+vlc_player_GetSpuTrackCount(vlc_player_t *player)
+{
+    return vlc_player_GetTrackCount(player, SPU_ES);
+}
+
+/**
+ * Get the spu track at a specific index (Helper).
+ */
+static inline const struct vlc_player_track *
+vlc_player_GetSpuTrackAt(vlc_player_t *player, size_t index)
+{
+    return vlc_player_GetTrackAt(player, SPU_ES, index);
+}
+
+/**
+ * Get a track from an ES identifier.
+ *
+ * The only way to save a player track when the player is not locked anymore
+ * (from the event thread to the UI main thread for example) is to hold the ES
+ * ID with vlc_es_id_Hold()). Then, the user can call this function in order to
+ * retrieve the up to date track information from the previously held ES ID.
+ *
+ * @warning The behaviour is undefined if there is no current media.
+ *
+ * @param player locked player instance
+ * @param id an ES ID (retrieved from the INPUT_EVENT_ES event or from
+ * vlc_player_GetTrackAt())
+ * @return a valid player track or NULL (if the track was terminated by the
+ * playback thread)
+ */
+VLC_API const struct vlc_player_track *
+vlc_player_GetTrack(vlc_player_t *player, vlc_es_id_t *id);
+
+/**
+ * Select a track from an ES identifier.
+ *
+ * @warning The behaviour is undefined if there is no current media.
+ *
+ * @param player locked player instance
+ * @param id an ES ID (retrieved from the INPUT_EVENT_ES event or from
+ * vlc_player_GetTrackAt())
+ */
+VLC_API void
+vlc_player_SelectTrack(vlc_player_t *player, vlc_es_id_t *id);
+
+/**
+ * Unselect a track from an ES identifier.
+ *
+ * @warning The behaviour is undefined if there is no current media.
+ *
+ * @param player locked player instance
+ * @param id an ES ID (retrieved from the INPUT_EVENT_ES event or from
+ * vlc_player_GetTrackAt())
+ */
+VLC_API void
+vlc_player_UnselectTrack(vlc_player_t *player, vlc_es_id_t *id);
+
+/**
+ * Restart a track from an ES identifier.
+ *
+ * @warning The behaviour is undefined if there is no current media.
+ *
+ * @param player locked player instance
+ * @param id an ES ID (retrieved from the INPUT_EVENT_ES event or from
+ * vlc_player_GetTrackAt())
+ */
+VLC_API void
+vlc_player_RestartTrack(vlc_player_t *player, vlc_es_id_t *id);
+
+/**
+ * Select the default track for an ES category.
+ *
+ * Tracks for this category will be automatically chosen according to the
+ * language for all future played media.
+ *
+ * @warning The behaviour is undefined if there is no current media.
+ *
+ * @param player locked player instance
+ * @param cat VIDEO_ES, AUDIO_ES or SPU_ES
+ * @param lang language (TODO: define it) or NULL to reset the default state
+ */
+VLC_API void
+vlc_player_SelectDefaultTrack(vlc_player_t *player,
+                              enum es_format_category_e cat, const char *lang);
+
+/**
+ * Select the default video track (Helper).
+ */
+static inline void
+vlc_player_SelectDefaultVideoTrack(vlc_player_t *player, const char *lang)
+{
+    vlc_player_SelectDefaultTrack(player, VIDEO_ES, lang);
+}
+
+/**
+ * Select the default audio track (Helper).
+ */
+static inline void
+vlc_player_SelectDefaultAudioTrack(vlc_player_t *player, const char *lang)
+{
+    vlc_player_SelectDefaultTrack(player, AUDIO_ES, lang);
+}
+
+/**
+ * Select the default spu track (Helper).
+ */
+static inline void
+vlc_player_SelectDefaultSpuTrack(vlc_player_t *player, const char *lang)
+{
+    vlc_player_SelectDefaultTrack(player, SPU_ES, lang);
+}
+
+/**
+ * Get the number of programs
+ *
+ * @warning The behaviour is undefined if there is no current media.
+ *
+ * @param player locked player instance
+ * @return number of programs (or 0)
+ */
+VLC_API size_t
+vlc_player_GetProgramCount(vlc_player_t *player);
+
+/**
+ * Get the program at a specific index.
+ *
+ * @warning The behaviour is undefined if there is no current media.
+ * @warning The behaviour is undefined if the index is not valid.
+ *
+ * @param player locked player instance
+ * @param index valid index in the range [0; count[
+ * @return a valid player program (can't be NULL)
+ */
+VLC_API const struct vlc_player_program *
+vlc_player_GetProgramAt(vlc_player_t *player, size_t index);
+
+/**
+ * Get a program from an ES identifier.
+ *
+ * @warning The behaviour is undefined if there is no current media.
+ *
+ * @param player locked player instance
+ * @param id an ES program ID (retrieved from the INPUT_EVENT_PROGRAM event or
+ * from vlc_player_GetProgramAt())
+ * @return a valid program or NULL (if the program was terminated by the
+ * playback thread)
+ */
+VLC_API const struct vlc_player_program *
+vlc_player_GetProgram(vlc_player_t *player, int id);
+
+/**
+ * Select a program from an ES program identifier.
+ *
+ * @warning The behaviour is undefined if there is no current media.
+ *
+ * @param player locked player instance
+ * @param id an ES program ID (retrieved from the INPUT_EVENT_PROGRAM event or
+ * from vlc_player_GetProgramAt())
+ */
+VLC_API void
+vlc_player_SelectProgram(vlc_player_t *player, int id);
+
+/**
+ * Set the renderer
+ *
+ * Valid for the current media and all future ones.
+ *
+ * @param player locked player instance
+ * @param renderer a valid renderer item or NULL (to disable it)
+ */
+VLC_API void
+vlc_player_SetRenderer(vlc_player_t *player, vlc_renderer_item_t *renderer);
+
+/** @} */
+#endif
diff --git a/src/Makefile.am b/src/Makefile.am
index 5ede6c9ee0..eb92f7b249 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -76,6 +76,7 @@ pluginsinclude_HEADERS = \
 	../include/vlc_picture.h \
 	../include/vlc_picture_fifo.h \
 	../include/vlc_picture_pool.h \
+	../include/vlc_player.h \
 	../include/vlc_playlist.h \
 	../include/vlc_plugin.h \
 	../include/vlc_probe.h \
@@ -243,6 +244,7 @@ libvlccore_la_SOURCES = \
 	input/es_out_timeshift.c \
 	input/event.c \
 	input/input.c \
+	input/player.c \
 	input/info.h \
 	input/meta.c \
 	clock/input_clock.h \
diff --git a/src/input/player.c b/src/input/player.c
new file mode 100644
index 0000000000..fc00454618
--- /dev/null
+++ b/src/input/player.c
@@ -0,0 +1,1038 @@
+/*****************************************************************************
+ * player.c: Player interface
+ *****************************************************************************
+ * Copyright © 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 <vlc_common.h>
+#include <vlc_player.h>
+#include <vlc_player.h>
+#include <vlc_aout.h>
+#include <vlc_renderer_discovery.h>
+#include <vlc_list.h>
+#include <vlc_vector.h>
+
+#include "libvlc.h"
+#include "input_internal.h"
+
+#define GAPLESS 0 /* TODO */
+
+struct vlc_player_listener_id
+{
+    const struct vlc_player_cbs * cbs;
+    void * cbs_data;
+    struct vlc_list node;
+};
+
+struct vlc_player_program_priv
+{
+    struct vlc_player_program p;
+    char *title;
+};
+typedef struct VLC_VECTOR(struct vlc_player_program_priv *)
+    vlc_player_program_vector;
+
+struct vlc_player_track_priv
+{
+    struct vlc_player_track p;
+    es_format_t fmt;
+    char *title;
+};
+typedef struct VLC_VECTOR(struct vlc_player_track_priv *)
+    vlc_player_track_vector;
+
+struct vlc_player_input
+{
+    input_thread_t *thread;
+    vlc_player_t *player;
+    bool started;
+
+    input_state_e state;
+    float rate;
+    int capabilities;
+    vlc_tick_t length;
+
+    vlc_tick_t position_ms;
+    float position_percent;
+    vlc_tick_t position_date;
+
+    vlc_player_program_vector program_vector;
+    vlc_player_track_vector video_track_vector;
+    vlc_player_track_vector audio_track_vector;
+    vlc_player_track_vector spu_track_vector;
+
+    struct vlc_list node;
+};
+
+struct vlc_player_t
+{
+    struct vlc_common_members obj;
+    vlc_mutex_t lock;
+
+    const struct vlc_player_owner_cbs *owner_cbs;
+    void *owner_cbs_data;
+
+    struct vlc_list listeners;
+
+    input_resource_t *resource;
+    vlc_renderer_item_t *renderer;
+
+    input_item_t *media;
+    struct vlc_player_input *input;
+
+#if GAPLESS
+    input_item_t *next_media;
+    struct vlc_player_input *next_input;
+#endif
+
+    struct
+    {
+        bool running;
+        vlc_thread_t thread;
+        vlc_cond_t cond;
+        struct vlc_list inputs;
+        size_t wait_count;
+    } destructor;
+};
+
+static void
+input_thread_events(input_thread_t *, const struct vlc_input_event *, void *);
+
+static inline void
+vlc_player_assert_locked(vlc_player_t *player)
+{
+    vlc_assert_locked(&player->lock);
+}
+
+static struct vlc_player_input *
+vlc_player_input_Create(vlc_player_t *player, input_item_t *item)
+{
+    struct vlc_player_input *input = malloc(sizeof(*input));
+    if (!input)
+        return NULL;
+
+    input->player = player;
+    input->started = false;
+
+    input->state = INIT_S;
+    input->rate = 1.f;
+    input->capabilities = 0;
+    input->length = input->position_ms =
+    input->position_date = VLC_TICK_INVALID;
+    input->position_percent = 0.f;
+
+    vlc_vector_init(&input->program_vector);
+    vlc_vector_init(&input->video_track_vector);
+    vlc_vector_init(&input->audio_track_vector);
+    vlc_vector_init(&input->spu_track_vector);
+
+    input->thread = input_Create(player, input_thread_events, input, item,
+                                 NULL, player->resource, player->renderer);
+    if (!input->thread)
+    {
+        free(input);
+        return NULL;
+    }
+    return input;
+}
+
+static void
+vlc_player_input_Destroy(struct vlc_player_input *input)
+{
+    assert(input->program_vector.size == 0);
+    assert(input->video_track_vector.size == 0);
+    assert(input->audio_track_vector.size == 0);
+    assert(input->spu_track_vector.size == 0);
+
+    vlc_vector_clear(&input->program_vector);
+    vlc_vector_clear(&input->video_track_vector);
+    vlc_vector_clear(&input->audio_track_vector);
+    vlc_vector_clear(&input->spu_track_vector);
+
+    input_Close(input->thread);
+    free(input);
+}
+
+static int
+vlc_player_input_Start(struct vlc_player_input *input)
+{
+    int ret = input_Start(input->thread);
+    if (ret != VLC_SUCCESS)
+        return ret;
+    input->started = true;
+    return ret;
+}
+
+static void
+vlc_player_input_StopAndClose(struct vlc_player_input *input)
+{
+    if (input->started)
+    {
+        input->started = false;
+        input_Stop(input->thread);
+
+        input->player->destructor.wait_count++;
+        /* This input will be cleaned when we receive the INPUT_EVENT_DEAD
+         * event */
+    }
+    else
+        vlc_player_input_Destroy(input);
+}
+
+static void *
+destructor_thread(void *data)
+{
+    vlc_player_t *player = data;
+    struct vlc_player_input *input;
+
+    vlc_mutex_lock(&player->lock);
+    while (player->destructor.running || player->destructor.wait_count > 0)
+    {
+        while (player->destructor.running
+            && vlc_list_is_empty(&player->destructor.inputs))
+            vlc_cond_wait(&player->destructor.cond, &player->lock);
+
+        vlc_list_foreach(input, &player->destructor.inputs, node)
+        {
+            vlc_player_input_Destroy(input);
+            vlc_list_remove(&input->node);
+            player->destructor.wait_count--;
+        }
+    }
+    vlc_mutex_unlock(&player->lock);
+    return NULL;
+}
+
+static void
+vlc_player_SendNewPlaybackEvent(vlc_player_t *player, input_item_t *new_item)
+{
+    vlc_player_assert_locked(player);
+
+    if (player->owner_cbs->on_current_media_changed)
+        player->owner_cbs->on_current_media_changed(player, new_item,
+                                                    player->owner_cbs_data);
+}
+
+static input_item_t *
+vlc_player_GetNextItem(vlc_player_t *player)
+{
+    vlc_player_assert_locked(player);
+
+    return player->owner_cbs->get_next_media ?
+           player->owner_cbs->get_next_media(player, player->owner_cbs_data) :
+           NULL;
+}
+
+static void
+vlc_player_SendInputEvent(vlc_player_t *player,
+                          const struct vlc_input_event *event)
+{
+    struct vlc_player_listener_id *listener;
+    vlc_list_foreach(listener, &player->listeners, node)
+    {
+        if (listener->cbs->on_playback_event)
+            listener->cbs->on_playback_event(player, player->media, event,
+                                             listener->cbs_data);
+    }
+}
+
+static int
+vlc_player_OpenNextItem(vlc_player_t *player)
+{
+    assert(player->input == NULL);
+
+    input_item_t *next_media = vlc_player_GetNextItem(player);
+    if (!next_media)
+        return VLC_EGENERIC;
+
+    if (player->media)
+        input_item_Release(player->media);
+    player->media = next_media;
+
+    player->input = vlc_player_input_Create(player, player->media);
+
+    return player->input ? VLC_SUCCESS : VLC_EGENERIC;
+}
+
+static struct vlc_player_program_priv *
+vlc_player_program_vector_FindById(vlc_player_program_vector *vec, int id,
+                                   size_t *idx)
+{
+    for (size_t i = 0; i < vec->size; ++i)
+    {
+        struct vlc_player_program_priv *prgm = vec->data[i];
+        if (prgm->p.id == id)
+        {
+            if (idx)
+                *idx = i;
+            return prgm;
+        }
+    }
+    return NULL;
+}
+
+size_t
+vlc_player_GetProgramCount(vlc_player_t *player)
+{
+    vlc_player_assert_locked(player);
+    assert(player->input);
+
+    return player->input->program_vector.size;
+}
+
+const struct vlc_player_program *
+vlc_player_GetProgramAt(vlc_player_t *player, size_t index)
+{
+    vlc_player_assert_locked(player);
+    assert(player->input);
+
+    assert(index < player->input->program_vector.size);
+    return &player->input->program_vector.data[index]->p;
+}
+
+const struct vlc_player_program *
+vlc_player_GetProgram(vlc_player_t *player, int id)
+{
+    vlc_player_assert_locked(player);
+    assert(player->input);
+
+    struct vlc_player_program_priv *prgm =
+        vlc_player_program_vector_FindById(&player->input->program_vector, id,
+                                           NULL);
+    return prgm ? &prgm->p : NULL;
+}
+
+void
+vlc_player_SelectProgram(vlc_player_t *player, int id)
+{
+    vlc_player_assert_locked(player);
+    assert(player->input != NULL);
+
+    input_ControlPushHelper(player->input->thread, INPUT_CONTROL_SET_PROGRAM,
+                            &(vlc_value_t) { .i_int = id });
+}
+
+static struct vlc_player_program_priv *
+vlc_player_program_Create(int id, const char *title)
+{
+    struct vlc_player_program_priv *prgm = malloc(sizeof(*prgm));
+    if (!prgm)
+        return NULL;
+    prgm->title = strdup(title);
+    if (!prgm->title)
+    {
+        free(prgm);
+        return NULL;
+    }
+    prgm->p.id = id;
+    prgm->p.title = prgm->title;
+    prgm->p.selected = prgm->p.scrambled = false;
+
+    return prgm;
+}
+
+static void
+vlc_player_program_Destroy(struct vlc_player_program_priv *prgm)
+{
+    free(prgm->title);
+    free(prgm);
+}
+
+static int
+vlc_player_input_HandleProgramEvent(struct vlc_player_input *input,
+                                    const struct vlc_input_event_program *ev)
+{
+    struct vlc_player_program_priv *prgm;
+    vlc_player_program_vector *vec = &input->program_vector;
+
+    switch (ev->action)
+    {
+        case VLC_INPUT_PROGRAM_ADDED:
+            assert(ev->title);
+            prgm = vlc_player_program_Create(ev->id, ev->title);
+            if (!prgm)
+                return VLC_ENOMEM;
+
+            if (vlc_vector_push(vec, prgm))
+                return VLC_SUCCESS;
+            vlc_player_program_Destroy(prgm);
+            return VLC_ENOMEM;
+        case VLC_INPUT_PROGRAM_DELETED:
+        {
+            size_t idx;
+            prgm = vlc_player_program_vector_FindById(vec, ev->id, &idx);
+            if (prgm)
+            {
+                vlc_vector_remove(vec, idx);
+                vlc_player_program_Destroy(prgm);
+                return VLC_SUCCESS;
+            }
+            return VLC_EGENERIC;
+        }
+        case VLC_INPUT_PROGRAM_SELECTED:
+        {
+            int ret = VLC_EGENERIC;
+            vlc_vector_foreach(prgm, vec)
+            {
+                if (prgm->p.id == ev->id)
+                {
+                    prgm->p.selected = true;
+                    ret = VLC_SUCCESS;
+                }
+                else
+                    prgm->p.selected = false;
+            }
+            return ret;
+        }
+        case VLC_INPUT_PROGRAM_SCRAMBLED:
+            prgm = vlc_player_program_vector_FindById(vec, ev->id, NULL);
+            if (prgm)
+            {
+                prgm->p.scrambled = ev->scrambled;
+                return VLC_SUCCESS;
+            }
+            return VLC_EGENERIC;
+        default:
+            vlc_assert_unreachable();
+    }
+}
+
+static inline vlc_player_track_vector *
+vlc_player_input_GetTrackVector(struct vlc_player_input *input,
+                                enum es_format_category_e cat)
+{
+    switch (cat)
+    {
+        case VIDEO_ES:
+            return &input->video_track_vector;
+        case AUDIO_ES:
+            return &input->audio_track_vector;
+        case SPU_ES:
+            return &input->spu_track_vector;
+        default:
+            return NULL;
+    }
+}
+
+static struct vlc_player_track_priv *
+vlc_player_track_vector_FindById(vlc_player_track_vector *vec, vlc_es_id_t *id,
+                                 size_t *idx)
+{
+    for (size_t i = 0; i < vec->size; ++i)
+    {
+        struct vlc_player_track_priv *es = vec->data[i];
+        if (es->p.id == id)
+        {
+            if (idx)
+                *idx = i;
+            return es;
+        }
+    }
+    return NULL;
+}
+
+size_t
+vlc_player_GetTrackCount(vlc_player_t *player, enum es_format_category_e cat)
+{
+    vlc_player_assert_locked(player);
+    assert(player->input);
+
+    vlc_player_track_vector *vec =
+        vlc_player_input_GetTrackVector(player->input, cat);
+    if (vec)
+        return 0;
+    return vec->size;
+}
+
+const struct vlc_player_track *
+vlc_player_GetTrackAt(vlc_player_t *player, enum es_format_category_e cat,
+                      size_t index)
+{
+    vlc_player_assert_locked(player);
+    assert(player->input);
+
+    vlc_player_track_vector *vec =
+        vlc_player_input_GetTrackVector(player->input, cat);
+    if (!vec)
+        return NULL;
+    assert(index < vec->size);
+    return &vec->data[index]->p;
+}
+
+const struct vlc_player_track *
+vlc_player_GetTrack(vlc_player_t *player, vlc_es_id_t *id)
+{
+    vlc_player_assert_locked(player);
+    struct vlc_player_input *input = player->input;
+    assert(input);
+
+    vlc_player_track_vector *vec =
+        vlc_player_input_GetTrackVector(input, vlc_es_id_GetCat(id));
+    if (!vec)
+        return NULL;
+    struct vlc_player_track_priv *es =
+        vlc_player_track_vector_FindById(vec, id, NULL);
+    return es ? &es->p : NULL;
+}
+
+void
+vlc_player_SelectTrack(vlc_player_t *player, vlc_es_id_t *id)
+{
+    vlc_player_assert_locked(player);
+    assert(player->input != NULL);
+
+    input_ControlPushEsHelper(player->input->thread, INPUT_CONTROL_SET_ES, id);
+}
+
+void
+vlc_player_UnselectTrack(vlc_player_t *player, vlc_es_id_t *id)
+{
+    vlc_player_assert_locked(player);
+    assert(player->input != NULL);
+
+    input_ControlPushEsHelper(player->input->thread, INPUT_CONTROL_UNSET_ES,
+                              id);
+}
+
+void
+vlc_player_RestartTrack(vlc_player_t *player, vlc_es_id_t *id)
+{
+    vlc_player_assert_locked(player);
+    assert(player->input != NULL);
+
+    input_ControlPushEsHelper(player->input->thread, INPUT_CONTROL_RESTART_ES,
+                              id);
+}
+
+void
+vlc_player_SelectDefaultTrack(vlc_player_t *player,
+                              enum es_format_category_e cat, const char *lang)
+{
+    vlc_player_assert_locked(player);
+    /* TODO */ (void) cat; (void) lang;
+}
+
+static struct vlc_player_track_priv *
+vlc_player_track_Create(vlc_es_id_t *id, const char *title,
+                        const es_format_t *fmt)
+{
+    struct vlc_player_track_priv *es = malloc(sizeof(*es));
+    if (!es)
+        return NULL;
+    es->title = strdup(title);
+    if (!es->title)
+    {
+        free(es);
+        return NULL;
+    }
+
+    int ret = es_format_Copy(&es->fmt, fmt);
+    if (ret != VLC_SUCCESS)
+    {
+        free(es->title);
+        free(es);
+        return NULL;
+    }
+    es->p.id = vlc_es_id_Hold(id);
+    es->p.fmt = &es->fmt;
+    es->p.title = es->title;
+    es->p.selected = false;
+
+    return es;
+}
+
+static void
+vlc_player_track_Destroy(struct vlc_player_track_priv *es)
+{
+    es_format_Clean(&es->fmt);
+    vlc_es_id_Release(es->p.id);
+    free(es->title);
+    free(es);
+}
+
+static int
+vlc_player_track_Update(struct vlc_player_track_priv *es, const char *title,
+                        const es_format_t *fmt)
+{
+    if (strcmp(title, es->title) != 0)
+    {
+        char *dup = strdup(title);
+        if (!dup)
+            return VLC_ENOMEM;
+        free(es->title);
+        es->title = dup;
+    }
+
+    es_format_t fmtdup;
+    int ret = es_format_Copy(&fmtdup, fmt);
+    if (ret != VLC_SUCCESS)
+        return ret;
+
+    es_format_Clean(&es->fmt);
+    es->fmt = fmtdup;
+    return VLC_SUCCESS;
+}
+
+static int
+vlc_player_input_HandleEsEvent(struct vlc_player_input *input,
+                               const struct vlc_input_event_es *ev)
+{
+    assert(ev->id && ev->title && ev->fmt);
+
+    struct vlc_player_track_priv *es;
+    vlc_player_track_vector *vec =
+        vlc_player_input_GetTrackVector(input, ev->fmt->i_cat);
+
+    if (!vec)
+        return VLC_EGENERIC; /* UNKNOWN_ES or DATA_ES not handled */
+
+    switch (ev->action)
+    {
+        case VLC_INPUT_ES_ADDED:
+            es = vlc_player_track_Create(ev->id, ev->title, ev->fmt);
+            if (!es)
+                return VLC_ENOMEM;
+
+            if (vlc_vector_push(vec, es))
+                return VLC_SUCCESS;
+            vlc_player_track_Destroy(es);
+            return VLC_ENOMEM;
+        case VLC_INPUT_ES_DELETED:
+        {
+            size_t idx;
+            es = vlc_player_track_vector_FindById(vec, ev->id, &idx);
+            if (es)
+            {
+                vlc_vector_remove(vec, idx);
+                vlc_player_track_Destroy(es);
+                return VLC_SUCCESS;
+            }
+            return VLC_EGENERIC;
+
+        }
+        case VLC_INPUT_ES_UPDATED:
+            es = vlc_player_track_vector_FindById(vec, ev->id, NULL);
+            if (es)
+                return vlc_player_track_Update(es, ev->title, ev->fmt);
+            return VLC_EGENERIC;
+        case VLC_INPUT_ES_SELECTED:
+        case VLC_INPUT_ES_UNSELECTED:
+            es = vlc_player_track_vector_FindById(vec, ev->id, NULL);
+            if (es)
+            {
+                es->p.selected = ev->action == VLC_INPUT_ES_SELECTED;
+                return VLC_SUCCESS;
+            }
+            return VLC_EGENERIC;
+        default:
+            vlc_assert_unreachable();
+    }
+}
+
+static void
+input_thread_events(input_thread_t *input_thread,
+                    const struct vlc_input_event *event, void *user_data)
+{
+    struct vlc_player_input *input = user_data;
+    vlc_player_t *player = input->player;
+
+    assert(input_thread == input->thread);
+
+    vlc_mutex_lock(&player->lock);
+
+    if (!input->started)
+    {
+        if (event->type == INPUT_EVENT_DEAD)
+        {
+            /* Don't increment wait_count: already done by
+             * vlc_player_input_StopAndClose() */
+            vlc_list_append(&input->node, &player->destructor.inputs);
+            vlc_cond_signal(&player->destructor.cond);
+        }
+
+        vlc_mutex_unlock(&player->lock);
+        return;
+    }
+
+    bool skip_event = false;
+    switch (event->type)
+    {
+        case INPUT_EVENT_STATE:
+            input->state = event->state;
+            break;
+        case INPUT_EVENT_RATE:
+            input->rate = event->rate;
+            break;
+        case INPUT_EVENT_CAPABILITIES:
+            input->capabilities = event->capabilities;
+            break;
+        case INPUT_EVENT_POSITION:
+#if GAPLESS
+            /* XXX case INPUT_EVENT_EOF: */
+            if (player->next_input == NULL)
+                break;
+            vlc_tick_t length = input->length;
+            vlc_tick_t time = event->position.ms;
+            if (length > 0 && time > 0
+             && length - time <= AOUT_MAX_PREPARE_TIME)
+                vlc_player_OpenNextItem(player);
+#endif
+            input->position_ms = event->position.ms;
+            input->position_percent = event->position.percentage;
+            input->position_date = vlc_tick_now();
+            break;
+        case INPUT_EVENT_LENGTH:
+            input->length = event->length;
+            break;
+        case INPUT_EVENT_PROGRAM:
+            if (vlc_player_input_HandleProgramEvent(input, &event->program))
+                skip_event = true;
+            break;
+        case INPUT_EVENT_ES:
+            if (vlc_player_input_HandleEsEvent(input, &event->es))
+                skip_event = true;
+            break;
+        case INPUT_EVENT_DEAD:
+            input->started = false;
+            vlc_list_append(&input->node, &player->destructor.inputs);
+            vlc_cond_signal(&player->destructor.cond);
+            player->destructor.wait_count++;
+
+            /* XXX: for now, play only one input at a time */
+            if (likely(input == player->input))
+            {
+                player->input = NULL;
+                if (vlc_player_OpenNextItem(player) == VLC_SUCCESS)
+                {
+                    vlc_player_SendNewPlaybackEvent(player, player->media);
+                    vlc_player_input_Start(player->input);
+                    skip_event = true;
+                }
+            }
+            break;
+        default:
+            break;
+    }
+
+    if (!skip_event)
+        vlc_player_SendInputEvent(player, event);
+    vlc_mutex_unlock(&player->lock);
+}
+
+void
+vlc_player_Delete(vlc_player_t *player)
+{
+    vlc_mutex_lock(&player->lock);
+
+    if (player->input)
+        vlc_player_input_StopAndClose(player->input);
+#if GAPLESS
+    if (player->next_input)
+        vlc_player_input_StopAndClose(player->next_input);
+#endif
+
+    player->destructor.running = false;
+    vlc_cond_signal(&player->destructor.cond);
+
+    if (player->media)
+        input_item_Release(player->media);
+#if GAPLESS
+    if (player->next_media)
+        input_item_Release(player->next_media);
+#endif
+
+    assert(vlc_list_is_empty(&player->listeners));
+
+    vlc_mutex_unlock(&player->lock);
+
+    vlc_join(player->destructor.thread, NULL);
+
+    vlc_mutex_destroy(&player->lock);
+    vlc_cond_destroy(&player->destructor.cond);
+
+    input_resource_Release(player->resource);
+    if (player->renderer)
+        vlc_renderer_item_release(player->renderer);
+
+    vlc_object_release(player);
+}
+
+vlc_player_t *
+vlc_player_New(vlc_object_t *parent,
+               const struct vlc_player_owner_cbs *owner_cbs,
+               void *owner_cbs_data)
+{
+    vlc_player_t *player = vlc_custom_create(parent, sizeof(*player), "player");
+    if (!player)
+        return NULL;
+
+    vlc_list_init(&player->destructor.inputs);
+    player->renderer = NULL;
+    player->owner_cbs = owner_cbs;
+    player->owner_cbs_data = owner_cbs_data;
+    player->media = NULL;
+    player->input = NULL;
+#if GAPLESS
+    player->next_media = NULL;
+    player->next_input = NULL;
+#endif
+    player->resource = input_resource_New(VLC_OBJECT(player));
+
+    if (!player->resource)
+        goto error;
+
+    player->destructor.running = true;
+    vlc_mutex_init(&player->lock);
+    vlc_cond_init(&player->destructor.cond);
+
+    if (vlc_clone(&player->destructor.thread, destructor_thread, player,
+                  VLC_THREAD_PRIORITY_LOW) != 0)
+    {
+        vlc_mutex_destroy(&player->lock);
+        vlc_cond_destroy(&player->destructor.cond);
+        goto error;
+    }
+
+    return player;
+
+error:
+    if (player->resource)
+        input_resource_Release(player->resource);
+
+    vlc_object_release(player);
+    return NULL;
+}
+
+void
+vlc_player_Lock(vlc_player_t *player)
+{
+    vlc_mutex_lock(&player->lock);
+}
+
+void
+vlc_player_Unlock(vlc_player_t *player)
+{
+    vlc_mutex_unlock(&player->lock);
+}
+
+struct vlc_player_listener_id *
+vlc_player_AddListener(vlc_player_t *player,
+                       const struct vlc_player_cbs *cbs, void *cbs_data)
+{
+    assert(cbs);
+    vlc_player_assert_locked(player);
+
+    struct vlc_player_listener_id *listener = malloc(sizeof(*listener));
+    if (!listener)
+        return NULL;
+
+    listener->cbs = cbs;
+    listener->cbs_data = cbs_data;
+
+    vlc_list_append(&listener->node, &player->listeners);
+
+    return listener;
+}
+
+void
+vlc_player_RemoveListener(vlc_player_t *player,
+                          struct vlc_player_listener_id *id)
+{
+    assert(id);
+    vlc_player_assert_locked(player);
+
+    vlc_list_remove(&id->node);
+    free(id);
+}
+
+int
+vlc_player_SetCurrentMedia(vlc_player_t *player, input_item_t *media)
+{
+    vlc_player_assert_locked(player);
+
+    if (player->input)
+    {
+        vlc_player_input_StopAndClose(player->input);
+        player->input = NULL;
+    }
+#if GAPLESS
+    if (player->next_input)
+    {
+        vlc_player_input_StopAndClose(player->next_input);
+        player->next_input = NULL;
+    }
+#endif
+
+    if (player->media)
+    {
+        input_item_Release(player->media);
+        player->media = NULL;
+    }
+#if GAPLESS
+    if (player->next_media)
+    {
+        input_item_Release(player->next_media);
+        player->next_media = NULL;
+    }
+#endif
+
+    player->media = input_item_Hold(media);
+    player->input = vlc_player_input_Create(player, player->media);
+
+    if (!player->input)
+    {
+        input_item_Release(player->media);
+        player->media = NULL;
+        return VLC_ENOMEM;
+    }
+    return VLC_SUCCESS;
+}
+
+input_item_t *
+vlc_player_GetCurrentMedia(vlc_player_t *player)
+{
+    vlc_player_assert_locked(player);
+
+    return player->media;
+}
+
+void
+vlc_player_InvalidateNextMedia(vlc_player_t *player)
+{
+    (void) player;
+#if GAPLESS
+    vlc_player_assert_locked(player);
+    if (player->next_media)
+    {
+        input_item_Release(player->next_media);
+        player->next_media = vlc_player_GetNextItem(player);
+    }
+    if (player->next_input)
+    {
+        /* Cause the get_next_media callback to be called when this input is
+         * dead */
+        vlc_player_input_StopAndClose(player->input);
+        player->next_input = NULL;
+    }
+#endif
+}
+
+int
+vlc_player_Start(vlc_player_t *player)
+{
+    vlc_player_assert_locked(player);
+    assert(player->media);
+
+    if (!player->input)
+    {
+        /* Possible if the player was stopped by the user */
+        assert(player->media);
+        player->input = vlc_player_input_Create(player, player->media);
+
+        if (!player->input)
+            return VLC_EGENERIC;
+    }
+    assert(!player->input->started);
+
+    return vlc_player_input_Start(player->input);
+}
+
+void
+vlc_player_Stop(vlc_player_t *player)
+{
+    vlc_player_assert_locked(player);
+    assert(player->input && player->input->started);
+
+    vlc_player_input_StopAndClose(player->input);
+    player->input = NULL;
+
+#if GAPLESS
+    if (player->next_input)
+    {
+        vlc_player_input_StopAndClose(player->next_input);
+        player->next_input = NULL;
+    }
+#endif
+}
+
+void
+vlc_player_Pause(vlc_player_t *player)
+{
+    vlc_player_assert_locked(player);
+    assert(player->input && player->input->started);
+    assert(player->input->capabilities & VLC_INPUT_CAPABILITIES_PAUSEABLE);
+
+    input_ControlPushHelper(player->input->thread, INPUT_CONTROL_SET_STATE,
+                            &(vlc_value_t) {.i_int = PAUSE_S});
+}
+
+void
+vlc_player_Resume(vlc_player_t *player)
+{
+    vlc_player_assert_locked(player);
+
+    assert(player->input && player->input->started);
+    assert(player->input->capabilities & VLC_INPUT_CAPABILITIES_PAUSEABLE);
+
+    input_ControlPushHelper(player->input->thread, INPUT_CONTROL_SET_STATE,
+                            &(vlc_value_t) { .i_int = PLAYING_S });
+}
+
+bool
+vlc_player_IsStarted(vlc_player_t *player)
+{
+    vlc_player_assert_locked(player);
+    return player->input && player->input->started;
+}
+
+bool
+vlc_player_IsPaused(vlc_player_t *player)
+{
+    vlc_player_assert_locked(player);
+    return !player->input || player->input->state != PLAYING_S;
+}
+
+int
+vlc_player_GetCapabilities(vlc_player_t *player)
+{
+    vlc_player_assert_locked(player);
+    assert(player->input);
+    return player->input->capabilities;
+}
+
+void
+vlc_player_SetRenderer(vlc_player_t *player, vlc_renderer_item_t *renderer)
+{
+    vlc_player_assert_locked(player);
+
+    if (player->renderer)
+        vlc_renderer_item_release(player->renderer);
+    player->renderer = vlc_renderer_item_hold(renderer);
+
+    if (player->input)
+        input_Control(player->input->thread, INPUT_SET_RENDERER,
+                      player->renderer);
+#if GAPLESS
+    if (player->next_input)
+        input_Control(player->next_input->thread, INPUT_SET_RENDERER,
+                      player->renderer);
+#endif
+}
diff --git a/src/libvlccore.sym b/src/libvlccore.sym
index c77f39d6b7..6bd19bbda5 100644
--- a/src/libvlccore.sym
+++ b/src/libvlccore.sym
@@ -779,3 +779,31 @@ vlc_rd_get_names
 vlc_rd_new
 vlc_rd_release
 vlc_rd_probe_add
+vlc_player_AddListener
+vlc_player_Delete
+vlc_player_GetCapabilities
+vlc_player_GetCurrentMedia
+vlc_player_GetProgram
+vlc_player_GetProgramAt
+vlc_player_GetProgramCount
+vlc_player_GetTrack
+vlc_player_GetTrackAt
+vlc_player_GetTrackCount
+vlc_player_InvalidateNextMedia
+vlc_player_IsPaused
+vlc_player_IsStarted
+vlc_player_Lock
+vlc_player_New
+vlc_player_Pause
+vlc_player_RemoveListener
+vlc_player_RestartTrack
+vlc_player_Resume
+vlc_player_SelectDefaultTrack
+vlc_player_SelectProgram
+vlc_player_SelectTrack
+vlc_player_SetCurrentMedia
+vlc_player_SetRenderer
+vlc_player_Start
+vlc_player_Stop
+vlc_player_Unlock
+vlc_player_UnselectTrack
-- 
2.18.0



More information about the vlc-devel mailing list