[vlc-devel] [PATCH 1/2] core: add player API

Thomas Guillem thomas at gllm.fr
Mon Oct 22 18:11:07 CEST 2018


This API will replace the usage of input_thread_t from interface modules. The
player implementation continue to use input_thread_t in private. The goal is to
hide the input_thread_t API when every modules are switched to the player API.

TODO (all need to be fixed, by me, for VLC 4.0):

 - Gapless: API is complete but not implemented (the player can play several
   medias in a row, but without gapless).
 - Position callbacks: still using the input_thread_t position that is really
   imprecise (notified every 250ms minimum, and sometime more, depending on
   pf_demux implementation).
 - Seek/discontinuity callbacks: when seeking, the player can still send
   position of the requested position, the actual position or the next position
   to come. This leads to UI inconsistency.
---
 include/vlc_player.h | 2407 ++++++++++++++++++++++++++++++++++++
 po/POTFILES.in       |    1 +
 src/Makefile.am      |    3 +
 src/input/player.c   | 2804 ++++++++++++++++++++++++++++++++++++++++++
 src/input/player.h   |   35 +
 src/libvlccore.sym   |   97 ++
 6 files changed, 5347 insertions(+)
 create mode 100644 include/vlc_player.h
 create mode 100644 src/input/player.c
 create mode 100644 src/input/player.h

diff --git a/include/vlc_player.h b/include/vlc_player.h
new file mode 100644
index 0000000000..0e0a9f3d11
--- /dev/null
+++ b/include/vlc_player.h
@@ -0,0 +1,2407 @@
+/*****************************************************************************
+ * 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>
+#include <vlc_aout.h>
+
+/**
+ * @defgroup player Player
+ * @ingroup input
+ * VLC Player API
+ * @brief
+ at dot
+digraph player_states {
+  label="Player state diagram";
+  new [style="invis"];
+  started [label="Started" URL="@ref VLC_PLAYER_STATE_STARTED"];
+  playing [label="Playing" URL="@ref VLC_PLAYER_STATE_PLAYING"];
+  paused [label="Paused" URL="@ref VLC_PLAYER_STATE_PAUSED"];
+  stopping [label="Stopping" URL="@ref VLC_PLAYER_STATE_STOPPING"];
+  stopped [label="Stopped" URL="@ref VLC_PLAYER_STATE_STOPPED"];
+  new -> stopped [label="vlc_player_New()" URL="@ref vlc_player_New" fontcolor="green3"];
+  started -> playing [style="dashed" label=<<i>internal transition</i>>];
+  started -> stopping [label="vlc_player_Stop()" URL="@ref vlc_player_Stop" fontcolor="red"];
+  playing -> paused [label="vlc_player_Pause()" URL="@ref vlc_player_Pause" fontcolor="blue"];
+  paused -> playing [label="vlc_player_Resume()" URL="@ref vlc_player_Resume" fontcolor="blue3"];
+  paused -> stopping [label="vlc_player_Stop()" URL="@ref vlc_player_Stop" fontcolor="red"];
+  playing -> stopping [label="vlc_player_Stop()" URL="@ref vlc_player_Stop" fontcolor="red"];
+  stopping -> stopped [style="dashed" label=<<i>internal transition</i>>];
+  stopped -> started [label="vlc_player_Start()" URL="@ref vlc_player_Start" fontcolor="darkgreen"];
+}
+ at enddot
+ * @{
+ * @file
+ * VLC Player API
+ */
+
+/**
+ * Player opaque 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().
+ */
+typedef struct vlc_player_listener_id vlc_player_listener_id;
+
+/**
+ * Player vout listener opaque structure.
+ *
+ * This opaque structure is returned by vlc_player_vout_AddListener() and can
+ * be used to remove the listener via vlc_player_vout_RemoveListener().
+ */
+typedef struct vlc_player_vout_listener_id vlc_player_vout_listener_id;
+
+/**
+ * Player aout listener opaque structure.
+ *
+ * This opaque structure is returned by vlc_player_aout_AddListener() and can
+ * be used to remove the listener via vlc_player_aout_RemoveListener().
+ */
+typedef struct vlc_player_aout_listener_id vlc_player_aout_listener_id;
+
+/**
+ * Player program structure.
+ */
+struct vlc_player_program
+{
+    /** Id used for vlc_player_SelectProgram() */
+    int group_id;
+    /** Program name, always valid */
+    const char *name;
+    /** True if the program is selected */
+    bool selected;
+    /** True if the program is scrambled */
+    bool scrambled;
+};
+
+/**
+ * Player track structure.
+ */
+struct vlc_player_track
+{
+    /** Id used for any player actions, like vlc_player_SelectTrack() */
+    vlc_es_id_t *es_id;
+    /** Track name, always valid */
+    const char *name;
+    /** Es format */
+    es_format_t fmt;
+    /** True if the track is selected */
+    bool selected;
+};
+
+/**
+ * Player chapter structure
+ */
+struct vlc_player_chapter
+{
+    /** Chapter name, always valid */
+    const char *name;
+    /** Position of this chapter */
+    vlc_tick_t time;
+};
+
+/** vlc_player_title.flags: The title is a menu. */
+#define VLC_PLAYER_TITLE_MENU         0x01
+/** vlc_player_title.flags: The title is interactive. */
+#define VLC_PLAYER_TITLE_INTERACTIVE  0x02
+
+/** Player title structure */
+struct vlc_player_title
+{
+    /** Title name, always valid */
+    const char *name;
+    /** Length of the title */
+    vlc_tick_t length;
+    /** Bit flag of @ref VLC_PLAYER_TITLE_MENU and @ref
+     * VLC_PLAYER_TITLE_INTERACTIVE */
+    unsigned flags;
+    /** Number of chapters, can be 0 */
+    size_t chapter_count;
+    /** Array of chapters, can be NULL */
+    const struct vlc_player_chapter *chapters;
+};
+
+/**
+ * Opaque structure representing a list of @ref vlc_player_title.
+ *
+ * @see vlc_player_GetTitleList()
+ * @see vlc_player_title_list_GetCount()
+ * @see vlc_player_title_list_GetAt()
+ */
+typedef struct vlc_player_title_list vlc_player_title_list;
+
+/**
+ * Menu (VCD/DVD/BD) and viewpoint navigations
+ *
+ * @see vlc_player_Navigate()
+ */
+enum vlc_player_nav
+{
+    /** Activate the navigation item selected */
+    VLC_PLAYER_NAV_ACTIVATE,
+    /** Select a navigation item above or move the viewpoint up */
+    VLC_PLAYER_NAV_UP,
+    /** Select a navigation item under or move the viewpoint down */
+    VLC_PLAYER_NAV_DOWN,
+    /** Select a navigation item on the left or move the viewpoint left */
+    VLC_PLAYER_NAV_LEFT,
+    /** Select a navigation item on the right or move the viewpoint right */
+    VLC_PLAYER_NAV_RIGHT,
+    /** Activate the popup Menu (for BD) */
+    VLC_PLAYER_NAV_POPUP,
+    /** Activate disc Root Menu */
+    VLC_PLAYER_NAV_MENU,
+};
+
+/**
+ * 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()), and
+ * from any thread (even the current one).
+ */
+struct vlc_player_media_provider
+{
+    /**
+     * Called when the player requires a new media.
+     *
+     * @note The returned media must be already held with input_item_Hold()
+     *
+     * @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)(vlc_player_t *player, void *data);
+};
+
+/**
+ * Action of vlc_player_cbs.on_track_list_changed,
+ * vlc_player_cbs.on_program_list_changed, and
+ * vlc_player_cbs.on_vout_list_changed callbacks
+ */
+enum vlc_player_list_action
+{
+    VLC_PLAYER_LIST_ADDED,
+    VLC_PLAYER_LIST_REMOVED,
+    VLC_PLAYER_LIST_UPDATED,
+};
+
+/**
+ * State of the player
+ *
+ * During a normal playback (no errors), the user is expected to receive all
+ * events in the following order: STARTED, PLAYING, STOPPING, STOPPED.
+ *
+ * @note When playing more than one media in a row, the player stay at the
+ * PLAYING state when doing the transition from the current media to the next
+ * media (that can be gapless). This means that STOPPING, STOPPED states (for
+ * the current media) and STARTED, PLAYING states (for the next one) won't be
+ * sent. Nevertheless, the vlc_player_cbs.on_current_media_changed callback
+ * will be called during this transition.
+ */
+enum vlc_player_state
+{
+    /**
+     * The player is stopped
+     *
+     * Initial state, or triggered by an internal transition from the STOPPING
+     * state.
+     */
+    VLC_PLAYER_STATE_STOPPED,
+
+    /**
+     * The player is started
+     *
+     * Triggered by vlc_player_Start()
+     */
+    VLC_PLAYER_STATE_STARTED,
+
+    /**
+     * The player is playing
+     *
+     * Triggered by vlc_player_Resume() or by an internal transition from the
+     * STARTED state.
+     */
+    VLC_PLAYER_STATE_PLAYING,
+
+    /**
+     * The player is paused
+     *
+     * Triggered by vlc_player_Pause().
+     */
+    VLC_PLAYER_STATE_PAUSED,
+
+    /**
+     * The player is stopping
+     *
+     * Triggered by vlc_player_Stop(), vlc_player_SetCurrentMedia() or by an
+     * internal transition (when the input reach the end of file for example).
+     */
+    VLC_PLAYER_STATE_STOPPING,
+};
+
+/**
+ * Error of the player
+ *
+ * @see vlc_player_GetError()
+ */
+enum vlc_player_error
+{
+    VLC_PLAYER_ERROR_NONE,
+    VLC_PLAYER_ERROR_GENERIC,
+};
+
+/**
+ * Seek speed type
+ *
+ * @see vlc_player_SeekByPos()
+ * @see vlc_player_SeekByTime()
+ */
+enum vlc_player_seek_speed
+{
+    /** Do a precise seek */
+    VLC_PLAYER_SEEK_PRECISE,
+    /** Do a fast seek */
+    VLC_PLAYER_SEEK_FAST,
+};
+
+/**
+ * Player seek/delay directive
+ *
+ * @see vlc_player_SeekByPos()
+ * @see vlc_player_SeekByTime()
+ * @see vlc_player_SetAudioDelay()
+ * @see vlc_player_SetSubtitleDelay()
+ */
+enum vlc_player_whence
+{
+    /** Given time/position */
+    VLC_PLAYER_WHENCE_ABSOLUTE,
+    /** The current position +/- the given time/position */
+    VLC_PLAYER_WHENCE_RELATIVE,
+};
+
+/**
+ * Action when the player is stopped
+ *
+ * @see vlc_player_SetMediaStoppedAction()
+ */
+enum vlc_player_media_stopped_action {
+    /** Continue (or stop if there is no next media), default behavior */
+    VLC_PLAYER_MEDIA_STOPPED_CONTINUE,
+    /** Pause when reaching the end of file */
+    VLC_PLAYER_MEDIA_STOPPED_PAUSE,
+    /** Stop, even if there is a next media to play */
+    VLC_PLAYER_MEDIA_STOPPED_STOP,
+    /** Exit VLC */
+    VLC_PLAYER_MEDIA_STOPPED_EXIT,
+};
+
+/**
+ * A to B loop state
+ */
+enum vlc_player_abloop
+{
+    VLC_PLAYER_ABLOOP_NONE,
+    VLC_PLAYER_ABLOOP_A,
+    VLC_PLAYER_ABLOOP_B,
+};
+
+/** Player capability: can seek */
+#define VLC_PLAYER_CAP_SEEK (1<<0)
+/** Player capability: can pause */
+#define VLC_PLAYER_CAP_PAUSE (1<<1)
+/** Player capability: can change the rate */
+#define VLC_PLAYER_CAP_CHANGE_RATE (1<<2)
+/** Player capability: can seek back */
+#define VLC_PLAYER_CAP_REWIND (1<<3)
+
+/** Player teletext key: Red */
+#define VLC_PLAYER_TELETEXT_KEY_RED ('r' << 16)
+/** Player teletext key: Green */
+#define VLC_PLAYER_TELETEXT_KEY_GREEN ('g' << 16)
+/** Player teletext key: Yellow */
+#define VLC_PLAYER_TELETEXT_KEY_YELLOW ('g' << 16)
+/** Player teletext key: Blue */
+#define VLC_PLAYER_TELETEXT_KEY_BLUE ('b' << 16)
+/** Player teletext key: Index */
+#define VLC_PLAYER_TELETEXT_KEY_INDEX ('i' << 16)
+
+/**
+ * Player callbacks
+ *
+ * Can be registered with vlc_player_AddListener().
+ *
+ * All callbacks are called with the player locked (cf. vlc_player_Lock()) and
+ * from any threads (and even synchronously from a vlc_player function in some
+ * cases). It is safe to call any vlc_player functions from these callbacks
+ * except vlc_player_Delete().
+ *
+ * @warning To avoid deadlocks, users should never call vlc_player functions
+ * with an external mutex locked and lock this same mutex from a player
+ * callback.
+ */
+struct vlc_player_cbs
+{
+    /**
+     * Called when the current media has changed
+     *
+     * @note This can be called from the PLAYING state (when the player plays
+     * the next media internally) or from the STOPPED state (from
+     * vlc_player_SetCurrentMedia() or from an internal transition).
+     *
+     * @see vlc_player_SetCurrentMedia()
+     * @see vlc_player_InvalidateNextMedia()
+     *
+     * @param player locked player instance
+     * @param new_media new media currently played or NULL (when there is no
+     * more media to play)
+     * @param data opaque pointer set by vlc_player_AddListener()
+     */
+    void (*on_current_media_changed)(vlc_player_t *player,
+        input_item_t *new_media, void *data);
+
+    /**
+     * Called when the player state has changed
+     *
+     * @see vlc_player_state
+     *
+     * @param player locked player instance
+     * @param new_state new player state
+     * @param data opaque pointer set by vlc_player_AddListener()
+     */
+    void (*on_state_changed)(vlc_player_t *player,
+        enum vlc_player_state new_state, void *data);
+
+    /**
+     * Called when a media triggered an error
+     *
+     * Can be called from any states. When it happens the player will stop
+     * itself. It is safe to play an other media or event restart the player
+     * (This will reset the error state).
+     *
+     * @param player locked player instance
+     * @param error player error
+     * @param data opaque pointer set by vlc_player_AddListener()
+     */
+    void (*on_error_changed)(vlc_player_t *player,
+        enum vlc_player_error error, void *data);
+
+    /**
+     * Called when the player buffering (or cache) has changed
+     *
+     * This event is always called with the 0 and 1 values before a playback
+     * (in case of success).  Values in between depends of the media type.
+     *
+     * @param player locked player instance
+     * @param new_buffering buffering in the range [0:1]
+     * @param data opaque pointer set by vlc_player_AddListener()
+     */
+    void (*on_buffering_changed)(vlc_player_t *player,
+        float new_buffering, void *data);
+
+    /**
+     * Called when the player rate has changed
+     *
+     * Triggered by vlc_player_ChangeRate(), not sent when the media starts
+     * with the default rate (1.f)
+     *
+     * @note The rate is saved across several medias.
+     *
+     * @param player locked player instance
+     * @param new_rate player
+     * @param data opaque pointer set by vlc_player_AddListener()
+     */
+    void (*on_rate_changed)(vlc_player_t *player,
+        float new_rate, void *data);
+
+    /**
+     * Called when the media capabilities has changed
+     *
+     * Always called when the media is opening. Can be called during playback.
+     *
+     * @param player locked player instance
+     * @param new_caps player capabilities
+     * @param data opaque pointer set by vlc_player_AddListener()
+     */
+    void (*on_capabilities_changed)(vlc_player_t *player,
+        int new_caps, void *data);
+
+    /**
+     * Called when the player position has changed
+     *
+     * @note A started and playing media doesn't have necessarily a valid time.
+     *
+     * @param player locked player instance
+     * @param new_time a valid time or VLC_TICK_INVALID
+     * @param new_pos a valid position
+     * @param data opaque pointer set by vlc_player_AddListener()
+     */
+    void (*on_position_changed)(vlc_player_t *player,
+        vlc_tick_t new_time, float new_pos, void *data);
+
+    /**
+     * Called when the media length has changed
+     *
+     * May be called when the media is opening or during playback.
+     *
+     * @note A started and playing media doesn't have necessarily a valid length.
+     *
+     * @param player locked player instance
+     * @param new_length a valid time or VLC_TICK_INVALID
+     * @param data opaque pointer set by vlc_player_AddListener()
+     */
+    void (*on_length_changed)(vlc_player_t *player,
+        vlc_tick_t new_length, void *data);
+
+    /**
+     * Called when a track is added, removed, or updated
+     *
+     * @note The track is only valid from this callback context. Users should
+     * duplicate this track via vlc_player_track_Dup() if they want to pass it
+     * to an other thread.
+     *
+     * @param player locked player instance
+     * @param action added, removed or updated
+     * @param track valid track
+     * @param data opaque pointer set by vlc_player_AddListener()
+     */
+    void (*on_track_list_changed)(vlc_player_t *player,
+        enum vlc_player_list_action action,
+        const struct vlc_player_track *track, void *data);
+
+    /**
+     * Called when a new track is selected and/or unselected
+     *
+     * @param player locked player instance
+     * @param unselected_id valid track id or NULL (when nothing is unselected)
+     * @param selected_id valid track id or NULL (when nothing is selected)
+     * @param data opaque pointer set by vlc_player_AddListener()
+     */
+    void (*on_track_selection_changed)(vlc_player_t *player,
+        vlc_es_id_t *unselected_id, vlc_es_id_t *selected_id, void *data);
+
+    /**
+     * Called when a new program is added, removed or updated
+     *
+     * @note The program is only valid from this callback context. Users should
+     * duplicate this program via vlc_player_program_Dup() if they want to pass
+     * it to an other thread.
+     *
+     * @param player locked player instance
+     * @param action added, removed or updated
+     * @param prgm valid program
+     * @param data opaque pointer set by vlc_player_AddListener()
+     */
+    void (*on_program_list_changed)(vlc_player_t *player,
+        enum vlc_player_list_action action,
+        const struct vlc_player_program *prgm, void *data);
+
+    /**
+     * Called when a new program is selected and/or unselected
+     *
+     * @param player locked player instance
+     * @param unselected_id valid program id or NULL (when nothing is unselected)
+     * @param selected_id valid program id or NULL (when nothing is selected)
+     * @param data opaque pointer set by vlc_player_AddListener()
+     */
+    void (*on_program_selection_changed)(vlc_player_t *player,
+        int unselected_id, int selected_id, void *data);
+
+    /**
+     * Called when the media titles has changed
+     *
+     * This event is not called when the opening media doesn't have any titles.
+     * This title list and all its elements are constant. If an element is to
+     * be updated, a new list will be sent from this callback.
+     *
+     * @note Users should hold this list with vlc_player_title_list_Hold() if
+     * they want to pass it to an other thread.
+     *
+     * @param player locked player instance
+     * @param titles valid title list or NULL
+     * @param data opaque pointer set by vlc_player_AddListener()
+     */
+    void (*on_titles_changed)(vlc_player_t *player,
+        vlc_player_title_list *titles, void *data);
+
+    /**
+     * Called when a new title is selected
+     *
+     * There are no events when a title is unselected. Titles are automatically
+     * unselected when the title list changes. Titles and indexes are always
+     * valid inside the vlc_player_title_list sent by
+     * vlc_player_cbs.on_titles_changed.
+     *
+     * @param player locked player instance
+     * @param new_title new selected title
+     * @param new_idx index of this title
+     * @param data opaque pointer set by vlc_player_AddListener()
+     */
+    void (*on_title_selection_changed)(vlc_player_t *player,
+        const struct vlc_player_title *new_title, size_t new_idx, void *data);
+
+    /**
+     * Called when a new chapter is selected
+     *
+     * There are no events when a chapter is unselected. Chapters are
+     * automatically unselected when the title list changes. Titles, chapters
+     * and indexes are always valid inside the vlc_player_title_list sent by
+     * vlc_player_cbs.on_titles_changed.
+     *
+     * @param player locked player instance
+     * @param title selected title
+     * @param title_idx selected title index
+     * @param chapter new selected chapter
+     * @param chapter_idx new selected chapter index
+     * @param data opaque pointer set by vlc_player_AddListener()
+     */
+    void (*on_chapter_selection_changed)(vlc_player_t *player,
+        const struct vlc_player_title *title, size_t title_idx,
+        const struct vlc_player_chapter *new_chapter, size_t new_chapter_idx,
+        void *data);
+
+    /**
+     * Called when the media has a teletext menu
+     *
+     * @param player locked player instance
+     * @param has_teletext_menu true if the media has a teletext menu
+     * @param data opaque pointer set by vlc_player_AddListener()
+     */
+    void (*on_teletext_menu_changed)(vlc_player_t *player,
+        bool has_teletext_menu, void *data);
+
+    /**
+     * Called when teletext is enabled or disabled
+     *
+     * @see vlc_player_SetTeletextEnabled()
+     *
+     * @param player locked player instance
+     * @param enabled true if teletext is enabled
+     * @param data opaque pointer set by vlc_player_AddListener()
+     */
+    void (*on_teletext_enabled_changed)(vlc_player_t *player,
+        bool enabled, void *data);
+
+    /**
+     * Called when the teletext page has changed
+     *
+     * @see vlc_player_SelectTeletextPage()
+     *
+     * @param player locked player instance
+     * @param new_page page in the range ]0;888]
+     * @param data opaque pointer set by vlc_player_AddListener()
+     */
+    void (*on_teletext_page_changed)(vlc_player_t *player,
+        unsigned new_page, void *data);
+
+    /**
+     * Called when the teletext transparency has changed
+     *
+     * @see vlc_player_SetTeletextTransparency()
+     *
+     * @param player locked player instance
+     * @param enabled true is the teletext overlay is transparent
+     * @param data opaque pointer set by vlc_player_AddListener()
+     */
+    void (*on_teletext_transparency_changed)(vlc_player_t *player,
+        bool enabled, void *data);
+
+    /**
+     * Called when the player audio delay has changed
+     *
+     * @see vlc_player_SetAudioDelay()
+     *
+     * @param player locked player instance
+     * @param new_delay audio delay
+     * @param data opaque pointer set by vlc_player_AddListener()
+     */
+    void (*on_audio_delay_changed)(vlc_player_t *player,
+        vlc_tick_t new_delay, void *data);
+
+    /**
+     * Called when the player subtitle delay has changed
+     *
+     * @see vlc_player_SetSubtitleDelay()
+     *
+     * @param player locked player instance
+     * @param new_delay subtitle delay
+     * @param data opaque pointer set by vlc_player_AddListener()
+     */
+    void (*on_subtitle_delay_changed)(vlc_player_t *player,
+        vlc_tick_t new_delay, void *data);
+
+    /**
+     * Called when associated subtitle has changed
+     *
+     * @see vlc_player_SetAssociatedSubsFPS()
+     *
+     * @param player locked player instance
+     * @param sub_fps subtitle fps
+     * @param data opaque pointer set by vlc_player_AddListener()
+     */
+    void (*on_associated_subs_fps_changed)(vlc_player_t *player,
+        float subs_fps, void *data);
+
+    /**
+     * Called when a new renderer item is set
+     *
+     * @see vlc_player_SetRenderer()
+     *
+     * @param player locked player instance
+     * @param new_item a valid renderer item or NULL (if unset)
+     * @param data opaque pointer set by vlc_player_AddListener()
+     */
+    void (*on_renderer_changed)(vlc_player_t *player,
+        vlc_renderer_item_t *new_item, void *data);
+
+    /**
+     * Called when the player recording state has changed
+     *
+     * @see vlc_player_SetRecordingEnabled()
+     *
+     * @param player locked player instance
+     * @param recording true if recording is enabled
+     * @param data opaque pointer set by vlc_player_AddListener()
+     */
+    void (*on_recording_changed)(vlc_player_t *player,
+        bool recording, void *data);
+
+    /**
+     * Called when the media signal has changed
+     *
+     * @param player locked player instance
+     * @param new_quality signal quality
+     * @param new_strength signal strength,
+     * @param data opaque pointer set by vlc_player_AddListener()
+     */
+    void (*on_signal_changed)(vlc_player_t *player,
+        float quality, float strength, void *data);
+
+    /**
+     * Called when the player has new statisics
+     *
+     * @note The stats structure is only valid from this callback context. It
+     * can be copied in order to pass it to an other thread.
+     *
+     * @param player locked player instance
+     * @param stats valid stats, only valid from this context
+     * @param data opaque pointer set by vlc_player_AddListener()
+     */
+    void (*on_statistics_changed)(vlc_player_t *player,
+        const struct input_stats_t *stats, void *data);
+
+    /**
+     * Called when the A to B loop has changed
+     *
+     * @see vlc_player_SetAtoBLoop()
+     *
+     * @param player locked player instance
+     * @param state A, when only A is set, B when both A and B are set, None by
+     * default
+     * @param time valid time or VLC_TICK_INVALID of the current state
+     * @param pos valid pos of the current state
+     * @param data opaque pointer set by vlc_player_AddListener()
+     */
+    void (*on_atobloop_changed)(vlc_player_t *player,
+        enum vlc_player_abloop new_state, vlc_tick_t time, float pos,
+        void *data);
+
+    /**
+     * Called when media stopped action has changed
+     *
+     * @see vlc_player_SetMediaStoppedAction()
+     *
+     * @param player locked player instance
+     * @param new_action action to execute when a media is stopped
+     * @param data opaque pointer set by vlc_player_AddListener()
+     */
+    void (*on_media_stopped_action_changed)(vlc_player_t *player,
+        enum vlc_player_media_stopped_action new_action, void *data);
+
+    /**
+     * Called when the media meta has changed
+     *
+     * @param player locked player instance
+     * @param media valid media
+     * @param data opaque pointer set by vlc_player_AddListener()
+     */
+    void (*on_media_meta_changed)(vlc_player_t *player,
+        input_item_t *media, void *data);
+
+    /**
+     * Called when media epg has changed
+     *
+     * @param player locked player instance
+     * @param media valid media
+     * @param data opaque pointer set by vlc_player_AddListener()
+     */
+    void (*on_media_epg_changed)(vlc_player_t *player,
+        input_item_t *media, void *data);
+
+    /**
+     * Called when the media has new subitems
+     *
+     * @param player locked player instance
+     * @param media valid media
+     * @param new_subitems node representing all media subitems
+     * @param data opaque pointer set by vlc_player_AddListener()
+     */
+    void (*on_media_subitems_changed)(vlc_player_t *player,
+        input_item_t *media, input_item_node_t *new_subitems, void *data);
+
+    /**
+     * Called when a new vout is added or removed
+     *
+     * @param player locked player instance
+     * @param action added or removed
+     * @param vout vout this added or removed
+     * @param data opaque pointer set by vlc_player_AddListener()
+     */
+    void (*on_vout_list_changed)(vlc_player_t *player,
+        enum vlc_player_list_action action, vout_thread_t *vout, void *data);
+};
+
+/**
+ * Player vout callbacks
+ *
+ * Can be registered with vlc_player_vout_AddListener().
+ *
+ * These callbacks are *not* called with the player locked. It is safe to lock
+ * the player and call any vlc_player functions from these callbacks.
+ *
+ * @note The state changed from the callbacks can be either applied on the
+ * player (and all future video outputs), or on a specified video output. The
+ * state is applied on the player when the vout argument is NULL.
+ *
+ * @warning To avoid deadlocks, users should never call vout_thread_t functions
+ * from these callbacks.
+ */
+struct vlc_player_vout_cbs
+{
+    /**
+     * Called when the player and/or vout fullscreen state has changed
+     *
+     * @see vlc_player_vout_SetFullscreen()
+     *
+     * @param player unlocked player instance
+     * @param vout cf. vlc_player_vout_cbs note
+     * @param enabled true when fullscreen is enabled
+     * @param data opaque pointer set by vlc_player_vout_AddListener()
+     */
+    void (*on_fullscreen_changed)(vlc_player_t *player,
+        vout_thread_t *vout, bool enabled, void *data);
+
+    /**
+     * Called when the player and/or vout wallpaper mode has changed
+     *
+     * @see vlc_player_vout_SetWallpaperModeEnabled()
+     *
+     * @param player unlocked player instance
+     * @param vout cf. vlc_player_vout_cbs note
+     * @param enabled true when wallpaper mode is enabled
+     * @param data opaque pointer set by vlc_player_vout_AddListener()
+     */
+    void (*on_wallpaper_mode_changed)(vlc_player_t *player,
+        vout_thread_t *vout, bool enabled, void *data);
+};
+
+/**
+ * Player aout callbacks
+ *
+ * Can be registered with vlc_player_aout_AddListener().
+ *
+ * These callbacks are *not* called with the player locked. It is safe to lock
+ * the player and call any vlc_player functions from these callbacks.
+ *
+ * @warning To avoid deadlocks, users should never call audio_output_t
+ * functions from these callbacks.
+ */
+struct vlc_player_aout_cbs
+{
+    /**
+     * Called when the volume has changed
+     *
+     * @see vlc_player_aout_SetVolume()
+     *
+     * @param player unlocked player instance
+     * @param new_volume volume in the range [0;8.f]
+     * @param data opaque pointer set by vlc_player_vout_AddListener()
+     */
+    void (*on_volume_changed)(vlc_player_t *player,
+        float new_volume, void *data);
+
+    /**
+     * Called when the mute state has changed
+     *
+     * @see vlc_player_aout_Mute()
+     *
+     * @param player unlocked player instance
+     * @param new_mute true if muted
+     * @param data opaque pointer set by vlc_player_vout_AddListener()
+     */
+    void (*on_mute_changed)(vlc_player_t *player,
+        bool new_muted, void *data);
+};
+
+/**
+ * Duplicate a track
+ *
+ * This function can be used to pass a track from a callback to an other
+ * thread, the es_id will be held by the duplicated track.
+ *
+ * @see vlc_player_cbs.on_track_list_changed
+ *
+ * @return a duplicated track or NULL on allocation error
+ */
+VLC_API struct vlc_player_track *
+vlc_player_track_Dup(const struct vlc_player_track *track);
+
+/**
+ * Delete a duplicated track
+ */
+VLC_API void
+vlc_player_track_Delete(struct vlc_player_track *track);
+
+/**
+ * Duplicate a program
+ *
+ * This function can be used to pass a program from a callback to an other
+ * thread.
+ *
+ * @see vlc_player_cbs.on_program_list_changed
+ *
+ * @return a duplicated program or NULL on allocation error
+ */
+VLC_API struct vlc_player_program *
+vlc_player_program_Dup(const struct vlc_player_program *prgm);
+
+/**
+ * Delete a duplicated program
+ */
+VLC_API void
+vlc_player_program_Delete(struct vlc_player_program *prgm);
+
+/**
+ * Hold the title list of the player
+ *
+ * This function can be used to pass this title list from a callback to an
+ * other thread.
+ *
+ * @see vlc_player_cbs.on_titles_changed
+ *
+ * @return the same instance
+ */
+VLC_API vlc_player_title_list *
+vlc_player_title_list_Hold(vlc_player_title_list *titles);
+
+/**
+ * Release of previously held title list
+ */
+VLC_API void
+vlc_player_title_list_Release(vlc_player_title_list *titles);
+
+/**
+ * Get the number of title of a list
+ */
+VLC_API size_t
+vlc_player_title_list_GetCount(vlc_player_title_list *titles);
+
+/**
+ * Get the title at a given index
+ *
+ * @param idx index in the range [0; count[
+ * @return a valid title (can't be NULL)
+ */
+VLC_API const struct vlc_player_title *
+vlc_player_title_list_GetAt(vlc_player_title_list *titles, size_t idx);
+
+/**
+ * Create a new player instance
+ *
+ * @param parent parent VLC object
+ * @param media_provider pointer to a media_provider structure or NULL, the
+ * structure must be valid during the lifetime of the player
+ * @param media_provider_data opaque data used by provider 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_media_provider *media_provider,
+               void *media_provider_data);
+
+/**
+ * Delete a player instance
+ *
+ * This function stop any playback previously started and wait for their
+ * termination.
+ *
+ * @warning Blocking function if the player state is not STOPPED, don't call it
+ * from an UI thread in that case.
+ *
+ * @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);
+
+/**
+ * Wait on a condition variable
+ *
+ * This call allow users to use their own condition with the player mutex.
+ *
+ * @param player locked player instance
+ * @param cond external condition
+ */
+VLC_API void
+vlc_player_CondWait(vlc_player_t *player, vlc_cond_t *cond);
+
+/**
+ * Add a listener callback
+ *
+ * @note Every registered callbacks need to be removed by the caller with
+ * vlc_player_RemoveListener().
+ *
+ * @param player locked player instance
+ * @param cbs pointer to a vlc_player_cbs structure, the structure must be
+ * valid during the lifetime of the player
+ * @param cbs_data opaque pointer used by the callbacks
+ * @return a valid listener id, or NULL in case of allocation error
+ */
+VLC_API 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 listener_id listener id returned by vlc_player_AddListener()
+ */
+VLC_API void
+vlc_player_RemoveListener(vlc_player_t *player,
+                          vlc_player_listener_id *listener_id);
+
+/**
+ * Set the current media
+ *
+ * This function replaces the current and next medias.
+ *
+ * @note A successful call will always result of
+ * vlc_player_cbs.on_current_media_changed being called. This function is not
+ * blocking. If a media is currently being played, this media will be stopped
+ * and the requested media will be set after.
+ *
+ * @warning This function is either synchronous (if the player state is
+ * STOPPED) or asynchronous. In the later case, vlc_player_GetCurrentMedia()
+ * will return the old media, even after this call, and until the
+ * vlc_player_cbs.on_current_media_changed is called.
+ *
+ * @param player locked player instance
+ * @param media new media to play
+ * @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.
+ *
+ * @see vlc_player_cbs.on_current_media_changed
+ *
+ * @param player locked player instance
+ * @return a valid media or NULL (if no media is set)
+ */
+VLC_API input_item_t *
+vlc_player_GetCurrentMedia(vlc_player_t *player);
+
+/**
+ * Helper that hold the current media
+ */
+static inline input_item_t *
+vlc_player_HoldCurrentMedia(vlc_player_t *player)
+{
+    input_item_t *item = vlc_player_GetCurrentMedia(player);
+    return item ? input_item_Hold(item) : NULL;
+}
+
+/**
+ * Invalidate the next media.
+ *
+ * This function can be used to invalidate the media returned by the
+ * vlc_player_media_provider.get_next 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_media_provider.get_next callback to be called again.
+ *
+ * @param player locked player instance
+ */
+VLC_API void
+vlc_player_InvalidateNextMedia(vlc_player_t *player);
+
+/**
+ * Ask to started in a paused state
+ *
+ * This function can be used before vlc_player_Start()
+ *
+ * @param player locked player instance
+ * @param start_paused true to start in a paused state
+ */
+VLC_API void
+vlc_player_SetStartPaused(vlc_player_t *player, bool start_paused);
+
+/**
+ * Setup an action when a media is stopped
+ *
+ * @param player locked player instance
+ * @param action action to do when a media is stopped
+ */
+VLC_API void
+vlc_player_SetMediaStoppedAction(vlc_player_t *player,
+                                 enum vlc_player_media_stopped_action action);
+
+/**
+ * Start the playback of the 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
+ *
+ * @note This function is asynchronous. Users should wait on
+ * STOPPED state event to know when the stop is finished.
+ *
+ * @param player locked player instance
+ */
+VLC_API void
+vlc_player_Stop(vlc_player_t *player);
+
+/**
+ * Pause the playback
+ *
+ * @param player locked player instance
+ */
+VLC_API void
+vlc_player_Pause(vlc_player_t *player);
+
+/**
+ * Resume the playback from a pause
+ *
+ * @param player locked player instance
+ */
+VLC_API void
+vlc_player_Resume(vlc_player_t *player);
+
+/**
+ * Pause and display the next video frame
+ *
+ * @param player locked player instance
+ */
+VLC_API void
+vlc_player_NextVideoFrame(vlc_player_t *player);
+
+/**
+ * Get the state of the player
+ *
+ * @note Since all players actions are asynchronous, this function won't
+ * reflect the new state immediately. Wait for the
+ * vlc_players_cbs.on_state_changed event to be notified.
+ *
+ * @see vlc_player_state
+ * @see vlc_player_cbs.on_state_changed
+ *
+ * @param player locked player instance
+ * @return the current player state
+ */
+VLC_API enum vlc_player_state
+vlc_player_GetState(vlc_player_t *player);
+
+/**
+ * Get the error state of the player
+ *
+ * @see vlc_player_cbs.on_capabilities_changed
+ *
+ * @param player locked player instance
+ * @return the current error state
+ */
+VLC_API enum vlc_player_error
+vlc_player_GetError(vlc_player_t *player);
+
+/**
+ * Helper to get the started state
+ */
+static inline bool
+vlc_player_IsStarted(vlc_player_t *player)
+{
+    switch (vlc_player_GetState(player))
+    {
+        case VLC_PLAYER_STATE_STARTED:
+        case VLC_PLAYER_STATE_PLAYING:
+        case VLC_PLAYER_STATE_PAUSED:
+            return true;
+        default:
+            return false;
+    }
+}
+
+/**
+ * Helper to get the paused state
+ */
+static inline bool
+vlc_player_IsPaused(vlc_player_t *player)
+{
+    return vlc_player_GetState(player) == VLC_PLAYER_STATE_PAUSED;
+}
+
+/**
+ * Helper to toggle the pause state
+ */
+static inline void
+vlc_player_TogglePause(vlc_player_t *player)
+{
+    if (vlc_player_IsStarted(player))
+    {
+        if (vlc_player_IsPaused(player))
+            vlc_player_Resume(player);
+        else
+            vlc_player_Pause(player);
+    }
+}
+
+/**
+ * Get the player capabilities
+ *
+ * @see vlc_player_cbs.on_capabilities_changed
+ *
+ * @param player locked player instance
+ * @return the player capabilities, a bitwise mask of @ref VLC_PLAYER_CAP_SEEK,
+ * @ref VLC_PLAYER_CAP_PAUSE, @ref VLC_PLAYER_CAP_CHANGE_RATE, @ref
+ * VLC_PLAYER_CAP_REWIND
+ */
+VLC_API int
+vlc_player_GetCapabilities(vlc_player_t *player);
+
+/**
+ * Helper to get the seek capability
+ */
+static inline bool
+vlc_player_CanSeek(vlc_player_t *player)
+{
+    return vlc_player_GetCapabilities(player) & VLC_PLAYER_CAP_SEEK;
+}
+
+/**
+ * Helper to get the pause capability
+ */
+static inline bool
+vlc_player_CanPause(vlc_player_t *player)
+{
+    return vlc_player_GetCapabilities(player) & VLC_PLAYER_CAP_PAUSE;
+}
+
+/**
+ * Helper to get the change-rate capability
+ */
+static inline bool
+vlc_player_CanChangeRate(vlc_player_t *player)
+{
+    return vlc_player_GetCapabilities(player) & VLC_PLAYER_CAP_CHANGE_RATE;
+}
+
+/**
+ * Helper to get the rewindable capability
+ */
+static inline bool
+vlc_player_CanRewind(vlc_player_t *player)
+{
+    return vlc_player_GetCapabilities(player) & VLC_PLAYER_CAP_REWIND;
+}
+
+/**
+ * Get the rate of the player
+ *
+ * @see vlc_player_cbs.on_rate_changed
+ *
+ * @param player locked player instance
+ * @return rate of the player (< 1.f is slower, > 1.f is faster)
+ */
+VLC_API float
+vlc_player_GetRate(vlc_player_t *player);
+
+/**
+ * Change the rate of the player
+ *
+ * @note The rate is saved across several medias
+ *
+ * @param player locked player instance
+ * @param rate new player (< 1.f is slower, > 1.f is faster)
+ */
+VLC_API void
+vlc_player_ChangeRate(vlc_player_t *player, float rate);
+
+/**
+ * Increment the rate of the player (faster)
+ *
+ * @param player locked player instance
+ */
+VLC_API void
+vlc_player_IncrementRate(vlc_player_t *player);
+
+/**
+ * Decrement the rate of the player (Slower)
+ *
+ * @param player locked player instance
+ */
+VLC_API void
+vlc_player_DecrementRate(vlc_player_t *player);
+
+/**
+ * Get the length of the current media
+ *
+ * @note A started and playing media doesn't have necessarily a valid length.
+ *
+ * @see vlc_player_cbs.on_length_changed
+ *
+ * @param player locked player instance
+ * @return a valid length or VLC_TICK_INVALID (if no media is set,
+ * playback is not yet started or in case of error)
+ */
+VLC_API vlc_tick_t
+vlc_player_GetLength(vlc_player_t *player);
+
+/**
+ * Get the time of the current media
+ *
+ * @note A started and playing media doesn't have necessarily a valid time.
+ *
+ * @see vlc_player_cbs.vlc_player_cbs.on_position_changed
+ *
+ * @param player locked player instance
+ * @return a valid time or VLC_TICK_INVALID (if no media is set, the media
+ * doesn't have any time, if playback is not yet started or in case of error)
+ */
+VLC_API vlc_tick_t
+vlc_player_GetTime(vlc_player_t *player);
+
+/**
+ * Get the position of the current media
+ *
+ * @see vlc_player_cbs.on_position_changed
+ *
+ * @param player locked player instance
+ * @return a valid position in the range [0.f;1.f] or -1.f (if no media is
+ * set,if playback is not yet started or in case of error)
+ */
+VLC_API float
+vlc_player_GetPosition(vlc_player_t *player);
+
+/**
+ * Seek the current media by position
+ *
+ * @note This function can be called before vlc_player_Start() in order to set
+ * a starting position.
+ *
+ * @param player locked player instance
+ * @param position position in the range [0.f;1.f]
+ * @param speed precise of fast
+ * @param whence absolute or relative
+ */
+VLC_API void
+vlc_player_SeekByPos(vlc_player_t *player, float position,
+                     enum vlc_player_seek_speed speed,
+                     enum vlc_player_whence whence);
+
+/**
+ * Seek the current media by time
+ *
+ * @note This function can be called before vlc_player_Start() in order to set
+ * a starting position.
+ *
+ * @warning This function has an effect only if the media has a valid length.
+ *
+ * @param player locked player instance
+ * @param time a time in the range [0;length]
+ * @param speed precise of fast
+ * @param whence absolute or relative
+ */
+VLC_API void
+vlc_player_SeekByTime(vlc_player_t *player, vlc_tick_t time,
+                      enum vlc_player_seek_speed speed,
+                      enum vlc_player_whence whence);
+
+/**
+ * Helper to set the absolute position precisely
+ */
+static inline void
+vlc_player_SetPosition(vlc_player_t *player, float position)
+{
+    vlc_player_SeekByPos(player, position, VLC_PLAYER_SEEK_PRECISE,
+                         VLC_PLAYER_WHENCE_ABSOLUTE);
+}
+
+/**
+ * Helper to set the absolute position fast
+ */
+static inline void
+vlc_player_SetPositionFast(vlc_player_t *player, float position)
+{
+    vlc_player_SeekByPos(player, position, VLC_PLAYER_SEEK_FAST,
+                         VLC_PLAYER_WHENCE_ABSOLUTE);
+}
+
+/**
+ * Helper to jump the position precisely
+ */
+static inline void
+vlc_player_JumpPos(vlc_player_t *player, float jumppos)
+{
+    /* No fask seek for jumps. Indeed, jumps can seek to the current position
+     * if not precise enough or if the jump value is too small. */
+    vlc_player_SeekByPos(player, jumppos, VLC_PLAYER_SEEK_PRECISE,
+                         VLC_PLAYER_WHENCE_RELATIVE);
+}
+
+/**
+ * Helper to set the absolute time precisely
+ */
+static inline void
+vlc_player_SetTime(vlc_player_t *player, vlc_tick_t time)
+{
+    vlc_player_SeekByTime(player, time, VLC_PLAYER_SEEK_PRECISE,
+                          VLC_PLAYER_WHENCE_ABSOLUTE);
+}
+
+/**
+ * Helper to set the absolute time fast
+ */
+static inline void
+vlc_player_SetTimeFast(vlc_player_t *player, vlc_tick_t time)
+{
+    vlc_player_SeekByTime(player, time, VLC_PLAYER_SEEK_FAST,
+                          VLC_PLAYER_WHENCE_ABSOLUTE);
+}
+
+/**
+ * Helper to jump the time precisely
+ */
+static inline void
+vlc_player_JumpTime(vlc_player_t *player, vlc_tick_t jumptime)
+{
+    /* No fask seek for jumps. Indeed, jumps can seek to the current position
+     * if not precise enough or if the jump value is too small. */
+    vlc_player_SeekByTime(player, jumptime, VLC_PLAYER_SEEK_PRECISE,
+                          VLC_PLAYER_WHENCE_RELATIVE);
+}
+
+/**
+ * Enable A to B loop of the current media
+ *
+ * This function need to be called 2 times with VLC_PLAYER_ABLOOP_A and
+ * VLC_PLAYER_ABLOOP_B to setup an A to B loop. It current the current
+ * time/position when called. The B time must be higher than the A time.
+ *
+ * @param player locked player instance
+ * @return VLC_SUCCESS or a VLC error code
+ */
+VLC_API int
+vlc_player_SetAtoBLoop(vlc_player_t *player, enum vlc_player_abloop abloop);
+
+/**
+ * Get the A to B loop status
+ *
+ * @note If the returned status is VLC_PLAYER_ABLOOP_A, then a_time and a_pos
+ * will be valid. If the returned status is VLC_PLAYER_ABLOOP_B, then all
+ * output parameters are valid. If the returned status is
+ * VLC_PLAYER_ABLOOP_NONE, then all output parameters are invalid.
+ *
+ * @see vlc_player_cbs.on_atobloop_changed
+ *
+ * @param player locked player instance
+ * @param a_time A time or VLC_TICK_INVALID (if the media doesn't have valid
+ * times)
+ * @param a_pos A position
+ * @param b_time B time or VLC_TICK_INVALID (if the media doesn't have valid
+ * times)
+ * @param b_pos B position
+ * @return A to B loop status
+ */
+VLC_API enum vlc_player_abloop
+vlc_player_GetAtoBLoop(vlc_player_t *player, vlc_tick_t *a_time, float *a_pos,
+                       vlc_tick_t *b_time, float *b_pos);
+
+/**
+ * Get the number of tracks for an ES category
+ *
+ * @warning The returned size becomes invalid when the player is unlocked.
+ *
+ * @param player locked player instance
+ * @param cat VIDEO_ES, AUDIO_ES or SPU_ES
+ * @return number of tracks, or 0 (in case of error, or if the media is not
+ * started)
+ */
+VLC_API size_t
+vlc_player_GetTrackCount(vlc_player_t *player, enum es_format_category_e cat);
+
+/**
+ * Get the track at a specific index for an ES category
+ *
+ * @warning The behaviour is undefined if the index is not valid.
+ *
+ * @warning The returned pointer becomes invalid when the player is unlocked.
+ * The referenced structure can be safely copied with vlc_player_track_Dup().
+ *
+ * @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 track (can't be NULL if vlc_player_GetTrackCount() returned
+ * a valid count)
+ */
+VLC_API const struct vlc_player_track *
+vlc_player_GetTrackAt(vlc_player_t *player, enum es_format_category_e cat,
+                      size_t index);
+
+/**
+ * Helper to get the video track count
+ */
+static inline size_t
+vlc_player_GetVideoTrackCount(vlc_player_t *player)
+{
+    return vlc_player_GetTrackCount(player, VIDEO_ES);
+}
+
+/**
+ * Helper to get a video track at a specific index
+ */
+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);
+}
+
+/**
+ * Helper to get the audio track count
+ */
+static inline size_t
+vlc_player_GetAudioTrackCount(vlc_player_t *player)
+{
+    return vlc_player_GetTrackCount(player, AUDIO_ES);
+}
+
+/**
+ * Helper to get an audio track at a specific index
+ */
+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);
+}
+
+/**
+ * Helper to get the subtitle track count
+ */
+static inline size_t
+vlc_player_GetSubtitleTrackCount(vlc_player_t *player)
+{
+    return vlc_player_GetTrackCount(player, SPU_ES);
+}
+
+/**
+ * Helper to get a subtitle track at a specific index
+ */
+static inline const struct vlc_player_track *
+vlc_player_GetSubtitleTrackAt(vlc_player_t *player, size_t index)
+{
+    return vlc_player_GetTrackAt(player, SPU_ES, index);
+}
+
+/**
+ * Get a track from an ES identifier
+ *
+ * @warning The returned pointer becomes invalid when the player is unlocked.
+ * The referenced structure can be safely copied with vlc_player_track_Dup().
+ *
+ * @param player locked player instance
+ * @param id an ES ID (retrieved from vlc_player_cbs.on_track_list_changed or
+ * 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 *es_id);
+
+/**
+ * Select a track from an ES identifier
+ *
+ * @note A successful call will trigger the
+ * vlc_player_cbs.on_track_selection_changed event.
+ *
+ * @param player locked player instance
+ * @param id an ES ID (retrieved from vlc_player_cbs.on_track_list_changed or
+ * vlc_player_GetTrackAt())
+ */
+VLC_API void
+vlc_player_SelectTrack(vlc_player_t *player, vlc_es_id_t *es_id);
+
+/**
+ * Unselect a track from an ES identifier
+ *
+ * @note A successful call will trigger the
+ * vlc_player_cbs.on_track_selection_changed event.
+ *
+ * @param player locked player instance
+ * @param id an ES ID (retrieved from vlc_player_cbs.on_track_list_changed or
+ * vlc_player_GetTrackAt())
+ */
+VLC_API void
+vlc_player_UnselectTrack(vlc_player_t *player, vlc_es_id_t *es_id);
+
+/**
+ * Helper to unselect all tracks from an ES category
+ */
+static inline void
+vlc_player_UnselectTrackCategory(vlc_player_t *player,
+                                 enum es_format_category_e cat)
+{
+    size_t count = vlc_player_GetTrackCount(player, cat);
+    for (size_t i = 0; i < count; ++i)
+    {
+        const struct vlc_player_track *track =
+            vlc_player_GetTrackAt(player, cat, i);
+        assert(track);
+        if (track->selected)
+            vlc_player_UnselectTrack(player, track->es_id);
+    }
+}
+
+/**
+ * Restart a track from an ES identifier
+ *
+ * @note A successful call will trigger the
+ * vlc_player_cbs.on_track_selection_changed event.
+ *
+ * @param player locked player instance
+ * @param id an ES ID (retrieved from vlc_player_cbs.on_track_list_changed or
+ * vlc_player_GetTrackAt())
+ */
+VLC_API void
+vlc_player_RestartTrack(vlc_player_t *player, vlc_es_id_t *es_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.
+ *
+ * @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);
+
+/**
+ * Helper to select the default video track
+ */
+static inline void
+vlc_player_SelectDefaultVideoTrack(vlc_player_t *player, const char *lang)
+{
+    vlc_player_SelectDefaultTrack(player, VIDEO_ES, lang);
+}
+
+/**
+ * Helper to select the default audio track
+ */
+static inline void
+vlc_player_SelectDefaultAudioTrack(vlc_player_t *player, const char *lang)
+{
+    vlc_player_SelectDefaultTrack(player, AUDIO_ES, lang);
+}
+
+/**
+ * Helper to select the default spu track
+ */
+static inline void
+vlc_player_SelectDefaultSubtitleTrack(vlc_player_t *player, const char *lang)
+{
+    vlc_player_SelectDefaultTrack(player, SPU_ES, lang);
+}
+
+/**
+ * Get the number of programs
+ *
+ * @warning The returned size becomes invalid when the player is unlocked.
+ *
+ * @param player locked player instance
+ * @return number of programs, or 0 (in case of error, or if the media is not
+ * started)
+ */
+VLC_API size_t
+vlc_player_GetProgramCount(vlc_player_t *player);
+
+/**
+ * Get the program at a specific index
+ *
+ * @warning The behaviour is undefined if the index is not valid.
+ *
+ * @warning The returned pointer becomes invalid when the player is unlocked.
+ * The referenced structure can be safely copied with vlc_player_program_Dup().
+ *
+ * @param player locked player instance
+ * @param index valid index in the range [0; count[
+ * @return a valid program (can't be NULL if vlc_player_GetProgramCount()
+ * returned a valid count)
+ */
+VLC_API const struct vlc_player_program *
+vlc_player_GetProgramAt(vlc_player_t *player, size_t index);
+
+/**
+ * Get a program from an ES group identifier
+ *
+ * @param player locked player instance
+ * @param group_id a program ID (retrieved from
+ * vlc_player_cbs.on_program_list_changed or 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 group_id);
+
+/**
+ * Select a program from an ES group identifier
+ *
+ * @param player locked player instance
+ * @param group_id a program ID (retrieved from
+ * vlc_player_cbs.on_program_list_changed or vlc_player_GetProgramAt())
+ */
+VLC_API void
+vlc_player_SelectProgram(vlc_player_t *player, int group_id);
+
+/**
+ * Check if the media has a teletext menu
+ *
+ * @see vlc_player_cbs.on_teletext_menu_changed
+ *
+ * @param player locked player instance
+ * @return true if the media has a teletext menu
+ */
+VLC_API bool
+vlc_player_HasTeletextMenu(vlc_player_t *player);
+
+/**
+ * Enable or disable teletext
+ *
+ * This function has an effect only if the player has a teletext menu.
+ *
+ * @note A successful call will trigger the
+ * vlc_player_cbs.on_teletext_enabled_changed event.
+ * 
+ * @param player locked player instance
+ * @param enabled true to enable
+ */
+VLC_API void
+vlc_player_SetTeletextEnabled(vlc_player_t *player, bool enabled);
+
+/**
+ * Check if teletext is enabled
+ *
+ * @see vlc_player_cbs.on_teletext_enabled_changed
+ *
+ * @param player locked player instance
+ */
+VLC_API bool
+vlc_player_IsTeletextEnabled(vlc_player_t *player);
+
+/**
+ * Select a teletext page or do an action from a key
+ *
+ * This function has an effect only if the player has a teletext menu.
+ *
+ * @note Page keys can be the following: @ref VLC_PLAYER_TELETEXT_KEY_RED,
+ * @ref VLC_PLAYER_TELETEXT_KEY_GREEN, @ref VLC_PLAYER_TELETEXT_KEY_YELLOW,
+ * @ref VLC_PLAYER_TELETEXT_KEY_BLUE or @ref VLC_PLAYER_TELETEXT_KEY_INDEX.
+
+ * @note A successful call will trigger the
+ * vlc_player_cbs.on_teletext_page_changed event.
+ *
+ * @param player locked player instance
+ * @param page a page in the range ]0;888] or a valid key
+ */
+VLC_API void
+vlc_player_SelectTeletextPage(vlc_player_t *player, unsigned page);
+
+/**
+ * Get the current teletext page
+ *
+ * @see vlc_player_cbs.on_teletext_page_changed
+ *
+ * @param player locked player instance
+ */
+VLC_API unsigned
+vlc_player_GetTeletextPage(vlc_player_t *player);
+
+/**
+ * Enable or disable teletext transparency
+ *
+ * This function has an effect only if the player has a teletext menu.
+
+ * @note A successful call will trigger the
+ * vlc_player_cbs.on_teletext_transparency_changed event.
+ *
+ * @param player locked player instance
+ * @param enabled true to enable
+ */
+VLC_API void
+vlc_player_SetTeletextTransparency(vlc_player_t *player, bool enabled);
+
+/**
+ * Check if teletext is transparent
+ *
+ * @param player locked player instance
+ */
+VLC_API bool
+vlc_player_IsTeletextTransparent(vlc_player_t *player);
+
+/**
+ * Get the title list of the current media
+ *
+ * @see vlc_player_cbs.on_titles_changed
+ *
+ * @param player locked player instance
+ */
+VLC_API vlc_player_title_list *
+vlc_player_GetTitleList(vlc_player_t *player);
+
+/**
+ * Get the selected title index for the current media
+ *
+ * @see vlc_player_cbs.on_title_selection_changed
+ *
+ * @param player locked player instance
+ */
+VLC_API ssize_t
+vlc_player_GetSelectedTitleIdx(vlc_player_t *player);
+
+/**
+ * Helper to get the current selected title
+ */
+static inline const struct vlc_player_title *
+vlc_player_GetSelectedTitle(vlc_player_t *player)
+{
+    vlc_player_title_list *titles = vlc_player_GetTitleList(player);
+    if (!titles)
+        return NULL;
+    ssize_t selected_idx = vlc_player_GetSelectedTitleIdx(player);
+    if (selected_idx < 0)
+        return NULL;
+    return vlc_player_title_list_GetAt(titles, selected_idx);
+}
+
+/**
+ * Select a title index for the current media
+ *
+ * @note A successful call will trigger the
+ * vlc_player_cbs.on_title_selection_changed event.
+ *
+ * @see vlc_player_title_list_GetAt()
+ * @see vlc_player_title_list_GetCount()
+ *
+ * @param player locked player instance
+ * @param index valid index in the range [0;count[
+ */
+VLC_API void
+vlc_player_SelectTitleIdx(vlc_player_t *player, size_t index);
+
+/**
+ * Select a title for the current media
+ *
+ * @note A successful call will trigger the
+ * vlc_player_cbs.on_title_selection_changed event.
+ *
+ * @see vlc_player_title_list_GetAt()
+ * @see vlc_player_title_list_GetCount()
+ *
+ * @param player locked player instance
+ * @param title a valid title coming from the vlc_player_title_list
+ */
+VLC_API void
+vlc_player_SelectTitle(vlc_player_t *player,
+                       const struct vlc_player_title *title);
+
+/**
+ * Select a chapter for the current media
+ *
+ * @note A successful call will trigger the
+ * vlc_player_cbs.on_chapter_selection_changed event.
+ *
+ * @param player locked player instance
+ * @param title the selected title
+ * @param chapter_idx index from vlc_player_title.chapters
+ */
+VLC_API void
+vlc_player_SelectChapter(vlc_player_t *player,
+                         const struct vlc_player_title *title,
+                         size_t chapter_idx);
+
+/**
+ * Select the next title for the current media
+ *
+ * @see vlc_player_SelectTitleIdx()
+ */
+VLC_API void
+vlc_player_SelectNextTitle(vlc_player_t *player);
+
+/**
+ * Select the previous title for the current media
+ *
+ * @see vlc_player_SelectTitleIdx()
+ */
+VLC_API void
+vlc_player_SelectPrevTitle(vlc_player_t *player);
+
+/**
+ * Get the selected chapter index for the current media
+ *
+ * @see vlc_player_cbs.on_chapter_selection_changed
+ *
+ * @param player locked player instance
+ */
+VLC_API ssize_t
+vlc_player_GetSelectedChapterIdx(vlc_player_t *player);
+
+/**
+ * Helper to get the current selected chapter
+ */
+static inline const struct vlc_player_chapter *
+vlc_player_GetSelectedChapter(vlc_player_t *player)
+{
+    const struct vlc_player_title *title = vlc_player_GetSelectedTitle(player);
+    if (!title || !title->chapter_count)
+        return NULL;
+    ssize_t chapter_idx = vlc_player_GetSelectedChapterIdx(player);
+    return chapter_idx >= 0 ? &title->chapters[chapter_idx] : NULL;
+}
+
+/**
+ * Select a chapter index for the current media
+ *
+ * @note A successful call will trigger the
+ * vlc_player_cbs.on_chaper_selection_changed event.
+ *
+ * @see vlc_player_title.chapters
+ *
+ * @param player locked player instance
+ * @param index valid index in the range [0;vlc_player_title.chapter_count[
+ */
+VLC_API void
+vlc_player_SelectChapterIdx(vlc_player_t *player, size_t index);
+
+/**
+ * Select the next chapter for the current media
+ *
+ * @see vlc_player_SelectChapterIdx()
+ */
+VLC_API void
+vlc_player_SelectNextChapter(vlc_player_t *player);
+
+/**
+ * Select the previous chapter for the current media
+ *
+ * @see vlc_player_SelectChapterIdx()
+ */
+VLC_API void
+vlc_player_SelectPrevChapter(vlc_player_t *player);
+
+/**
+ * Add an associated (or external) media to the current media
+ *
+ * @param player locked player instance
+ * @param cat AUDIO_ES or SPU_ES
+ * @param uri absolute uri of the external media
+ * @param select true to select the track of this external media
+ * @param notify true to notify the OSD
+ * @param check_ext true to check subtitles extension
+ */
+VLC_API int
+vlc_player_AddAssociatedMedia(vlc_player_t *player,
+                              enum es_format_category_e cat, const char *uri,
+                              bool select, bool notify, bool check_ext);
+
+/**
+ * Set the associated subtitle FPS
+ *
+ * In order to correct the rate of the associated media according to this FPS
+ * and the media video FPS.
+ *
+ * @note A successful call will trigger the
+ * vlc_player_cbs.on_associated_subs_fps_changed event.
+ *
+ * @warning this function will change the rate of all external subtitle files
+ * associated with the current media.
+ *
+ * @param player locked player instance
+ * @param fps FPS of the subtitle file
+ */
+VLC_API void
+vlc_player_SetAssociatedSubsFPS(vlc_player_t *player, float fps);
+
+/**
+ * Get the associated subtitle FPS
+ *
+ * @param player locked player instance
+ * @return fps
+ */
+VLC_API float
+vlc_player_GetAssociatedSubsFPS(vlc_player_t *player);
+
+/**
+ * Set the renderer
+ *
+ * Valid for the current media and all future ones.
+ *
+ * @note A successful call will trigger the vlc_player_cbs.on_renderer_changed
+ * event.
+ *
+ * @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);
+
+/**
+ * Get the renderer
+ *
+ * @see vlc_player_cbs.on_renderer_changed
+ *
+ * @param player locked player instance
+ * @return the renderer item set by vlc_player_SetRenderer()
+ */
+VLC_API vlc_renderer_item_t *
+vlc_player_GetRenderer(vlc_player_t *player);
+
+/**
+ * Navigate (for DVD/Bluray menus or viewpoint)
+ *
+ * @param player locked player instance
+ * @param nav navigation key
+ */
+VLC_API void
+vlc_player_Navigate(vlc_player_t *player, enum vlc_player_nav nav);
+
+/**
+ * Check if the playing is recording
+ *
+ * @see vlc_player_cbs.on_recording_changed
+ *
+ * @param player locked player instance
+ * @return true if the player is recording
+ */
+VLC_API bool
+vlc_player_IsRecording(vlc_player_t *player);
+
+/**
+ * Enable or disable recording for the current media
+ *
+ * @note A successful call will trigger the vlc_player_cbs.on_recording_changed
+ * event.
+ *
+ * @param player locked player instance
+ * @param enabled true to enable recording
+ */
+VLC_API void
+vlc_player_SetRecordingEnabled(vlc_player_t *player, bool enabled);
+
+/**
+ * Helper to toggle the recording state
+ */
+static inline void
+vlc_player_ToggleRecording(vlc_player_t *player)
+{
+    vlc_player_SetRecordingEnabled(player, !vlc_player_IsRecording(player));
+}
+
+/**
+ * Get the audio delay for the current media
+ *
+ * @see vlc_player_cbs.on_audio_delay_changed
+ *
+ * @param player locked player instance
+ */
+VLC_API vlc_tick_t
+vlc_player_GetAudioDelay(vlc_player_t *player);
+
+/**
+ * Set the audio delay for the current media
+ *
+ * @note A successful call will trigger the
+ * vlc_player_cbs.on_audio_delay_changed event.
+ *
+ * @param player locked player instance
+ * @param delay a valid time
+ * @param whence absolute or relative
+ */
+VLC_API void
+vlc_player_SetAudioDelay(vlc_player_t *player, vlc_tick_t delay,
+                         enum vlc_player_whence whence);
+
+/**
+ * Get the subtitle delay for the current media
+ *
+ * @see vlc_player_cbs.on_audio_delay_changed
+ *
+ * @param player locked player instance
+ */
+VLC_API vlc_tick_t
+vlc_player_GetSubtitleDelay(vlc_player_t *player);
+
+/**
+ * Set the subtitle delay for the current media
+ *
+ * @note A successful call will trigger the
+ * vlc_player_cbs.on_subtitle_delay_changed event.
+ *
+ * @param player locked player instance
+ * @param delay a valid time
+ * @param whence absolute or relative
+ */
+VLC_API void
+vlc_player_SetSubtitleDelay(vlc_player_t *player, vlc_tick_t delay,
+                            enum vlc_player_whence whence);
+
+/**
+ * Get the signal quality and strength of the current media
+ *
+ * @param player locked player instance
+ */
+VLC_API int
+vlc_player_GetSignal(vlc_player_t *player, float *quality, float *strength);
+
+/**
+ * Get the statistics of the current media
+ *
+ * @warning The returned pointer becomes invalid when the player is unlocked.
+ * The referenced structure can be safely copied.
+ *
+ * @see vlc_player_cbs.on_statistics_changed
+ *
+ * @return pointer to the player stats structure or NULL.
+ * safely copied.
+ */
+VLC_API const struct input_stats_t *
+vlc_player_GetStatistics(vlc_player_t *player);
+
+/**
+ * Get the list of video output
+ *
+ * @warning All vout_thread_t * element of the array must be released with
+ * vlc_object_release(). The returned must be freed.
+ *
+ * @see vlc_players_cbs.on_vout_list_changed
+ *
+ * @param player locked player instance
+ * @param count valid pointer to store the array count
+ * @return a array of vout_thread_t * or NULL, cf. warning
+ */
+VLC_API vout_thread_t **
+vlc_player_GetVouts(vlc_player_t *player, size_t *count);
+
+/**
+ * Get the audio output
+ *
+ * @warning The returned pointer must be released with vlc_object_release().
+ *
+ * @param player locked player instance
+ * @return a valid audio_output_t * or NULL
+ */
+VLC_API audio_output_t *
+vlc_player_GetAout(vlc_player_t *player);
+
+/**
+ * Add a listener callback for audio output events
+ *
+ * @note The player instance doesn't need to be locked for vlc_player_aout_*()
+ * functions.
+ * @note Every registered callbacks need to be removed by the caller with
+ * vlc_player_aout_RemoveListener().
+ *
+ * @param player player instance
+ * @param cbs pointer to a vlc_player_aout_cbs structure, the structure must be
+ * valid during the lifetime of the player
+ * @param cbs_data opaque pointer used by the callbacks
+ * @return a valid listener id, or NULL in case of allocation error
+ */
+VLC_API vlc_player_aout_listener_id *
+vlc_player_aout_AddListener(vlc_player_t *player,
+                            const struct vlc_player_aout_cbs *cbs,
+                            void *cbs_data);
+
+/**
+ * Remove a aout listener callback
+ *
+ * @param player player instance
+ * @param listener_id listener id returned by vlc_player_aout_AddListener()
+ */
+VLC_API void
+vlc_player_aout_RemoveListener(vlc_player_t *player,
+                               vlc_player_aout_listener_id *listener_id);
+
+/**
+ * Get the audio volume
+ *
+ * @note The player instance doesn't need to be locked for vlc_player_aout_*()
+ * functions.
+ *
+ * @see vlc_player_aout_cbs.on_volume_changed
+ *
+ * @param player player instance
+ * @return volume in the range [0;8.f] or -1.f if there is no audio outputs
+ */
+VLC_API float
+vlc_player_aout_GetVolume(vlc_player_t *player);
+
+/**
+ * Set the audio volume
+ *
+ * @note The player instance doesn't need to be locked for vlc_player_aout_*()
+ * functions.
+ *
+ * @note A successful call will trigger the
+ * vlc_player_vout_cbs.on_volume_changed event.
+ *
+ * @param player player instance
+ * @param volume volume in the range [0;8.f]
+ * @return VLC_SUCCESS or VLC_EGENERIC if there is no audio outputs
+ */
+VLC_API int
+vlc_player_aout_SetVolume(vlc_player_t *player, float volume);
+
+/**
+ * Increment the audio volume
+ *
+ * @see vlc_player_aout_SetVolume()
+ *
+ * @param player player instance
+ * @param volume volume in the range [0;8.f]
+ * @param result pointer to store the resulting volume
+ * @return VLC_SUCCESS or VLC_EGENERIC if there is no audio outputs
+ */
+VLC_API int
+vlc_player_aout_IncrementVolume(vlc_player_t *player, float volume,
+                                float *result);
+
+/**
+ * Helper to decrement the audio volume
+ */
+static inline int
+vlc_player_aout_DecrementVolume(vlc_player_t *player, float volume,
+                                float *result)
+{
+    return vlc_player_aout_IncrementVolume(player, -volume, result);
+}
+
+/**
+ * Check if the audio output is muted
+ *
+ * @note The player instance doesn't need to be locked for vlc_player_aout_*()
+ * functions.
+ *
+ * @see vlc_player_aout_cbs.on_mute_changed
+ *
+ * @param player player instance
+ * @return 0 if not muted, 1 if mutex, -1 if there is no audio outputs
+ */
+VLC_API int
+vlc_player_aout_IsMuted(vlc_player_t *player);
+
+/**
+ * Mute or unmute the audio output
+ *
+ * @note The player instance doesn't need to be locked for vlc_player_aout_*()
+ * functions.
+ *
+ * @note A successful call will trigger the
+ * vlc_player_aout_cbs.on_mute_changed event.
+ *
+ * @param player player instance
+ * @param mute true to mute
+ * @return VLC_SUCCESS or VLC_EGENERIC if there is no audio outputs
+ */
+VLC_API int
+vlc_player_aout_Mute(vlc_player_t *player, bool mute);
+
+/**
+ * Enable or disable an audio filter
+ *
+ * @see aout_EnableFilter()
+ *
+ * @return VLC_SUCCESS or VLC_EGENERIC if there is no audio outputs
+ */
+VLC_API int
+vlc_player_aout_EnableFilter(vlc_player_t *player, const char *name, bool add);
+
+/**
+ * Add a listener callback for video output events
+ *
+ * @note The player instance doesn't need to be locked for vlc_player_vout_*()
+ * functions.
+ * @note Every registered callbacks need to be removed by the caller with
+ * vlc_player_vout_RemoveListener().
+ *
+ * @param player player instance
+ * @param cbs pointer to a vlc_player_vout_cbs structure, the structure must be
+ * valid during the lifetime of the player
+ * @param cbs_data opaque pointer used by the callbacks
+ * @return a valid listener id, or NULL in case of allocation error
+ */
+VLC_API vlc_player_vout_listener_id *
+vlc_player_vout_AddListener(vlc_player_t *player,
+                            const struct vlc_player_vout_cbs *cbs,
+                            void *cbs_data);
+
+/**
+ * Remove a vout listener callback
+ *
+ * @param player player instance
+ * @param listener_id listener id returned by vlc_player_vout_AddListener()
+ */
+VLC_API void
+vlc_player_vout_RemoveListener(vlc_player_t *player,
+                               vlc_player_vout_listener_id *listener_id);
+
+/**
+ * Check if the player is fullscreen
+ *
+ * @warning The fullscreen state of the player and all vouts can be different.
+ *
+ * @note The player instance doesn't need to be locked for vlc_player_vout_*()
+ * functions.
+ *
+ * @see vlc_player_vout_cbs.on_fullscreen_changed
+ *
+ * @param player player instance
+ * @return true if the player is fullscreen
+ */
+VLC_API bool
+vlc_player_vout_IsFullscreen(vlc_player_t *player);
+
+/**
+ * Enable or disable the player fullscreen state
+ *
+ * This will have an effect on all current and future vouts.
+ *
+ * @note The player instance doesn't need to be locked for vlc_player_vout_*()
+ * functions.
+ * @note A successful call will trigger the
+ * vlc_player_vout_cbs.on_fullscreen_changed event.
+ *
+ * @param player player instance
+ * @param enabled true to enable fullscreen
+ */
+VLC_API void
+vlc_player_vout_SetFullscreen(vlc_player_t *player, bool enabled);
+
+/**
+ * Helper to toggle the player fullscreen state
+ */
+static inline void
+vlc_player_vout_ToggleFullscreen(vlc_player_t *player)
+{
+    vlc_player_vout_SetFullscreen(player,
+                                  !vlc_player_vout_IsFullscreen(player));
+}
+
+/**
+ * Check if the player has wallpaper-mode enaled
+ *
+ * @warning The wallpaper-mode state of the player and all vouts can be
+ * different.
+ *
+ * @note The player instance doesn't need to be locked for vlc_player_vout_*()
+ * functions.
+ *
+ * @see vlc_player_vout_cbs.on_wallpaper_mode_changed
+ *
+ * @param player player instance
+ * @return true if the player is fullscreen
+ */
+VLC_API bool
+vlc_player_vout_IsWallpaperModeEnabled(vlc_player_t *player);
+
+/**
+ * Enable or disable the player wallpaper-mode
+ *
+ * This will have an effect on all current and future vouts.
+ *
+ * @note The player instance doesn't need to be locked for vlc_player_vout_*()
+ * functions.
+ * @note A successful call will trigger the
+ * vlc_player_vout_cbs.on_wallpaper_mode_changed event.
+ *
+ * @param player player instance
+ * @param enabled true to enable wallpaper-mode
+ */
+VLC_API void
+vlc_player_vout_SetWallpaperModeEnabled(vlc_player_t *player, bool enabled);
+
+/**
+ * Helper to toggle the player wallpaper-mode state
+ */
+static inline void
+vlc_player_vout_ToggleWallpaperMode(vlc_player_t *player)
+{
+    vlc_player_vout_SetWallpaperModeEnabled(player,
+        !vlc_player_vout_IsWallpaperModeEnabled(player));
+}
+
+/** @} */
+#endif
diff --git a/po/POTFILES.in b/po/POTFILES.in
index 504e3ffd4b..fdcd521891 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -79,6 +79,7 @@ src/input/input.c
 src/input/input_internal.h
 src/input/item.c
 src/input/meta.c
+src/input/player.c
 src/input/stream.c
 src/input/stream.h
 src/input/stream_memory.c
diff --git a/src/Makefile.am b/src/Makefile.am
index 4a28cdb837..7a6df52864 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 \
@@ -242,6 +243,8 @@ libvlccore_la_SOURCES = \
 	input/es_out.c \
 	input/es_out_timeshift.c \
 	input/input.c \
+	input/player.c \
+	input/player.h \
 	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..215b4c37ac
--- /dev/null
+++ b/src/input/player.c
@@ -0,0 +1,2804 @@
+/*****************************************************************************
+ * 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 "player.h"
+#include <vlc_aout.h>
+#include <vlc_interface.h>
+#include <vlc_renderer_discovery.h>
+#include <vlc_list.h>
+#include <vlc_vector.h>
+#include <vlc_atomic.h>
+
+#include "libvlc.h"
+#include "input_internal.h"
+#include "resource.h"
+#include "../audio_output/aout_internal.h"
+
+#define RETRY_TIMEOUT_BASE VLC_TICK_FROM_MS(100)
+#define RETRY_TIMEOUT_MAX VLC_TICK_FROM_MS(3200)
+
+static_assert(VLC_PLAYER_CAP_SEEK == VLC_INPUT_CAPABILITIES_SEEKABLE &&
+              VLC_PLAYER_CAP_PAUSE == VLC_INPUT_CAPABILITIES_PAUSEABLE &&
+              VLC_PLAYER_CAP_CHANGE_RATE == VLC_INPUT_CAPABILITIES_CHANGE_RATE &&
+              VLC_PLAYER_CAP_REWIND == VLC_INPUT_CAPABILITIES_REWINDABLE,
+              "player/input capabilities mismatch");
+
+static_assert(VLC_PLAYER_TITLE_MENU == INPUT_TITLE_MENU &&
+              VLC_PLAYER_TITLE_INTERACTIVE == INPUT_TITLE_INTERACTIVE,
+              "player/input title flag mismatch");
+
+#define GAPLESS 0 /* TODO */
+
+typedef struct VLC_VECTOR(struct vlc_player_program *)
+    vlc_player_program_vector;
+
+typedef struct VLC_VECTOR(struct vlc_player_track *)
+    vlc_player_track_vector;
+
+struct vlc_player_listener_id
+{
+    const struct vlc_player_cbs *cbs;
+    void *cbs_data;
+    struct vlc_list node;
+};
+
+struct vlc_player_vout_listener_id
+{
+    const struct vlc_player_vout_cbs *cbs;
+    void *cbs_data;
+    struct vlc_list node;
+};
+
+struct vlc_player_aout_listener_id
+{
+    const struct vlc_player_aout_cbs *cbs;
+    void *cbs_data;
+    struct vlc_list node;
+};
+
+struct vlc_player_title_list
+{
+    vlc_atomic_rc_t rc;
+    size_t count;
+    struct vlc_player_title array[];
+};
+
+struct vlc_player_input
+{
+    input_thread_t *thread;
+    vlc_player_t *player;
+    bool started;
+
+    enum vlc_player_state state;
+    enum vlc_player_error error;
+    float rate;
+    int capabilities;
+    vlc_tick_t length;
+
+    vlc_tick_t position_ms;
+    float position_percent;
+
+    bool recording;
+
+    float signal_quality;
+    float signal_strength;
+    float cache;
+
+    struct input_stats_t stats;
+
+    vlc_tick_t audio_delay;
+    vlc_tick_t subtitle_delay;
+
+    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_player_track *teletext_menu;
+
+    struct vlc_player_title_list *titles;
+
+    size_t title_selected;
+    size_t chapter_selected;
+
+    struct vlc_list node;
+
+    bool teletext_enabled;
+    bool teletext_transparent;
+    unsigned teletext_page;
+
+    struct
+    {
+        vlc_tick_t time;
+        float pos;
+        bool set;
+    } abloop_state[2];
+};
+
+struct vlc_player_t
+{
+    struct vlc_common_members obj;
+    vlc_mutex_t lock;
+    vlc_mutex_t aout_listeners_lock;
+    vlc_mutex_t vout_listeners_lock;
+    vlc_cond_t start_delay_cond;
+
+    enum vlc_player_media_stopped_action media_stopped_action;
+    bool start_paused;
+
+    const struct vlc_player_media_provider *media_provider;
+    void *media_provider_data;
+
+    struct vlc_list listeners;
+    struct vlc_list aout_listeners;
+    struct vlc_list vout_listeners;
+
+    input_resource_t *resource;
+    vlc_renderer_item_t *renderer;
+
+    input_item_t *media;
+    struct vlc_player_input *input;
+
+    bool releasing_media;
+    bool has_next_media;
+    input_item_t *next_media;
+#if GAPLESS
+    struct vlc_player_input *next_input;
+#endif
+
+    enum vlc_player_state global_state;
+    bool started;
+
+    unsigned error_count;
+
+    bool deleting;
+    struct
+    {
+        vlc_thread_t thread;
+        vlc_cond_t wait;
+        vlc_cond_t notify;
+        struct vlc_list inputs;
+        struct vlc_list stopping_inputs;
+        struct vlc_list joinable_inputs;
+    } destructor;
+};
+
+#define vlc_player_SendEvent(player, event, ...) do { \
+    vlc_player_listener_id *listener; \
+    vlc_list_foreach(listener, &player->listeners, node) \
+    { \
+        if (listener->cbs->event) \
+            listener->cbs->event(player, ##__VA_ARGS__, listener->cbs_data); \
+    } \
+} while(0)
+
+#define vlc_player_aout_SendEvent(player, event, ...) do { \
+    vlc_mutex_lock(&player->aout_listeners_lock); \
+    vlc_player_aout_listener_id *listener; \
+    vlc_list_foreach(listener, &player->aout_listeners, node) \
+    { \
+        if (listener->cbs->event) \
+            listener->cbs->event(player, ##__VA_ARGS__, listener->cbs_data); \
+    } \
+    vlc_mutex_unlock(&player->aout_listeners_lock); \
+} while(0)
+
+#define vlc_player_vout_SendEvent(player, event, ...) do { \
+    vlc_mutex_lock(&player->vout_listeners_lock); \
+    vlc_player_vout_listener_id *listener; \
+    vlc_list_foreach(listener, &player->vout_listeners, node) \
+    { \
+        if (listener->cbs->event) \
+            listener->cbs->event(player, ##__VA_ARGS__, listener->cbs_data); \
+    } \
+    vlc_mutex_unlock(&player->vout_listeners_lock); \
+} while(0)
+
+#if GAPLESS
+#define vlc_player_foreach_inputs(it) \
+    for (struct vlc_player_input *it = player->input; \
+         it != NULL; \
+         it = (it == player->input ? player->next_input : NULL))
+#else
+#define vlc_player_foreach_inputs(it) \
+    for (struct vlc_player_input *it = player->input; it != NULL; it = NULL)
+#endif
+
+static void
+input_thread_Events(input_thread_t *, const struct vlc_input_event *, void *);
+static void
+vlc_player_input_HandleState(struct vlc_player_input *, enum vlc_player_state);
+static int
+vlc_player_VoutCallback(vlc_object_t *this, const char *var,
+                        vlc_value_t oldval, vlc_value_t newval, void *data);
+
+
+void
+vlc_player_assert_locked(vlc_player_t *player)
+{
+    assert(player);
+    vlc_mutex_assert(&player->lock);
+}
+
+static inline struct vlc_player_input *
+vlc_player_get_input_locked(vlc_player_t *player)
+{
+    vlc_player_assert_locked(player);
+    return player->input;
+}
+
+static char *
+vlc_player_program_DupTitle(int id, const char *title)
+{
+    char *dup;
+    if (title)
+        dup = strdup(title);
+    else if (asprintf(&dup, "%d", id) == -1)
+        dup = NULL;
+    return dup;
+}
+
+static struct vlc_player_program *
+vlc_player_program_New(int id, const char *name)
+{
+    struct vlc_player_program *prgm = malloc(sizeof(*prgm));
+    if (!prgm)
+        return NULL;
+    prgm->name = vlc_player_program_DupTitle(id, name);
+    if (!prgm->name)
+    {
+        free(prgm);
+        return NULL;
+    }
+    prgm->group_id = id;
+    prgm->selected = prgm->scrambled = false;
+
+    return prgm;
+}
+
+static int
+vlc_player_program_Update(struct vlc_player_program *prgm, int id,
+                          const char *name)
+{
+    free((char *)prgm->name);
+    prgm->name = vlc_player_program_DupTitle(id, name);
+    return prgm->name != NULL ? VLC_SUCCESS : VLC_ENOMEM;
+}
+
+struct vlc_player_program *
+vlc_player_program_Dup(const struct vlc_player_program *src)
+{
+    struct vlc_player_program *dup =
+        vlc_player_program_New(src->group_id, src->name);
+
+    if (!dup)
+        return NULL;
+    dup->selected = src->selected;
+    dup->scrambled = src->scrambled;
+    return dup;
+}
+
+void
+vlc_player_program_Delete(struct vlc_player_program *prgm)
+{
+    free((char *)prgm->name);
+    free(prgm);
+}
+
+static struct vlc_player_program *
+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 *prgm = vec->data[i];
+        if (prgm->group_id == id)
+        {
+            if (idx)
+                *idx = i;
+            return prgm;
+        }
+    }
+    return NULL;
+}
+
+static struct vlc_player_track *
+vlc_player_track_New(vlc_es_id_t *id, const char *name, const es_format_t *fmt)
+{
+    struct vlc_player_track *track = malloc(sizeof(*track));
+    if (!track)
+        return NULL;
+    track->name = strdup(name);
+    if (!track->name)
+    {
+        free(track);
+        return NULL;
+    }
+
+    int ret = es_format_Copy(&track->fmt, fmt);
+    if (ret != VLC_SUCCESS)
+    {
+        free((char *)track->name);
+        free(track);
+        return NULL;
+    }
+    track->es_id = vlc_es_id_Hold(id);
+    track->selected = false;
+
+    return track;
+}
+
+struct vlc_player_track *
+vlc_player_track_Dup(const struct vlc_player_track *src)
+{
+    struct vlc_player_track *dup =
+        vlc_player_track_New(src->es_id, src->name, &src->fmt);
+
+    if (!dup)
+        return NULL;
+    dup->selected = src->selected;
+    return dup;
+}
+
+void
+vlc_player_track_Delete(struct vlc_player_track *track)
+{
+    es_format_Clean(&track->fmt);
+    free((char *)track->name);
+    vlc_es_id_Release(track->es_id);
+    free(track);
+}
+
+static int
+vlc_player_track_Update(struct vlc_player_track *track,
+                        const char *name, const es_format_t *fmt)
+{
+    if (strcmp(name, track->name) != 0)
+    {
+        char *dup = strdup(name);
+        if (!dup)
+            return VLC_ENOMEM;
+        free((char *)track->name);
+        track->name = dup;
+    }
+
+    es_format_t fmtdup;
+    int ret = es_format_Copy(&fmtdup, fmt);
+    if (ret != VLC_SUCCESS)
+        return ret;
+
+    es_format_Clean(&track->fmt);
+    track->fmt = fmtdup;
+    return VLC_SUCCESS;
+}
+
+struct vlc_player_title_list *
+vlc_player_title_list_Hold(struct vlc_player_title_list *titles)
+{
+    vlc_atomic_rc_inc(&titles->rc);
+    return titles;
+}
+
+void
+vlc_player_title_list_Release(struct vlc_player_title_list *titles)
+{
+    if (!vlc_atomic_rc_dec(&titles->rc))
+        return;
+    for (size_t title_idx = 0; title_idx < titles->count; ++title_idx)
+    {
+        struct vlc_player_title *title = &titles->array[title_idx];
+        free((char *)title->name);
+        for (size_t chapter_idx = 0; chapter_idx < title->chapter_count;
+             ++chapter_idx)
+        {
+            const struct vlc_player_chapter *chapter =
+                &title->chapters[chapter_idx];
+            free((char *)chapter->name);
+        }
+        free((void *)title->chapters);
+    }
+    free(titles);
+}
+
+static char *
+input_title_GetName(const struct input_title_t *input_title, int idx,
+                    int title_offset)
+{
+    int ret;
+    char length_str[MSTRTIME_MAX_SIZE + sizeof(" []")];
+
+    if (input_title->i_length > 0)
+    {
+        strcpy(length_str, " [");
+        secstotimestr(&length_str[2], SEC_FROM_VLC_TICK(input_title->i_length));
+        strcat(length_str, "]");
+    }
+    else
+        length_str[0] = '\0';
+
+    char *dup;
+    if (input_title->psz_name && input_title->psz_name[0] != '\0')
+        ret = asprintf(&dup, "%s%s", input_title->psz_name, length_str);
+    else
+        ret = asprintf(&dup, _("Title %i%s"), idx + title_offset, length_str);
+    if (ret == -1)
+        return NULL;
+    return dup;
+}
+
+static char *
+seekpoint_GetName(seekpoint_t *seekpoint, int idx, int chapter_offset)
+{
+    if (seekpoint->psz_name && seekpoint->psz_name[0] != '\0' )
+        return strdup(seekpoint->psz_name);
+
+    char *dup;
+    int ret = asprintf(&dup, _("Chapter %i"), idx + chapter_offset);
+    if (ret == -1)
+        return NULL;
+    return dup;
+}
+
+static struct vlc_player_title_list *
+vlc_player_title_list_Create(const input_title_t **array, size_t count,
+                             int title_offset, int chapter_offset)
+{
+    if (count == 0)
+        return NULL;
+
+    /* Allocate the struct + the whole list */
+    size_t size;
+    if (mul_overflow(count, sizeof(struct vlc_player_title), &size))
+        return NULL;
+    if (add_overflow(size, sizeof(struct vlc_player_title_list), &size))
+        return NULL;
+    struct vlc_player_title_list *titles = malloc(size);
+    if (!titles)
+        return NULL;
+
+    vlc_atomic_rc_init(&titles->rc);
+    titles->count = count;
+
+    for (size_t title_idx = 0; title_idx < titles->count; ++title_idx)
+    {
+        const struct input_title_t *input_title = array[title_idx];
+        struct vlc_player_title *title = &titles->array[title_idx];
+
+        title->name = input_title_GetName(input_title, title_idx, title_offset);
+        title->length = input_title->i_length;
+        title->flags = input_title->i_flags;
+        const size_t seekpoint_count = input_title->i_seekpoint > 0 ?
+                                       input_title->i_seekpoint : 0;
+        title->chapter_count = seekpoint_count;
+
+        struct vlc_player_chapter *chapters = title->chapter_count == 0 ? NULL :
+            vlc_alloc(title->chapter_count, sizeof(*chapters));
+
+        if (chapters)
+        {
+            for (size_t chapter_idx = 0; chapter_idx < title->chapter_count;
+                 ++chapter_idx)
+            {
+                struct vlc_player_chapter *chapter = &chapters[chapter_idx];
+                seekpoint_t *seekpoint = input_title->seekpoint[chapter_idx];
+
+                chapter->name = seekpoint_GetName(seekpoint, chapter_idx,
+                                                  chapter_offset);
+                chapter->time = seekpoint->i_time_offset;
+                if (!chapter->name) /* Will trigger the error path */
+                    title->chapter_count = chapter_idx;
+            }
+        }
+        else if (seekpoint_count > 0) /* Will trigger the error path */
+            title->chapter_count = 0;
+
+        title->chapters = chapters;
+
+        if (!title->name || seekpoint_count != title->chapter_count)
+        {
+            titles->count = title_idx;
+            vlc_player_title_list_Release(titles);
+            return NULL;
+        }
+    }
+    return titles;
+}
+
+const struct vlc_player_title *
+vlc_player_title_list_GetAt(struct vlc_player_title_list *titles, size_t idx)
+{
+    assert(idx < titles->count);
+    return &titles->array[idx];
+}
+
+size_t
+vlc_player_title_list_GetCount(struct vlc_player_title_list *titles)
+{
+    return titles->count;
+}
+
+static struct vlc_player_input *
+vlc_player_input_New(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 = VLC_PLAYER_STATE_STOPPED;
+    input->error = VLC_PLAYER_ERROR_NONE;
+    input->rate = 1.f;
+    input->capabilities = 0;
+    input->length = input->position_ms =
+    input->position_percent = 0.f;
+
+    input->recording = false;
+
+    input->cache = 0.f;
+    input->signal_quality = input->signal_strength = -1.f;
+
+    memset(&input->stats, 0, sizeof(input->stats));
+
+    input->audio_delay = input->subtitle_delay = 0;
+
+    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->teletext_menu = NULL;
+
+    input->titles = NULL;
+    input->title_selected = input->chapter_selected = 0;
+
+    input->teletext_enabled = input->teletext_transparent = false;
+    input->teletext_page = 0;
+
+    input->abloop_state[0].set = input->abloop_state[1].set = false;
+
+    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_Delete(struct vlc_player_input *input)
+{
+    vlc_player_t *player = input->player;
+
+    if (input->titles)
+    {
+        vlc_player_title_list_Release(input->titles);
+        vlc_player_SendEvent(player, on_titles_changed, NULL);
+    }
+
+    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);
+    assert(input->teletext_menu == NULL);
+
+    vlc_vector_destroy(&input->program_vector);
+    vlc_vector_destroy(&input->video_track_vector);
+    vlc_vector_destroy(&input->audio_track_vector);
+    vlc_vector_destroy(&input->spu_track_vector);
+
+    vlc_player_input_HandleState(input, VLC_PLAYER_STATE_STOPPED);
+    const bool started = player->started;
+    vlc_player_Unlock(player);
+
+    const bool keep_sout = var_GetBool(input->thread, "sout-keep");
+    input_Close(input->thread);
+    if (!keep_sout)
+        input_resource_TerminateSout(player->resource);
+
+    if (!started)
+        input_resource_TerminateVout(player->resource);
+
+    free(input);
+
+    vlc_player_Lock(player);
+}
+
+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_GetNextMedia(vlc_player_t *player)
+{
+    vlc_player_assert_locked(player);
+
+    if (!player->media_provider 
+     || player->media_stopped_action != VLC_PLAYER_MEDIA_STOPPED_CONTINUE
+     || player->has_next_media)
+        return;
+
+    assert(player->next_media == NULL);
+    player->next_media =
+        player->media_provider->get_next(player, player->media_provider_data);
+    player->has_next_media = true;
+}
+
+static int
+vlc_player_OpenNextMedia(vlc_player_t *player)
+{
+    assert(player->input == NULL);
+
+    player->has_next_media = false;
+
+    int ret = VLC_SUCCESS;
+    if (player->releasing_media)
+    {
+        assert(player->media);
+        input_item_Release(player->media);
+        player->media = NULL;
+        player->releasing_media = false;
+    }
+    else
+    {
+        if (!player->next_media)
+            return VLC_EGENERIC;
+
+        if (player->media)
+            input_item_Release(player->media);
+        player->media = player->next_media;
+        player->next_media = NULL;
+
+        player->input = vlc_player_input_New(player, player->media);
+        if (!player->input)
+        {
+            input_item_Release(player->media);
+            player->media = NULL;
+            ret = VLC_ENOMEM;
+        }
+    }
+    vlc_player_SendEvent(player, on_current_media_changed, player->media);
+    return ret;
+}
+
+static void
+vlc_player_CancelWaitError(vlc_player_t *player)
+{
+    if (player->error_count != 0)
+    {
+        player->error_count = 0;
+        vlc_cond_signal(&player->start_delay_cond);
+    }
+}
+
+static bool
+vlc_list_HasInput(struct vlc_list *list, struct vlc_player_input *input)
+{
+    struct vlc_player_input *other_input;
+    vlc_list_foreach(other_input, list, node)
+    {
+        if (other_input == input)
+            return true;
+    }
+    return false;
+}
+
+static void
+vlc_player_destructor_AddInput(vlc_player_t *player,
+                               struct vlc_player_input *input)
+{
+    /* Add this input to the stop list: it will be stopped by the
+     * destructor thread */
+    input->started = false;
+    assert(!vlc_list_HasInput(&player->destructor.stopping_inputs, input));
+    assert(!vlc_list_HasInput(&player->destructor.joinable_inputs, input));
+
+    vlc_list_append(&input->node, &player->destructor.inputs);
+    vlc_cond_signal(&input->player->destructor.wait);
+}
+
+static void
+vlc_player_destructor_AddStoppingInput(vlc_player_t *player,
+                                       struct vlc_player_input *input)
+{
+    /* Add this input to the stopping list */
+    if (vlc_list_HasInput(&player->destructor.inputs, input))
+        vlc_list_remove(&input->node);
+    if (!vlc_list_HasInput(&player->destructor.stopping_inputs, input))
+    {
+        vlc_list_append(&input->node, &player->destructor.stopping_inputs);
+        vlc_cond_signal(&input->player->destructor.wait);
+    }
+}
+
+static void
+vlc_player_destructor_AddJoinableInput(vlc_player_t *player,
+                                       struct vlc_player_input *input)
+{
+    /* Add this input to the joinable list: it will be deleted by the
+     * destructor thread */
+    assert(!vlc_list_HasInput(&player->destructor.inputs, input));
+    assert(!vlc_list_HasInput(&player->destructor.joinable_inputs, input));
+
+    assert(vlc_list_HasInput(&player->destructor.stopping_inputs, input));
+    vlc_list_remove(&input->node);
+
+    vlc_list_append(&input->node, &player->destructor.joinable_inputs);
+
+    vlc_cond_signal(&input->player->destructor.wait);
+}
+
+static bool vlc_player_destructor_IsEmpty(vlc_player_t *player)
+{
+    return vlc_list_is_empty(&player->destructor.inputs)
+        && vlc_list_is_empty(&player->destructor.stopping_inputs)
+        && vlc_list_is_empty(&player->destructor.joinable_inputs);
+}
+
+static void *
+vlc_player_destructor_Thread(void *data)
+{
+    vlc_player_t *player = data;
+
+    vlc_mutex_lock(&player->lock);
+    while (!player->deleting
+        || !vlc_player_destructor_IsEmpty(player))
+    {
+        struct vlc_player_input *input = NULL;
+        vlc_cond_wait(&player->destructor.wait, &player->lock);
+
+        vlc_list_foreach(input, &player->destructor.inputs, node)
+        {
+            vlc_list_remove(&input->node);
+
+            vlc_player_input_HandleState(input, VLC_PLAYER_STATE_STOPPING);
+            input_Stop(input->thread);
+        }
+
+        vlc_list_foreach(input, &player->destructor.joinable_inputs, node)
+        {
+            vlc_list_remove(&input->node);
+            vlc_player_input_Delete(input);
+        }
+    }
+    vlc_mutex_unlock(&player->lock);
+    return NULL;
+}
+
+static bool
+vlc_player_WaitRetryDelay(vlc_player_t *player)
+{
+    if (player->error_count)
+    {
+        /* Delay the next opening in case of error to avoid busy loops */
+        vlc_tick_t delay = RETRY_TIMEOUT_BASE;
+        for (unsigned i = 1; i < player->error_count
+          && delay < RETRY_TIMEOUT_MAX; ++i)
+            delay *= 2; /* Wait 100, 200, 400, 800, 1600 and finally 3200ms */
+        delay += vlc_tick_now();
+
+        while (player->error_count > 0
+            && vlc_cond_timedwait(&player->start_delay_cond, &player->lock,
+                                  delay) == 0);
+        if (player->error_count == 0)
+            return false; /* canceled */
+    }
+    return true;
+}
+
+static void
+vlc_player_input_HandleState(struct vlc_player_input *input,
+                             enum vlc_player_state state)
+{
+    vlc_player_t *player = input->player;
+
+    /* The STOPPING state can be set earlier by the player. In that case,
+     * ignore all future events except the STOPPED one */
+    if (input->state == VLC_PLAYER_STATE_STOPPING
+     && state != VLC_PLAYER_STATE_STOPPED)
+        return;
+
+    input->state = state;
+
+    /* Override the global state if the player is still playing and has a next
+     * media to play */
+    bool send_event = player->global_state != state;
+    switch (input->state)
+    {
+        case VLC_PLAYER_STATE_STOPPED:
+            assert(!input->started);
+            assert(input != player->input);
+
+            if (input->error != VLC_PLAYER_ERROR_NONE)
+                player->error_count++;
+            else
+                player->error_count = 0;
+
+            vlc_player_WaitRetryDelay(player);
+
+            if (!player->deleting)
+                vlc_player_OpenNextMedia(player);
+            if (!player->input)
+                player->started = false;
+
+            switch (player->media_stopped_action)
+            {
+                case VLC_PLAYER_MEDIA_STOPPED_EXIT:
+                    if (player->input && player->started)
+                        vlc_player_input_Start(player->input);
+                    else
+                        libvlc_Quit(player->obj.libvlc);
+                    break;
+                case VLC_PLAYER_MEDIA_STOPPED_CONTINUE:
+                    if (player->input && player->started)
+                        vlc_player_input_Start(player->input);
+                    break;
+                default:
+                    break;
+            }
+
+            send_event = !player->started;
+            break;
+        case VLC_PLAYER_STATE_STOPPING:
+            input->started = false;
+            if (input == player->input)
+                player->input = NULL;
+
+            if (player->started)
+            {
+                vlc_player_GetNextMedia(player);
+                if (!player->next_media)
+                    player->started = false;
+            }
+            send_event = !player->started;
+            vlc_player_destructor_AddStoppingInput(player, input);
+            break;
+        case VLC_PLAYER_STATE_STARTED:
+        case VLC_PLAYER_STATE_PLAYING:
+            if (player->started &&
+                player->global_state == VLC_PLAYER_STATE_PLAYING)
+                send_event = false;
+            break;
+
+        case VLC_PLAYER_STATE_PAUSED:
+            assert(player->started && input->started);
+            break;
+        default:
+            vlc_assert_unreachable();
+    }
+
+    if (send_event)
+    {
+        player->global_state = input->state;
+        vlc_player_SendEvent(player, on_state_changed, player->global_state);
+    }
+}
+
+size_t
+vlc_player_GetProgramCount(vlc_player_t *player)
+{
+    struct vlc_player_input *input = vlc_player_get_input_locked(player);
+
+    return input ? input->program_vector.size : 0;
+}
+
+const struct vlc_player_program *
+vlc_player_GetProgramAt(vlc_player_t *player, size_t index)
+{
+    struct vlc_player_input *input = vlc_player_get_input_locked(player);
+
+    if (!input)
+        return NULL;
+
+    assert(index < input->program_vector.size);
+    return input->program_vector.data[index];
+}
+
+const struct vlc_player_program *
+vlc_player_GetProgram(vlc_player_t *player, int id)
+{
+    struct vlc_player_input *input = vlc_player_get_input_locked(player);
+
+    if (!input)
+        return NULL;
+
+    struct vlc_player_program *prgm =
+        vlc_player_program_vector_FindById(&input->program_vector, id, NULL);
+    return prgm;
+}
+
+void
+vlc_player_SelectProgram(vlc_player_t *player, int id)
+{
+    struct vlc_player_input *input = vlc_player_get_input_locked(player);
+
+    if (input)
+        input_ControlPushHelper(input->thread, INPUT_CONTROL_SET_PROGRAM,
+                                &(vlc_value_t) { .i_int = id });
+}
+
+static void
+vlc_player_input_HandleProgramEvent(struct vlc_player_input *input,
+                                    const struct vlc_input_event_program *ev)
+{
+    vlc_player_t *player = input->player;
+    struct vlc_player_program *prgm;
+    vlc_player_program_vector *vec = &input->program_vector;
+
+    switch (ev->action)
+    {
+        case VLC_INPUT_PROGRAM_ADDED:
+            prgm = vlc_player_program_New(ev->id, ev->title);
+            if (!prgm)
+                break;
+
+            if (!vlc_vector_push(vec, prgm))
+            {
+                vlc_player_program_Delete(prgm);
+                break;
+            }
+            vlc_player_SendEvent(player, on_program_list_changed,
+                                 VLC_PLAYER_LIST_ADDED, prgm);
+            break;
+        case VLC_INPUT_PROGRAM_DELETED:
+        {
+            size_t idx;
+            prgm = vlc_player_program_vector_FindById(vec, ev->id, &idx);
+            if (prgm)
+            {
+                vlc_player_SendEvent(player, on_program_list_changed,
+                                     VLC_PLAYER_LIST_REMOVED, prgm);
+                vlc_vector_remove(vec, idx);
+                vlc_player_program_Delete(prgm);
+            }
+            break;
+        }
+        case VLC_INPUT_PROGRAM_UPDATED:
+        case VLC_INPUT_PROGRAM_SCRAMBLED:
+            prgm = vlc_player_program_vector_FindById(vec, ev->id, NULL);
+            if (!prgm)
+                break;
+            if (ev->action == VLC_INPUT_PROGRAM_UPDATED)
+            {
+                if (vlc_player_program_Update(prgm, ev->id, ev->title) != 0)
+                    break;
+            }
+            else
+                prgm->scrambled = ev->scrambled;
+            vlc_player_SendEvent(player, on_program_list_changed,
+                                 VLC_PLAYER_LIST_UPDATED, prgm);
+            break;
+        case VLC_INPUT_PROGRAM_SELECTED:
+        {
+            int unselected_id = -1, selected_id = -1;
+            vlc_vector_foreach(prgm, vec)
+            {
+                if (prgm->group_id == ev->id)
+                {
+                    if (!prgm->selected)
+                    {
+                        assert(selected_id == -1);
+                        prgm->selected = true;
+                        selected_id = prgm->group_id;
+                    }
+                }
+                else
+                {
+                    if (prgm->selected)
+                    {
+                        assert(unselected_id == -1);
+                        prgm->selected = false;
+                        unselected_id = prgm->group_id;
+                    }
+                }
+            }
+            if (unselected_id != -1 || selected_id != -1)
+                vlc_player_SendEvent(player, on_program_selection_changed,
+                                     unselected_id, selected_id);
+            break;
+        }
+        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 *
+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 *track = vec->data[i];
+        if (track->es_id == id)
+        {
+            if (idx)
+                *idx = i;
+            return track;
+        }
+    }
+    return NULL;
+}
+
+size_t
+vlc_player_GetTrackCount(vlc_player_t *player, enum es_format_category_e cat)
+{
+    struct vlc_player_input *input = vlc_player_get_input_locked(player);
+
+    if (!input)
+        return 0;
+    vlc_player_track_vector *vec = vlc_player_input_GetTrackVector(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)
+{
+    struct vlc_player_input *input = vlc_player_get_input_locked(player);
+
+    if (!input)
+        return NULL;
+    vlc_player_track_vector *vec = vlc_player_input_GetTrackVector(input, cat);
+    if (!vec)
+        return NULL;
+    assert(index < vec->size);
+    return vec->data[index];
+}
+
+const struct vlc_player_track *
+vlc_player_GetTrack(vlc_player_t *player, vlc_es_id_t *id)
+{
+    struct vlc_player_input *input = vlc_player_get_input_locked(player);
+
+    if (!input)
+        return NULL;
+    vlc_player_track_vector *vec =
+        vlc_player_input_GetTrackVector(input, vlc_es_id_GetCat(id));
+    if (!vec)
+        return NULL;
+    return vlc_player_track_vector_FindById(vec, id, NULL);
+}
+
+void
+vlc_player_SelectTrack(vlc_player_t *player, vlc_es_id_t *id)
+{
+    struct vlc_player_input *input = vlc_player_get_input_locked(player);
+
+    if (input)
+        input_ControlPushEsHelper(input->thread, INPUT_CONTROL_SET_ES, id);
+}
+
+void
+vlc_player_UnselectTrack(vlc_player_t *player, vlc_es_id_t *id)
+{
+    struct vlc_player_input *input = vlc_player_get_input_locked(player);
+
+    if (input)
+        input_ControlPushEsHelper(input->thread, INPUT_CONTROL_UNSET_ES, id);
+}
+
+void
+vlc_player_RestartTrack(vlc_player_t *player, vlc_es_id_t *id)
+{
+    struct vlc_player_input *input = vlc_player_get_input_locked(player);
+
+    if (input)
+        input_ControlPushEsHelper(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 void
+vlc_player_input_HandleTeletextMenu(struct vlc_player_input *input,
+                                    const struct vlc_input_event_es *ev)
+{
+    vlc_player_t *player = input->player;
+    switch (ev->action)
+    {
+        case VLC_INPUT_ES_ADDED:
+            if (input->teletext_menu)
+            {
+                msg_Warn(player, "Can't handle more than one teletext menu "
+                         "track. Using the last one.");
+                vlc_player_track_Delete(input->teletext_menu);
+            }
+            input->teletext_menu = vlc_player_track_New(ev->id, ev->title,
+                                                        ev->fmt);
+            if (!input->teletext_menu)
+                return;
+
+            vlc_player_SendEvent(player, on_teletext_menu_changed, true);
+            break;
+        case VLC_INPUT_ES_DELETED:
+        {
+            if (input->teletext_menu && input->teletext_menu->es_id == ev->id)
+            {
+                assert(!input->teletext_enabled);
+
+                vlc_player_track_Delete(input->teletext_menu);
+                input->teletext_menu = NULL;
+                vlc_player_SendEvent(player, on_teletext_menu_changed, false);
+            }
+            break;
+        }
+        case VLC_INPUT_ES_UPDATED:
+            break;
+        case VLC_INPUT_ES_SELECTED:
+        case VLC_INPUT_ES_UNSELECTED:
+            if (input->teletext_menu->es_id == ev->id)
+            {
+                input->teletext_enabled = ev->action == VLC_INPUT_ES_SELECTED;
+                vlc_player_SendEvent(player, on_teletext_enabled_changed,
+                                     input->teletext_enabled);
+            }
+            break;
+        default:
+            vlc_assert_unreachable();
+    }
+}
+
+void
+vlc_player_SetTeletextEnabled(vlc_player_t *player, bool enabled)
+{
+    struct vlc_player_input *input = vlc_player_get_input_locked(player);
+    if (!input || !input->teletext_menu)
+        return;
+    if (enabled)
+        vlc_player_SelectTrack(player, input->teletext_menu->es_id);
+    else
+        vlc_player_UnselectTrack(player, input->teletext_menu->es_id);
+}
+
+void
+vlc_player_SelectTeletextPage(vlc_player_t *player, unsigned page)
+{
+    struct vlc_player_input *input = vlc_player_get_input_locked(player);
+    if (!input || !input->teletext_menu)
+        return;
+
+    input_ControlPush(input->thread, INPUT_CONTROL_SET_VBI_PAGE,
+        &(input_control_param_t) {
+            .vbi_page.id = input->teletext_menu->es_id,
+            .vbi_page.page = page,
+    });
+}
+
+void
+vlc_player_SetTeletextTransparency(vlc_player_t *player, bool enabled)
+{
+    struct vlc_player_input *input = vlc_player_get_input_locked(player);
+    if (!input || !input->teletext_menu)
+        return;
+
+    input_ControlPush(input->thread, INPUT_CONTROL_SET_VBI_TRANSPARENCY,
+        &(input_control_param_t) {
+            .vbi_transparency.id = input->teletext_menu->es_id,
+            .vbi_transparency.enabled = enabled,
+    });
+}
+
+bool
+vlc_player_HasTeletextMenu(vlc_player_t *player)
+{
+    struct vlc_player_input *input = vlc_player_get_input_locked(player);
+    return input && input->teletext_menu;
+}
+
+bool
+vlc_player_IsTeletextEnabled(vlc_player_t *player)
+{
+    struct vlc_player_input *input = vlc_player_get_input_locked(player);
+    if (input && input->teletext_enabled)
+    {
+        assert(input->teletext_menu);
+        return true;
+    }
+    return false;
+}
+
+unsigned
+vlc_player_GetTeletextPage(vlc_player_t *player)
+{
+    struct vlc_player_input *input = vlc_player_get_input_locked(player);
+    return vlc_player_IsTeletextEnabled(player) ? input->teletext_page : 0;
+}
+
+bool
+vlc_player_IsTeletextTransparent(vlc_player_t *player)
+{
+    struct vlc_player_input *input = vlc_player_get_input_locked(player);
+    return vlc_player_IsTeletextEnabled(player) && input->teletext_transparent;
+}
+
+static void
+vlc_player_input_HandleEsEvent(struct vlc_player_input *input,
+                               const struct vlc_input_event_es *ev)
+{
+    assert(ev->id && ev->title && ev->fmt);
+
+    if (ev->fmt->i_cat == SPU_ES && ev->fmt->i_codec == VLC_CODEC_TELETEXT
+     && (ev->fmt->subs.teletext.i_magazine == 1
+      || ev->fmt->subs.teletext.i_magazine == -1))
+    {
+        vlc_player_input_HandleTeletextMenu(input, ev);
+        return;
+    }
+
+    vlc_player_track_vector *vec =
+        vlc_player_input_GetTrackVector(input, ev->fmt->i_cat);
+    if (!vec)
+        return; /* UNKNOWN_ES or DATA_ES not handled */
+
+    vlc_player_t *player = input->player;
+    struct vlc_player_track *track;
+    switch (ev->action)
+    {
+        case VLC_INPUT_ES_ADDED:
+            track = vlc_player_track_New(ev->id, ev->title, ev->fmt);
+            if (!track)
+                break;
+
+            if (!vlc_vector_push(vec, track))
+            {
+                vlc_player_track_Delete(track);
+                break;
+            }
+            vlc_player_SendEvent(player, on_track_list_changed,
+                                 VLC_PLAYER_LIST_ADDED, track);
+            break;
+        case VLC_INPUT_ES_DELETED:
+        {
+            size_t idx;
+            track = vlc_player_track_vector_FindById(vec, ev->id, &idx);
+            if (track)
+            {
+                vlc_player_SendEvent(player, on_track_list_changed,
+                                     VLC_PLAYER_LIST_REMOVED, track);
+                vlc_vector_remove(vec, idx);
+                vlc_player_track_Delete(track);
+            }
+            break;
+        }
+        case VLC_INPUT_ES_UPDATED:
+            track = vlc_player_track_vector_FindById(vec, ev->id, NULL);
+            if (!track)
+                break;
+            if (vlc_player_track_Update(track, ev->title, ev->fmt) != 0)
+                break;
+            vlc_player_SendEvent(player, on_track_list_changed,
+                                 VLC_PLAYER_LIST_UPDATED, track);
+            break;
+        case VLC_INPUT_ES_SELECTED:
+            track = vlc_player_track_vector_FindById(vec, ev->id, NULL);
+            if (track)
+            {
+                track->selected = true;
+                vlc_player_SendEvent(player, on_track_selection_changed,
+                                     NULL, track->es_id);
+            }
+            break;
+        case VLC_INPUT_ES_UNSELECTED:
+            track = vlc_player_track_vector_FindById(vec, ev->id, NULL);
+            if (track)
+            {
+                track->selected = false;
+                vlc_player_SendEvent(player, on_track_selection_changed,
+                                     track->es_id, NULL);
+            }
+            break;
+        default:
+            vlc_assert_unreachable();
+    }
+}
+
+static void
+vlc_player_input_HandleTitleEvent(struct vlc_player_input *input,
+                                  const struct vlc_input_event_title *ev)
+{
+    vlc_player_t *player = input->player;
+    switch (ev->action)
+    {
+        case VLC_INPUT_TITLE_NEW_LIST:
+        {
+            input_thread_private_t *input_th = input_priv(input->thread);
+            const int title_offset = input_th->i_title_offset;
+            const int chapter_offset = input_th->i_seekpoint_offset;
+
+            if (input->titles)
+                vlc_player_title_list_Release(input->titles);
+            input->title_selected = input->chapter_selected = 0;
+            input->titles =
+                vlc_player_title_list_Create(ev->list.array, ev->list.count,
+                                             title_offset, chapter_offset);
+            vlc_player_SendEvent(player, on_titles_changed, input->titles);
+            if (input->titles)
+                vlc_player_SendEvent(player, on_title_selection_changed,
+                                     &input->titles->array[0], 0);
+            break;
+        }
+        case VLC_INPUT_TITLE_SELECTED:
+            if (!input->titles)
+                return; /* a previous VLC_INPUT_TITLE_NEW_LIST failed */
+            assert(ev->selected_idx < input->titles->count);
+            input->title_selected = ev->selected_idx;
+            vlc_player_SendEvent(player, on_title_selection_changed,
+                                 &input->titles->array[input->title_selected],
+                                 input->title_selected);
+            break;
+        default:
+            vlc_assert_unreachable();
+    }
+}
+
+static void
+vlc_player_input_HandleChapterEvent(struct vlc_player_input *input,
+                                    const struct vlc_input_event_chapter *ev)
+{
+    vlc_player_t *player = input->player;
+    if (!input->titles || ev->title < 0 || ev->seekpoint < 0)
+        return; /* a previous VLC_INPUT_TITLE_NEW_LIST failed */
+
+    assert((size_t)ev->title < input->titles->count);
+    const struct vlc_player_title *title = &input->titles->array[ev->title];
+    if (!title->chapter_count)
+        return;
+
+    assert(ev->seekpoint < (int)title->chapter_count);
+    input->title_selected = ev->title;
+    input->chapter_selected = ev->seekpoint;
+
+    const struct vlc_player_chapter *chapter = &title->chapters[ev->seekpoint];
+    vlc_player_SendEvent(player, on_chapter_selection_changed, title, ev->title,
+                         chapter, ev->seekpoint);
+}
+
+struct vlc_player_title_list *
+vlc_player_GetTitleList(vlc_player_t *player)
+{
+    struct vlc_player_input *input = vlc_player_get_input_locked(player);
+    return input ? input->titles : NULL;
+}
+
+ssize_t
+vlc_player_GetSelectedTitleIdx(vlc_player_t *player)
+{
+    struct vlc_player_input *input = vlc_player_get_input_locked(player);
+
+    if (!input)
+        return -1;
+    return input->title_selected;
+}
+
+static ssize_t
+vlc_player_GetTitleIdx(vlc_player_t *player,
+                       const struct vlc_player_title *title)
+{
+    struct vlc_player_input *input = vlc_player_get_input_locked(player);
+    if (input && input->titles)
+        for (size_t i = 0; i < input->titles->count; ++i)
+            if (&input->titles->array[i] == title)
+                return i;
+    return -1;
+}
+
+void
+vlc_player_SelectTitleIdx(vlc_player_t *player, size_t index)
+{
+    struct vlc_player_input *input = vlc_player_get_input_locked(player);
+    if (input)
+        input_ControlPushHelper(input->thread, INPUT_CONTROL_SET_TITLE,
+                                &(vlc_value_t){ .i_int = index });
+}
+
+void
+vlc_player_SelectTitle(vlc_player_t *player,
+                       const struct vlc_player_title *title)
+{
+    ssize_t idx = vlc_player_GetTitleIdx(player, title);
+    if (idx != -1)
+        vlc_player_SelectTitleIdx(player, idx);
+}
+
+void
+vlc_player_SelectChapter(vlc_player_t *player,
+                         const struct vlc_player_title *title,
+                         size_t chapter_idx)
+{
+    ssize_t idx = vlc_player_GetTitleIdx(player, title);
+    if (idx != -1 && idx == vlc_player_GetSelectedTitleIdx(player))
+        vlc_player_SelectChapterIdx(player, chapter_idx);
+}
+
+void
+vlc_player_SelectNextTitle(vlc_player_t *player)
+{
+    struct vlc_player_input *input = vlc_player_get_input_locked(player);
+    if (input)
+        input_ControlPush(input->thread, INPUT_CONTROL_SET_TITLE_NEXT, NULL);
+}
+
+void
+vlc_player_SelectPrevTitle(vlc_player_t *player)
+{
+    struct vlc_player_input *input = vlc_player_get_input_locked(player);
+    if (input)
+        input_ControlPush(input->thread, INPUT_CONTROL_SET_TITLE_PREV, NULL);
+}
+
+ssize_t
+vlc_player_GetSelectedChapterIdx(vlc_player_t *player)
+{
+    struct vlc_player_input *input = vlc_player_get_input_locked(player);
+
+    if (!input)
+        return -1;
+    return input->chapter_selected;
+}
+
+void
+vlc_player_SelectChapterIdx(vlc_player_t *player, size_t index)
+{
+    struct vlc_player_input *input = vlc_player_get_input_locked(player);
+    if (input)
+        input_ControlPushHelper(input->thread, INPUT_CONTROL_SET_SEEKPOINT,
+                                &(vlc_value_t){ .i_int = index });
+}
+
+void
+vlc_player_SelectNextChapter(vlc_player_t *player)
+{
+    struct vlc_player_input *input = vlc_player_get_input_locked(player);
+    if (input)
+        input_ControlPush(input->thread, INPUT_CONTROL_SET_SEEKPOINT_NEXT, NULL);
+}
+
+void
+vlc_player_SelectPrevChapter(vlc_player_t *player)
+{
+    struct vlc_player_input *input = vlc_player_get_input_locked(player);
+    if (input)
+        input_ControlPush(input->thread, INPUT_CONTROL_SET_SEEKPOINT_PREV, NULL);
+}
+
+static void
+vlc_player_input_HandleVoutEvent(struct vlc_player_input *input,
+                                 const struct vlc_input_event_vout *ev)
+{
+    assert(ev->vout);
+
+    vlc_player_t *player = input->player;
+    enum vlc_player_list_action action;
+    switch (ev->action)
+    {
+        case VLC_INPUT_EVENT_VOUT_ADDED:
+            action = VLC_PLAYER_LIST_ADDED;
+            break;
+        case VLC_INPUT_EVENT_VOUT_DELETED:
+            action = VLC_PLAYER_LIST_REMOVED;
+            /* Un-register vout callbacks before the vout list event */
+            var_DelCallback(ev->vout, "fullscreen",
+                            vlc_player_VoutCallback, player);
+            var_DelCallback(ev->vout, "video-wallpaper",
+                            vlc_player_VoutCallback, player);
+            break;
+        default:
+            vlc_assert_unreachable();
+    }
+    vlc_player_SendEvent(player, on_vout_list_changed, action, ev->vout);
+
+    /* Register vout callbacks after the vout list event */
+    if (ev->action == VLC_INPUT_EVENT_VOUT_ADDED)
+    {
+        var_AddCallback(ev->vout, "fullscreen",
+                        vlc_player_VoutCallback, player);
+        var_AddCallback(ev->vout, "video-wallpaper",
+                        vlc_player_VoutCallback, player);
+    }
+}
+
+static void
+vlc_player_input_HandleStateEvent(struct vlc_player_input *input,
+                                  input_state_e state)
+{
+    switch (state)
+    {
+        case OPENING_S:
+            vlc_player_input_HandleState(input, VLC_PLAYER_STATE_STARTED);
+            break;
+        case PLAYING_S:
+            vlc_player_input_HandleState(input, VLC_PLAYER_STATE_PLAYING);
+            break;
+        case PAUSE_S:
+            vlc_player_input_HandleState(input, VLC_PLAYER_STATE_PAUSED);
+            break;
+        case END_S:
+            vlc_player_input_HandleState(input, VLC_PLAYER_STATE_STOPPING);
+            break;
+        case ERROR_S:
+            /* Contrary to the input_thead_t, an error is not a state */
+            input->error = VLC_PLAYER_ERROR_GENERIC;
+            vlc_player_SendEvent(input->player, on_error_changed, input->error);
+            break;
+        default:
+            vlc_assert_unreachable();
+    }
+}
+
+static void
+vlc_player_HandleAtoBLoop(vlc_player_t *player)
+{
+    struct vlc_player_input *input = vlc_player_get_input_locked(player);
+    assert(input);
+    assert(input->abloop_state[0].set && input->abloop_state[1].set);
+
+    if (input->position_ms != VLC_TICK_INVALID
+     && input->abloop_state[0].time != VLC_TICK_INVALID
+     && input->abloop_state[1].time != VLC_TICK_INVALID)
+    {
+        if (input->position_ms >= input->abloop_state[1].time)
+            vlc_player_SetTime(player, input->abloop_state[0].time);
+    }
+    else if (input->position_percent >= input->abloop_state[1].pos)
+        vlc_player_SetPosition(player, input->abloop_state[0].pos);
+}
+
+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);
+
+    switch (event->type)
+    {
+        case INPUT_EVENT_STATE:
+            vlc_player_input_HandleStateEvent(input, event->state);
+            break;
+        case INPUT_EVENT_RATE:
+            input->rate = event->rate;
+            vlc_player_SendEvent(player, on_rate_changed, input->rate);
+            break;
+        case INPUT_EVENT_CAPABILITIES:
+            input->capabilities = event->capabilities;
+            vlc_player_SendEvent(player, on_capabilities_changed,
+                                 input->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_OpenNextMedia(player);
+#endif
+            if (input->position_ms != event->position.ms ||
+                input->position_percent != event->position.percentage)
+            {
+                input->position_ms = event->position.ms;
+                input->position_percent = event->position.percentage;
+                vlc_player_SendEvent(player, on_position_changed,
+                                     input->position_ms,
+                                     input->position_percent);
+
+                if (input->abloop_state[0].set && input->abloop_state[1].set
+                 && input == player->input)
+                    vlc_player_HandleAtoBLoop(player);
+            }
+            break;
+        case INPUT_EVENT_LENGTH:
+            if (input->length != event->length)
+            {
+                input->length = event->length;
+                vlc_player_SendEvent(player, on_length_changed, input->length);
+            }
+            break;
+        case INPUT_EVENT_PROGRAM:
+            vlc_player_input_HandleProgramEvent(input, &event->program);
+            break;
+        case INPUT_EVENT_ES:
+            vlc_player_input_HandleEsEvent(input, &event->es);
+            break;
+        case INPUT_EVENT_TITLE:
+            vlc_player_input_HandleTitleEvent(input, &event->title);
+            break;
+        case INPUT_EVENT_CHAPTER:
+            vlc_player_input_HandleChapterEvent(input, &event->chapter);
+            break;
+        case INPUT_EVENT_RECORD:
+            input->recording = event->record;
+            vlc_player_SendEvent(player, on_recording_changed, input->recording);
+            break;
+        case INPUT_EVENT_STATISTICS:
+            input->stats = *event->stats;
+            vlc_player_SendEvent(player, on_statistics_changed, &input->stats);
+            break;
+        case INPUT_EVENT_SIGNAL:
+            input->signal_quality = event->signal.quality;
+            input->signal_strength = event->signal.strength;
+            vlc_player_SendEvent(player, on_signal_changed,
+                                 input->signal_quality, input->signal_strength);
+            break;
+        case INPUT_EVENT_AUDIO_DELAY:
+            input->audio_delay = event->audio_delay;
+            vlc_player_SendEvent(player, on_audio_delay_changed,
+                                 input->audio_delay);
+            break;
+        case INPUT_EVENT_SUBTITLE_DELAY:
+            input->subtitle_delay = event->subtitle_delay;
+            vlc_player_SendEvent(player, on_subtitle_delay_changed,
+                                 input->subtitle_delay);
+            break;
+        case INPUT_EVENT_CACHE:
+            input->cache = event->cache;
+            vlc_player_SendEvent(player, on_buffering_changed, event->cache);
+            break;
+        case INPUT_EVENT_VOUT:
+            vlc_player_input_HandleVoutEvent(input, &event->vout);
+            break;
+        case INPUT_EVENT_ITEM_META:
+            vlc_player_SendEvent(player, on_media_meta_changed,
+                                 input_GetItem(input->thread));
+            break;
+        case INPUT_EVENT_ITEM_EPG:
+            vlc_player_SendEvent(player, on_media_epg_changed,
+                                 input_GetItem(input->thread));
+            break;
+        case INPUT_EVENT_SUBITEMS:
+            vlc_player_SendEvent(player, on_media_subitems_changed,
+                                 input_GetItem(input->thread), event->subitems);
+            break;
+        case INPUT_EVENT_DEAD:
+            assert(!input->started);
+            vlc_player_destructor_AddJoinableInput(player, input);
+            break;
+        case INPUT_EVENT_VBI_PAGE:
+            input->teletext_page = event->vbi_page < 999 ? event->vbi_page : 100;
+            vlc_player_SendEvent(player, on_teletext_page_changed,
+                                 input->teletext_page);
+            break;
+        case INPUT_EVENT_VBI_TRANSPARENCY:
+            input->teletext_transparent = event->vbi_transparent;
+            vlc_player_SendEvent(player, on_teletext_transparency_changed,
+                                 input->teletext_transparent);
+            break;
+        default:
+            break;
+    }
+
+    vlc_mutex_unlock(&player->lock);
+}
+
+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);
+}
+
+void
+vlc_player_CondWait(vlc_player_t *player, vlc_cond_t *cond)
+{
+    vlc_player_assert_locked(player);
+    vlc_cond_wait(cond, &player->lock);
+}
+
+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);
+
+    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,
+                          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);
+
+    vlc_player_CancelWaitError(player);
+
+    vlc_player_InvalidateNextMedia(player);
+
+    if (media)
+    {
+        /* Switch to this new media when the current input is stopped */
+        player->next_media = input_item_Hold(media);
+        player->releasing_media = false;
+        player->has_next_media = true;
+    }
+    else
+    {
+        /* The current media will be set to NULL once the current input is
+         * stopped */
+        player->releasing_media = true;
+        player->has_next_media = false;
+    }
+
+    if (player->input)
+    {
+        vlc_player_destructor_AddInput(player, player->input);
+        player->input = NULL;
+    }
+
+    assert(media == player->next_media);
+    if (!vlc_player_destructor_IsEmpty(player))
+    {
+        /* This media will be opened when the input is finally stopped */
+        return VLC_SUCCESS;
+    }
+
+    /* We can switch to the next media directly */
+    return vlc_player_OpenNextMedia(player);
+}
+
+input_item_t *
+vlc_player_GetCurrentMedia(vlc_player_t *player)
+{
+    vlc_player_assert_locked(player);
+
+    return player->media;
+}
+
+int
+vlc_player_AddAssociatedMedia(vlc_player_t *player,
+                              enum es_format_category_e cat, const char *uri,
+                              bool select, bool notify, bool check_ext)
+{
+    struct vlc_player_input *input = vlc_player_get_input_locked(player);
+
+    if (!input)
+        return VLC_EGENERIC;
+
+    enum slave_type type;
+    switch (cat)
+    {
+        case AUDIO_ES:
+            type = SLAVE_TYPE_AUDIO;
+            break;
+        case SPU_ES:
+            type = SLAVE_TYPE_SPU;
+            break;
+        default:
+            return VLC_EGENERIC;
+    }
+    return input_AddSlave(input->thread, type, uri, select, notify, check_ext);
+}
+
+void
+vlc_player_SetAssociatedSubsFPS(vlc_player_t *player, float fps)
+{
+    struct vlc_player_input *input = vlc_player_get_input_locked(player);
+
+    var_SetFloat(player, "sub-fps", fps);
+    if (input)
+        input_ControlPushHelper(input->thread, INPUT_CONTROL_SET_SUBS_FPS,
+                                &(vlc_value_t) { .f_float = fps });
+    vlc_player_SendEvent(player, on_associated_subs_fps_changed, fps);
+}
+
+float
+vlc_player_GetAssociatedSubsFPS(vlc_player_t *player)
+{
+    vlc_player_assert_locked(player);
+    return var_GetFloat(player, "sub-fps");
+}
+
+void
+vlc_player_InvalidateNextMedia(vlc_player_t *player)
+{
+    vlc_player_assert_locked(player);
+    if (player->next_media)
+    {
+        input_item_Release(player->next_media);
+        player->next_media = NULL;
+    }
+    player->has_next_media = false;
+
+#if GAPLESS
+    if (player->next_input)
+    {
+        /* Cause the get_next callback to be called when this input is
+         * dead */
+        vlc_player_destructor_AddInput(player, player->next_input);
+        player->next_input = NULL;
+    }
+#endif
+}
+
+int
+vlc_player_Start(vlc_player_t *player)
+{
+    vlc_player_assert_locked(player);
+
+    vlc_player_CancelWaitError(player);
+
+    if (player->started)
+        return VLC_SUCCESS;
+
+    if (!vlc_player_destructor_IsEmpty(player))
+    {
+        if (player->next_media)
+        {
+            player->started = true;
+            return VLC_SUCCESS;
+        }
+        else
+            return VLC_EGENERIC;
+    }
+
+    if (!player->media)
+        return VLC_EGENERIC;
+
+    if (!player->input)
+    {
+        /* Possible if the player was stopped by the user */
+        player->input = vlc_player_input_New(player, player->media);
+
+        if (!player->input)
+            return VLC_ENOMEM;
+    }
+    assert(!player->input->started);
+
+    if (player->start_paused)
+    {
+        var_Create(player->input->thread, "start-paused", VLC_VAR_BOOL);
+        var_SetBool(player->input->thread, "start-paused", true);
+    }
+
+    int ret = vlc_player_input_Start(player->input);
+    if (ret == VLC_SUCCESS)
+        player->started = true;
+    return ret;
+}
+
+void
+vlc_player_Stop(vlc_player_t *player)
+{
+    struct vlc_player_input *input = vlc_player_get_input_locked(player);
+
+    vlc_player_CancelWaitError(player);
+
+    if (!input || !player->started)
+        return;
+    player->started = false;
+
+    vlc_player_destructor_AddInput(player, input);
+    player->input = NULL;
+
+#if GAPLESS
+    if (player->next_input)
+    {
+        vlc_player_destructor_AddInput(player, next_input);
+        player->next_input = NULL;
+    }
+#endif
+}
+
+void
+vlc_player_SetMediaStoppedAction(vlc_player_t *player,
+                                 enum vlc_player_media_stopped_action action)
+{
+    vlc_player_assert_locked(player);
+    player->media_stopped_action = action;
+    var_SetBool(player, "play-and-pause",
+                action == VLC_PLAYER_MEDIA_STOPPED_PAUSE);
+    vlc_player_SendEvent(player, on_media_stopped_action_changed, action);
+}
+
+void
+vlc_player_SetStartPaused(vlc_player_t *player, bool start_paused)
+{
+    vlc_player_assert_locked(player);
+    player->start_paused = start_paused;
+}
+
+static void
+vlc_player_SetPause(vlc_player_t *player, bool pause)
+{
+    struct vlc_player_input *input = vlc_player_get_input_locked(player);
+
+    if (!input || !input->started)
+        return;
+
+    vlc_value_t val = { .i_int = pause ? PAUSE_S : PLAYING_S };
+    input_ControlPushHelper(input->thread, INPUT_CONTROL_SET_STATE, &val);
+}
+
+void
+vlc_player_Pause(vlc_player_t *player)
+{
+    vlc_player_SetPause(player, true);
+}
+
+void
+vlc_player_Resume(vlc_player_t *player)
+{
+    vlc_player_SetPause(player, false);
+}
+
+void
+vlc_player_NextVideoFrame(vlc_player_t *player)
+{
+    struct vlc_player_input *input = vlc_player_get_input_locked(player);
+    if (input)
+        input_ControlPushHelper(input->thread, INPUT_CONTROL_SET_FRAME_NEXT,
+                                NULL);
+}
+
+enum vlc_player_state
+vlc_player_GetState(vlc_player_t *player)
+{
+    vlc_player_assert_locked(player);
+    return player->global_state;
+}
+
+enum vlc_player_error
+vlc_player_GetError(vlc_player_t *player)
+{
+    struct vlc_player_input *input = vlc_player_get_input_locked(player);
+    return input ? input->error : VLC_PLAYER_ERROR_NONE;
+}
+
+int
+vlc_player_GetCapabilities(vlc_player_t *player)
+{
+    struct vlc_player_input *input = vlc_player_get_input_locked(player);
+    return input ? input->capabilities : 0;
+}
+
+float
+vlc_player_GetRate(vlc_player_t *player)
+{
+    struct vlc_player_input *input = vlc_player_get_input_locked(player);
+    if (input)
+        return input->rate;
+    else
+        return var_GetFloat(player, "rate");
+}
+
+void
+vlc_player_ChangeRate(vlc_player_t *player, float rate)
+{
+    struct vlc_player_input *input = vlc_player_get_input_locked(player);
+
+    if (rate == 0.0)
+        return;
+
+    /* Save rate accross inputs */
+    var_SetFloat(player, "rate", rate);
+
+    if (input)
+    {
+        input_ControlPushHelper(input->thread, INPUT_CONTROL_SET_RATE,
+            &(vlc_value_t) { .i_int = INPUT_RATE_DEFAULT / rate });
+    }
+    else
+        vlc_player_SendEvent(player, on_rate_changed, rate);
+}
+
+static void
+vlc_player_ChangeRateOffset(vlc_player_t *player, bool increment)
+{
+    static const float rates[] = {
+        1.0/64, 1.0/32, 1.0/16, 1.0/8, 1.0/4, 1.0/3, 1.0/2, 2.0/3,
+        1.0/1,
+        3.0/2, 2.0/1, 3.0/1, 4.0/1, 8.0/1, 16.0/1, 32.0/1, 64.0/1,
+    };
+    float rate = vlc_player_GetRate(player) * (increment ? 1.1f : 0.9f);
+
+    /* find closest rate (if any) in the desired direction */
+    for (size_t i = 0; i < ARRAY_SIZE(rates); ++i)
+    {
+        if ((increment && rates[i] > rate) ||
+            (!increment && rates[i] >= rate && i))
+        {
+            rate = increment ? rates[i] : rates[i-1];
+            break;
+        }
+    }
+
+    vlc_player_ChangeRate(player, rate);
+}
+
+void
+vlc_player_IncrementRate(vlc_player_t *player)
+{
+    vlc_player_ChangeRateOffset(player, true);
+}
+
+void
+vlc_player_DecrementRate(vlc_player_t *player)
+{
+    vlc_player_ChangeRateOffset(player, false);
+}
+
+vlc_tick_t
+vlc_player_GetLength(vlc_player_t *player)
+{
+    struct vlc_player_input *input = vlc_player_get_input_locked(player);
+    return input ? input->length : VLC_TICK_INVALID;
+}
+
+vlc_tick_t
+vlc_player_GetTime(vlc_player_t *player)
+{
+    struct vlc_player_input *input = vlc_player_get_input_locked(player);
+
+    if (!input || input->position_ms == VLC_TICK_INVALID)
+        return VLC_TICK_INVALID;
+
+    return input->position_ms;
+}
+
+float
+vlc_player_GetPosition(vlc_player_t *player)
+{
+    struct vlc_player_input *input = vlc_player_get_input_locked(player);
+
+    return input ? input->position_percent : -1.f;
+}
+
+static inline void
+vlc_player_assert_seek_params(enum vlc_player_seek_speed speed,
+                              enum vlc_player_whence whence)
+{
+    assert(speed == VLC_PLAYER_SEEK_PRECISE
+        || speed == VLC_PLAYER_SEEK_FAST);
+    assert(whence == VLC_PLAYER_WHENCE_ABSOLUTE
+        || whence == VLC_PLAYER_WHENCE_RELATIVE);
+    (void) speed; (void) whence;
+}
+
+void
+vlc_player_SeekByPos(vlc_player_t *player, float position,
+                     enum vlc_player_seek_speed speed,
+                     enum vlc_player_whence whence)
+{
+    struct vlc_player_input *input = vlc_player_get_input_locked(player);
+    vlc_player_assert_seek_params(speed, whence);
+
+    const int type =
+        whence == VLC_PLAYER_WHENCE_ABSOLUTE ? INPUT_CONTROL_SET_POSITION
+                                             : INPUT_CONTROL_JUMP_POSITION;
+    if (input)
+        input_ControlPush(input->thread, type,
+            &(input_control_param_t) {
+                .pos.f_val = position,
+                .pos.b_fast_seek = speed == VLC_PLAYER_SEEK_FAST,
+        });
+}
+
+void
+vlc_player_SeekByTime(vlc_player_t *player, vlc_tick_t time,
+                      enum vlc_player_seek_speed speed,
+                      enum vlc_player_whence whence)
+{
+    struct vlc_player_input *input = vlc_player_get_input_locked(player);
+    vlc_player_assert_seek_params(speed, whence);
+
+    const int type =
+        whence == VLC_PLAYER_WHENCE_ABSOLUTE ? INPUT_CONTROL_SET_TIME
+                                             : INPUT_CONTROL_JUMP_TIME;
+    if (input)
+        input_ControlPush(input->thread, type,
+            &(input_control_param_t) {
+                .time.i_val = time,
+                .time.b_fast_seek = speed == VLC_PLAYER_SEEK_FAST,
+        });
+}
+
+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 = renderer ? vlc_renderer_item_hold(renderer) : NULL;
+
+    vlc_player_foreach_inputs(input)
+    {
+        vlc_value_t val = {
+            .p_address = renderer ? vlc_renderer_item_hold(renderer) : NULL
+        };
+        input_ControlPushHelper(input->thread, INPUT_CONTROL_SET_RENDERER,
+                                &val);
+    }
+    vlc_player_SendEvent(player, on_renderer_changed, player->renderer);
+}
+
+vlc_renderer_item_t *
+vlc_player_GetRenderer(vlc_player_t *player)
+{
+    vlc_player_assert_locked(player);
+    return player->renderer;
+}
+
+int
+vlc_player_SetAtoBLoop(vlc_player_t *player, enum vlc_player_abloop abloop)
+{
+    struct vlc_player_input *input = vlc_player_get_input_locked(player);
+
+    if (!input || !vlc_player_CanSeek(player))
+        return VLC_EGENERIC;
+
+    vlc_tick_t time = vlc_player_GetTime(player);
+    float pos = vlc_player_GetPosition(player);
+    int ret = VLC_SUCCESS;
+    switch (abloop)
+    {
+        case VLC_PLAYER_ABLOOP_A:
+            if (input->abloop_state[1].set)
+                return VLC_EGENERIC;
+            input->abloop_state[0].time = time;
+            input->abloop_state[0].pos = pos;
+            input->abloop_state[0].set = true;
+            break;
+        case VLC_PLAYER_ABLOOP_B:
+            if (!input->abloop_state[0].set)
+                return VLC_EGENERIC;
+            input->abloop_state[1].time = time;
+            input->abloop_state[1].pos = pos;
+            input->abloop_state[1].set = true;
+            if (input->abloop_state[0].time != VLC_TICK_INVALID
+             && time != VLC_TICK_INVALID)
+            {
+                if (time > input->abloop_state[0].time)
+                {
+                    vlc_player_SetTime(player, input->abloop_state[0].time);
+                    break;
+                }
+            }
+            else if (pos > input->abloop_state[0].pos)
+            {
+                vlc_player_SetPosition(player, input->abloop_state[0].pos);
+                break;
+            }
+
+            /* Error: A time is superior to B time. */
+            abloop = VLC_PLAYER_ABLOOP_NONE;
+            ret = VLC_EGENERIC;
+            /* fall-through */
+        case VLC_PLAYER_ABLOOP_NONE:
+            input->abloop_state[0].set = input->abloop_state[1].set = false;
+            time = VLC_TICK_INVALID;
+            pos = 0.f;
+            break;
+        default:
+            vlc_assert_unreachable();
+    }
+    vlc_player_SendEvent(player, on_atobloop_changed, abloop, time, pos);
+    return ret;
+}
+
+enum vlc_player_abloop
+vlc_player_GetAtoBLoop(vlc_player_t *player, vlc_tick_t *a_time, float *a_pos,
+                       vlc_tick_t *b_time, float *b_pos)
+{
+    struct vlc_player_input *input = vlc_player_get_input_locked(player);
+
+    if (!input || !vlc_player_CanSeek(player) || !input->abloop_state[0].set)
+        return VLC_PLAYER_ABLOOP_NONE;
+
+    if (a_time)
+        *a_time = input->abloop_state[0].time;
+    if (a_pos)
+        *a_pos = input->abloop_state[0].pos;
+    if (!input->abloop_state[1].set)
+        return VLC_PLAYER_ABLOOP_A;
+
+    if (b_time)
+        *b_time = input->abloop_state[1].time;
+    if (b_pos)
+        *b_pos = input->abloop_state[1].pos;
+    return VLC_PLAYER_ABLOOP_B;
+}
+
+void
+vlc_player_Navigate(vlc_player_t *player, enum vlc_player_nav nav)
+{
+    struct vlc_player_input *input = vlc_player_get_input_locked(player);
+
+    if (!input)
+        return;
+
+    enum input_control_e control;
+    switch (nav)
+    {
+        case VLC_PLAYER_NAV_ACTIVATE:
+            control = INPUT_CONTROL_NAV_ACTIVATE;
+            break;
+        case VLC_PLAYER_NAV_UP:
+            control = INPUT_CONTROL_NAV_UP;
+            break;
+        case VLC_PLAYER_NAV_DOWN:
+            control = INPUT_CONTROL_NAV_DOWN;
+            break;
+        case VLC_PLAYER_NAV_LEFT:
+            control = INPUT_CONTROL_NAV_LEFT;
+            break;
+        case VLC_PLAYER_NAV_RIGHT:
+            control = INPUT_CONTROL_NAV_RIGHT;
+            break;
+        case VLC_PLAYER_NAV_POPUP:
+            control = INPUT_CONTROL_NAV_POPUP;
+            break;
+        case VLC_PLAYER_NAV_MENU:
+            control = INPUT_CONTROL_NAV_MENU;
+            break;
+        default:
+            vlc_assert_unreachable();
+    }
+    input_ControlPushHelper(input->thread, control, NULL);
+}
+
+bool
+vlc_player_IsRecording(vlc_player_t *player)
+{
+    struct vlc_player_input *input = vlc_player_get_input_locked(player);
+
+    return input ? input->recording : false;
+}
+
+void
+vlc_player_SetRecordingEnabled(vlc_player_t *player, bool enabled)
+{
+    struct vlc_player_input *input = vlc_player_get_input_locked(player);
+    if (input)
+        input_ControlPushHelper(input->thread, INPUT_CONTROL_SET_RECORD_STATE,
+                                &(vlc_value_t) { .b_bool = enabled });
+}
+
+void
+vlc_player_SetAudioDelay(vlc_player_t *player, vlc_tick_t delay,
+                         enum vlc_player_whence whence)
+{
+    struct vlc_player_input *input = vlc_player_get_input_locked(player);
+    if (input)
+        input_ControlPush(input->thread, INPUT_CONTROL_SET_AUDIO_DELAY,
+            &(input_control_param_t) {
+                .delay = {
+                    .b_absolute = whence == VLC_PLAYER_WHENCE_ABSOLUTE,
+                    .i_val = delay,
+                },
+        });
+}
+
+vlc_tick_t
+vlc_player_GetAudioDelay(vlc_player_t *player)
+{
+    struct vlc_player_input *input = vlc_player_get_input_locked(player);
+    return input ? input->audio_delay : 0;
+}
+
+void
+vlc_player_SetSubtitleDelay(vlc_player_t *player, vlc_tick_t delay,
+                            enum vlc_player_whence whence)
+{
+    struct vlc_player_input *input = vlc_player_get_input_locked(player);
+
+    if (input)
+        input_ControlPush(input->thread, INPUT_CONTROL_SET_SPU_DELAY,
+            &(input_control_param_t) {
+                .delay = {
+                    .b_absolute = whence == VLC_PLAYER_WHENCE_ABSOLUTE,
+                    .i_val = delay,
+                },
+        });
+}
+
+vlc_tick_t
+vlc_player_GetSubtitleDelay(vlc_player_t *player)
+{
+    struct vlc_player_input *input = vlc_player_get_input_locked(player);
+    return input ? input->subtitle_delay : 0;
+}
+
+int
+vlc_player_GetSignal(vlc_player_t *player, float *quality, float *strength)
+{
+    struct vlc_player_input *input = vlc_player_get_input_locked(player);
+
+    if (input && input->signal_quality >= 0 && input->signal_strength >= 0)
+    {
+        *quality = input->signal_quality;
+        *strength = input->signal_strength;
+        return VLC_SUCCESS;
+    }
+    return VLC_EGENERIC;
+}
+
+const struct input_stats_t *
+vlc_player_GetStatistics(vlc_player_t *player)
+{
+    struct vlc_player_input *input = vlc_player_get_input_locked(player);
+
+    return input ? &input->stats : NULL;
+}
+
+vout_thread_t **
+vlc_player_GetVouts(vlc_player_t *player, size_t *count)
+{
+    vout_thread_t **vouts;
+    input_resource_HoldVouts(player->resource, &vouts, count);
+    return vouts;
+}
+
+audio_output_t *
+vlc_player_GetAout(vlc_player_t *player)
+{
+    return input_resource_HoldAout(player->resource);
+}
+
+vlc_player_aout_listener_id *
+vlc_player_aout_AddListener(vlc_player_t *player,
+                            const struct vlc_player_aout_cbs *cbs,
+                            void *cbs_data)
+{
+    assert(cbs);
+
+    vlc_player_aout_listener_id *listener = malloc(sizeof(*listener));
+    if (!listener)
+        return NULL;
+
+    listener->cbs = cbs;
+    listener->cbs_data = cbs_data;
+
+    vlc_mutex_lock(&player->aout_listeners_lock);
+    vlc_list_append(&listener->node, &player->aout_listeners);
+    vlc_mutex_unlock(&player->aout_listeners_lock);
+
+    return listener;
+}
+
+void
+vlc_player_aout_RemoveListener(vlc_player_t *player,
+                               vlc_player_aout_listener_id *id)
+{
+    assert(id);
+
+    vlc_mutex_lock(&player->aout_listeners_lock);
+    vlc_list_remove(&id->node);
+    vlc_mutex_unlock(&player->aout_listeners_lock);
+    free(id);
+}
+
+static int
+vlc_player_AoutCallback(vlc_object_t *this, const char *var,
+                        vlc_value_t oldval, vlc_value_t newval, void *data)
+{
+    vlc_player_t *player = data;
+
+    if (strcmp(var, "volume") == 0)
+    {
+        if (oldval.f_float != newval.f_float)
+            vlc_player_aout_SendEvent(player, on_volume_changed, newval.f_float);
+    }
+    else if (strcmp(var, "mute") == 0)
+    {
+        if (oldval.b_bool != newval.b_bool )
+            vlc_player_aout_SendEvent(player, on_mute_changed, newval.b_bool);
+    }
+    else
+        vlc_assert_unreachable();
+
+    (void) this;
+    return VLC_SUCCESS;
+}
+
+float
+vlc_player_aout_GetVolume(vlc_player_t *player)
+{
+    audio_output_t *aout = vlc_player_GetAout(player);
+    if (!aout)
+        return -1.f;
+    return aout_VolumeGet(aout);
+}
+
+int
+vlc_player_aout_SetVolume(vlc_player_t *player, float volume)
+{
+    audio_output_t *aout = vlc_player_GetAout(player);
+    if (!aout)
+        return -1;
+    int ret = aout_VolumeSet(aout, volume);
+    vlc_object_release(aout);
+
+    return ret;
+}
+
+int
+vlc_player_aout_IncrementVolume(vlc_player_t *player, float volume,
+                                float *result)
+{
+    audio_output_t *aout = vlc_player_GetAout(player);
+    if (!aout)
+        return -1;
+    int ret = aout_VolumeUpdate(aout, volume, result);
+    vlc_object_release(aout);
+
+    return ret;
+}
+
+int
+vlc_player_aout_IsMuted(vlc_player_t *player)
+{
+    audio_output_t *aout = vlc_player_GetAout(player);
+    if (!aout)
+        return -1;
+    int ret = aout_MuteGet(aout);
+    vlc_object_release(aout);
+
+    return ret;
+}
+
+int
+vlc_player_aout_Mute(vlc_player_t *player, bool mute)
+{
+    audio_output_t *aout = vlc_player_GetAout(player);
+    if (!aout)
+        return -1;
+    int ret = aout_MuteSet (aout, mute);
+    vlc_object_release(aout);
+
+    return ret;
+}
+
+
+int
+vlc_player_aout_EnableFilter(vlc_player_t *player, const char *name, bool add)
+{
+    audio_output_t *aout = vlc_player_GetAout(player);
+    if (!aout)
+        return -1;
+    aout_EnableFilter(aout, name, add);
+    vlc_object_release(aout);
+    return 0;
+}
+
+vlc_player_vout_listener_id *
+vlc_player_vout_AddListener(vlc_player_t *player,
+                            const struct vlc_player_vout_cbs *cbs,
+                            void *cbs_data)
+{
+    assert(cbs);
+
+    vlc_player_vout_listener_id *listener = malloc(sizeof(*listener));
+    if (!listener)
+        return NULL;
+
+    listener->cbs = cbs;
+    listener->cbs_data = cbs_data;
+
+    vlc_mutex_lock(&player->vout_listeners_lock);
+    vlc_list_append(&listener->node, &player->vout_listeners);
+    vlc_mutex_unlock(&player->vout_listeners_lock);
+
+    return listener;
+}
+
+void
+vlc_player_vout_RemoveListener(vlc_player_t *player,
+                               vlc_player_vout_listener_id *id)
+{
+    assert(id);
+
+    vlc_mutex_lock(&player->vout_listeners_lock);
+    vlc_list_remove(&id->node);
+    vlc_mutex_unlock(&player->vout_listeners_lock);
+    free(id);
+}
+
+bool
+vlc_player_vout_IsFullscreen(vlc_player_t *player)
+{
+    vlc_player_assert_locked(player);
+    return var_GetBool(player, "fullscreen");
+}
+
+static int
+vlc_player_VoutCallback(vlc_object_t *this, const char *var,
+                        vlc_value_t oldval, vlc_value_t newval, void *data)
+{
+    vlc_player_t *player = data;
+
+    if (strcmp(var, "fullscreen") == 0)
+    {
+        if (oldval.b_bool != newval.b_bool )
+            vlc_player_vout_SendEvent(player, on_fullscreen_changed,
+                                      (vout_thread_t *)this, newval.b_bool);
+    }
+    else if (strcmp(var, "video-wallpaper") == 0)
+    {
+        if (oldval.b_bool != newval.b_bool )
+            vlc_player_vout_SendEvent(player, on_wallpaper_mode_changed,
+                                      (vout_thread_t *)this, newval.b_bool);
+    }
+    else
+        vlc_assert_unreachable();
+
+    return VLC_SUCCESS;
+}
+
+static void
+vlc_player_vout_SetOptionEnabled(vlc_player_t *player, const char *option,
+                                 bool enabled)
+{
+    var_SetBool(player, option, enabled);
+
+    size_t count;
+    vout_thread_t **vouts = vlc_player_GetVouts(player, &count);
+    for (size_t i = 0; i < count; i++)
+    {
+        var_SetBool(vouts[i], option, enabled);
+        vlc_object_release(vouts[i]);
+    }
+    free(vouts);
+}
+
+void
+vlc_player_vout_SetFullscreen(vlc_player_t *player, bool enabled)
+{
+    vlc_player_assert_locked(player);
+    vlc_player_vout_SetOptionEnabled(player, "fullscreen", enabled);
+    vlc_player_vout_SendEvent(player, on_fullscreen_changed, NULL, enabled);
+}
+
+bool
+vlc_player_vout_IsWallpaperModeEnabled(vlc_player_t *player)
+{
+    return var_GetBool(player, "video-wallpaper");
+}
+
+void
+vlc_player_vout_SetWallpaperModeEnabled(vlc_player_t *player, bool enabled)
+{
+    vlc_player_assert_locked(player);
+    vlc_player_vout_SetOptionEnabled(player, "video-wallpaper", enabled);
+    vlc_player_vout_SendEvent(player, on_wallpaper_mode_changed, NULL, enabled);
+}
+
+static void
+vlc_player_InitLocks(vlc_player_t *player)
+{
+    vlc_mutex_init(&player->lock);
+    vlc_mutex_init(&player->vout_listeners_lock);
+    vlc_mutex_init(&player->aout_listeners_lock);
+    vlc_cond_init(&player->start_delay_cond);
+    vlc_cond_init(&player->destructor.wait);
+}
+
+static void
+vlc_player_DestroyLocks(vlc_player_t *player)
+{
+    vlc_mutex_destroy(&player->lock);
+    vlc_mutex_destroy(&player->vout_listeners_lock);
+    vlc_mutex_destroy(&player->aout_listeners_lock);
+    vlc_cond_destroy(&player->start_delay_cond);
+    vlc_cond_destroy(&player->destructor.wait);
+}
+
+void
+vlc_player_Delete(vlc_player_t *player)
+{
+    vlc_mutex_lock(&player->lock);
+
+    if (player->input)
+        vlc_player_destructor_AddInput(player, player->input);
+#if GAPLESS
+    if (player->next_input)
+        vlc_player_destructor_AddInput(player, player->next_inpu);
+#endif
+
+    player->deleting = true;
+    vlc_cond_signal(&player->destructor.wait);
+
+    assert(vlc_list_is_empty(&player->listeners));
+
+    vlc_mutex_unlock(&player->lock);
+
+    vlc_join(player->destructor.thread, NULL);
+
+    if (player->media)
+        input_item_Release(player->media);
+    if (player->next_media)
+        input_item_Release(player->next_media);
+
+    vlc_player_DestroyLocks(player);
+
+    audio_output_t *aout = vlc_player_GetAout(player);
+    if (aout)
+    {
+        var_DelCallback(aout, "volume", vlc_player_AoutCallback, player);
+        var_DelCallback(aout, "mute", vlc_player_AoutCallback, player);
+        vlc_object_release(aout);
+    }
+    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_media_provider *media_provider,
+               void *media_provider_data)
+{
+    audio_output_t *aout = NULL;
+    vlc_player_t *player = vlc_custom_create(parent, sizeof(*player), "player");
+    if (!player)
+        return NULL;
+
+    assert(!media_provider || media_provider->get_next);
+
+    vlc_list_init(&player->listeners);
+    vlc_list_init(&player->vout_listeners);
+    vlc_list_init(&player->aout_listeners);
+    vlc_list_init(&player->destructor.inputs);
+    vlc_list_init(&player->destructor.stopping_inputs);
+    vlc_list_init(&player->destructor.joinable_inputs);
+    player->media_stopped_action = VLC_PLAYER_MEDIA_STOPPED_CONTINUE;
+    player->start_paused = false;
+    player->renderer = NULL;
+    player->media_provider = media_provider;
+    player->media_provider_data = media_provider_data;
+    player->media = NULL;
+    player->input = NULL;
+    player->global_state = VLC_PLAYER_STATE_STOPPED;
+    player->started = false;
+
+    player->error_count = 0;
+
+    player->releasing_media = false;
+    player->has_next_media = false;
+    player->next_media = NULL;
+#if GAPLESS
+    player->next_input = NULL;
+#endif
+
+#define VAR_CREATE(var, flag) do { \
+    if (var_Create(player, var, flag) != VLC_SUCCESS) \
+        goto error; \
+} while(0)
+
+    VAR_CREATE("rate", VLC_VAR_FLOAT | VLC_VAR_DOINHERIT);
+    VAR_CREATE("sub-fps", VLC_VAR_FLOAT | VLC_VAR_DOINHERIT);
+    VAR_CREATE("fullscreen", VLC_VAR_BOOL | VLC_VAR_DOINHERIT);
+    VAR_CREATE("video-on-top", VLC_VAR_BOOL | VLC_VAR_DOINHERIT);
+    VAR_CREATE("video-wallpaper", VLC_VAR_BOOL | VLC_VAR_DOINHERIT);
+    VAR_CREATE("mute", VLC_VAR_BOOL);
+    /* TODO: Override these variables since the player handle media ended
+     * action itself. */
+    VAR_CREATE("start-paused", VLC_VAR_BOOL);
+    VAR_CREATE("play-and-pause", VLC_VAR_BOOL);
+
+#undef VAR_CREATE
+
+    player->resource = input_resource_New(VLC_OBJECT(player));
+
+    if (!player->resource)
+        goto error;
+
+    aout = input_resource_GetAout(player->resource);
+    if (aout != NULL)
+    {
+        var_AddCallback(aout, "volume", vlc_player_AoutCallback, player);
+        var_AddCallback(aout, "mute", vlc_player_AoutCallback, player);
+        input_resource_PutAout(player->resource, aout);
+    }
+
+    player->deleting = false;
+    vlc_player_InitLocks(player);
+
+    if (vlc_clone(&player->destructor.thread, vlc_player_destructor_Thread,
+                  player, VLC_THREAD_PRIORITY_LOW) != 0)
+    {
+        vlc_player_DestroyLocks(player);
+        goto error;
+    }
+
+    return player;
+
+error:
+    if (aout)
+    {
+        var_DelCallback(aout, "volume", vlc_player_AoutCallback, player);
+        var_DelCallback(aout, "mute", vlc_player_AoutCallback, player);
+    }
+    if (player->resource)
+        input_resource_Release(player->resource);
+
+    vlc_object_release(player);
+    return NULL;
+}
+
+
diff --git a/src/input/player.h b/src/input/player.h
new file mode 100644
index 0000000000..cc10236a98
--- /dev/null
+++ b/src/input/player.h
@@ -0,0 +1,35 @@
+/*****************************************************************************
+ * player.h: Player internal 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.
+ *****************************************************************************/
+
+#ifndef VLC_PLAYER_INTERNAL_H
+#define VLC_PLAYER_INTERNAL_H
+
+#include <vlc_player.h>
+
+/**
+ * Assert that the player mutex is locked.
+ *
+ * This is exposed in this internal header because the playlist and its
+ * associated player share the lock to avoid lock-order inversion issues.
+ */
+void
+vlc_player_assert_locked(vlc_player_t *player);
+
+#endif
diff --git a/src/libvlccore.sym b/src/libvlccore.sym
index cf0a7e091c..e53c20e253 100644
--- a/src/libvlccore.sym
+++ b/src/libvlccore.sym
@@ -786,3 +786,100 @@ vlc_es_id_Hold
 vlc_es_id_Release
 vlc_es_id_GetInputId
 vlc_es_id_GetCat
+vlc_player_AddAssociatedMedia
+vlc_player_AddListener
+vlc_player_aout_AddListener
+vlc_player_aout_EnableFilter
+vlc_player_aout_GetVolume
+vlc_player_aout_IncrementVolume
+vlc_player_aout_IsMuted
+vlc_player_aout_Mute
+vlc_player_aout_RemoveListener
+vlc_player_aout_SetVolume
+vlc_player_ChangeRate
+vlc_player_CondWait
+vlc_player_DecrementRate
+vlc_player_Delete
+vlc_player_GetAout
+vlc_player_GetAudioDelay
+vlc_player_GetCapabilities
+vlc_player_GetCurrentMedia
+vlc_player_GetError
+vlc_player_GetLength
+vlc_player_GetPosition
+vlc_player_GetProgram
+vlc_player_GetProgramAt
+vlc_player_GetProgramCount
+vlc_player_GetRate
+vlc_player_GetRenderer
+vlc_player_GetSelectedChapterIdx
+vlc_player_GetSelectedTitleIdx
+vlc_player_GetSignal
+vlc_player_GetState
+vlc_player_GetStatistics
+vlc_player_GetSubtitleDelay
+vlc_player_GetTeletextPage
+vlc_player_GetTime
+vlc_player_GetTitleList
+vlc_player_GetTrack
+vlc_player_GetTrackAt
+vlc_player_GetTrackCount
+vlc_player_GetVouts
+vlc_player_HasTeletextMenu
+vlc_player_IncrementRate
+vlc_player_InvalidateNextMedia
+vlc_player_IsRecording
+vlc_player_IsStarted
+vlc_player_IsTeletextEnabled
+vlc_player_IsTeletextTransparent
+vlc_player_Lock
+vlc_player_Navigate
+vlc_player_New
+vlc_player_NextVideoFrame
+vlc_player_Pause
+vlc_player_program_Delete
+vlc_player_program_Dup
+vlc_player_RemoveListener
+vlc_player_RestartTrack
+vlc_player_Resume
+vlc_player_SeekByPos
+vlc_player_SeekByTime
+vlc_player_SelectChapter
+vlc_player_SelectChapterIdx
+vlc_player_SelectDefaultTrack
+vlc_player_SelectNextChapter
+vlc_player_SelectNextTitle
+vlc_player_SelectPrevChapter
+vlc_player_SelectPrevTitle
+vlc_player_SelectProgram
+vlc_player_SelectTeletextPage
+vlc_player_SelectTitle
+vlc_player_SelectTitleIdx
+vlc_player_SelectTrack
+vlc_player_SetAssociatedSubsFPS
+vlc_player_SetAtoBLoop
+vlc_player_SetAudioDelay
+vlc_player_SetCurrentMedia
+vlc_player_SetMediaStoppedAction
+vlc_player_SetRecordingEnabled
+vlc_player_SetRenderer
+vlc_player_SetStartPaused
+vlc_player_SetSubtitleDelay
+vlc_player_SetTeletextEnabled
+vlc_player_SetTeletextTransparency
+vlc_player_Start
+vlc_player_Stop
+vlc_player_title_list_GetAt
+vlc_player_title_list_GetCount
+vlc_player_title_list_Hold
+vlc_player_title_list_Release
+vlc_player_track_Delete
+vlc_player_track_Dup
+vlc_player_Unlock
+vlc_player_UnselectTrack
+vlc_player_vout_AddListener
+vlc_player_vout_IsFullscreen
+vlc_player_vout_IsWallpaperModeEnabled
+vlc_player_vout_RemoveListener
+vlc_player_vout_SetFullscreen
+vlc_player_vout_SetWallpaperModeEnabled
-- 
2.19.1



More information about the vlc-devel mailing list