Quellcodebibliothek Statistik Leitseite products/Sources/formale Sprachen/C/VLC/src/player/   (VLC Media Plyer ©)  Datei vom 23.9.2026 mit Größe 63 kB image not shown  

Quelle  player.c   Sprache: C

 

/*****************************************************************************
 * player.c: Player implementation
 *****************************************************************************
 * 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 <vlc_aout.h>
#include <vlc_renderer_discovery.h>
#include <vlc_tick.h>
#include <vlc_decoder.h>
#include <vlc_memstream.h>
#include <vlc_http.h>
#include <vlc_vout_osd.h>

#include "../libvlc.h"
#include "input/resource.h"
#include "audio_output/aout_internal.h"

static_assert(VLC_PLAYER_CAP_SEEK == VLC_INPUT_CAPABILITIES_SEEKABLE &&
              VLC_PLAYER_CAP_PAUSE == VLC_INPUT_CAPABILITIES_PAUSEABLE &&
              VLC_PLAYER_CAP_CHANGE_RATE == VLC_INPUT_CAPABILITIES_CHANGE_RATE &&
              VLC_PLAYER_CAP_REWIND == VLC_INPUT_CAPABILITIES_REWINDABLE,
              "player/input capabilities mismatch");

static_assert(VLC_PLAYER_TITLE_MENU == INPUT_TITLE_MENU &&
              VLC_PLAYER_TITLE_INTERACTIVE == INPUT_TITLE_INTERACTIVE &&
              VLC_PLAYER_TITLE_MAIN == INPUT_TITLE_MAIN,
              "player/input title flag mismatch");

#define vlc_player_foreach_inputs(it) \
    for (struct vlc_player_input *it = player->input; it != NULL; it = NULL)

int
vlc_player_OpenNextMedia(vlc_player_t *player)
{
    assert(player->input == NULL);

    /* Tracks string ids are only remembered for one media */
    free(player->video_string_ids);
    free(player->audio_string_ids);
    free(player->sub_string_ids);
    player->video_string_ids = player->audio_string_ids =
    player->sub_string_ids = NULL;

    int ret = VLC_SUCCESS;
    if (player->releasing_media)
    {
        assert(player->media);
        input_item_Release(player->media);
        player->media = NULL;
        player->releasing_media = false;
    }
    else
    {
        if (!player->next_media)
            return VLC_EGENERIC;

        if (player->media)
            input_item_Release(player->media);
        player->media = player->next_media;
        player->next_media = NULL;

        struct vlc_player_input *input = player->input =
            vlc_player_input_New(player, player->media);
        if (!input)
        {
            input_item_Release(player->media);
            player->media = NULL;
            ret = VLC_ENOMEM;
        }
    }
    vlc_player_SendEvent(player, on_current_media_changed, player->media);
    if (player->input && player->input->ml.delay_restore)
    {
        vlc_player_SendEvent(player, on_playback_restore_queried);
        player->input->ml.delay_restore = false;
    }
    return ret;
}

static void
vlc_player_CancelWaitError(vlc_player_t *player)
{
    if (player->eos_burst_count != 0)
    {
        player->eos_burst_count = 0;
        vlc_cond_signal(&player->start_delay_cond);
    }
}

static bool
vlc_list_HasInput(struct vlc_list *list, struct vlc_player_input *input)
{
    struct vlc_player_input *other_input;
    vlc_list_foreach(other_input, list, node)
    {
        if (other_input == input)
            return true;
    }
    return false;
}

static void
vlc_player_destructor_AddInput(vlc_player_t *player,
                               struct vlc_player_input *input)
{
    if (input->started)
    {
        input->started = false;
        /* Add this input to the stop list: it will be stopped by the
         * destructor thread */

        assert(!vlc_list_HasInput(&player->mainloop.stopping_inputs, input));
        assert(!vlc_list_HasInput(&player->mainloop.joinable_inputs, input));
        vlc_list_append(&input->node, &player->mainloop.stop_inputs);
    }
    else
    {
        /* Add this input to the joinable list: it will be deleted by the
         * destructor thread */

        assert(!vlc_list_HasInput(&player->mainloop.stop_inputs, input));
        assert(!vlc_list_HasInput(&player->mainloop.joinable_inputs, input));
        vlc_list_append(&input->node, &player->mainloop.joinable_inputs);
    }

    vlc_cond_signal(&input->player->mainloop.wait);
}

void
vlc_player_destructor_AddStoppingInput(vlc_player_t *player,
                                       struct vlc_player_input *input)
{
    /* Add this input to the stopping list */
    if (vlc_list_HasInput(&player->mainloop.stop_inputs, input))
        vlc_list_remove(&input->node);
    if (!vlc_list_HasInput(&player->mainloop.stopping_inputs, input))
    {
        vlc_list_append(&input->node, &player->mainloop.stopping_inputs);
        vlc_cond_signal(&input->player->mainloop.wait);
    }
}

void
vlc_player_destructor_AddJoinableInput(vlc_player_t *player,
                                       struct vlc_player_input *input)
{
    if (vlc_list_HasInput(&player->mainloop.stopping_inputs, input))
        vlc_list_remove(&input->node);

    assert(!input->started);
    vlc_player_destructor_AddInput(player, input);
}

static bool vlc_player_destructor_IsEmpty(vlc_player_t *player)
{
    return vlc_list_is_empty(&player->mainloop.stop_inputs)
        && vlc_list_is_empty(&player->mainloop.stopping_inputs)
        && vlc_list_is_empty(&player->mainloop.joinable_inputs);
}

void
vlc_player_SignalAtoBLoop(vlc_player_t *player)
{
    struct vlc_player_input *input = vlc_player_get_input_locked(player);

    if (!input || !input->abloop_state[0].set || !input->abloop_state[1].set)
        return;

    vlc_cond_signal(&player->mainloop.wait);
}

static vlc_tick_t
vlc_player_GetAtoBLoopDeadline(vlc_player_t *player)
{
    struct vlc_player_input *input = vlc_player_get_input_locked(player);

    if (!input || input->pause_date != VLC_TICK_INVALID || !input->abloop_state[0].set || !input->abloop_state[1].set)
        return VLC_TICK_MIN;

    vlc_tick_t now = vlc_tick_now();
    vlc_tick_t ts;
    bool seeking = false;
    if (vlc_player_GetTimerPoint(player, &seeking, now, &ts, NULL) != 0)
        return VLC_TICK_MIN;
    if (seeking)
        return VLC_TICK_MIN;

    vlc_tick_t b_time = input->abloop_state[1].time;
    if (b_time == VLC_TICK_INVALID)
    {
        if (input->length == VLC_TICK_INVALID)
            return VLC_TICK_MIN;
        b_time = input->abloop_state[1].pos * input->length;
    }
    return now + (vlc_tick_t) ((b_time - ts) / input->rate);
}

static void *
vlc_player_mainloop_Thread(void *data)
{
    vlc_player_t *player = data;

    vlc_thread_set_name("vlc-player-end");

    vlc_mutex_lock(&player->lock);

    /* Terminate this thread when the player is deleting (vlc_player_Delete()
     * was called) and when all input_thread_t all stopped and released. */

    while (!player->deleting
        || !vlc_player_destructor_IsEmpty(player))
    {
        /* Wait for an input to stop or close. No while loop here since we want
         * to leave this code path when the player is deleting. */

        vlc_tick_t deadline = vlc_player_GetAtoBLoopDeadline(player);
        bool timeout = false;
        if (vlc_list_is_empty(&player->mainloop.stop_inputs)
         && vlc_list_is_empty(&player->mainloop.joinable_inputs))
        {
            if (deadline == VLC_TICK_MIN)
                vlc_cond_wait(&player->mainloop.wait, &player->lock);
            else
                timeout = vlc_cond_timedwait(&player->mainloop.wait,
                                             &player->lock, deadline) != 0;
        }

        if (timeout && player->input != NULL)
            vlc_player_input_HandleAtoBLoop(player->input, true);

        struct vlc_player_input *input;
        vlc_list_foreach(input, &player->mainloop.stop_inputs, node)
        {
            input_Stop(input->thread);
            vlc_player_input_HandleState(input, VLC_PLAYER_STATE_STOPPING,
                                         VLC_TICK_INVALID);
            vlc_player_destructor_AddStoppingInput(player, input);
        }

        bool keep_sout = true;
        const bool inputs_changed =
            !vlc_list_is_empty(&player->mainloop.joinable_inputs);
        vlc_list_foreach(input, &player->mainloop.joinable_inputs, node)
        {
            vlc_player_UpdateMLStates(player, input);

            keep_sout = var_GetBool(input->thread, "sout-keep");

            vlc_player_input_HandleState(input, VLC_PLAYER_STATE_STOPPED,
                                         VLC_TICK_INVALID);

            vlc_list_remove(&input->node);
            vlc_player_input_Delete(input);
        }

        if (inputs_changed)
        {
            const bool started = player->started;
            vlc_player_Unlock(player);
            if (!started)
                input_resource_StopFreeVout(player->resource);
            if (!keep_sout)
                input_resource_TerminateSout(player->resource);
            vlc_player_Lock(player);
        }
    }
    vlc_mutex_unlock(&player->lock);
    return NULL;
}

size_t
vlc_player_GetProgramCount(vlc_player_t *player)
{
    struct vlc_player_input *input = vlc_player_get_input_locked(player);

    return input ? input->program_vector.size : 0;
}

const struct vlc_player_program *
vlc_player_GetProgramAt(vlc_player_t *player, size_t index)
{
    struct vlc_player_input *input = vlc_player_get_input_locked(player);

    if (!input)
        return NULL;

    assert(index < input->program_vector.size);
    return input->program_vector.data[index];
}

const struct vlc_player_program *
vlc_player_GetProgram(vlc_player_t *player, int id)
{
    struct vlc_player_input *input = vlc_player_get_input_locked(player);

    if (!input)
        return NULL;

    struct vlc_player_program *prgm =
        vlc_player_program_vector_FindById(&input->program_vector, id, NULL);
    return prgm;
}

void
vlc_player_SelectProgram(vlc_player_t *player, int id)
{
    struct vlc_player_input *input = vlc_player_get_input_locked(player);
    if (!input)
        return;

    input_SetProgramId(input->thread, id);

    const struct vlc_player_program *prgm =
        vlc_player_program_vector_FindById(&input->program_vector,
                                           id, NULL);
    if (prgm != NULL)
        vlc_player_osd_Program(player, prgm->name);
}

static void
vlc_player_CycleProgram(vlc_player_t *player, bool next)
{
    size_t count = vlc_player_GetProgramCount(player);
    if (!count)
        return;
    size_t index = 0;
    bool selected = false;
    for (size_t i = 0; i < count; ++i)
    {
        const struct vlc_player_program *prgm =
            vlc_player_GetProgramAt(player, i);
        if (prgm->selected)
        {
            /* Only one program can be selected at a time */
            assert(!selected);
            index = i;
            selected = true;
        }
    }
    assert(selected);
    if (next && index + 1 == count) /* First program */
        index = 0;
    else if (!next && index == 0) /* Last program */
        index = count - 1;
    else /* Next or Previous program */
        index = index + (next ? 1 : -1);

    const struct vlc_player_program *prgm =
        vlc_player_GetProgramAt(player, index);
    assert(prgm);
    vlc_player_SelectProgram(player, prgm->group_id);
}

void
vlc_player_SelectNextProgram(vlc_player_t *player)
{
    vlc_player_CycleProgram(player, true);
}

void
vlc_player_SelectPrevProgram(vlc_player_t *player)
{
    vlc_player_CycleProgram(player, false);
}

size_t
vlc_player_GetTrackCount(vlc_player_t *player, enum es_format_category_e cat)
{
    struct vlc_player_input *input = vlc_player_get_input_locked(player);

    if (!input)
        return 0;
    vlc_player_track_vector *vec = vlc_player_input_GetTrackVector(input, cat);
    if (!vec)
        return 0;
    return vec->size;
}

const struct vlc_player_track *
vlc_player_GetTrackAt(vlc_player_t *player, enum es_format_category_e cat,
                      size_t index)
{
    struct vlc_player_input *input = vlc_player_get_input_locked(player);

    if (!input)
        return NULL;
    vlc_player_track_vector *vec = vlc_player_input_GetTrackVector(input, cat);
    if (!vec)
        return NULL;
    assert(index < vec->size);
    return &vec->data[index]->t;
}

static struct vlc_player_track_priv *
vlc_player_GetPrivTrack(vlc_player_t *player, vlc_es_id_t *id)

{
    struct vlc_player_input *input = vlc_player_get_input_locked(player);

    if (!input)
        return NULL;
    vlc_player_track_vector *vec =
        vlc_player_input_GetTrackVector(input, vlc_es_id_GetCat(id));
    if (!vec)
        return NULL;
    return vlc_player_track_vector_FindById(vec, id, NULL);
}

const struct vlc_player_track *
vlc_player_GetTrack(vlc_player_t *player, vlc_es_id_t *id)
{
    struct vlc_player_track_priv *trackpriv =
        vlc_player_GetPrivTrack(player, id);
    return trackpriv ? &trackpriv->t : NULL;
}

vout_thread_t *
vlc_player_GetEsIdVout(vlc_player_t *player, vlc_es_id_t *es_id,
                       enum vlc_vout_order *order)
{
    struct vlc_player_track_priv *trackpriv =
        vlc_player_GetPrivTrack(player, es_id);
    if (trackpriv)
    {
        if (order)
            *order = trackpriv->vout_order;
        return trackpriv->vout;
    }
    return NULL;
}

vlc_es_id_t *
vlc_player_GetEsIdFromVout(vlc_player_t *player, vout_thread_t *vout)
{
    struct vlc_player_input *input = vlc_player_get_input_locked(player);

    if (!input)
        return NULL;

    static const enum es_format_category_e cats[] = {
        VIDEO_ES, SPU_ES, AUDIO_ES /* for visualisation filters */
    };
    for (size_t i = 0; i < ARRAY_SIZE(cats); ++i)
    {
        enum es_format_category_e cat = cats[i];
        vlc_player_track_vector *vec =
            vlc_player_input_GetTrackVector(input, cat);
        for (size_t j = 0; j < vec->size; ++j)
        {
            struct vlc_player_track_priv *trackpriv = vec->data[j];
            if (trackpriv->vout == vout)
                return trackpriv->t.es_id;
        }
    }
    return NULL;
}

unsigned
vlc_player_SelectEsIdList(vlc_player_t *player,
                          enum es_format_category_e cat,
                          vlc_es_id_t *const es_id_list[])
{
    static const size_t max_tracks_by_cat[] = {
        [UNKNOWN_ES] = 0,
        [VIDEO_ES] = UINT_MAX,
        [AUDIO_ES] = 1,
        [SPU_ES] = 2,
        [DATA_ES] = 0,
    };

    struct vlc_player_input *input = vlc_player_get_input_locked(player);
    if (!input)
        return 0;

    const size_t max_tracks = max_tracks_by_cat[cat];

    if (max_tracks == 0)
        return 0;

    /* First, count and hold all the ES Ids.
       Ids will be released in input.c:ControlRelease */

    size_t track_count = 0;
    for (size_t i = 0; es_id_list[i] != NULL && track_count < max_tracks; i++)
        if (vlc_es_id_GetCat(es_id_list[i]) == cat)
            track_count++;

    /* Copy es_id_list into an allocated list so that it remains in memory until
       selection completes. The list will be freed in input.c:ControlRelease */

    struct vlc_es_id_t **allocated_ids =
        vlc_alloc(track_count + 1, sizeof(vlc_es_id_t *));
    struct vlc_es_id_t **osd_ids = /* create two copies, one for osd message */
        vlc_alloc(track_count + 1, sizeof(vlc_es_id_t *));

    if (allocated_ids == NULL || osd_ids == NULL)
        return 0;

    track_count = 0;
    for (size_t i = 0; es_id_list[i] != NULL && track_count < max_tracks; i++)
    {
        vlc_es_id_t *es_id = es_id_list[i];
        if (vlc_es_id_GetCat(es_id_list[i]) == cat)
        {
            vlc_es_id_Hold(es_id);
            osd_ids[track_count] = es_id;
            allocated_ids[track_count++] = es_id;
        }
    }
    osd_ids[track_count] = NULL;
    allocated_ids[track_count] = NULL;

    /* Attempt to select all the requested tracks */
    int ret = input_ControlPush(input->thread, INPUT_CONTROL_SET_ES_LIST,
        &(input_control_param_t) {
            .list.cat = cat,
            .list.ids = allocated_ids,
        });
    if (ret != VLC_SUCCESS)
    {
        free(osd_ids);
        return 0;
    }

    /* Display track selection message */
    vlc_player_osd_Tracks(player, osd_ids, NULL);
    free(osd_ids);

    return track_count;
}

/* Returns an array of selected tracks, putting id in first position (if any).
 * */

static vlc_es_id_t **
vlc_player_GetEsIdList(vlc_player_t *player,
                       const enum es_format_category_e cat,
                       vlc_es_id_t *id)
{
    const size_t track_count = vlc_player_GetTrackCount(player, cat);
    if (track_count == 0)
        return NULL;

    size_t selected_track_count = id ? 1 : 0;
    for (size_t i = 0; i < track_count; ++i)
    {
        const struct vlc_player_track *track =
            vlc_player_GetTrackAt(player, cat, i);
        if (track->selected && track->es_id != id)
            selected_track_count++;
    }

    vlc_es_id_t **es_id_list =
        vlc_alloc(selected_track_count + 1 /* NULL */, sizeof(vlc_es_id_t*));
    if (!es_id_list)
        return NULL;

    size_t es_id_list_idx = 0;
    /* Assure to select the requested track */
    if (id)
        es_id_list[es_id_list_idx++] = id;

    for (size_t i = 0; i < track_count; ++i)
    {
        const struct vlc_player_track *track =
            vlc_player_GetTrackAt(player, cat, i);
        if (track->selected && track->es_id != id)
            es_id_list[es_id_list_idx++] = track->es_id;
    }
    es_id_list[selected_track_count] = NULL;

    return es_id_list;
}

unsigned
vlc_player_SelectEsId(vlc_player_t *player, vlc_es_id_t *id,
                      enum vlc_player_select_policy policy)
{
    struct vlc_player_input *input = vlc_player_get_input_locked(player);
    if (!input)
        return 0;

    if (policy == VLC_PLAYER_SELECT_EXCLUSIVE)
    {
        if (input_ControlPushEsHelper(input->thread, INPUT_CONTROL_SET_ES, id)
         == VLC_SUCCESS)
            vlc_player_osd_Track(player, id, true);
        return 1;
    }

    /* VLC_PLAYER_SELECT_SIMULTANEOUS */
    const enum es_format_category_e cat = vlc_es_id_GetCat(id);
    vlc_es_id_t **es_id_list = vlc_player_GetEsIdList(player, cat, id);
    if (!es_id_list)
        return 0;

    unsigned ret = vlc_player_SelectEsIdList(player, cat, es_id_list);
    free(es_id_list);
    return ret;
}

void
vlc_player_SelectTracksByStringIds(vlc_player_t *player,
                                   enum es_format_category_e cat,
                                   const char *str_ids)
{
    vlc_player_assert_locked(player);
    char **cat_str_ids;

    switch (cat)
    {
        case VIDEO_ES: cat_str_ids = &player->video_string_ids; break;
        case AUDIO_ES: cat_str_ids = &player->audio_string_ids; break;
        case SPU_ES:   cat_str_ids = &player->sub_string_ids; break;
        default: return;
    }

    free(*cat_str_ids);
    *cat_str_ids = str_ids ? strdup(str_ids) : NULL;

    struct vlc_player_input *input = vlc_player_get_input_locked(player);
    if (input)
        vlc_player_input_SelectTracksByStringIds(input, cat, str_ids);
}

void
vlc_player_CycleTrack(vlc_player_t *player, enum es_format_category_e cat,
                      enum vlc_vout_order vout_order, bool next)
{
    size_t count = vlc_player_GetTrackCount(player, cat);
    if (!count)
        return;

    vlc_es_id_t *keep_id = NULL;

    /* Check how many tracks are already selected */
    size_t selected_count = 0;

    /* Find out the current selected index.
     * If no track selected, select the first or the last track */

    size_t cycle_index = next ? count-1 : count;

    for (size_t i = 0; i < count && selected_count < 2; ++i)
    {
        const struct vlc_player_track *track =
            vlc_player_GetTrackAt(player, cat, i);
        assert(track);

        if (track->selected)
        {
            enum vlc_vout_order order;
            vlc_player_GetEsIdVout(player, track->es_id, &order);
            if (order == vout_order)
                cycle_index = i;
            else
                keep_id = track->es_id;
            ++selected_count;
        }
    }

    vlc_es_id_t * cycle_id = NULL;
    /* Look for the next free (unselected) track */
    for (size_t i = 0; i < count; ++i)
    {
        cycle_index = (cycle_index + (next ? 1 : -1) + count) % count;

        /* Unselect if we reach the end of the cycle
         * Unless cycling PRIMARY with a selected SECONDARY then wrap around */

        if (((next && cycle_index == 0) || (!next && cycle_index + 1 == count))
         && ((selected_count == 1 && vout_order == VLC_VOUT_ORDER_PRIMARY)
          || (selected_count == 2 && vout_order == VLC_VOUT_ORDER_SECONDARY)))
            break;

        const struct vlc_player_track *track =
            vlc_player_GetTrackAt(player, cat, cycle_index);
        if (!track->selected)
        {
            cycle_id = track->es_id;
            break;
        }
    }

    // We want the PRIMARY in first position
    vlc_es_id_t *esIds[] = { cycle_id, keep_id, NULL };
    if (vout_order == VLC_VOUT_ORDER_SECONDARY)
    {
        esIds[0] = keep_id;
        esIds[1] = cycle_id;
    }
    if (!esIds[0])
    {
        esIds[0] = esIds[1];
        esIds[1] = NULL;
    }

    vlc_player_SelectEsIdList(player, cat, esIds);
}

void
vlc_player_UnselectEsId(vlc_player_t *player, vlc_es_id_t *id)
{
    struct vlc_player_input *input = vlc_player_get_input_locked(player);
    if (!input)
        return;

    int ret = input_ControlPushEsHelper(input->thread, INPUT_CONTROL_UNSET_ES,
                                        id);
    if (ret != VLC_SUCCESS)
        return;

    const enum es_format_category_e cat = vlc_es_id_GetCat(id);
    vlc_es_id_t **selected_es = vlc_player_GetEsIdList(player, cat, NULL);
    if (selected_es == NULL)
        return;

    vlc_player_osd_Tracks(player, selected_es, id);
    free(selected_es);
}

void
vlc_player_RestartEsId(vlc_player_t *player, vlc_es_id_t *id)
{
    struct vlc_player_input *input = vlc_player_get_input_locked(player);

    if (input)
        input_ControlPushEsHelper(input->thread, INPUT_CONTROL_RESTART_ES, id);
}

void
vlc_player_SelectCategoryLanguage(vlc_player_t *player,
                                  enum es_format_category_e cat,
                                  const char *lang)
{
    vlc_player_assert_locked(player);
    switch (cat)
    {
        case AUDIO_ES:
            var_SetString(player, "audio-language", lang);
            break;
        case SPU_ES:
            var_SetString(player, "sub-language", lang);
            break;
        default:
            vlc_assert_unreachable();
    }
}

char *
vlc_player_GetCategoryLanguage(vlc_player_t *player,
                               enum es_format_category_e cat)
{
    vlc_player_assert_locked(player);
    switch (cat)
    {
        case AUDIO_ES:
            return var_GetString(player, "audio-language");
        case SPU_ES:
            return var_GetString(player, "sub-language");
        default:
            vlc_assert_unreachable();
    }
}

void
vlc_player_SetTeletextEnabled(vlc_player_t *player, bool enabled)
{
    struct vlc_player_input *input = vlc_player_get_input_locked(player);
    if (!input || !input->teletext_source)
        return;
    if (enabled)
        vlc_player_SelectEsId(player, input->teletext_source->t.es_id,
                              VLC_PLAYER_SELECT_EXCLUSIVE);
    else
        vlc_player_UnselectEsId(player, input->teletext_source->t.es_id);
}

void
vlc_player_SelectTeletextPage(vlc_player_t *player, unsigned page)
{
    struct vlc_player_input *input = vlc_player_get_input_locked(player);
    if (!input || !input->teletext_source)
        return;
    input_ControlPush(input->thread, INPUT_CONTROL_SET_VBI_PAGE,
        &(input_control_param_t) {
            .vbi_page.id = input->teletext_source->t.es_id,
            .vbi_page.page = page,
    });
}

void
vlc_player_SetTeletextTransparency(vlc_player_t *player, bool enabled)
{
    struct vlc_player_input *input = vlc_player_get_input_locked(player);
    if (!input || !input->teletext_source)
        return;

    input_ControlPush(input->thread, INPUT_CONTROL_SET_VBI_TRANSPARENCY,
        &(input_control_param_t) {
            .vbi_transparency.id = input->teletext_source->t.es_id,
            .vbi_transparency.enabled = enabled,
    });
}

bool
vlc_player_HasTeletextMenu(vlc_player_t *player)
{
    struct vlc_player_input *input = vlc_player_get_input_locked(player);
    return input && input->teletext_source;
}

bool
vlc_player_IsTeletextEnabled(vlc_player_t *player)
{
    struct vlc_player_input *input = vlc_player_get_input_locked(player);
    if (input && input->teletext_enabled)
    {
        assert(input->teletext_source);
        return true;
    }
    return false;
}

unsigned
vlc_player_GetTeletextPage(vlc_player_t *player)
{
    struct vlc_player_input *input = vlc_player_get_input_locked(player);
    return vlc_player_IsTeletextEnabled(player) ? input->teletext_page : 0;
}

bool
vlc_player_IsTeletextTransparent(vlc_player_t *player)
{
    struct vlc_player_input *input = vlc_player_get_input_locked(player);
    return vlc_player_IsTeletextEnabled(player) && input->teletext_transparent;
}

struct vlc_player_title_list *
vlc_player_GetTitleList(vlc_player_t *player)
{
    struct vlc_player_input *input = vlc_player_get_input_locked(player);
    return input ? input->titles : NULL;
}

ssize_t
vlc_player_GetSelectedTitleIdx(vlc_player_t *player)
{
    struct vlc_player_input *input = vlc_player_get_input_locked(player);

    if (!input)
        return -1;
    return input->title_selected;
}

static ssize_t
vlc_player_GetTitleIdx(vlc_player_t *player,
                       const struct vlc_player_title *title)
{
    struct vlc_player_input *input = vlc_player_get_input_locked(player);
    if (input && input->titles)
        for (size_t i = 0; i < input->titles->count; ++i)
            if (&input->titles->array[i] == title)
                return i;
    return -1;
}

void
vlc_player_SelectTitleIdx(vlc_player_t *player, size_t index)
{
    struct vlc_player_input *input = vlc_player_get_input_locked(player);
    if (input)
        input_ControlPushHelper(input->thread, INPUT_CONTROL_SET_TITLE,
                                &(vlc_value_t){ .i_int = index });
}

void
vlc_player_SelectTitle(vlc_player_t *player,
                       const struct vlc_player_title *title)
{
    ssize_t idx = vlc_player_GetTitleIdx(player, title);
    if (idx != -1)
        vlc_player_SelectTitleIdx(player, idx);
}

void
vlc_player_SelectChapter(vlc_player_t *player,
                         const struct vlc_player_title *title,
                         size_t chapter_idx)
{
    ssize_t idx = vlc_player_GetTitleIdx(player, title);
    if (idx != -1 && idx == vlc_player_GetSelectedTitleIdx(player))
        vlc_player_SelectChapterIdx(player, chapter_idx);
}

void
vlc_player_SelectNextTitle(vlc_player_t *player)
{
    struct vlc_player_input *input = vlc_player_get_input_locked(player);
    if (!input)
        return;
    int ret = input_ControlPush(input->thread, INPUT_CONTROL_SET_TITLE_NEXT,
                                NULL);
    if (ret == VLC_SUCCESS)
        vlc_player_osd_Message(player, _("Next title"));
}

void
vlc_player_SelectPrevTitle(vlc_player_t *player)
{
    struct vlc_player_input *input = vlc_player_get_input_locked(player);
    if (!input)
        return;
    int ret = input_ControlPush(input->thread, INPUT_CONTROL_SET_TITLE_PREV,
                                NULL);
    if (ret == VLC_SUCCESS)
        vlc_player_osd_Message(player, _("Previous title"));
}

ssize_t
vlc_player_GetSelectedChapterIdx(vlc_player_t *player)
{
    struct vlc_player_input *input = vlc_player_get_input_locked(player);

    if (!input)
        return -1;
    return input->chapter_selected;
}

static vlc_tick_t
vlc_player_ChapterTime(struct vlc_player_input *input, size_t index)
{
    if (!input->titles)
        return -1;
    const struct vlc_player_title *title =
        &input->titles->array[input->title_selected];
    if (index >= title->chapter_count)
        return -1;
    return title->chapters[index].time;
}

/* move the timer right away so the seek bar does not wait for new data */
static void
vlc_player_UpdateChapterSeekState(struct vlc_player_input *input, size_t index)
{
    vlc_tick_t time = vlc_player_ChapterTime(input, index);
    if (time < 0)
        return;
    if (time < VLC_TICK_0)
        time = VLC_TICK_0;
    vlc_player_UpdateTimerSeekState(input->player, time, -1);
}

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;
    vlc_player_UpdateChapterSeekState(input, index);
    int ret = input_ControlPushHelper(input->thread, INPUT_CONTROL_SET_SEEKPOINT,
                                      &(vlc_value_t){ .i_int = index });
    if (ret == VLC_SUCCESS)
        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;
    vlc_player_UpdateChapterSeekState(input, input->chapter_selected + 1);
    int ret = input_ControlPush(input->thread, INPUT_CONTROL_SET_SEEKPOINT_NEXT,
                                NULL);
    if (ret == VLC_SUCCESS)
        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;
    size_t index = input->chapter_selected;
    vlc_tick_t time = vlc_player_ChapterTime(input, index);
    /* same rule as the input thread */
    if (time >= 0 && vlc_player_input_GetTime(input, true, vlc_tick_now())
                     >= time + VLC_TICK_FROM_SEC(3))
        vlc_player_UpdateChapterSeekState(input, index);
    else if (index > 0)
        vlc_player_UpdateChapterSeekState(input, index - 1);
    int ret = input_ControlPush(input->thread, INPUT_CONTROL_SET_SEEKPOINT_PREV,
                                NULL);
    if (ret == VLC_SUCCESS)
        vlc_player_osd_Message(player, _("Previous chapter"));
}

void
vlc_player_Lock(vlc_player_t *player)
{
    /* Metadata, Vout and aout locks should not be held, cf.
     * vlc_player_metadata_cbs, vlc_player_vout_cbs and vlc_player_aout_cbs
     * documentation */

    assert(!vlc_mutex_held(&player->metadata_listeners_lock));
    assert(!vlc_mutex_held(&player->vout_listeners_lock));
    assert(!vlc_mutex_held(&player->aout_listeners_lock));
    /* The timer lock should not be held (possible lock-order-inversion), cf.
     * vlc_player_timer_cbs.on_update documentation */

    assert(!vlc_mutex_held(&player->timer.lock));

    vlc_mutex_lock(&player->lock);
}

void
vlc_player_Unlock(vlc_player_t *player)
{
    vlc_mutex_unlock(&player->lock);
}

void
vlc_player_CondWait(vlc_player_t *player, vlc_cond_t *cond)
{
    vlc_player_assert_locked(player);
    vlc_cond_wait(cond, &player->lock);
}

vlc_player_listener_id *
vlc_player_AddListener(vlc_player_t *player,
                       const struct vlc_player_cbs *cbs, void *cbs_data)
{
    assert(cbs);
    vlc_player_assert_locked(player);

    vlc_player_listener_id *listener = malloc(sizeof(*listener));
    if (!listener)
        return NULL;

    listener->cbs = cbs;
    listener->cbs_data = cbs_data;

    vlc_list_append(&listener->node, &player->listeners);

    return listener;
}

void
vlc_player_RemoveListener(vlc_player_t *player,
                          vlc_player_listener_id *id)
{
    assert(id);
    vlc_player_assert_locked(player);

    vlc_list_remove(&id->node);
    free(id);
}

static void
vlc_player_InvalidateNextMedia(vlc_player_t *player)
{
    vlc_player_assert_locked(player);
    if (player->next_media)
    {
        input_item_Release(player->next_media);
        player->next_media = NULL;
    }
}

int
vlc_player_SetCurrentMedia(vlc_player_t *player, input_item_t *media)
{
    vlc_player_assert_locked(player);

    vlc_player_CancelWaitError(player);

    vlc_player_InvalidateNextMedia(player);

    if (media)
    {
        /* Switch to this new media when the current input is stopped */
        player->next_media = input_item_Hold(media);
        player->releasing_media = false;
    }
    else if (player->media)
    {
        /* The current media will be set to NULL once the current input is
         * stopped */

        player->releasing_media = true;
    }
    else
        return VLC_SUCCESS;

    if (player->input)
    {
        vlc_player_destructor_AddInput(player, player->input);
        player->input = NULL;
    }

    assert(media == player->next_media);
    if (!vlc_player_destructor_IsEmpty(player))
    {
        /* This media will be opened when the input is finally stopped */
        return VLC_SUCCESS;
    }

    /* We can switch to the next media directly */
    return vlc_player_OpenNextMedia(player);
}

void
vlc_player_SetNextMedia(vlc_player_t *player, input_item_t *media)
{
    vlc_player_assert_locked(player);

    /* Order is important, hold the new media before releasing the old one */
    input_item_t *next_media = media != NULL ? input_item_Hold(media) : NULL;

    if (player->next_media != NULL)
        input_item_Release(player->next_media);

    player->next_media = next_media;
}

input_item_t *
vlc_player_GetNextMedia(vlc_player_t *player)
{
    vlc_player_assert_locked(player);

    return player->next_media;
}

input_item_t *
vlc_player_GetCurrentMedia(vlc_player_t *player)
{
    vlc_player_assert_locked(player);

    return player->media;
}

int
vlc_player_AddAssociatedMedia(vlc_player_t *player,
                              enum es_format_category_e cat, const char *uri,
                              bool select, bool notify, bool check_ext)
{
    struct vlc_player_input *input = vlc_player_get_input_locked(player);

    if (!input || !uri)
        return VLC_EGENERIC;

    enum slave_type type;
    switch (cat)
    {
        case SPU_ES:
            type = SLAVE_TYPE_SPU;
            break;
        default:
            type = SLAVE_TYPE_GENERIC;
            break;
    }

    if (check_ext && type == SLAVE_TYPE_SPU && !subtitles_Filter(uri))
        return VLC_EGENERIC;

    input_item_slave_t *slave =
        input_item_slave_New(uri, type, SLAVE_PRIORITY_USER);
    if (!slave)
        return VLC_ENOMEM;
    slave->b_forced = select;

    vlc_value_t val = { .p_address = slave };
    int ret = input_ControlPushHelper(input->thread, INPUT_CONTROL_ADD_SLAVE,
                                      &val);
    if (ret != VLC_SUCCESS)
        return ret;

    if (notify)
    {
        switch( type )
        {
            case SLAVE_TYPE_GENERIC:
                vlc_player_osd_Message(player, "%s",
                                       vlc_gettext("Slave added"));
                break;
            case SLAVE_TYPE_SPU:
                vlc_player_osd_Message(player, "%s",
                                       vlc_gettext("Subtitle slave added"));
                break;
        }
    }
    return VLC_SUCCESS;
}

void
vlc_player_SetAssociatedSubsFPS(vlc_player_t *player, float fps)
{
    struct vlc_player_input *input = vlc_player_get_input_locked(player);

    var_SetFloat(player, "sub-fps", fps);
    if (input)
        input_ControlPushHelper(input->thread, INPUT_CONTROL_SET_SUBS_FPS,
                                &(vlc_value_t) { .f_float = fps });
    vlc_player_SendEvent(player, on_associated_subs_fps_changed, fps);
}

float
vlc_player_GetAssociatedSubsFPS(vlc_player_t *player)
{
    vlc_player_assert_locked(player);
    return var_GetFloat(player, "sub-fps");
}

int
vlc_player_Start(vlc_player_t *player)
{
    vlc_player_assert_locked(player);

    vlc_player_CancelWaitError(player);

    if (player->started)
        return VLC_SUCCESS;

    if (!vlc_player_destructor_IsEmpty(player))
    {
        if (player->next_media)
        {
            player->started = true;
            return VLC_SUCCESS;
        }
        else if (unlikely(player->media != NULL))
        {
            /* The current media is being stopped while the user requested to
             * play it again. Tell the thread to play the same media when
             * ready. */

            player->started = true;
            player->next_media = input_item_Hold(player->media);
            player->releasing_media = false;
            return VLC_SUCCESS;
        }
        else
            return VLC_EGENERIC;
    }

    if (!player->media)
    {
        if (player->next_media != NULL)
        {
            /* The user called only SetNextMedia() and not SetCurrentMedia(),
             * so open the next media directly from here. */

            int ret = vlc_player_OpenNextMedia(player);
            if (ret != VLC_SUCCESS)
                return ret;
        }
        else
            return VLC_EGENERIC;
    }

    if (!player->input)
    {
        /* Possible if the player was stopped by the user */
        player->input = vlc_player_input_New(player, player->media);

        if (!player->input)
            return VLC_ENOMEM;
    }
    assert(!player->input->started);

    if (player->start_paused)
    {
        var_Create(player->input->thread, "start-paused", VLC_VAR_BOOL);
        var_SetBool(player->input->thread, "start-paused", true);
    }

    int ret = vlc_player_input_Start(player->input);
    if (ret == VLC_SUCCESS)
    {
        player->started = true;
        vlc_player_osd_Icon(player, OSD_PLAY_ICON);
    }
    return ret;
}

int
vlc_player_Stop(vlc_player_t *player)
{
    struct vlc_player_input *input = vlc_player_get_input_locked(player);

    vlc_player_CancelWaitError(player);

    vlc_player_InvalidateNextMedia(player);

    if (!input || !player->started)
        return VLC_EGENERIC;
    player->started = false;

    vlc_player_destructor_AddInput(player, input);
    player->input = NULL;
    return VLC_SUCCESS;
}

void
vlc_player_SetStartPaused(vlc_player_t *player, bool start_paused)
{
    vlc_player_assert_locked(player);
    player->start_paused = start_paused;
}

void
vlc_player_SetPlayAndPause(vlc_player_t *player, bool play_and_pause)
{
    vlc_player_assert_locked(player);
    player->play_and_pause = play_and_pause;
}

void
vlc_player_SetRepeatCount(vlc_player_t *player, unsigned repeat_count)
{
    vlc_player_assert_locked(player);
    player->repeat = repeat_count;
}

static void
vlc_player_SetPause(vlc_player_t *player, bool pause)
{
    struct vlc_player_input *input = vlc_player_get_input_locked(player);

    if (!input || !input->started)
        return;

    vlc_value_t val = { .i_int = pause ? PAUSE_S : PLAYING_S };
    int ret = input_ControlPushHelper(input->thread, INPUT_CONTROL_SET_STATE,
                                      &val);

    if (ret == VLC_SUCCESS)
        vlc_player_osd_Icon(player, pause ? OSD_PAUSE_ICON : OSD_PLAY_ICON);
}

void
vlc_player_Pause(vlc_player_t *player)
{
    vlc_player_SetPause(player, true);
}

void
vlc_player_Resume(vlc_player_t *player)
{
    vlc_player_SetPause(player, false);
}

void
vlc_player_NextVideoFrame(vlc_player_t *player)
{
    struct vlc_player_input *input = vlc_player_get_input_locked(player);
    if (!input)
        return;
    input_ControlPushHelper(input->thread,
                            INPUT_CONTROL_SET_FRAME_NEXT, NULL);
}

void
vlc_player_PreviousVideoFrame(vlc_player_t *player)
{
    struct vlc_player_input *input = vlc_player_get_input_locked(player);
    if (!input)
        return;
    input_ControlPushHelper(input->thread,
                            INPUT_CONTROL_SET_FRAME_PREVIOUS, NULL);
}

enum vlc_player_state
vlc_player_GetState(vlc_player_t *player)
{
    vlc_player_assert_locked(player);
    return player->global_state;
}

enum vlc_player_error
vlc_player_GetError(vlc_player_t *player)
{
    struct vlc_player_input *input = vlc_player_get_input_locked(player);
    return input ? input->error : VLC_PLAYER_ERROR_NONE;
}

int
vlc_player_GetCapabilities(vlc_player_t *player)
{
    struct vlc_player_input *input = vlc_player_get_input_locked(player);
    return input ? input->capabilities : 0;
}

float
vlc_player_GetRate(vlc_player_t *player)
{
    struct vlc_player_input *input = vlc_player_get_input_locked(player);
    if (input)
        return input->rate;
    else
        return var_GetFloat(player, "rate");
}

void
vlc_player_ChangeRate(vlc_player_t *player, float rate)
{
    struct vlc_player_input *input = vlc_player_get_input_locked(player);

    if (rate == 0.0)
        return;

    /* Save rate across inputs */
    var_SetFloat(player, "rate", rate);

    /* The event is sent from the thread processing the control */
    if (input
     && input_ControlPushHelper(input->thread, INPUT_CONTROL_SET_RATE,
                                &(vlc_value_t) { .f_float = rate }) == VLC_SUCCESS)
        vlc_player_osd_Message(player, ("Speed: %.2fx"), rate);
    else /* Send the event anyway since it's a global state */
        vlc_player_SendEvent(player, on_rate_changed, rate);

}

static void
vlc_player_ChangeRateOffset(vlc_player_t *player, bool increment)
{
    static const float rates[] = {
        1.0f/64, 1.0f/32, 1.0f/16, 1.0f/8, 1.0f/4, 1.0f/3, 1.0f/2, 2.0f/3,
        1.0f/1,
        3.0f/2, 2.0f/1, 3.0f/1, 4.0f/1, 8.0f/1, 16.0f/1, 32.0f/1, 64.0f/1,
    };
    float rate = vlc_player_GetRate(player) * (increment ? 1.1f : 0.9f);

    /* find closest rate (if any) in the desired direction */
    for (size_t i = 0; i < ARRAY_SIZE(rates); ++i)
    {
        if ((increment && rates[i] > rate) ||
            (!increment && rates[i] >= rate && i))
        {
            rate = increment ? rates[i] : rates[i-1];
            break;
        }
    }

    vlc_player_ChangeRate(player, rate);
}

void
vlc_player_IncrementRate(vlc_player_t *player)
{
    vlc_player_ChangeRateOffset(player, true);
}

void
vlc_player_DecrementRate(vlc_player_t *player)
{
    vlc_player_ChangeRateOffset(player, false);
}

vlc_tick_t
vlc_player_GetLength(vlc_player_t *player)
{
    struct vlc_player_input *input = vlc_player_get_input_locked(player);
    return input ? input->length : VLC_TICK_INVALID;
}

vlc_tick_t
vlc_player_GetTime(vlc_player_t *player)
{
    struct vlc_player_input *input = vlc_player_get_input_locked(player);

    if (!input)
        return VLC_TICK_INVALID;

    return vlc_player_input_GetTime(input, false, vlc_tick_now());
}

double
vlc_player_GetPosition(vlc_player_t *player)
{
    struct vlc_player_input *input = vlc_player_get_input_locked(player);

    return input ? vlc_player_input_GetPos(input, false, vlc_tick_now()) : -1.f;
}

void
vlc_player_DisplayPosition(vlc_player_t *player)
{
    struct vlc_player_input *input = vlc_player_get_input_locked(player);
    if (!input)
        return;

    vlc_tick_t now = vlc_tick_now();
    vlc_player_osd_Position(player, input,
                            vlc_player_input_GetTime(input, false, now),
                            vlc_player_input_GetPos(input, false, now));
}

void
vlc_player_SeekByPos(vlc_player_t *player, double position,
                     enum vlc_player_seek_speed speed,
                     enum vlc_player_whence whence)
{
    struct vlc_player_input *input = vlc_player_get_input_locked(player);
    if (input != NULL)
        vlc_player_input_SeekByPos(input, position, speed, whence);
}

void
vlc_player_SeekByTime(vlc_player_t *player, vlc_tick_t time,
                      enum vlc_player_seek_speed speed,
                      enum vlc_player_whence whence)
{
    struct vlc_player_input *input = vlc_player_get_input_locked(player);
    if (input != NULL)
        vlc_player_input_SeekByTime(input, time, speed, whence);
}

void
vlc_player_SetRenderer(vlc_player_t *player, vlc_renderer_item_t *renderer)
{
    vlc_player_assert_locked(player);

    if (player->renderer)
        vlc_renderer_item_release(player->renderer);
    player->renderer = renderer ? vlc_renderer_item_hold(renderer) : NULL;

    vlc_player_foreach_inputs(input)
    {
        vlc_value_t val = {
            .p_address = renderer ? vlc_renderer_item_hold(renderer) : NULL
        };
        input_ControlPushHelper(input->thread, INPUT_CONTROL_SET_RENDERER,
                                &val);
    }
    vlc_player_SendEvent(player, on_renderer_changed, player->renderer);
}

vlc_renderer_item_t *
vlc_player_GetRenderer(vlc_player_t *player)
{
    vlc_player_assert_locked(player);
    return player->renderer;
}

int
vlc_player_SetAtoBLoop(vlc_player_t *player, enum vlc_player_abloop abloop)
{
    struct vlc_player_input *input = vlc_player_get_input_locked(player);

    if (!input )
        return VLC_EINVAL;

    vlc_tick_t time = vlc_player_GetTime(player);
    float pos = vlc_player_GetPosition(player);
    int ret = VLC_SUCCESS;
    switch (abloop)
    {
        case VLC_PLAYER_ABLOOP_A:
            if (input->abloop_state[1].set)
                return VLC_EGENERIC;
            input->abloop_state[0].time = time;
            input->abloop_state[0].pos = pos;
            input->abloop_state[0].set = true;
            break;
        case VLC_PLAYER_ABLOOP_B:
            if (!vlc_player_CanSeek(player))
            {
                input->abloop_state[0].set = false;
                return VLC_EGENERIC;
            }

            if (!input->abloop_state[0].set)
                return VLC_EGENERIC;
            input->abloop_state[1].time = time;
            input->abloop_state[1].pos = pos;
            input->abloop_state[1].set = true;
            if (input->abloop_state[0].time != VLC_TICK_INVALID
             && time != VLC_TICK_INVALID)
            {
                if (time > input->abloop_state[0].time)
                {
                    vlc_player_SetTime(player, input->abloop_state[0].time);
                    break;
                }
            }
            else if (pos > input->abloop_state[0].pos)
            {
                vlc_player_SetPosition(player, input->abloop_state[0].pos);
                break;
            }

            /* Error: A time is superior to B time. */
            abloop = VLC_PLAYER_ABLOOP_NONE;
            ret = VLC_EGENERIC;
            /* fall-through */
        case VLC_PLAYER_ABLOOP_NONE:
            input->abloop_state[0].set = input->abloop_state[1].set = false;
            time = VLC_TICK_INVALID;
            pos = 0.f;
            break;
        default:
            vlc_assert_unreachable();
    }
    vlc_player_SendEvent(player, on_atobloop_changed, abloop, time, pos);
    return ret;
}

int
vlc_player_SetAtoBLoopTime(vlc_player_t *player, vlc_tick_t a_time, vlc_tick_t b_time)
{
    if (a_time < 0 || b_time < 0)
        return VLC_EINVAL;

    if (a_time == VLC_TICK_INVALID || b_time == VLC_TICK_INVALID)
        return VLC_EINVAL;

    if (b_time <= a_time)
        return VLC_EINVAL;

    struct vlc_player_input *input = vlc_player_get_input_locked(player);

    if (!input)
        return VLC_EINVAL;

    input->abloop_state[0].time = a_time;
    input->abloop_state[0].pos = 0;
    input->abloop_state[0].set = true;
    input->abloop_state[1].time = b_time;
    input->abloop_state[1].pos = 0;
    input->abloop_state[1].set = true;

    vlc_tick_t current = vlc_player_input_GetTime(input, false, vlc_tick_now());
    if (current == VLC_TICK_INVALID || current < a_time || current > b_time)
        vlc_player_SetTime(player, a_time);

    vlc_player_SendEvent(player, on_atobloop_changed, VLC_PLAYER_ABLOOP_A, a_time, 0);
    vlc_player_SendEvent(player, on_atobloop_changed, VLC_PLAYER_ABLOOP_B, b_time, 0);
    return VLC_SUCCESS;
}

int
vlc_player_SetAtoBLoopPosition(vlc_player_t *player, double a_pos, double b_pos)
{
    if (a_pos < 0 || a_pos > 1.0 || b_pos < 0 || b_pos > 1.0)
        return VLC_EINVAL;

    if (b_pos <= a_pos)
        return VLC_EINVAL;

    struct vlc_player_input *input = vlc_player_get_input_locked(player);

    if (!input)
        return VLC_EINVAL;

    input->abloop_state[0].time = VLC_TICK_INVALID;
    input->abloop_state[0].pos = a_pos;
    input->abloop_state[0].set = true;
    input->abloop_state[1].time = VLC_TICK_INVALID;
    input->abloop_state[1].pos = b_pos;
    input->abloop_state[1].set = true;

    double current = vlc_player_input_GetPos(input, false, vlc_tick_now());
    if (current < a_pos || current > b_pos)
        vlc_player_SetPosition(player, a_pos);

    vlc_player_SendEvent(player, on_atobloop_changed, VLC_PLAYER_ABLOOP_A, VLC_TICK_INVALID, a_pos);
    vlc_player_SendEvent(player, on_atobloop_changed, VLC_PLAYER_ABLOOP_B, VLC_TICK_INVALID, b_pos);
    return VLC_SUCCESS;
}

int
vlc_player_ResetAtoBLoop(vlc_player_t *player) {
    struct vlc_player_input *input = vlc_player_get_input_locked(player);

    if (!input || !vlc_player_CanSeek(player))
        return VLC_EGENERIC;

    input->abloop_state[0].time = VLC_TICK_INVALID;
    input->abloop_state[0].pos = 0;
    input->abloop_state[0].set = false;
    input->abloop_state[1].time = VLC_TICK_INVALID;
    input->abloop_state[1].pos = 0;
    input->abloop_state[1].set = false;

    vlc_player_SendEvent(player, on_atobloop_changed, VLC_PLAYER_ABLOOP_NONE, VLC_TICK_INVALID, 0);
    return VLC_SUCCESS;
}

enum vlc_player_abloop
vlc_player_GetAtoBLoop(vlc_player_t *player, vlc_tick_t *a_time, double *a_pos,
                       vlc_tick_t *b_time, double *b_pos)
{
    struct vlc_player_input *input = vlc_player_get_input_locked(player);

    if (!input || !vlc_player_CanSeek(player) || !input->abloop_state[0].set)
        return VLC_PLAYER_ABLOOP_NONE;

    if (a_time)
        *a_time = input->abloop_state[0].time;
    if (a_pos)
        *a_pos = input->abloop_state[0].pos;
    if (!input->abloop_state[1].set)
        return VLC_PLAYER_ABLOOP_A;

    if (b_time)
        *b_time = input->abloop_state[1].time;
    if (b_pos)
        *b_pos = input->abloop_state[1].pos;
    return VLC_PLAYER_ABLOOP_B;
}

void
vlc_player_Navigate(vlc_player_t *player, enum vlc_player_nav nav)
{
    struct vlc_player_input *input = vlc_player_get_input_locked(player);

    if (!input)
        return;

    enum input_control_e control;
    switch (nav)
    {
        case VLC_PLAYER_NAV_ACTIVATE:
            control = INPUT_CONTROL_NAV_ACTIVATE;
            break;
        case VLC_PLAYER_NAV_UP:
            control = INPUT_CONTROL_NAV_UP;
            break;
        case VLC_PLAYER_NAV_DOWN:
            control = INPUT_CONTROL_NAV_DOWN;
            break;
        case VLC_PLAYER_NAV_LEFT:
            control = INPUT_CONTROL_NAV_LEFT;
            break;
        case VLC_PLAYER_NAV_RIGHT:
            control = INPUT_CONTROL_NAV_RIGHT;
            break;
        case VLC_PLAYER_NAV_POPUP:
            control = INPUT_CONTROL_NAV_POPUP;
            break;
        case VLC_PLAYER_NAV_MENU:
            control = INPUT_CONTROL_NAV_MENU;
            break;
        default:
            vlc_assert_unreachable();
    }
    input_ControlPushHelper(input->thread, control, NULL);
}

void
vlc_player_UpdateViewpoint(vlc_player_t *player,
                           const vlc_viewpoint_t *viewpoint,
                           enum vlc_player_whence whence)
{
    struct vlc_player_input *input = vlc_player_get_input_locked(player);
    if (input != NULL)
        vlc_player_input_UpdateViewpoint(input, viewpoint, whence);
}

bool
vlc_player_IsRecording(vlc_player_t *player)
{
    struct vlc_player_input *input = vlc_player_get_input_locked(player);

    return input ? input->recording : false;
}

void
vlc_player_SetRecordingEnabled(vlc_player_t *player, bool enable,
                               const char *dir_path_)
{
    struct vlc_player_input *input = vlc_player_get_input_locked(player);
    if (!input)
        return;
    char *dir_path;
    if (dir_path_ != NULL)
    {
        dir_path = strdup(dir_path_);
        if (dir_path == NULL)
            return;
    }
    else
        dir_path = NULL;

    const input_control_param_t param = { .record_state = { enable, dir_path } };
    int ret = input_ControlPush(input->thread,
                                INPUT_CONTROL_SET_RECORD_STATE, ¶m);

    if (ret == VLC_SUCCESS)
        vlc_player_osd_Message(player, enable ?
                               _("Recording") : _("Recording done"));
}

int
vlc_player_SetCategoryDelay(vlc_player_t *player, enum es_format_category_e cat,
                            vlc_tick_t delay, enum vlc_player_whence whence)
{
    bool absolute = whence == VLC_PLAYER_WHENCE_ABSOLUTE;
    struct vlc_player_input *input = vlc_player_get_input_locked(player);
    if (!input)
        return VLC_EGENERIC;

    if (cat != AUDIO_ES && cat != SPU_ES && cat != VIDEO_ES)
        return VLC_EGENERIC;
    vlc_tick_t *cat_delay = &input->cat_delays[cat];

    if (absolute)
        *cat_delay = delay;
    else
    {
        *cat_delay += delay;
        delay = *cat_delay;
    }

    int ret = input_SetEsCatDelay(input->thread, cat, delay);
    if (ret == VLC_SUCCESS)
    {
        vlc_player_osd_Message(player, _("%s delay: %i ms"),
                               es_format_category_to_string(cat),
                               (int)MS_FROM_VLC_TICK(delay));
        vlc_player_SendEvent(player, on_category_delay_changed, cat, delay);
    }
    return VLC_SUCCESS;
}

vlc_tick_t
vlc_player_GetCategoryDelay(vlc_player_t *player, enum es_format_category_e cat)
{
    struct vlc_player_input *input = vlc_player_get_input_locked(player);
    if (!input)
        return 0;

    if (cat != AUDIO_ES && cat != SPU_ES)
        return 0;

    return input->cat_delays[cat];
}

int
vlc_player_SetEsIdDelay(vlc_player_t *player, vlc_es_id_t *es_id,
                        vlc_tick_t delay, enum vlc_player_whence whence)
{
    bool absolute = whence == VLC_PLAYER_WHENCE_ABSOLUTE;
    struct vlc_player_input *input = vlc_player_get_input_locked(player);
    if (!input)
        return VLC_EGENERIC;

    struct vlc_player_track_priv *trackpriv =
        vlc_player_input_FindTrackById(input, es_id, NULL);
    if (trackpriv == NULL ||
        (trackpriv->t.fmt.i_cat != AUDIO_ES && trackpriv->t.fmt.i_cat != SPU_ES))
        return VLC_EGENERIC;

    if (absolute)
        trackpriv->delay = delay;
    else
    {
        if (trackpriv->delay == VLC_TICK_MAX)
            trackpriv->delay = 0;
        trackpriv->delay += delay;
        delay = trackpriv->delay;
    }

    const input_control_param_t param = { .es_delay = { es_id, delay } };
    int ret = input_ControlPush(input->thread, INPUT_CONTROL_SET_ES_DELAY,
                                ¶m);
    if (ret == VLC_SUCCESS)
    {
        if (delay != VLC_TICK_MAX)
            vlc_player_osd_Message(player, _("%s delay: %i ms"),
                                   trackpriv->t.name,
                                   (int)MS_FROM_VLC_TICK(delay));
        vlc_player_SendEvent(player, on_track_delay_changed, es_id, delay);
    }

    return VLC_SUCCESS;
}

vlc_tick_t
vlc_player_GetEsIdDelay(vlc_player_t *player, vlc_es_id_t *es_id)
{
    struct vlc_player_input *input = vlc_player_get_input_locked(player);
    if (!input)
        return 0;

    struct vlc_player_track_priv *trackpriv =
        vlc_player_input_FindTrackById(input, es_id, NULL);
    return trackpriv ? trackpriv->delay : VLC_TICK_MAX;
}

static struct {
    const char var[sizeof("video")];
    const char sout_var[sizeof("sout-video")];
} cat2vars[] = {
    [VIDEO_ES] = { "video", "sout-video" },
    [AUDIO_ES] = { "audio", "sout-audio" },
    [SPU_ES] = { "spu", "sout-spu" },
};

void
vlc_player_SetTrackCategoryEnabled(vlc_player_t *player,
                                   enum es_format_category_e cat, bool enabled)
{
    assert(cat >= UNKNOWN_ES && cat <= DATA_ES);
    struct vlc_player_input *input = vlc_player_get_input_locked(player);

    var_SetBool(player, cat2vars[cat].var, enabled);
    var_SetBool(player, cat2vars[cat].sout_var, enabled);

    if (input)
    {
        var_SetBool(input->thread, cat2vars[cat].var, enabled);
        var_SetBool(input->thread, cat2vars[cat].sout_var, enabled);

        if (!enabled)
            vlc_player_UnselectTrackCategory(player, cat);
    }
}

bool
vlc_player_IsTrackCategoryEnabled(vlc_player_t *player,
                                  enum es_format_category_e cat)
{
    assert(cat >= UNKNOWN_ES && cat <= DATA_ES);
    return var_GetBool(player, cat2vars[cat].var);
}

void
vlc_player_SetSubtitleTextScale(vlc_player_t *player, unsigned scale)
{
    assert(scale >= 10 && scale <= 500);
    var_SetInteger(player, "sub-text-scale", scale);
}

unsigned
vlc_player_GetSubtitleTextScale(vlc_player_t *player)
{
    return var_GetInteger(player, "sub-text-scale");
}

int
vlc_player_GetSignal(vlc_player_t *player, float *quality, float *strength)
{
    assert(quality && strength);
    struct vlc_player_input *input = vlc_player_get_input_locked(player);

    if (input && input->signal_quality >= 0 && input->signal_strength >= 0)
    {
        *quality = input->signal_quality;
        *strength = input->signal_strength;
        return VLC_SUCCESS;
    }
    return VLC_EGENERIC;
}

const struct input_stats_t *
vlc_player_GetStatistics(vlc_player_t *player)
{
    struct vlc_player_input *input = vlc_player_get_input_locked(player);

    return input ? &input->stats : NULL;
}

void
vlc_player_SetPauseOnCork(vlc_player_t *player, bool enabled)
{
    vlc_player_assert_locked(player);
    player->pause_on_cork = enabled;
}

static int
vlc_player_CorkCallback(vlc_object_t *this, const char *var,
                        vlc_value_t oldval, vlc_value_t newval, void *data)
{
    vlc_player_t *player = data;

    if (oldval.i_int == newval.i_int )
        return VLC_SUCCESS;

    vlc_player_Lock(player);

    if (player->pause_on_cork)
    {
        if (newval.i_int)
        {
            player->corked = player->global_state == VLC_PLAYER_STATE_PLAYING
                          || player->global_state == VLC_PLAYER_STATE_STARTED;
            if (player->corked)
                vlc_player_Pause(player);
        }
        else
        {
            if (player->corked)
            {
                vlc_player_Resume(player);
                player->corked = false;
            }
        }
    }
    else
        vlc_player_SendEvent(player, on_cork_changed, newval.i_int);

    vlc_player_Unlock(player);

    (void) this; (void) var;
    return VLC_SUCCESS;
}

vlc_object_t *
vlc_player_GetV4l2Object(vlc_player_t *player)
{
    struct vlc_player_input *input = vlc_player_get_input_locked(player);
    return input && var_Type(input->thread, "controls") != 0 ?
           VLC_OBJECT(input->thread) : NULL;
}

static void
vlc_player_InitLocks(vlc_player_t *player, enum vlc_player_lock_type lock_type)
{
    if (lock_type == VLC_PLAYER_LOCK_REENTRANT)
        vlc_mutex_init_recursive(&player->lock);
    else
        vlc_mutex_init(&player->lock);

    vlc_mutex_init(&player->metadata_listeners_lock);
    vlc_mutex_init(&player->vout_listeners_lock);
    vlc_mutex_init(&player->aout_listeners_lock);
    vlc_cond_init(&player->start_delay_cond);
    vlc_cond_init(&player->mainloop.wait);
}

void
vlc_player_Delete(vlc_player_t *player)
{
    vlc_mutex_lock(&player->lock);

    if (player->input)
    {
        vlc_player_destructor_AddInput(player, player->input);
        player->input = NULL;
    }

    player->deleting = true;
    vlc_cond_signal(&player->mainloop.wait);

    assert(vlc_list_is_empty(&player->listeners));
    assert(vlc_list_is_empty(&player->metadata_listeners));
    assert(vlc_list_is_empty(&player->vout_listeners));
    assert(vlc_list_is_empty(&player->aout_listeners));

    vlc_mutex_unlock(&player->lock);

    vlc_join(player->mainloop.thread, NULL);

    if (player->media)
        input_item_Release(player->media);
    if (player->next_media)
        input_item_Release(player->next_media);

    free(player->video_string_ids);
    free(player->audio_string_ids);
    free(player->sub_string_ids);

    vlc_player_DestroyTimer(player);

    vlc_player_aout_Deinit(player);
    var_DelCallback(player, "corks", vlc_player_CorkCallback, player);

    input_resource_Release(player->resource);
    if (player->renderer)
        vlc_renderer_item_release(player->renderer);

    vlc_http_cookie_jar_t *cookies = var_GetAddress(player, "http-cookies");
    if (cookies != NULL)
    {
        var_Destroy(player, "http-cookies");
        vlc_http_cookies_destroy(cookies);
    }

    assert(!vlc_mutex_held(&player->lock));

    vlc_object_delete(player);
}

vlc_player_t *
vlc_player_New(vlc_object_t *parent, enum vlc_player_lock_type lock_type)
{
    audio_output_t *aout = NULL;
    vlc_player_t *player = vlc_custom_create(parent, sizeof(*player), "player");
    if (!player)
        return NULL;

    vlc_list_init(&player->listeners);
    vlc_list_init(&player->metadata_listeners);
    vlc_list_init(&player->vout_listeners);
    vlc_list_init(&player->aout_listeners);
    vlc_list_init(&player->mainloop.stop_inputs);
    vlc_list_init(&player->mainloop.stopping_inputs);
    vlc_list_init(&player->mainloop.joinable_inputs);
    player->start_paused = false;
    player->pause_on_cork = false;
    player->corked = false;
    player->renderer = NULL;
    player->media = NULL;
    player->input = NULL;
    player->global_state = VLC_PLAYER_STATE_STOPPED;
    player->started = false;

    player->last_eos = VLC_TICK_INVALID;
    player->eos_burst_count = 0;

    player->releasing_media = false;
    player->next_media = NULL;

    player->video_string_ids = player->audio_string_ids =
    player->sub_string_ids = NULL;

    player->play_and_pause = false;
    player->repeat = 0;

#define VAR_CREATE(var, flag) do { \
    if (var_Create(player, var, flag) != VLC_SUCCESS) \
        goto error; \
} while(0)

    /* player variables */
    VAR_CREATE("rate", VLC_VAR_FLOAT | VLC_VAR_DOINHERIT);
    VAR_CREATE("sub-fps", VLC_VAR_FLOAT | VLC_VAR_DOINHERIT);
    VAR_CREATE("sub-text-scale", VLC_VAR_INTEGER | VLC_VAR_DOINHERIT);
    VAR_CREATE("demux-filter", VLC_VAR_STRING | VLC_VAR_DOINHERIT);

    /* aout variables */
    VAR_CREATE("audio-filter", VLC_VAR_STRING | VLC_VAR_DOINHERIT);
    VAR_CREATE("mute", VLC_VAR_BOOL);
    VAR_CREATE("corks", VLC_VAR_INTEGER);

    /* es_out variables */
    VAR_CREATE("sout", VLC_VAR_STRING | VLC_VAR_DOINHERIT);
    VAR_CREATE("video", VLC_VAR_BOOL | VLC_VAR_DOINHERIT);
    VAR_CREATE("sout-video", VLC_VAR_BOOL | VLC_VAR_DOINHERIT);
    VAR_CREATE("audio", VLC_VAR_BOOL | VLC_VAR_DOINHERIT);
    VAR_CREATE("sout-audio", VLC_VAR_BOOL | VLC_VAR_DOINHERIT);
    VAR_CREATE("spu", VLC_VAR_BOOL | VLC_VAR_DOINHERIT);
    VAR_CREATE("sout-spu", VLC_VAR_BOOL | VLC_VAR_DOINHERIT);
    VAR_CREATE("audio-language", VLC_VAR_STRING | VLC_VAR_DOINHERIT);
    VAR_CREATE("sub-language", VLC_VAR_STRING | VLC_VAR_DOINHERIT);

    /* TODO: Override these variables since the player handle media ended
     * action itself. */

    VAR_CREATE("start-paused", VLC_VAR_BOOL);

    /* Initialize the shared HTTP cookie jar */
    vlc_value_t cookies;
    cookies.p_address = vlc_http_cookies_new();
    if (likely(cookies.p_address != NULL))
    {
        VAR_CREATE("http-cookies", VLC_VAR_ADDRESS);
        var_SetChecked(player, "http-cookies", VLC_VAR_ADDRESS, cookies);
    }
#undef VAR_CREATE

    player->resource = input_resource_New(VLC_OBJECT(player));

    if (!player->resource)
        goto error;

    /* Ensure the player has a valid aout */
    aout = vlc_player_aout_Init(player);
    var_AddCallback(player, "corks", vlc_player_CorkCallback, player);

    player->deleting = false;
    vlc_player_InitLocks(player, lock_type);
    vlc_player_InitTimer(player);

    if (vlc_clone(&player->mainloop.thread, vlc_player_mainloop_Thread,
                  player) != 0)
    {
        vlc_player_DestroyTimer(player);
        goto error;
    }

    return player;

error:
    if (aout)
        vlc_player_aout_Deinit(player);
    var_DelCallback(player, "corks", vlc_player_CorkCallback, player);
    if (player->resource)
        input_resource_Release(player->resource);

    vlc_object_delete(player);
    return NULL;
}

vlc_object_t *
vlc_player_GetObject(vlc_player_t *player)
{
    return VLC_OBJECT(player);
}

Messung
CHZG
82979491

¤ Dauer der Verarbeitung: 0.29 Sekunden  ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

PVS Prover

Isabelle Prover


Cephes Mathematical Library

Vienna Development Method

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.