vlc/src/player/player.c

2013 lines
58 KiB
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 "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)
void
vlc_player_PrepareNextMedia(vlc_player_t *player)
{
vlc_player_assert_locked(player);
if (!player->media_provider
|| player->next_media_requested)
return;
assert(player->next_media == NULL);
player->next_media =
player->media_provider->get_next(player, player->media_provider_data);
player->next_media_requested = true;
}
int
vlc_player_OpenNextMedia(vlc_player_t *player)
{
assert(player->input == NULL);
player->next_media_requested = false;
/* 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->destructor.stopping_inputs, input));
assert(!vlc_list_HasInput(&player->destructor.joinable_inputs, input));
vlc_list_append(&input->node, &player->destructor.inputs);
}
else
{
/* Add this input to the joinable list: it will be deleted by the
* destructor thread */
assert(!vlc_list_HasInput(&player->destructor.inputs, input));
assert(!vlc_list_HasInput(&player->destructor.joinable_inputs, input));
vlc_list_append(&input->node, &player->destructor.joinable_inputs);
}
vlc_cond_signal(&input->player->destructor.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->destructor.inputs, input))
vlc_list_remove(&input->node);
if (!vlc_list_HasInput(&player->destructor.stopping_inputs, input))
{
vlc_list_append(&input->node, &player->destructor.stopping_inputs);
vlc_cond_signal(&input->player->destructor.wait);
}
}
void
vlc_player_destructor_AddJoinableInput(vlc_player_t *player,
struct vlc_player_input *input)
{
if (vlc_list_HasInput(&player->destructor.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->destructor.inputs)
&& vlc_list_is_empty(&player->destructor.stopping_inputs)
&& vlc_list_is_empty(&player->destructor.joinable_inputs);
}
static void *
vlc_player_destructor_Thread(void *data)
{
vlc_player_t *player = data;
vlc_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. */
if (vlc_list_is_empty(&player->destructor.inputs)
&& vlc_list_is_empty(&player->destructor.joinable_inputs))
vlc_cond_wait(&player->destructor.wait, &player->lock);
struct vlc_player_input *input;
vlc_list_foreach(input, &player->destructor.inputs, node)
{
vlc_player_input_HandleState(input, VLC_PLAYER_STATE_STOPPING,
VLC_TICK_INVALID);
vlc_player_destructor_AddStoppingInput(player, input);
/* Note: no need to hold the media here, it will be valid
* until input_Close() and the event is sent from the thread
* that will call this function. */
input_item_t *media = input_GetItem(input->thread);
input_Stop(input->thread);
vlc_player_SendEvent(player, on_stopping_current_media, media);
}
bool keep_sout = true;
const bool inputs_changed =
!vlc_list_is_empty(&player->destructor.joinable_inputs);
vlc_list_foreach(input, &player->destructor.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);
}
static void
vlc_player_CycleTrack(vlc_player_t *player, enum es_format_category_e cat,
bool next)
{
size_t count = vlc_player_GetTrackCount(player, cat);
if (!count)
return;
size_t index;
bool selected = false;
for (size_t i = 0; i < count; ++i)
{
const struct vlc_player_track *track =
vlc_player_GetTrackAt(player, cat, i);
assert(track);
if (track->selected)
{
if (selected)
{
/* Can't cycle through tracks if there are more than one
* selected */
return;
}
index = i;
selected = true;
}
}
if (!selected)
{
/* No track selected: select the first or the last track */
index = next ? 0 : count - 1;
selected = true;
}
else
{
/* Unselect if we reach the end of the cycle */
if ((next && index + 1 == count) || (!next && index == 0))
selected = false;
else /* Switch to the next or previous track */
index = index + (next ? 1 : -1);
}
const struct vlc_player_track *track =
vlc_player_GetTrackAt(player, cat, index);
if (selected)
vlc_player_SelectTrack(player, track, VLC_PLAYER_SELECT_EXCLUSIVE);
else
vlc_player_UnselectTrack(player, track);
}
void
vlc_player_SelectNextTrack(vlc_player_t *player,
enum es_format_category_e cat)
{
vlc_player_CycleTrack(player, cat, true);
}
void
vlc_player_SelectPrevTrack(vlc_player_t *player,
enum es_format_category_e cat)
{
vlc_player_CycleTrack(player, cat, false);
}
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;
}
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;
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;
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;
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);
}
int
vlc_player_SetCurrentMedia(vlc_player_t *player, input_item_t *media)
{
vlc_player_assert_locked(player);
vlc_player_CancelWaitError(player);
vlc_player_InvalidateNextMedia(player);
if (media)
{
/* Switch to this new media when the current input is stopped */
player->next_media = input_item_Hold(media);
player->releasing_media = false;
player->next_media_requested = true;
}
else if (player->media)
{
/* The current media will be set to NULL once the current input is
* stopped */
player->releasing_media = true;
player->next_media_requested = false;
}
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);
}
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");
}
void
vlc_player_InvalidateNextMedia(vlc_player_t *player)
{
vlc_player_assert_locked(player);
if (player->next_media)
{
input_item_Release(player->next_media);
player->next_media = NULL;
}
player->next_media_requested = false;
}
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;
player->next_media_requested = true;
return VLC_SUCCESS;
}
else
return VLC_EGENERIC;
}
if (!player->media)
return VLC_EGENERIC;
if (!player->input)
{
/* Possible if the player was stopped by the user */
player->input = vlc_player_input_New(player, player->media);
if (!player->input)
return VLC_ENOMEM;
}
assert(!player->input->started);
if (player->start_paused)
{
var_Create(player->input->thread, "start-paused", VLC_VAR_BOOL);
var_SetBool(player->input->thread, "start-paused", true);
}
int ret = vlc_player_input_Start(player->input);
if (ret == VLC_SUCCESS)
{
player->started = true;
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;
}
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;
int ret = input_ControlPushHelper(input->thread,
INPUT_CONTROL_SET_FRAME_NEXT, NULL);
if (ret == VLC_SUCCESS)
vlc_player_osd_Message(player, _("Next frame"));
}
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 || !vlc_player_CanSeek(player))
return VLC_EGENERIC;
vlc_tick_t time = vlc_player_GetTime(player);
float pos = vlc_player_GetPosition(player);
int ret = VLC_SUCCESS;
switch (abloop)
{
case VLC_PLAYER_ABLOOP_A:
if (input->abloop_state[1].set)
return VLC_EGENERIC;
input->abloop_state[0].time = time;
input->abloop_state[0].pos = pos;
input->abloop_state[0].set = true;
break;
case VLC_PLAYER_ABLOOP_B:
if (!input->abloop_state[0].set)
return VLC_EGENERIC;
input->abloop_state[1].time = time;
input->abloop_state[1].pos = pos;
input->abloop_state[1].set = true;
if (input->abloop_state[0].time != VLC_TICK_INVALID
&& time != VLC_TICK_INVALID)
{
if (time > input->abloop_state[0].time)
{
vlc_player_SetTime(player, input->abloop_state[0].time);
break;
}
}
else if (pos > input->abloop_state[0].pos)
{
vlc_player_SetPosition(player, input->abloop_state[0].pos);
break;
}
/* Error: A time is superior to B time. */
abloop = VLC_PLAYER_ABLOOP_NONE;
ret = VLC_EGENERIC;
/* fall-through */
case VLC_PLAYER_ABLOOP_NONE:
input->abloop_state[0].set = input->abloop_state[1].set = false;
time = VLC_TICK_INVALID;
pos = 0.f;
break;
default:
vlc_assert_unreachable();
}
vlc_player_SendEvent(player, on_atobloop_changed, abloop, time, pos);
return ret;
}
enum vlc_player_abloop
vlc_player_GetAtoBLoop(vlc_player_t *player, vlc_tick_t *a_time, float *a_pos,
vlc_tick_t *b_time, float *b_pos)
{
struct vlc_player_input *input = vlc_player_get_input_locked(player);
if (!input || !vlc_player_CanSeek(player) || !input->abloop_state[0].set)
return VLC_PLAYER_ABLOOP_NONE;
if (a_time)
*a_time = input->abloop_state[0].time;
if (a_pos)
*a_pos = input->abloop_state[0].pos;
if (!input->abloop_state[1].set)
return VLC_PLAYER_ABLOOP_A;
if (b_time)
*b_time = input->abloop_state[1].time;
if (b_pos)
*b_pos = input->abloop_state[1].pos;
return VLC_PLAYER_ABLOOP_B;
}
void
vlc_player_Navigate(vlc_player_t *player, enum vlc_player_nav nav)
{
struct vlc_player_input *input = vlc_player_get_input_locked(player);
if (!input)
return;
enum input_control_e control;
switch (nav)
{
case VLC_PLAYER_NAV_ACTIVATE:
control = INPUT_CONTROL_NAV_ACTIVATE;
break;
case VLC_PLAYER_NAV_UP:
control = INPUT_CONTROL_NAV_UP;
break;
case VLC_PLAYER_NAV_DOWN:
control = INPUT_CONTROL_NAV_DOWN;
break;
case VLC_PLAYER_NAV_LEFT:
control = INPUT_CONTROL_NAV_LEFT;
break;
case VLC_PLAYER_NAV_RIGHT:
control = INPUT_CONTROL_NAV_RIGHT;
break;
case VLC_PLAYER_NAV_POPUP:
control = INPUT_CONTROL_NAV_POPUP;
break;
case VLC_PLAYER_NAV_MENU:
control = INPUT_CONTROL_NAV_MENU;
break;
default:
vlc_assert_unreachable();
}
input_ControlPushHelper(input->thread, control, NULL);
}
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, &param);
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,
&param);
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);
return VLC_SUCCESS;
(void) this; (void) var;
}
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->destructor.wait);
}
void
vlc_player_Delete(vlc_player_t *player)
{
vlc_mutex_lock(&player->lock);
if (player->input)
{
vlc_player_destructor_AddInput(player, player->input);
player->input = NULL;
}
player->deleting = true;
vlc_cond_signal(&player->destructor.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->destructor.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,
const struct vlc_player_media_provider *media_provider,
void *media_provider_data)
{
audio_output_t *aout = NULL;
vlc_player_t *player = vlc_custom_create(parent, sizeof(*player), "player");
if (!player)
return NULL;
assert(!media_provider || media_provider->get_next);
vlc_list_init(&player->listeners);
vlc_list_init(&player->metadata_listeners);
vlc_list_init(&player->vout_listeners);
vlc_list_init(&player->aout_listeners);
vlc_list_init(&player->destructor.inputs);
vlc_list_init(&player->destructor.stopping_inputs);
vlc_list_init(&player->destructor.joinable_inputs);
player->start_paused = false;
player->pause_on_cork = false;
player->corked = false;
player->renderer = NULL;
player->media_provider = media_provider;
player->media_provider_data = media_provider_data;
player->media = NULL;
player->input = NULL;
player->global_state = VLC_PLAYER_STATE_STOPPED;
player->started = false;
player->last_eos = VLC_TICK_INVALID;
player->eos_burst_count = 0;
player->releasing_media = false;
player->next_media_requested = false;
player->next_media = NULL;
player->video_string_ids = player->audio_string_ids =
player->sub_string_ids = NULL;
#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);
VAR_CREATE("play-and-pause", 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->destructor.thread, vlc_player_destructor_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);
}