[vlc-devel] [PATCH 2/2] player: split implementation into several files

Thomas Guillem thomas at gllm.fr
Tue Aug 13 16:02:54 CEST 2019


player.c was starting getting huge (~4000 lines), and therefore harder to
navigate.


RFC: What do you prefer between src/input/player_foo.c and
src/input/player/foo.c ?

---
 src/Makefile.am          |    6 +
 src/input/player.c       | 1992 +-------------------------------------
 src/input/player.h       |  339 ++++++-
 src/input/player_aout.c  |  219 +++++
 src/input/player_input.c |  771 +++++++++++++++
 src/input/player_osd.c   |  308 ++++++
 src/input/player_title.c |  174 ++++
 src/input/player_track.c |  207 ++++
 src/input/player_vout.c  |  203 ++++
 9 files changed, 2272 insertions(+), 1947 deletions(-)
 create mode 100644 src/input/player_aout.c
 create mode 100644 src/input/player_input.c
 create mode 100644 src/input/player_osd.c
 create mode 100644 src/input/player_title.c
 create mode 100644 src/input/player_track.c
 create mode 100644 src/input/player_vout.c

diff --git a/src/Makefile.am b/src/Makefile.am
index 8c56c0e165..f0ee136291 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -261,6 +261,12 @@ libvlccore_la_SOURCES = \
 	input/es_out_timeshift.c \
 	input/input.c \
 	input/player.c \
+	input/player_input.c \
+	input/player_track.c \
+	input/player_title.c \
+	input/player_aout.c \
+	input/player_vout.c \
+	input/player_osd.c \
 	input/player.h \
 	input/info.h \
 	input/meta.c \
diff --git a/src/input/player.c b/src/input/player.c
index 9ab8926f09..7e43e853b1 100644
--- a/src/input/player.c
+++ b/src/input/player.c
@@ -1,7 +1,7 @@
 /*****************************************************************************
  * player.c: Player interface
  *****************************************************************************
- * Copyright © 2018 VLC authors and VideoLAN
+ * Copyright © 2018-2019 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
@@ -27,23 +27,15 @@
 #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 <vlc_tick.h>
 #include <vlc_decoder.h>
 #include <vlc_memstream.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 &&
@@ -54,745 +46,10 @@ static_assert(VLC_PLAYER_TITLE_MENU == INPUT_TITLE_MENU &&
               VLC_PLAYER_TITLE_INTERACTIVE == INPUT_TITLE_INTERACTIVE,
               "player/input title flag mismatch");
 
-struct vlc_player_track_priv
-{
-    struct vlc_player_track t;
-    vout_thread_t *vout; /* weak reference */
-    vlc_tick_t delay;
-    /* only valid if selected and if category is VIDEO_ES or SPU_ES */
-    enum vlc_vout_order vout_order;
-};
-
-typedef struct VLC_VECTOR(struct vlc_player_program *)
-    vlc_player_program_vector;
-
-typedef struct VLC_VECTOR(struct vlc_player_track_priv *)
-    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 time;
-    float position;
-
-    bool recording;
-
-    float signal_quality;
-    float signal_strength;
-    float cache;
-
-    struct input_stats_t stats;
-
-    vlc_tick_t cat_delays[DATA_ES];
-
-    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_priv *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_object_t 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;
-
-    bool pause_on_cork;
-    bool corked;
-
-    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 next_media_requested;
-    input_item_t *next_media;
-
-    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)
-
 #define vlc_player_foreach_inputs(it) \
     for (struct vlc_player_input *it = player->input; it != NULL; it = NULL)
 
-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,
-                             vlc_tick_t);
-static int
-vlc_player_VoutCallback(vlc_object_t *this, const char *var,
-                        vlc_value_t oldval, vlc_value_t newval, void *data);
-static int
-vlc_player_VoutOSDCallback(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 vout_thread_t **
-vlc_player_vout_OSDHoldAll(vlc_player_t *player, size_t *count)
-{
-    vout_thread_t **vouts;
-    input_resource_HoldVouts(player->resource, &vouts, count);
-
-    for (size_t i = 0; i < *count; ++i)
-    {
-        vout_FlushSubpictureChannel(vouts[i], VOUT_SPU_CHANNEL_OSD);
-        vout_FlushSubpictureChannel(vouts[i], VOUT_SPU_CHANNEL_OSD_HSLIDER);
-        vout_FlushSubpictureChannel(vouts[i], VOUT_SPU_CHANNEL_OSD_HSLIDER);
-    }
-    return vouts;
-}
-
-static void
-vlc_player_vout_OSDReleaseAll(vlc_player_t *player, vout_thread_t **vouts,
-                            size_t count)
-{
-    for (size_t i = 0; i < count; ++i)
-        vout_Release(vouts[i]);
-    free(vouts);
-    (void) player;
-}
-
-static inline void
-vouts_osd_Message(vout_thread_t **vouts, size_t count, const char *fmt, ...)
-{
-    va_list args;
-    va_start(args, fmt);
-    for (size_t i = 0; i < count; ++i)
-    {
-        va_list acpy;
-        va_copy(acpy, args);
-        vout_OSDMessageVa(vouts[i], VOUT_SPU_CHANNEL_OSD, fmt, acpy);
-        va_end(acpy);
-    }
-    va_end(args);
-}
-
-static inline void
-vouts_osd_Icon(vout_thread_t **vouts, size_t count, short type)
-{
-    for (size_t i = 0; i < count; ++i)
-        vout_OSDIcon(vouts[i], VOUT_SPU_CHANNEL_OSD, type);
-}
-
-static inline void
-vouts_osd_Slider(vout_thread_t **vouts, size_t count, int position, short type)
-{
-    int channel = type == OSD_HOR_SLIDER ?
-        VOUT_SPU_CHANNEL_OSD_HSLIDER : VOUT_SPU_CHANNEL_OSD_VSLIDER;
-    for (size_t i = 0; i < count; ++i)
-        vout_OSDSlider(vouts[i], channel, position, type);
-}
-
-void
-vlc_player_osd_Message(vlc_player_t *player, const char *fmt, ...)
-{
-    size_t count;
-    vout_thread_t **vouts = vlc_player_vout_OSDHoldAll(player, &count);
-
-    va_list args;
-    va_start(args, fmt);
-    for (size_t i = 0; i < count; ++i)
-    {
-        va_list acpy;
-        va_copy(acpy, args);
-        vout_OSDMessageVa(vouts[i], VOUT_SPU_CHANNEL_OSD, fmt, acpy);
-        va_end(acpy);
-    }
-    va_end(args);
-
-    vlc_player_vout_OSDReleaseAll(player, vouts, count);
-}
-
-static void
-vlc_player_vout_OSDIcon(vlc_player_t *player, short type)
-{
-    size_t count;
-    vout_thread_t **vouts = vlc_player_vout_OSDHoldAll(player, &count);
-
-    vouts_osd_Icon(vouts, count, type);
-
-    vlc_player_vout_OSDReleaseAll(player, vouts, count);
-}
-
-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_priv *
-vlc_player_track_New(vlc_es_id_t *id, const char *name, const es_format_t *fmt)
-{
-    struct vlc_player_track_priv *trackpriv = malloc(sizeof(*trackpriv));
-    if (!trackpriv)
-        return NULL;
-    struct vlc_player_track *track = &trackpriv->t;
-
-    trackpriv->delay = INT64_MAX;
-    trackpriv->vout = NULL;
-    trackpriv->vout_order = VLC_VOUT_ORDER_NONE;
-
-    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 trackpriv;
-}
-
-struct vlc_player_track *
-vlc_player_track_Dup(const struct vlc_player_track *src)
-{
-    struct vlc_player_track_priv *duppriv =
-        vlc_player_track_New(src->es_id, src->name, &src->fmt);
-
-    if (!duppriv)
-        return NULL;
-    duppriv->t.selected = src->selected;
-    return &duppriv->t;
-}
-
-static void
-vlc_player_track_priv_Delete(struct vlc_player_track_priv *trackpriv)
-{
-    struct vlc_player_track *track = &trackpriv->t;
-    es_format_Clean(&track->fmt);
-    free((char *)track->name);
-    vlc_es_id_Release(track->es_id);
-    free(trackpriv);
-}
-
-void
-vlc_player_track_Delete(struct vlc_player_track *track)
-{
-    struct vlc_player_track_priv *trackpriv =
-        container_of(track, struct vlc_player_track_priv, t);
-    vlc_player_track_priv_Delete(trackpriv);
-}
-
-static int
-vlc_player_track_priv_Update(struct vlc_player_track_priv *trackpriv,
-                        const char *name, const es_format_t *fmt)
-{
-    struct vlc_player_track *track = &trackpriv->t;
-
-    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(input_title_t *const *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)
-        {
-            /* Release titles up to title_idx */
-            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->time = VLC_TICK_INVALID;
-    input->position = 0.f;
-
-    input->recording = false;
-
-    input->cache = 0.f;
-    input->signal_quality = input->signal_strength = -1.f;
-
-    memset(&input->stats, 0, sizeof(input->stats));
-
-    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,
-                                 player->resource, player->renderer);
-    if (!input->thread)
-    {
-        free(input);
-        return NULL;
-    }
-
-    /* Initial sub/audio delay */
-    const vlc_tick_t cat_delays[DATA_ES] = {
-        [AUDIO_ES] =
-            VLC_TICK_FROM_MS(var_InheritInteger(player, "audio-desync")),
-        [SPU_ES] =
-            vlc_tick_from_samples(var_InheritInteger(player, "sub-delay"), 10),
-    };
-
-    for (enum es_format_category_e i = UNKNOWN_ES; i < DATA_ES; ++i)
-    {
-        input->cat_delays[i] = cat_delays[i];
-        if (cat_delays[i] != 0)
-        {
-            const input_control_param_t param = {
-                .cat_delay = { i, cat_delays[i] }
-            };
-            input_ControlPush(input->thread, INPUT_CONTROL_SET_CATEGORY_DELAY,
-                              &param);
-            vlc_player_SendEvent(player, on_category_delay_changed, i,
-                                 cat_delays[i]);
-        }
-    }
-    return input;
-}
-
-static void
-vlc_player_input_Delete(struct vlc_player_input *input)
-{
-    assert(input->titles == 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);
-
-    input_Close(input->thread);
-    free(input);
-}
-
-static void
-vlc_player_input_HandleAtoBLoop(struct vlc_player_input *input, vlc_tick_t time,
-                                float pos)
-{
-    vlc_player_t *player = input->player;
-
-    if (player->input != input)
-        return;
-
-    assert(input->abloop_state[0].set && input->abloop_state[1].set);
-
-    if (time != VLC_TICK_INVALID
-     && input->abloop_state[0].time != VLC_TICK_INVALID
-     && input->abloop_state[1].time != VLC_TICK_INVALID)
-    {
-        if (time >= input->abloop_state[1].time)
-            vlc_player_SetTime(player, input->abloop_state[0].time);
-    }
-    else if (pos >= input->abloop_state[1].pos)
-        vlc_player_SetPosition(player, input->abloop_state[0].pos);
-}
-
-static inline vlc_tick_t
-vlc_player_input_GetTime(struct vlc_player_input *input)
-{
-    return input->time;
-}
-
-static inline float
-vlc_player_input_GetPos(struct vlc_player_input *input)
-{
-    return input->position;
-}
-
-static void
-vlc_player_input_UpdateTime(struct vlc_player_input *input)
-{
-    if (input->abloop_state[0].set && input->abloop_state[1].set)
-        vlc_player_input_HandleAtoBLoop(input, vlc_player_input_GetTime(input),
-                                        vlc_player_input_GetPos(input));
-}
-
-static int
-vlc_player_input_Start(struct vlc_player_input *input)
-{
-    int ret = input_Start(input->thread);
-    if (ret != VLC_SUCCESS)
-        return ret;
-    input->started = true;
-    return ret;
-}
-
-static void
 vlc_player_PrepareNextMedia(vlc_player_t *player)
 {
     vlc_player_assert_locked(player);
@@ -808,7 +65,7 @@ vlc_player_PrepareNextMedia(vlc_player_t *player)
     player->next_media_requested = true;
 }
 
-static int
+int
 vlc_player_OpenNextMedia(vlc_player_t *player)
 {
     assert(player->input == NULL);
@@ -833,8 +90,9 @@ vlc_player_OpenNextMedia(vlc_player_t *player)
         player->media = player->next_media;
         player->next_media = NULL;
 
-        player->input = vlc_player_input_New(player, player->media);
-        if (!player->input)
+        struct vlc_player_input *input = player->input =
+            vlc_player_input_New(player, player->media);
+        if (!input)
         {
             input_item_Release(player->media);
             player->media = NULL;
@@ -892,7 +150,7 @@ vlc_player_destructor_AddInput(vlc_player_t *player,
     vlc_cond_signal(&input->player->destructor.wait);
 }
 
-static void
+void
 vlc_player_destructor_AddStoppingInput(vlc_player_t *player,
                                        struct vlc_player_input *input)
 {
@@ -906,7 +164,7 @@ vlc_player_destructor_AddStoppingInput(vlc_player_t *player,
     }
 }
 
-static void
+void
 vlc_player_destructor_AddJoinableInput(vlc_player_t *player,
                                        struct vlc_player_input *input)
 {
@@ -982,121 +240,6 @@ vlc_player_destructor_Thread(void *data)
     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_tick_t state_date)
-{
-    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->titles)
-            {
-                vlc_player_title_list_Release(input->titles);
-                input->titles = NULL;
-                vlc_player_SendEvent(player, on_titles_changed, NULL);
-            }
-
-            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(vlc_object_instance(player));
-                    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_PrepareNextMedia(player);
-                if (!player->next_media)
-                    player->started = false;
-            }
-            send_event = !player->started;
-            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)
 {
@@ -1130,12 +273,6 @@ vlc_player_GetProgram(vlc_player_t *player, int id)
     return prgm;
 }
 
-static inline void
-vlc_player_vout_OSDProgram(vlc_player_t *player, const char *name)
-{
-    vlc_player_osd_Message(player, _("Program Service ID: %s"), name);
-}
-
 void
 vlc_player_SelectProgram(vlc_player_t *player, int id)
 {
@@ -1150,7 +287,7 @@ vlc_player_SelectProgram(vlc_player_t *player, int id)
     {
         input_ControlPushHelper(input->thread, INPUT_CONTROL_SET_PROGRAM,
                                 &(vlc_value_t) { .i_int = id });
-        vlc_player_vout_OSDProgram(player, prgm->name);
+        vlc_player_osd_Program(player, prgm->name);
     }
 }
 
@@ -1200,134 +337,6 @@ vlc_player_SelectPrevProgram(vlc_player_t *player)
     vlc_player_CycleProgram(player, false);
 }
 
-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_priv *
-vlc_player_track_vector_FindById(vlc_player_track_vector *vec, vlc_es_id_t *id,
-                                 size_t *idx)
-{
-    for (size_t i = 0; i < vec->size; ++i)
-    {
-        struct vlc_player_track_priv *trackpriv = vec->data[i];
-        if (trackpriv->t.es_id == id)
-        {
-            if (idx)
-                *idx = i;
-            return trackpriv;
-        }
-    }
-    return NULL;
-}
-
-static struct vlc_player_track_priv *
-vlc_player_input_FindTrackById(struct vlc_player_input *input, vlc_es_id_t *id,
-                               size_t *idx)
-{
-    vlc_player_track_vector *vec =
-        vlc_player_input_GetTrackVector(input, vlc_es_id_GetCat(id));
-    return vec ? vlc_player_track_vector_FindById(vec, id, idx) : NULL;
-}
-
 size_t
 vlc_player_GetTrackCount(vlc_player_t *player, enum es_format_category_e cat)
 {
@@ -1420,32 +429,6 @@ vlc_player_GetEsIdFromVout(vlc_player_t *player, vout_thread_t *vout)
     return NULL;
 }
 
-static inline const char *
-es_format_category_to_string(enum es_format_category_e cat)
-{
-    switch (cat)
-    {
-        case VIDEO_ES: return "Video";
-        case AUDIO_ES: return "Audio";
-        case SPU_ES: return "Subtitle";
-        default: return NULL;
-    }
-}
-
-static void
-vlc_player_vout_OSDTrack(vlc_player_t *player, vlc_es_id_t *id, bool select)
-{
-    enum es_format_category_e cat = vlc_es_id_GetCat(id);
-    const struct vlc_player_track *track = vlc_player_GetTrack(player, id);
-    if (!track && select)
-        return;
-
-    const char *cat_name = es_format_category_to_string(cat);
-    assert(cat_name);
-    const char *track_name = select ? track->name : _("N/A");
-    vlc_player_osd_Message(player, _("%s track: %s"), cat_name, track_name);
-}
-
 unsigned
 vlc_player_SelectEsIdList(vlc_player_t *player,
                           enum es_format_category_e cat,
@@ -1507,7 +490,7 @@ vlc_player_SelectEsIdList(vlc_player_t *player,
     if (track_count == 0)
         vlc_player_osd_Message(player, _("%s track: %s"), cat_name, _("N/A"));
     else if (track_count == 1)
-        vlc_player_vout_OSDTrack(player, es_id_list[0], true);
+        vlc_player_osd_Track(player, es_id_list[0], true);
     else
     {
         struct vlc_memstream stream;
@@ -1545,7 +528,7 @@ vlc_player_SelectEsId(vlc_player_t *player, vlc_es_id_t *id,
     if (policy == VLC_PLAYER_SELECT_EXCLUSIVE)
     {
         input_ControlPushEsHelper(input->thread, INPUT_CONTROL_SET_ES, id);
-        vlc_player_vout_OSDTrack(player, id, true);
+        vlc_player_osd_Track(player, id, true);
         return 1;
     }
 
@@ -1568,7 +551,7 @@ vlc_player_SelectEsId(vlc_player_t *player, vlc_es_id_t *id,
     if (selected_track_count == 1)
     {
         input_ControlPushEsHelper(input->thread, INPUT_CONTROL_SET_ES, id);
-        vlc_player_vout_OSDTrack(player, id, true);
+        vlc_player_osd_Track(player, id, true);
         return 1;
     }
 
@@ -1668,7 +651,7 @@ vlc_player_UnselectEsId(vlc_player_t *player, vlc_es_id_t *id)
         return;
 
     input_ControlPushEsHelper(input->thread, INPUT_CONTROL_UNSET_ES, id);
-    vlc_player_vout_OSDTrack(player, id, false);
+    vlc_player_osd_Track(player, id, false);
 }
 
 void
@@ -1715,55 +698,6 @@ vlc_player_GetCategoryLanguage(vlc_player_t *player,
     }
 }
 
-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_priv_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->t.es_id == ev->id)
-            {
-                assert(!input->teletext_enabled);
-
-                vlc_player_track_priv_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->t.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)
 {
@@ -1838,148 +772,6 @@ vlc_player_IsTeletextTransparent(vlc_player_t *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 > 8))
-    {
-        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_priv *trackpriv;
-    switch (ev->action)
-    {
-        case VLC_INPUT_ES_ADDED:
-            trackpriv = vlc_player_track_New(ev->id, ev->title, ev->fmt);
-            if (!trackpriv)
-                break;
-
-            if (!vlc_vector_push(vec, trackpriv))
-            {
-                vlc_player_track_priv_Delete(trackpriv);
-                break;
-            }
-            vlc_player_SendEvent(player, on_track_list_changed,
-                                 VLC_PLAYER_LIST_ADDED, &trackpriv->t);
-            break;
-        case VLC_INPUT_ES_DELETED:
-        {
-            size_t idx;
-            trackpriv = vlc_player_track_vector_FindById(vec, ev->id, &idx);
-            if (trackpriv)
-            {
-                vlc_player_SendEvent(player, on_track_list_changed,
-                                     VLC_PLAYER_LIST_REMOVED, &trackpriv->t);
-                vlc_vector_remove(vec, idx);
-                vlc_player_track_priv_Delete(trackpriv);
-            }
-            break;
-        }
-        case VLC_INPUT_ES_UPDATED:
-            trackpriv = vlc_player_track_vector_FindById(vec, ev->id, NULL);
-            if (!trackpriv)
-                break;
-            if (vlc_player_track_priv_Update(trackpriv, ev->title, ev->fmt) != 0)
-                break;
-            vlc_player_SendEvent(player, on_track_list_changed,
-                                 VLC_PLAYER_LIST_UPDATED, &trackpriv->t);
-            break;
-        case VLC_INPUT_ES_SELECTED:
-            trackpriv = vlc_player_track_vector_FindById(vec, ev->id, NULL);
-            if (trackpriv)
-            {
-                trackpriv->t.selected = true;
-                vlc_player_SendEvent(player, on_track_selection_changed,
-                                     NULL, trackpriv->t.es_id);
-            }
-            break;
-        case VLC_INPUT_ES_UNSELECTED:
-            trackpriv = vlc_player_track_vector_FindById(vec, ev->id, NULL);
-            if (trackpriv)
-            {
-                trackpriv->t.selected = false;
-                vlc_player_SendEvent(player, on_track_selection_changed,
-                                     trackpriv->t.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)
 {
@@ -2071,248 +863,31 @@ void
 vlc_player_SelectChapterIdx(vlc_player_t *player, size_t index)
 {
     struct vlc_player_input *input = vlc_player_get_input_locked(player);
-    if (!input)
-        return;
-    input_ControlPushHelper(input->thread, INPUT_CONTROL_SET_SEEKPOINT,
-                            &(vlc_value_t){ .i_int = index });
-    vlc_player_osd_Message(player, _("Chapter %ld"), index);
-}
-
-void
-vlc_player_SelectNextChapter(vlc_player_t *player)
-{
-    struct vlc_player_input *input = vlc_player_get_input_locked(player);
-    if (!input)
-        return;
-    input_ControlPush(input->thread, INPUT_CONTROL_SET_SEEKPOINT_NEXT, NULL);
-    vlc_player_osd_Message(player, _("Next chapter"));
-}
-
-void
-vlc_player_SelectPrevChapter(vlc_player_t *player)
-{
-    struct vlc_player_input *input = vlc_player_get_input_locked(player);
-    if (!input)
-        return;
-    input_ControlPush(input->thread, INPUT_CONTROL_SET_SEEKPOINT_PREV, NULL);
-    vlc_player_osd_Message(player, _("Previous chapter"));
-}
-
-static void
-vlc_player_input_HandleVoutEvent(struct vlc_player_input *input,
-                                 const struct vlc_input_event_vout *ev)
-{
-    assert(ev->vout);
-    assert(ev->id);
-
-    static const char osd_vars[][sizeof("secondary-sub-margin")] = {
-        "aspect-ratio", "autoscale", "crop", "crop-bottom",
-        "crop-top", "crop-left", "crop-right", "deinterlace",
-        "deinterlace-mode", "sub-margin", "secondary-sub-margin", "zoom"
-    };
-
-    vlc_player_t *player = input->player;
-
-    struct vlc_player_track_priv *trackpriv =
-        vlc_player_input_FindTrackById(input, ev->id, NULL);
-    if (!trackpriv)
-        return;
-
-    const bool is_video_es = trackpriv->t.fmt.i_cat == VIDEO_ES;
-
-    switch (ev->action)
-    {
-        case VLC_INPUT_EVENT_VOUT_ADDED:
-            trackpriv->vout = ev->vout;
-            vlc_player_SendEvent(player, on_vout_changed,
-                                 VLC_PLAYER_VOUT_STARTED, ev->vout,
-                                 ev->order, ev->id);
-
-            if (is_video_es)
-            {
-                /* Register vout callbacks after the vout list event */
-                var_AddCallback(ev->vout, "fullscreen",
-                                vlc_player_VoutCallback, player);
-                var_AddCallback(ev->vout, "video-wallpaper",
-                                vlc_player_VoutCallback, player);
-                for (size_t i = 0; i < ARRAY_SIZE(osd_vars); ++i)
-                    var_AddCallback(ev->vout, osd_vars[i],
-                                    vlc_player_VoutOSDCallback, player);
-            }
-            break;
-        case VLC_INPUT_EVENT_VOUT_DELETED:
-            if (is_video_es)
-            {
-                /* 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);
-                for (size_t i = 0; i < ARRAY_SIZE(osd_vars); ++i)
-                    var_DelCallback(ev->vout, osd_vars[i],
-                                    vlc_player_VoutOSDCallback, player);
-            }
-
-            trackpriv->vout = NULL;
-            vlc_player_SendEvent(player, on_vout_changed,
-                                 VLC_PLAYER_VOUT_STOPPED, ev->vout,
-                                 VLC_VOUT_ORDER_NONE, ev->id);
-            break;
-        default:
-            vlc_assert_unreachable();
-    }
-}
-
-static void
-vlc_player_input_HandleStateEvent(struct vlc_player_input *input,
-                                  input_state_e state, vlc_tick_t state_date)
-{
-    switch (state)
-    {
-        case OPENING_S:
-            vlc_player_input_HandleState(input, VLC_PLAYER_STATE_STARTED,
-                                         VLC_TICK_INVALID);
-            break;
-        case PLAYING_S:
-            vlc_player_input_HandleState(input, VLC_PLAYER_STATE_PLAYING,
-                                         state_date);
-            break;
-        case PAUSE_S:
-            vlc_player_input_HandleState(input, VLC_PLAYER_STATE_PAUSED,
-                                         state_date);
-            break;
-        case END_S:
-            vlc_player_input_HandleState(input, VLC_PLAYER_STATE_STOPPING,
-                                         VLC_TICK_INVALID);
-            vlc_player_destructor_AddStoppingInput(input->player, input);
-            break;
-        case ERROR_S:
-            /* Don't send errors if the input is stopped by the user */
-            if (input->started)
-            {
-                /* 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
-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.value,
-                                              event->state.date);
-            break;
-        case INPUT_EVENT_RATE:
-            input->rate = event->rate;
-            vlc_player_SendEvent(player, on_rate_changed, input->rate);
-            break;
-        case INPUT_EVENT_CAPABILITIES:
-        {
-            int old_caps = input->capabilities;
-            input->capabilities = event->capabilities;
-            vlc_player_SendEvent(player, on_capabilities_changed,
-                                 old_caps, input->capabilities);
-            break;
-        }
-        case INPUT_EVENT_TIMES:
-            if (event->times.ms != VLC_TICK_INVALID
-             && (input->time != event->times.ms
-              || input->position != event->times.percentage))
-            {
-                input->time = event->times.ms;
-                input->position = event->times.percentage;
-                vlc_player_SendEvent(player, on_position_changed,
-                                     input->time, input->position);
-
-                vlc_player_input_UpdateTime(input);
-            }
-            if (input->length != event->times.length)
-            {
-                input->length = event->times.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_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:
-            if (input->started) /* Can happen with early input_thread fails */
-                vlc_player_input_HandleState(input, VLC_PLAYER_STATE_STOPPING,
-                                             VLC_TICK_INVALID);
-            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;
-    }
+    if (!input)
+        return;
+    input_ControlPushHelper(input->thread, INPUT_CONTROL_SET_SEEKPOINT,
+                            &(vlc_value_t){ .i_int = index });
+    vlc_player_osd_Message(player, _("Chapter %ld"), index);
+}
 
-    vlc_mutex_unlock(&player->lock);
+void
+vlc_player_SelectNextChapter(vlc_player_t *player)
+{
+    struct vlc_player_input *input = vlc_player_get_input_locked(player);
+    if (!input)
+        return;
+    input_ControlPush(input->thread, INPUT_CONTROL_SET_SEEKPOINT_NEXT, NULL);
+    vlc_player_osd_Message(player, _("Next chapter"));
+}
+
+void
+vlc_player_SelectPrevChapter(vlc_player_t *player)
+{
+    struct vlc_player_input *input = vlc_player_get_input_locked(player);
+    if (!input)
+        return;
+    input_ControlPush(input->thread, INPUT_CONTROL_SET_SEEKPOINT_PREV, NULL);
+    vlc_player_osd_Message(player, _("Previous chapter"));
 }
 
 void
@@ -2542,7 +1117,7 @@ vlc_player_Start(vlc_player_t *player)
     if (ret == VLC_SUCCESS)
     {
         player->started = true;
-        vlc_player_vout_OSDIcon(player, OSD_PLAY_ICON);
+        vlc_player_osd_Icon(player, OSD_PLAY_ICON);
     }
     return ret;
 }
@@ -2594,7 +1169,7 @@ vlc_player_SetPause(vlc_player_t *player, bool pause)
     vlc_value_t val = { .i_int = pause ? PAUSE_S : PLAYING_S };
     input_ControlPushHelper(input->thread, INPUT_CONTROL_SET_STATE, &val);
 
-    vlc_player_vout_OSDIcon(player, pause ? OSD_PAUSE_ICON : OSD_PLAY_ICON);
+    vlc_player_osd_Icon(player, pause ? OSD_PAUSE_ICON : OSD_PLAY_ICON);
 }
 
 void
@@ -2745,63 +1320,13 @@ vlc_player_assert_seek_params(enum vlc_player_seek_speed speed,
     (void) speed; (void) whence;
 }
 
-static void
-vlc_player_vout_OSDPosition(vlc_player_t *player,
-                            struct vlc_player_input *input, vlc_tick_t time,
-                            float position, enum vlc_player_whence whence)
-{
-    if (input->length != VLC_TICK_INVALID)
-    {
-        if (time == VLC_TICK_INVALID)
-            time = position * input->length;
-        else
-            position = time / (float) input->length;
-    }
-
-    size_t count;
-    vout_thread_t **vouts = vlc_player_vout_OSDHoldAll(player, &count);
-
-    if (time != VLC_TICK_INVALID)
-    {
-        if (whence == VLC_PLAYER_WHENCE_RELATIVE)
-        {
-            time += vlc_player_input_GetTime(input); /* XXX: TOCTOU */
-            if (time < 0)
-                time = 0;
-        }
-
-        char time_text[MSTRTIME_MAX_SIZE];
-        secstotimestr(time_text, SEC_FROM_VLC_TICK(time));
-        if (input->length != VLC_TICK_INVALID)
-        {
-            char len_text[MSTRTIME_MAX_SIZE];
-            secstotimestr(len_text, SEC_FROM_VLC_TICK(input->length));
-            vouts_osd_Message(vouts, count, "%s / %s", time_text, len_text);
-        }
-        else
-            vouts_osd_Message(vouts, count, "%s", time_text);
-    }
-
-    if (vlc_player_vout_IsFullscreen(player))
-    {
-        if (whence == VLC_PLAYER_WHENCE_RELATIVE)
-        {
-            position += vlc_player_input_GetPos(input); /* XXX: TOCTOU */
-            if (position < 0.f)
-                position = 0.f;
-        }
-        vouts_osd_Slider(vouts, count, position * 100, OSD_HOR_SLIDER);
-    }
-    vlc_player_vout_OSDReleaseAll(player, vouts, count);
-}
-
 void
 vlc_player_DisplayPosition(vlc_player_t *player)
 {
     struct vlc_player_input *input = vlc_player_get_input_locked(player);
     if (!input)
         return;
-    vlc_player_vout_OSDPosition(player, input,
+    vlc_player_osd_Position(player, input,
                                 vlc_player_input_GetTime(input),
                                 vlc_player_input_GetPos(input),
                                 VLC_PLAYER_WHENCE_ABSOLUTE);
@@ -2827,7 +1352,7 @@ vlc_player_SeekByPos(vlc_player_t *player, float position,
             .pos.b_fast_seek = speed == VLC_PLAYER_SEEK_FAST,
     });
 
-    vlc_player_vout_OSDPosition(player, input, VLC_TICK_INVALID, position,
+    vlc_player_osd_Position(player, input, VLC_TICK_INVALID, position,
                                    whence);
 }
 
@@ -2851,7 +1376,7 @@ vlc_player_SeekByTime(vlc_player_t *player, vlc_tick_t time,
             .time.b_fast_seek = speed == VLC_PLAYER_SEEK_FAST,
     });
 
-    vlc_player_vout_OSDPosition(player, input, time, -1, whence);
+    vlc_player_osd_Position(player, input, time, -1, whence);
 }
 
 void
@@ -3247,386 +1772,6 @@ vlc_player_CorkCallback(vlc_object_t *this, const char *var,
     (void) this; (void) var;
 }
 
-audio_output_t *
-vlc_player_aout_Hold(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 void
-vlc_player_vout_OSDVolume(vlc_player_t *player, bool mute_action)
-{
-    size_t count;
-    vout_thread_t **vouts = vlc_player_vout_OSDHoldAll(player, &count);
-
-    bool mute = vlc_player_aout_IsMuted(player);
-    int volume = lroundf(vlc_player_aout_GetVolume(player) * 100.f);
-    if (mute_action && mute)
-        vouts_osd_Icon(vouts, count, OSD_MUTE_ICON);
-    else
-    {
-        if (vlc_player_vout_IsFullscreen(player))
-            vouts_osd_Slider(vouts, count, volume, OSD_VERT_SLIDER);
-        vouts_osd_Message(vouts, count, _("Volume: %ld%%"), volume);
-    }
-
-    vlc_player_vout_OSDReleaseAll(player, vouts, count);
-}
-
-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);
-            vlc_player_vout_OSDVolume(player, false);
-        }
-    }
-    else if (strcmp(var, "mute") == 0)
-    {
-        if (oldval.b_bool != newval.b_bool)
-        {
-            vlc_player_aout_SendEvent(player, on_mute_changed, newval.b_bool);
-            vlc_player_vout_OSDVolume(player, true);
-        }
-    }
-    else if (strcmp(var, "device") == 0)
-    {
-        const char *old = oldval.psz_string;
-        const char *new = newval.psz_string;
-        /* support NULL values for string comparison */
-        if (old != new && (!old || !new || strcmp(old, new)))
-            vlc_player_aout_SendEvent(player, on_device_changed,
-                                      newval.psz_string);
-    }
-    else
-        vlc_assert_unreachable();
-
-    return VLC_SUCCESS;
-    (void) this;
-}
-
-float
-vlc_player_aout_GetVolume(vlc_player_t *player)
-{
-    audio_output_t *aout = vlc_player_aout_Hold(player);
-    if (!aout)
-        return -1.f;
-    float vol = aout_VolumeGet(aout);
-    aout_Release(aout);
-
-    return vol;
-}
-
-int
-vlc_player_aout_SetVolume(vlc_player_t *player, float volume)
-{
-    audio_output_t *aout = vlc_player_aout_Hold(player);
-    if (!aout)
-        return -1;
-    int ret = aout_VolumeSet(aout, volume);
-    aout_Release(aout);
-
-    return ret;
-}
-
-int
-vlc_player_aout_IncrementVolume(vlc_player_t *player, int steps, float *result)
-{
-    audio_output_t *aout = vlc_player_aout_Hold(player);
-    if (!aout)
-        return -1;
-    int ret = aout_VolumeUpdate(aout, steps, result);
-    aout_Release(aout);
-
-    return ret;
-}
-
-int
-vlc_player_aout_IsMuted(vlc_player_t *player)
-{
-    audio_output_t *aout = vlc_player_aout_Hold(player);
-    if (!aout)
-        return -1;
-    int ret = aout_MuteGet(aout);
-    aout_Release(aout);
-
-    return ret;
-}
-
-int
-vlc_player_aout_Mute(vlc_player_t *player, bool mute)
-{
-    audio_output_t *aout = vlc_player_aout_Hold(player);
-    if (!aout)
-        return -1;
-    int ret = aout_MuteSet (aout, mute);
-    aout_Release(aout);
-
-    return ret;
-}
-
-
-int
-vlc_player_aout_EnableFilter(vlc_player_t *player, const char *name, bool add)
-{
-    audio_output_t *aout = vlc_player_aout_Hold(player);
-    if (!aout)
-        return -1;
-    aout_EnableFilter(aout, name, add);
-    aout_Release(aout);
-
-    return 0;
-}
-
-vout_thread_t *
-vlc_player_vout_Hold(vlc_player_t *player)
-{
-    vout_thread_t *vout = input_resource_HoldVout(player->resource);
-    return vout ? vout : input_resource_HoldDummyVout(player->resource);
-}
-
-vout_thread_t **
-vlc_player_vout_HoldAll(vlc_player_t *player, size_t *count)
-{
-    vout_thread_t **vouts;
-    input_resource_HoldVouts(player->resource, &vouts, count);
-
-    if (*count == 0)
-    {
-        vouts = vlc_alloc(1, sizeof(*vouts));
-        if (vouts)
-        {
-            *count = 1;
-            vouts[0] = input_resource_HoldDummyVout(player->resource);
-        }
-    }
-    return vouts;
-}
-
-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)
-{
-    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 bool
-vout_osd_PrintVariableText(vout_thread_t *vout, const char *varname, int vartype,
-                           vlc_value_t varval, const char *osdfmt)
-{
-    bool found = false;
-    bool isvarstring = vartype == VLC_VAR_STRING;
-    size_t num_choices;
-    vlc_value_t *choices;
-    char **choices_text;
-    var_Change(vout, varname, VLC_VAR_GETCHOICES,
-               &num_choices, &choices, &choices_text);
-    for (size_t i = 0; i < num_choices; ++i)
-    {
-        if (!found)
-            if ((isvarstring &&
-                 strcmp(choices[i].psz_string, varval.psz_string) == 0) ||
-                (!isvarstring && choices[i].f_float == varval.f_float))
-            {
-                vouts_osd_Message(&vout, 1, osdfmt, choices_text[i]);
-                found = true;
-            }
-        if (isvarstring)
-            free(choices[i].psz_string);
-        free(choices_text[i]);
-    }
-    free(choices);
-    free(choices_text);
-    return found;
-}
-
-static int
-vlc_player_VoutOSDCallback(vlc_object_t *this, const char *var,
-                           vlc_value_t oldval, vlc_value_t newval, void *data)
-{
-    VLC_UNUSED(oldval);
-
-    vout_thread_t *vout = (vout_thread_t *)this;
-
-    if (strcmp(var, "aspect-ratio") == 0)
-        vout_osd_PrintVariableText(vout, var, VLC_VAR_STRING,
-                                   newval, _("Aspect ratio: %s"));
-
-    else if (strcmp(var, "autoscale") == 0)
-        vouts_osd_Message(&vout, 1, newval.b_bool ?
-                          _("Scaled to screen") : _("Original size"));
-
-    else if (strcmp(var, "crop") == 0)
-        vout_osd_PrintVariableText(vout, var, VLC_VAR_STRING, newval,
-                                   _("Crop: %s"));
-
-    else if (strcmp(var, "crop-bottom") == 0)
-        vouts_osd_Message(&vout, 1, _("Bottom crop: %d px"), newval.i_int);
-
-    else if (strcmp(var, "crop-top") == 0)
-        vouts_osd_Message(&vout, 1, _("Top crop: %d px"), newval.i_int);
-
-    else if (strcmp(var, "crop-left") == 0)
-        vouts_osd_Message(&vout, 1, _("Left crop: %d px"), newval.i_int);
-
-    else if (strcmp(var, "crop-right") == 0)
-        vouts_osd_Message(&vout, 1, _("Right crop: %d px"), newval.i_int);
-
-    else if (strcmp(var, "deinterlace") == 0 ||
-             strcmp(var, "deinterlace-mode") == 0)
-    {
-        bool varmode = strcmp(var, "deinterlace-mode") == 0;
-        int on = !varmode ?
-            newval.i_int : var_GetInteger(vout, "deinterlace");
-        char *mode = varmode ?
-            newval.psz_string : var_GetString(vout, "deinterlace-mode");
-        vouts_osd_Message(&vout, 1, _("Deinterlace %s (%s)"),
-                          on == 1 ? _("On") : _("Off"), mode);
-        if (!varmode)
-            free(mode);
-    }
-
-    else if (strcmp(var, "sub-margin") == 0)
-        vouts_osd_Message(&vout, 1, _("Subtitle position %d px"), newval.i_int);
-
-    else if (strcmp(var, "secondary-sub-margin") == 0)
-        vouts_osd_Message(&vout, 1, _("Secondary subtitle position %d px"), newval.i_int);
-
-    else if (strcmp(var, "sub-text-scale") == 0)
-        vouts_osd_Message(&vout, 1, _("Subtitle text scale %d%%"), newval.i_int);
-
-    else if (strcmp(var, "zoom") == 0)
-    {
-        if (newval.f_float == 1.f)
-            vouts_osd_Message(&vout, 1, _("Zooming reset"));
-        else
-        {
-            bool found =  vout_osd_PrintVariableText(vout, var, VLC_VAR_FLOAT,
-                                                     newval, _("Zoom mode: %s"));
-            if (!found)
-                vouts_osd_Message(&vout, 1, _("Zoom: x%f"), newval.f_float);
-        }
-    }
-
-    (void) data;
-    return VLC_SUCCESS;
-}
-
-static void
-vlc_player_vout_SetVar(vlc_player_t *player, const char *name, int type,
-                       vlc_value_t val)
-{
-    vout_thread_t *vout = vlc_player_vout_Hold(player);
-    var_SetChecked(vout, name, type, val);
-    vout_Release(vout);
-}
-
-
-static void
-vlc_player_vout_TriggerOption(vlc_player_t *player, const char *option)
-{
-    /* Don't use vlc_player_vout_Hold() since there is nothing to trigger if it
-     * returns a dummy vout */
-    vout_thread_t *vout = input_resource_HoldVout(player->resource);
-    var_TriggerCallback(vout, option);
-    vout_Release(vout);
-}
-
 vlc_object_t *
 vlc_player_GetV4l2Object(vlc_player_t *player)
 {
@@ -3635,34 +1780,6 @@ vlc_player_GetV4l2Object(vlc_player_t *player)
            (vlc_object_t*) input->thread : NULL;
 }
 
-void
-vlc_player_vout_SetFullscreen(vlc_player_t *player, bool enabled)
-{
-    vlc_player_vout_SetVar(player, "fullscreen", VLC_VAR_BOOL,
-                           (vlc_value_t) { .b_bool = 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_vout_SetVar(player, "video-wallpaper", VLC_VAR_BOOL,
-                           (vlc_value_t) { .b_bool = enabled });
-    vlc_player_vout_SendEvent(player, on_wallpaper_mode_changed, NULL, enabled);
-}
-
-void
-vlc_player_vout_Snapshot(vlc_player_t *player)
-{
-    vlc_player_vout_TriggerOption(player, "video-snapshot");
-}
-
 static void
 vlc_player_InitLocks(vlc_player_t *player, enum vlc_player_lock_type lock_type)
 {
@@ -3713,14 +1830,9 @@ vlc_player_Delete(vlc_player_t *player)
 
     vlc_player_DestroyLocks(player);
 
-    audio_output_t *aout = vlc_player_aout_Hold(player);
-    if (aout)
-    {
-        var_DelCallback(aout, "volume", vlc_player_AoutCallback, player);
-        var_DelCallback(aout, "mute", vlc_player_AoutCallback, player);
-        var_DelCallback(player, "corks", vlc_player_CorkCallback, NULL);
-        aout_Release(aout);
-    }
+    vlc_player_aout_DelCallbacks(player);
+    var_DelCallback(player, "corks", vlc_player_CorkCallback, NULL);
+
     input_resource_Release(player->resource);
     if (player->renderer)
         vlc_renderer_item_release(player->renderer);
@@ -3808,16 +1920,14 @@ vlc_player_New(vlc_object_t *parent, enum vlc_player_lock_type lock_type,
     if (!player->resource)
         goto error;
 
-
+    /* Ensure the player has a valid aout */
     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);
-        var_AddCallback(aout, "device", vlc_player_AoutCallback, player);
-        var_AddCallback(player, "corks", vlc_player_CorkCallback, NULL);
+        vlc_player_aout_AddCallbacks(player);
         input_resource_PutAout(player->resource, aout);
     }
+    var_AddCallback(player, "corks", vlc_player_CorkCallback, NULL);
 
     player->deleting = false;
     vlc_player_InitLocks(player, lock_type);
@@ -3833,12 +1943,8 @@ vlc_player_New(vlc_object_t *parent, enum vlc_player_lock_type lock_type,
 
 error:
     if (aout)
-    {
-        var_DelCallback(aout, "volume", vlc_player_AoutCallback, player);
-        var_DelCallback(aout, "mute", vlc_player_AoutCallback, player);
-        var_DelCallback(aout, "device", vlc_player_AoutCallback, player);
-        var_DelCallback(player, "corks", vlc_player_AoutCallback, NULL);
-    }
+        vlc_player_aout_DelCallbacks(player);
+    var_DelCallback(player, "corks", vlc_player_CorkCallback, NULL);
     if (player->resource)
         input_resource_Release(player->resource);
 
diff --git a/src/input/player.h b/src/input/player.h
index f65be833ba..5ad169fd28 100644
--- a/src/input/player.h
+++ b/src/input/player.h
@@ -1,7 +1,7 @@
 /*****************************************************************************
  * player.h: Player internal interface
  *****************************************************************************
- * Copyright © 2018 VLC authors and VideoLAN
+ * Copyright © 2018-2019 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
@@ -22,17 +22,348 @@
 #define VLC_PLAYER_INTERNAL_H
 
 #include <vlc_player.h>
+#include <vlc_list.h>
+#include <vlc_vector.h>
+#include <vlc_atomic.h>
 
-/**
+#include "input_internal.h"
+
+struct vlc_player_track_priv
+{
+    struct vlc_player_track t;
+    vout_thread_t *vout; /* weak reference */
+    vlc_tick_t delay;
+    /* only valid if selected and if category is VIDEO_ES or SPU_ES */
+    enum vlc_vout_order vout_order;
+};
+
+typedef struct VLC_VECTOR(struct vlc_player_program *)
+    vlc_player_program_vector;
+
+typedef struct VLC_VECTOR(struct vlc_player_track_priv *)
+    vlc_player_track_vector;
+
+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;
+    double output_rate;
+    int capabilities;
+    vlc_tick_t length;
+
+    float position;
+    vlc_tick_t time;
+    vlc_tick_t input_time_date;
+
+    vlc_tick_t output_time;
+    vlc_tick_t output_time_date;
+
+    vlc_tick_t pause_date;
+
+    bool recording;
+
+    float signal_quality;
+    float signal_strength;
+    float cache;
+
+    struct input_stats_t stats;
+
+    vlc_tick_t cat_delays[DATA_ES];
+
+    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_priv *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_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_t
+{
+    struct vlc_object_t 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;
+
+    bool pause_on_cork;
+    bool corked;
+
+    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 next_media_requested;
+    input_item_t *next_media;
+
+    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;
+};
+
+/*
  * 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);
+static inline 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;
+}
+
+#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)
+
+static inline const char *
+es_format_category_to_string(enum es_format_category_e cat)
+{
+    switch (cat)
+    {
+        case VIDEO_ES: return "Video";
+        case AUDIO_ES: return "Audio";
+        case SPU_ES: return "Subtitle";
+        default: return NULL;
+    }
+}
+
+/*
+ * player.c
+ */
 
 vlc_object_t *
 vlc_player_GetObject(vlc_player_t *player);
 
+int
+vlc_player_OpenNextMedia(vlc_player_t *player);
+
+void
+vlc_player_PrepareNextMedia(vlc_player_t *player);
+
+void
+vlc_player_destructor_AddStoppingInput(vlc_player_t *player,
+                                       struct vlc_player_input *input);
+
+void
+vlc_player_destructor_AddJoinableInput(vlc_player_t *player,
+                                       struct vlc_player_input *input);
+
+/*
+ * player_track.c
+ */
+
+struct vlc_player_program *
+vlc_player_program_New(int id, const char *name);
+
+int
+vlc_player_program_Update(struct vlc_player_program *prgm, int id,
+                          const char *name);
+
+struct vlc_player_program *
+vlc_player_program_vector_FindById(vlc_player_program_vector *vec, int id,
+                                   size_t *idx);
+
+struct vlc_player_track_priv *
+vlc_player_track_priv_New(vlc_es_id_t *id, const char *name, const es_format_t *fmt);
+
+void
+vlc_player_track_priv_Delete(struct vlc_player_track_priv *trackpriv);
+
+int
+vlc_player_track_priv_Update(struct vlc_player_track_priv *trackpriv,
+                             const char *name, const es_format_t *fmt);
+
+struct vlc_player_track_priv *
+vlc_player_track_vector_FindById(vlc_player_track_vector *vec, vlc_es_id_t *id,
+                                 size_t *idx);
+
+/*
+ * player_title.c
+ */
+
+struct vlc_player_title_list *
+vlc_player_title_list_Create(input_title_t *const *array, size_t count,
+                             int title_offset, int chapter_offset);
+
+/*
+ * player_input.c
+ */
+
+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;
+    }
+}
+
+struct vlc_player_track_priv *
+vlc_player_input_FindTrackById(struct vlc_player_input *input, vlc_es_id_t *id,
+                               size_t *idx);
+
+struct vlc_player_input *
+vlc_player_input_New(vlc_player_t *player, input_item_t *item);
+
+void
+vlc_player_input_Delete(struct vlc_player_input *input);
+
+vlc_tick_t
+vlc_player_input_GetTime(struct vlc_player_input *input);
+
+float
+vlc_player_input_GetPos(struct vlc_player_input *input);
+
+int
+vlc_player_input_Start(struct vlc_player_input *input);
+
+void
+vlc_player_input_HandleState(struct vlc_player_input *, enum vlc_player_state,
+                             vlc_tick_t);
+
+/*
+ * player_vout.c
+ */
+
+void
+vlc_player_vout_AddCallbacks(vlc_player_t *player, vout_thread_t *vout);
+
+void
+vlc_player_vout_DelCallbacks(vlc_player_t *player, vout_thread_t *vout);
+
+/*
+ * player_aout.c
+ */
+
+void
+vlc_player_aout_AddCallbacks(vlc_player_t *player);
+
+void
+vlc_player_aout_DelCallbacks(vlc_player_t *player);
+
+/*
+ * player_osd.c
+ */
+
+void
+vlc_player_osd_Message(vlc_player_t *player, const char *fmt, ...);
+
+void
+vlc_player_osd_Icon(vlc_player_t *player, short type);
+
+void
+vlc_player_osd_Position(vlc_player_t *player,
+                        struct vlc_player_input *input, vlc_tick_t time,
+                        float position, enum vlc_player_whence whence);
+void
+vlc_player_osd_Volume(vlc_player_t *player, bool mute_action);
+
+int
+vlc_player_vout_OSDCallback(vlc_object_t *this, const char *var,
+                            vlc_value_t oldval, vlc_value_t newval, void *data);
+
+void
+vlc_player_osd_Track(vlc_player_t *player, vlc_es_id_t *id, bool select);
+
+void
+vlc_player_osd_Program(vlc_player_t *player, const char *name);
+
 #endif
diff --git a/src/input/player_aout.c b/src/input/player_aout.c
new file mode 100644
index 0000000000..9c4cf84dd5
--- /dev/null
+++ b/src/input/player_aout.c
@@ -0,0 +1,219 @@
+/*****************************************************************************
+ * player.c: Player interface
+ *****************************************************************************
+ * Copyright © 2018-2019 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 <limits.h>
+
+#include <vlc_common.h>
+#include <vlc_decoder.h>
+#include "player.h"
+#include "resource.h"
+
+#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)
+
+audio_output_t *
+vlc_player_aout_Hold(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);
+            vlc_player_osd_Volume(player, false);
+        }
+    }
+    else if (strcmp(var, "mute") == 0)
+    {
+        if (oldval.b_bool != newval.b_bool)
+        {
+            vlc_player_aout_SendEvent(player, on_mute_changed, newval.b_bool);
+            vlc_player_osd_Volume(player, true);
+        }
+    }
+    else if (strcmp(var, "device") == 0)
+    {
+        const char *old = oldval.psz_string;
+        const char *new = newval.psz_string;
+        /* support NULL values for string comparison */
+        if (old != new && (!old || !new || strcmp(old, new)))
+            vlc_player_aout_SendEvent(player, on_device_changed,
+                                      newval.psz_string);
+    }
+    else
+        vlc_assert_unreachable();
+
+    return VLC_SUCCESS;
+    (void) this;
+}
+
+float
+vlc_player_aout_GetVolume(vlc_player_t *player)
+{
+    audio_output_t *aout = vlc_player_aout_Hold(player);
+    if (!aout)
+        return -1.f;
+    float vol = aout_VolumeGet(aout);
+    aout_Release(aout);
+
+    return vol;
+}
+
+int
+vlc_player_aout_SetVolume(vlc_player_t *player, float volume)
+{
+    audio_output_t *aout = vlc_player_aout_Hold(player);
+    if (!aout)
+        return -1;
+    int ret = aout_VolumeSet(aout, volume);
+    aout_Release(aout);
+
+    return ret;
+}
+
+int
+vlc_player_aout_IncrementVolume(vlc_player_t *player, int steps, float *result)
+{
+    audio_output_t *aout = vlc_player_aout_Hold(player);
+    if (!aout)
+        return -1;
+    int ret = aout_VolumeUpdate(aout, steps, result);
+    aout_Release(aout);
+
+    return ret;
+}
+
+int
+vlc_player_aout_IsMuted(vlc_player_t *player)
+{
+    audio_output_t *aout = vlc_player_aout_Hold(player);
+    if (!aout)
+        return -1;
+    int ret = aout_MuteGet(aout);
+    aout_Release(aout);
+
+    return ret;
+}
+
+int
+vlc_player_aout_Mute(vlc_player_t *player, bool mute)
+{
+    audio_output_t *aout = vlc_player_aout_Hold(player);
+    if (!aout)
+        return -1;
+    int ret = aout_MuteSet (aout, mute);
+    aout_Release(aout);
+
+    return ret;
+}
+
+int
+vlc_player_aout_EnableFilter(vlc_player_t *player, const char *name, bool add)
+{
+    audio_output_t *aout = vlc_player_aout_Hold(player);
+    if (!aout)
+        return -1;
+    aout_EnableFilter(aout, name, add);
+    aout_Release(aout);
+
+    return 0;
+}
+
+
+void
+vlc_player_aout_AddCallbacks(vlc_player_t *player)
+{
+    audio_output_t *aout = vlc_player_aout_Hold(player);
+    if (!aout)
+        return;
+
+    var_AddCallback(aout, "volume", vlc_player_AoutCallback, player);
+    var_AddCallback(aout, "mute", vlc_player_AoutCallback, player);
+    var_AddCallback(aout, "device", vlc_player_AoutCallback, player);
+
+    aout_Release(aout);
+}
+
+void
+vlc_player_aout_DelCallbacks(vlc_player_t *player)
+{
+    audio_output_t *aout = vlc_player_aout_Hold(player);
+    if (!aout)
+        return;
+
+    var_DelCallback(aout, "volume", vlc_player_AoutCallback, player);
+    var_DelCallback(aout, "mute", vlc_player_AoutCallback, player);
+    var_DelCallback(aout, "device", vlc_player_AoutCallback, player);
+
+    aout_Release(aout);
+}
diff --git a/src/input/player_input.c b/src/input/player_input.c
new file mode 100644
index 0000000000..73ececb6df
--- /dev/null
+++ b/src/input/player_input.c
@@ -0,0 +1,771 @@
+/*****************************************************************************
+ * player.c: Player interface
+ *****************************************************************************
+ * Copyright © 2018-2019 VLC authors and VideoLAN
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2.1 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
+ *****************************************************************************/
+
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif
+
+#include <vlc_common.h>
+#include <vlc_interface.h>
+#include "player.h"
+
+struct vlc_player_track_priv *
+vlc_player_input_FindTrackById(struct vlc_player_input *input, vlc_es_id_t *id,
+                               size_t *idx)
+{
+    vlc_player_track_vector *vec =
+        vlc_player_input_GetTrackVector(input, vlc_es_id_GetCat(id));
+    return vec ? vlc_player_track_vector_FindById(vec, id, idx) : NULL;
+}
+
+static void
+vlc_player_input_HandleAtoBLoop(struct vlc_player_input *input, vlc_tick_t time,
+                                float pos)
+{
+    vlc_player_t *player = input->player;
+
+    if (player->input != input)
+        return;
+
+    assert(input->abloop_state[0].set && input->abloop_state[1].set);
+
+    if (time != VLC_TICK_INVALID
+     && input->abloop_state[0].time != VLC_TICK_INVALID
+     && input->abloop_state[1].time != VLC_TICK_INVALID)
+    {
+        if (time >= input->abloop_state[1].time)
+            vlc_player_SetTime(player, input->abloop_state[0].time);
+    }
+    else if (pos >= input->abloop_state[1].pos)
+        vlc_player_SetPosition(player, input->abloop_state[0].pos);
+}
+
+vlc_tick_t
+vlc_player_input_GetTime(struct vlc_player_input *input)
+{
+    return input->time;
+}
+
+float
+vlc_player_input_GetPos(struct vlc_player_input *input)
+{
+    return input->position;
+}
+
+static void
+vlc_player_input_UpdateTime(struct vlc_player_input *input)
+{
+    if (input->abloop_state[0].set && input->abloop_state[1].set)
+        vlc_player_input_HandleAtoBLoop(input, vlc_player_input_GetTime(input),
+                                        vlc_player_input_GetPos(input));
+}
+
+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 bool
+vlc_player_WaitRetryDelay(vlc_player_t *player)
+{
+#define RETRY_TIMEOUT_BASE VLC_TICK_FROM_MS(100)
+#define RETRY_TIMEOUT_MAX VLC_TICK_FROM_MS(3200)
+    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;
+}
+
+void
+vlc_player_input_HandleState(struct vlc_player_input *input,
+                             enum vlc_player_state state, vlc_tick_t state_date)
+{
+    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->titles)
+            {
+                vlc_player_title_list_Release(input->titles);
+                input->titles = NULL;
+                vlc_player_SendEvent(player, on_titles_changed, NULL);
+            }
+
+            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(vlc_object_instance(player));
+                    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_PrepareNextMedia(player);
+                if (!player->next_media)
+                    player->started = false;
+            }
+            send_event = !player->started;
+            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);
+    }
+}
+
+static void
+vlc_player_input_HandleStateEvent(struct vlc_player_input *input,
+                                  input_state_e state, vlc_tick_t state_date)
+{
+    switch (state)
+    {
+        case OPENING_S:
+            vlc_player_input_HandleState(input, VLC_PLAYER_STATE_STARTED,
+                                         VLC_TICK_INVALID);
+            break;
+        case PLAYING_S:
+            vlc_player_input_HandleState(input, VLC_PLAYER_STATE_PLAYING,
+                                         state_date);
+            break;
+        case PAUSE_S:
+            vlc_player_input_HandleState(input, VLC_PLAYER_STATE_PAUSED,
+                                         state_date);
+            break;
+        case END_S:
+            vlc_player_input_HandleState(input, VLC_PLAYER_STATE_STOPPING,
+                                         VLC_TICK_INVALID);
+            vlc_player_destructor_AddStoppingInput(input->player, input);
+            break;
+        case ERROR_S:
+            /* Don't send errors if the input is stopped by the user */
+            if (input->started)
+            {
+                /* 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_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 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_priv_Delete(input->teletext_menu);
+            }
+            input->teletext_menu = vlc_player_track_priv_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->t.es_id == ev->id)
+            {
+                assert(!input->teletext_enabled);
+
+                vlc_player_track_priv_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->t.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();
+    }
+}
+
+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 > 8))
+    {
+        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_priv *trackpriv;
+    switch (ev->action)
+    {
+        case VLC_INPUT_ES_ADDED:
+            trackpriv = vlc_player_track_priv_New(ev->id, ev->title, ev->fmt);
+            if (!trackpriv)
+                break;
+
+            if (!vlc_vector_push(vec, trackpriv))
+            {
+                vlc_player_track_priv_Delete(trackpriv);
+                break;
+            }
+            vlc_player_SendEvent(player, on_track_list_changed,
+                                 VLC_PLAYER_LIST_ADDED, &trackpriv->t);
+            break;
+        case VLC_INPUT_ES_DELETED:
+        {
+            size_t idx;
+            trackpriv = vlc_player_track_vector_FindById(vec, ev->id, &idx);
+            if (trackpriv)
+            {
+                vlc_player_SendEvent(player, on_track_list_changed,
+                                     VLC_PLAYER_LIST_REMOVED, &trackpriv->t);
+                vlc_vector_remove(vec, idx);
+                vlc_player_track_priv_Delete(trackpriv);
+            }
+            break;
+        }
+        case VLC_INPUT_ES_UPDATED:
+            trackpriv = vlc_player_track_vector_FindById(vec, ev->id, NULL);
+            if (!trackpriv)
+                break;
+            if (vlc_player_track_priv_Update(trackpriv, ev->title, ev->fmt) != 0)
+                break;
+            vlc_player_SendEvent(player, on_track_list_changed,
+                                 VLC_PLAYER_LIST_UPDATED, &trackpriv->t);
+            break;
+        case VLC_INPUT_ES_SELECTED:
+            trackpriv = vlc_player_track_vector_FindById(vec, ev->id, NULL);
+            if (trackpriv)
+            {
+                trackpriv->t.selected = true;
+                vlc_player_SendEvent(player, on_track_selection_changed,
+                                     NULL, trackpriv->t.es_id);
+            }
+            break;
+        case VLC_INPUT_ES_UNSELECTED:
+            trackpriv = vlc_player_track_vector_FindById(vec, ev->id, NULL);
+            if (trackpriv)
+            {
+                trackpriv->t.selected = false;
+                vlc_player_SendEvent(player, on_track_selection_changed,
+                                     trackpriv->t.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);
+}
+
+static void
+vlc_player_input_HandleVoutEvent(struct vlc_player_input *input,
+                                 const struct vlc_input_event_vout *ev)
+{
+    assert(ev->vout);
+    assert(ev->id);
+
+    vlc_player_t *player = input->player;
+
+    struct vlc_player_track_priv *trackpriv =
+        vlc_player_input_FindTrackById(input, ev->id, NULL);
+    if (!trackpriv)
+        return;
+
+    const bool is_video_es = trackpriv->t.fmt.i_cat == VIDEO_ES;
+
+    switch (ev->action)
+    {
+        case VLC_INPUT_EVENT_VOUT_ADDED:
+            trackpriv->vout = ev->vout;
+            vlc_player_SendEvent(player, on_vout_changed,
+                                 VLC_PLAYER_VOUT_STARTED, ev->vout,
+                                 ev->order, ev->id);
+
+            if (is_video_es)
+            {
+                /* Register vout callbacks after the vout list event */
+                vlc_player_vout_AddCallbacks(player, ev->vout);
+            }
+            break;
+        case VLC_INPUT_EVENT_VOUT_DELETED:
+            if (is_video_es)
+            {
+                /* Un-register vout callbacks before the vout list event */
+                vlc_player_vout_DelCallbacks(player, ev->vout);
+            }
+
+            trackpriv->vout = NULL;
+            vlc_player_SendEvent(player, on_vout_changed,
+                                 VLC_PLAYER_VOUT_STOPPED, ev->vout,
+                                 VLC_VOUT_ORDER_NONE, ev->id);
+            break;
+        default:
+            vlc_assert_unreachable();
+    }
+}
+
+static void
+input_thread_Events(input_thread_t *input_thread,
+                    const struct vlc_input_event *event, void *user_data)
+{
+    struct vlc_player_input *input = user_data;
+    vlc_player_t *player = input->player;
+
+    assert(input_thread == input->thread);
+
+    vlc_mutex_lock(&player->lock);
+
+    switch (event->type)
+    {
+        case INPUT_EVENT_STATE:
+            vlc_player_input_HandleStateEvent(input, event->state.value,
+                                              event->state.date);
+            break;
+        case INPUT_EVENT_RATE:
+            input->rate = event->rate;
+            vlc_player_SendEvent(player, on_rate_changed, input->rate);
+            break;
+        case INPUT_EVENT_CAPABILITIES:
+        {
+            int old_caps = input->capabilities;
+            input->capabilities = event->capabilities;
+            vlc_player_SendEvent(player, on_capabilities_changed,
+                                 old_caps, input->capabilities);
+            break;
+        }
+        case INPUT_EVENT_TIMES:
+            if (event->times.ms != VLC_TICK_INVALID
+             && (input->time != event->times.ms
+              || input->position != event->times.percentage))
+            {
+                input->time = event->times.ms;
+                input->position = event->times.percentage;
+                vlc_player_SendEvent(player, on_position_changed,
+                                     input->time, input->position);
+
+                vlc_player_input_UpdateTime(input);
+            }
+            if (input->length != event->times.length)
+            {
+                input->length = event->times.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_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:
+            if (input->started) /* Can happen with early input_thread fails */
+                vlc_player_input_HandleState(input, VLC_PLAYER_STATE_STOPPING,
+                                             VLC_TICK_INVALID);
+            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);
+}
+
+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->time = VLC_TICK_INVALID;
+    input->position = 0.f;
+
+    input->recording = false;
+
+    input->cache = 0.f;
+    input->signal_quality = input->signal_strength = -1.f;
+
+    memset(&input->stats, 0, sizeof(input->stats));
+
+    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,
+                                 player->resource, player->renderer);
+    if (!input->thread)
+    {
+        free(input);
+        return NULL;
+    }
+
+    /* Initial sub/audio delay */
+    const vlc_tick_t cat_delays[DATA_ES] = {
+        [AUDIO_ES] =
+            VLC_TICK_FROM_MS(var_InheritInteger(player, "audio-desync")),
+        [SPU_ES] =
+            vlc_tick_from_samples(var_InheritInteger(player, "sub-delay"), 10),
+    };
+
+    for (enum es_format_category_e i = UNKNOWN_ES; i < DATA_ES; ++i)
+    {
+        input->cat_delays[i] = cat_delays[i];
+        if (cat_delays[i] != 0)
+        {
+            const input_control_param_t param = {
+                .cat_delay = { i, cat_delays[i] }
+            };
+            input_ControlPush(input->thread, INPUT_CONTROL_SET_CATEGORY_DELAY,
+                              &param);
+            vlc_player_SendEvent(player, on_category_delay_changed, i,
+                                 cat_delays[i]);
+        }
+    }
+    return input;
+}
+
+void
+vlc_player_input_Delete(struct vlc_player_input *input)
+{
+    assert(input->titles == 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);
+
+    input_Close(input->thread);
+    free(input);
+}
+
diff --git a/src/input/player_osd.c b/src/input/player_osd.c
new file mode 100644
index 0000000000..c8bc9bb983
--- /dev/null
+++ b/src/input/player_osd.c
@@ -0,0 +1,308 @@
+/*****************************************************************************
+ * player.c: Player interface
+ *****************************************************************************
+ * Copyright © 2018-2019 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 <limits.h>
+
+#include <vlc_common.h>
+#include "player.h"
+#include "resource.h"
+
+static vout_thread_t **
+vlc_player_osd_HoldAll(vlc_player_t *player, size_t *count)
+{
+    vout_thread_t **vouts;
+    input_resource_HoldVouts(player->resource, &vouts, count);
+
+    for (size_t i = 0; i < *count; ++i)
+    {
+        vout_FlushSubpictureChannel(vouts[i], VOUT_SPU_CHANNEL_OSD);
+        vout_FlushSubpictureChannel(vouts[i], VOUT_SPU_CHANNEL_OSD_HSLIDER);
+        vout_FlushSubpictureChannel(vouts[i], VOUT_SPU_CHANNEL_OSD_HSLIDER);
+    }
+    return vouts;
+}
+
+static void
+vlc_player_osd_ReleaseAll(vlc_player_t *player, vout_thread_t **vouts,
+                            size_t count)
+{
+    for (size_t i = 0; i < count; ++i)
+        vout_Release(vouts[i]);
+    free(vouts);
+    (void) player;
+}
+
+static inline void
+vouts_osd_Message(vout_thread_t **vouts, size_t count, const char *fmt, ...)
+{
+    va_list args;
+    va_start(args, fmt);
+    for (size_t i = 0; i < count; ++i)
+    {
+        va_list acpy;
+        va_copy(acpy, args);
+        vout_OSDMessageVa(vouts[i], VOUT_SPU_CHANNEL_OSD, fmt, acpy);
+        va_end(acpy);
+    }
+    va_end(args);
+}
+
+static inline void
+vouts_osd_Icon(vout_thread_t **vouts, size_t count, short type)
+{
+    for (size_t i = 0; i < count; ++i)
+        vout_OSDIcon(vouts[i], VOUT_SPU_CHANNEL_OSD, type);
+}
+
+static inline void
+vouts_osd_Slider(vout_thread_t **vouts, size_t count, int position, short type)
+{
+    int channel = type == OSD_HOR_SLIDER ?
+        VOUT_SPU_CHANNEL_OSD_HSLIDER : VOUT_SPU_CHANNEL_OSD_VSLIDER;
+    for (size_t i = 0; i < count; ++i)
+        vout_OSDSlider(vouts[i], channel, position, type);
+}
+
+void
+vlc_player_osd_Message(vlc_player_t *player, const char *fmt, ...)
+{
+    size_t count;
+    vout_thread_t **vouts = vlc_player_osd_HoldAll(player, &count);
+
+    va_list args;
+    va_start(args, fmt);
+    for (size_t i = 0; i < count; ++i)
+    {
+        va_list acpy;
+        va_copy(acpy, args);
+        vout_OSDMessageVa(vouts[i], VOUT_SPU_CHANNEL_OSD, fmt, acpy);
+        va_end(acpy);
+    }
+    va_end(args);
+
+    vlc_player_osd_ReleaseAll(player, vouts, count);
+}
+
+void
+vlc_player_osd_Icon(vlc_player_t *player, short type)
+{
+    size_t count;
+    vout_thread_t **vouts = vlc_player_osd_HoldAll(player, &count);
+
+    vouts_osd_Icon(vouts, count, type);
+
+    vlc_player_osd_ReleaseAll(player, vouts, count);
+}
+
+void
+vlc_player_osd_Position(vlc_player_t *player,
+                        struct vlc_player_input *input, vlc_tick_t time,
+                        float position, enum vlc_player_whence whence)
+{
+    if (input->length != VLC_TICK_INVALID)
+    {
+        if (time == VLC_TICK_INVALID)
+            time = position * input->length;
+        else
+            position = time / (float) input->length;
+    }
+
+    size_t count;
+    vout_thread_t **vouts = vlc_player_osd_HoldAll(player, &count);
+
+    if (time != VLC_TICK_INVALID)
+    {
+        if (whence == VLC_PLAYER_WHENCE_RELATIVE)
+        {
+            time += vlc_player_input_GetTime(input); /* XXX: TOCTOU */
+            if (time < 0)
+                time = 0;
+        }
+
+        char time_text[MSTRTIME_MAX_SIZE];
+        secstotimestr(time_text, SEC_FROM_VLC_TICK(time));
+        if (input->length != VLC_TICK_INVALID)
+        {
+            char len_text[MSTRTIME_MAX_SIZE];
+            secstotimestr(len_text, SEC_FROM_VLC_TICK(input->length));
+            vouts_osd_Message(vouts, count, "%s / %s", time_text, len_text);
+        }
+        else
+            vouts_osd_Message(vouts, count, "%s", time_text);
+    }
+
+    if (vlc_player_vout_IsFullscreen(player))
+    {
+        if (whence == VLC_PLAYER_WHENCE_RELATIVE)
+        {
+            position += vlc_player_input_GetPos(input); /* XXX: TOCTOU */
+            if (position < 0.f)
+                position = 0.f;
+        }
+        vouts_osd_Slider(vouts, count, position * 100, OSD_HOR_SLIDER);
+    }
+    vlc_player_osd_ReleaseAll(player, vouts, count);
+}
+
+void
+vlc_player_osd_Volume(vlc_player_t *player, bool mute_action)
+{
+    size_t count;
+    vout_thread_t **vouts = vlc_player_osd_HoldAll(player, &count);
+
+    bool mute = vlc_player_aout_IsMuted(player);
+    int volume = lroundf(vlc_player_aout_GetVolume(player) * 100.f);
+    if (mute_action && mute)
+        vouts_osd_Icon(vouts, count, OSD_MUTE_ICON);
+    else
+    {
+        if (vlc_player_vout_IsFullscreen(player))
+            vouts_osd_Slider(vouts, count, volume, OSD_VERT_SLIDER);
+        vouts_osd_Message(vouts, count, _("Volume: %ld%%"), volume);
+    }
+
+    vlc_player_osd_ReleaseAll(player, vouts, count);
+}
+
+void
+vlc_player_osd_Track(vlc_player_t *player, vlc_es_id_t *id, bool select)
+{
+    enum es_format_category_e cat = vlc_es_id_GetCat(id);
+    const struct vlc_player_track *track = vlc_player_GetTrack(player, id);
+    if (!track && select)
+        return;
+
+    const char *cat_name = es_format_category_to_string(cat);
+    assert(cat_name);
+    const char *track_name = select ? track->name : _("N/A");
+    vlc_player_osd_Message(player, _("%s track: %s"), cat_name, track_name);
+}
+
+void
+vlc_player_osd_Program(vlc_player_t *player, const char *name)
+{
+    vlc_player_osd_Message(player, _("Program Service ID: %s"), name);
+}
+
+static bool
+vout_osd_PrintVariableText(vout_thread_t *vout, const char *varname, int vartype,
+                           vlc_value_t varval, const char *osdfmt)
+{
+    bool found = false;
+    bool isvarstring = vartype == VLC_VAR_STRING;
+    size_t num_choices;
+    vlc_value_t *choices;
+    char **choices_text;
+    var_Change(vout, varname, VLC_VAR_GETCHOICES,
+               &num_choices, &choices, &choices_text);
+    for (size_t i = 0; i < num_choices; ++i)
+    {
+        if (!found)
+            if ((isvarstring &&
+                 strcmp(choices[i].psz_string, varval.psz_string) == 0) ||
+                (!isvarstring && choices[i].f_float == varval.f_float))
+            {
+                vouts_osd_Message(&vout, 1, osdfmt, choices_text[i]);
+                found = true;
+            }
+        if (isvarstring)
+            free(choices[i].psz_string);
+        free(choices_text[i]);
+    }
+    free(choices);
+    free(choices_text);
+    return found;
+}
+
+int
+vlc_player_vout_OSDCallback(vlc_object_t *this, const char *var,
+                            vlc_value_t oldval, vlc_value_t newval, void *data)
+{
+    VLC_UNUSED(oldval);
+
+    vout_thread_t *vout = (vout_thread_t *)this;
+
+    if (strcmp(var, "aspect-ratio") == 0)
+        vout_osd_PrintVariableText(vout, var, VLC_VAR_STRING,
+                                   newval, _("Aspect ratio: %s"));
+
+    else if (strcmp(var, "autoscale") == 0)
+        vouts_osd_Message(&vout, 1, newval.b_bool ?
+                          _("Scaled to screen") : _("Original size"));
+
+    else if (strcmp(var, "crop") == 0)
+        vout_osd_PrintVariableText(vout, var, VLC_VAR_STRING, newval,
+                                   _("Crop: %s"));
+
+    else if (strcmp(var, "crop-bottom") == 0)
+        vouts_osd_Message(&vout, 1, _("Bottom crop: %d px"), newval.i_int);
+
+    else if (strcmp(var, "crop-top") == 0)
+        vouts_osd_Message(&vout, 1, _("Top crop: %d px"), newval.i_int);
+
+    else if (strcmp(var, "crop-left") == 0)
+        vouts_osd_Message(&vout, 1, _("Left crop: %d px"), newval.i_int);
+
+    else if (strcmp(var, "crop-right") == 0)
+        vouts_osd_Message(&vout, 1, _("Right crop: %d px"), newval.i_int);
+
+    else if (strcmp(var, "deinterlace") == 0 ||
+             strcmp(var, "deinterlace-mode") == 0)
+    {
+        bool varmode = strcmp(var, "deinterlace-mode") == 0;
+        int on = !varmode ?
+            newval.i_int : var_GetInteger(vout, "deinterlace");
+        char *mode = varmode ?
+            newval.psz_string : var_GetString(vout, "deinterlace-mode");
+        vouts_osd_Message(&vout, 1, _("Deinterlace %s (%s)"),
+                          on == 1 ? _("On") : _("Off"), mode);
+        if (!varmode)
+            free(mode);
+    }
+
+    else if (strcmp(var, "sub-margin") == 0)
+        vouts_osd_Message(&vout, 1, _("Subtitle position %d px"), newval.i_int);
+
+    else if (strcmp(var, "secondary-sub-margin") == 0)
+        vouts_osd_Message(&vout, 1, _("Secondary subtitle position %d px"), newval.i_int);
+
+    else if (strcmp(var, "sub-text-scale") == 0)
+        vouts_osd_Message(&vout, 1, _("Subtitle text scale %d%%"), newval.i_int);
+
+    else if (strcmp(var, "zoom") == 0)
+    {
+        if (newval.f_float == 1.f)
+            vouts_osd_Message(&vout, 1, _("Zooming reset"));
+        else
+        {
+            bool found =  vout_osd_PrintVariableText(vout, var, VLC_VAR_FLOAT,
+                                                     newval, _("Zoom mode: %s"));
+            if (!found)
+                vouts_osd_Message(&vout, 1, _("Zoom: x%f"), newval.f_float);
+        }
+    }
+
+    (void) data;
+    return VLC_SUCCESS;
+}
diff --git a/src/input/player_title.c b/src/input/player_title.c
new file mode 100644
index 0000000000..c961b03dbd
--- /dev/null
+++ b/src/input/player_title.c
@@ -0,0 +1,174 @@
+/*****************************************************************************
+ * player.c: Player interface
+ *****************************************************************************
+ * Copyright © 2018-2019 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 <limits.h>
+
+#include <vlc_common.h>
+#include "player.h"
+
+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;
+}
+
+struct vlc_player_title_list *
+vlc_player_title_list_Create(input_title_t *const *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)
+        {
+            /* Release titles up to title_idx */
+            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;
+}
diff --git a/src/input/player_track.c b/src/input/player_track.c
new file mode 100644
index 0000000000..d8d9184d1c
--- /dev/null
+++ b/src/input/player_track.c
@@ -0,0 +1,207 @@
+/*****************************************************************************
+ * player.c: Player interface
+ *****************************************************************************
+ * Copyright © 2018-2019 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 <limits.h>
+
+#include <vlc_common.h>
+#include "player.h"
+
+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;
+}
+
+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;
+}
+
+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);
+}
+
+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;
+}
+
+struct vlc_player_track_priv *
+vlc_player_track_priv_New(vlc_es_id_t *id, const char *name, const es_format_t *fmt)
+{
+    struct vlc_player_track_priv *trackpriv = malloc(sizeof(*trackpriv));
+    if (!trackpriv)
+        return NULL;
+    struct vlc_player_track *track = &trackpriv->t;
+
+    trackpriv->delay = INT64_MAX;
+    trackpriv->vout = NULL;
+    trackpriv->vout_order = VLC_VOUT_ORDER_NONE;
+
+    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 trackpriv;
+}
+
+void
+vlc_player_track_priv_Delete(struct vlc_player_track_priv *trackpriv)
+{
+    struct vlc_player_track *track = &trackpriv->t;
+    es_format_Clean(&track->fmt);
+    free((char *)track->name);
+    vlc_es_id_Release(track->es_id);
+    free(trackpriv);
+}
+
+void
+vlc_player_track_Delete(struct vlc_player_track *track)
+{
+    struct vlc_player_track_priv *trackpriv =
+        container_of(track, struct vlc_player_track_priv, t);
+    vlc_player_track_priv_Delete(trackpriv);
+}
+
+struct vlc_player_track *
+vlc_player_track_Dup(const struct vlc_player_track *src)
+{
+    struct vlc_player_track_priv *duppriv =
+        vlc_player_track_priv_New(src->es_id, src->name, &src->fmt);
+
+    if (!duppriv)
+        return NULL;
+    duppriv->t.selected = src->selected;
+    return &duppriv->t;
+}
+
+int
+vlc_player_track_priv_Update(struct vlc_player_track_priv *trackpriv,
+                             const char *name, const es_format_t *fmt)
+{
+    struct vlc_player_track *track = &trackpriv->t;
+
+    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_track_priv *
+vlc_player_track_vector_FindById(vlc_player_track_vector *vec, vlc_es_id_t *id,
+                                 size_t *idx)
+{
+    for (size_t i = 0; i < vec->size; ++i)
+    {
+        struct vlc_player_track_priv *trackpriv = vec->data[i];
+        if (trackpriv->t.es_id == id)
+        {
+            if (idx)
+                *idx = i;
+            return trackpriv;
+        }
+    }
+    return NULL;
+}
diff --git a/src/input/player_vout.c b/src/input/player_vout.c
new file mode 100644
index 0000000000..d7af571d70
--- /dev/null
+++ b/src/input/player_vout.c
@@ -0,0 +1,203 @@
+/*****************************************************************************
+ * player.c: Player interface
+ *****************************************************************************
+ * Copyright © 2018-2019 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 <limits.h>
+
+#include <vlc_common.h>
+#include "player.h"
+#include "resource.h"
+
+#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)
+
+vout_thread_t *
+vlc_player_vout_Hold(vlc_player_t *player)
+{
+    vout_thread_t *vout = input_resource_HoldVout(player->resource);
+    return vout ? vout : input_resource_HoldDummyVout(player->resource);
+}
+
+vout_thread_t **
+vlc_player_vout_HoldAll(vlc_player_t *player, size_t *count)
+{
+    vout_thread_t **vouts;
+    input_resource_HoldVouts(player->resource, &vouts, count);
+
+    if (*count == 0)
+    {
+        vouts = vlc_alloc(1, sizeof(*vouts));
+        if (vouts)
+        {
+            *count = 1;
+            vouts[0] = input_resource_HoldDummyVout(player->resource);
+        }
+    }
+    return vouts;
+}
+
+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)
+{
+    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 const char osd_vars[][sizeof("secondary-sub-margin")] = {
+    "aspect-ratio", "autoscale", "crop", "crop-bottom",
+    "crop-top", "crop-left", "crop-right", "deinterlace",
+    "deinterlace-mode", "sub-margin", "secondary-sub-margin", "zoom"
+};
+
+void
+vlc_player_vout_AddCallbacks(vlc_player_t *player, vout_thread_t *vout)
+{
+    var_AddCallback(vout, "fullscreen", vlc_player_VoutCallback, player);
+    var_AddCallback(vout, "video-wallpaper", vlc_player_VoutCallback, player);
+
+    for (size_t i = 0; i < ARRAY_SIZE(osd_vars); ++i)
+        var_AddCallback(vout, osd_vars[i], vlc_player_vout_OSDCallback, player);
+}
+
+void
+vlc_player_vout_DelCallbacks(vlc_player_t *player, vout_thread_t *vout)
+{
+    var_DelCallback(vout, "fullscreen", vlc_player_VoutCallback, player);
+    var_DelCallback(vout, "video-wallpaper", vlc_player_VoutCallback, player);
+
+    for (size_t i = 0; i < ARRAY_SIZE(osd_vars); ++i)
+        var_DelCallback(vout, osd_vars[i], vlc_player_vout_OSDCallback, player);
+}
+
+static void
+vlc_player_vout_SetVar(vlc_player_t *player, const char *name, int type,
+                       vlc_value_t val)
+{
+    vout_thread_t *vout = vlc_player_vout_Hold(player);
+    var_SetChecked(vout, name, type, val);
+    vout_Release(vout);
+}
+
+
+static void
+vlc_player_vout_TriggerOption(vlc_player_t *player, const char *option)
+{
+    /* Don't use vlc_player_vout_Hold() since there is nothing to trigger if it
+     * returns a dummy vout */
+    vout_thread_t *vout = input_resource_HoldVout(player->resource);
+    var_TriggerCallback(vout, option);
+    vout_Release(vout);
+}
+
+
+void
+vlc_player_vout_SetFullscreen(vlc_player_t *player, bool enabled)
+{
+    vlc_player_vout_SetVar(player, "fullscreen", VLC_VAR_BOOL,
+                           (vlc_value_t) { .b_bool = 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_vout_SetVar(player, "video-wallpaper", VLC_VAR_BOOL,
+                           (vlc_value_t) { .b_bool = enabled });
+    vlc_player_vout_SendEvent(player, on_wallpaper_mode_changed, NULL, enabled);
+}
+
+void
+vlc_player_vout_Snapshot(vlc_player_t *player)
+{
+    vlc_player_vout_TriggerOption(player, "video-snapshot");
+}
-- 
2.20.1




More information about the vlc-devel mailing list