/*****************************************************************************
* player_input.c: Player input 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 <vlc_common.h>
#include <vlc_memstream.h>
#include <vlc_dialog.h>
#include "player.h"
struct vlc_player_track_priv *
vlc_player_input_FindTrackById(struct vlc_player_input *input, vlc_es_id_t *id,
size_t *idx)
{
vlc_player_track_vector *vec =
vlc_player_input_GetTrackVector(input, vlc_es_id_GetCat(id));
return vec ? vlc_player_track_vector_FindById(vec, id, idx) : NULL;
}
vlc_tick_t
vlc_player_input_GetTime(struct vlc_player_input *input, bool seeking,
vlc_tick_t system_now)
{
vlc_player_t *player = input->player;
vlc_tick_t ts;
if (input == player->input
&& vlc_player_GetTimerPoint(player, &seeking, system_now, &ts, NULL) == 0)
return ts;
return input->time;
}
double
vlc_player_input_GetPos(struct vlc_player_input *input, bool seeking,
vlc_tick_t system_now)
{
vlc_player_t *player = input->player;
double pos;
if (input == player->input
&& vlc_player_GetTimerPoint(player, &seeking, system_now, NULL, &pos) == 0)
return pos;
return input->position;
}
bool
vlc_player_input_HandleAtoBLoop(struct vlc_player_input *input, bool forced)
{
if (!input->abloop_state[0].set || !input->abloop_state[1].set
|| (input->capabilities & VLC_PLAYER_CAP_SEEK) == 0)
return false;
vlc_player_t *player = input->player;
if (player->input != input)
return false;
vlc_tick_t now = vlc_tick_now();
if (input->abloop_state[0].time != VLC_TICK_INVALID
&& input->abloop_state[1].time != VLC_TICK_INVALID)
{
vlc_tick_t time = vlc_player_input_GetTime(input, false, now);
if (forced
|| (time != VLC_TICK_INVALID && time >= input->abloop_state[1].time))
vlc_player_SetTime(player, input->abloop_state[0].time);
return true;
}
double pos = vlc_player_input_GetPos(input, false, now);
if (forced || pos >= input->abloop_state[1].pos)
{
vlc_player_SetPosition(player, input->abloop_state[0].pos);
return true;
}
return false;
}
int
vlc_player_input_Start(struct vlc_player_input *input)
{
int ret = input_Start(input->thread);
if (ret != VLC_SUCCESS)
return ret;
input->started = true;
return ret;
}
static inline void
vlc_player_assert_seek_params(enum vlc_player_seek_speed speed,
enum vlc_player_whence whence)
{
assert(speed == VLC_PLAYER_SEEK_PRECISE
|| speed == VLC_PLAYER_SEEK_FAST);
assert(whence == VLC_PLAYER_WHENCE_ABSOLUTE
|| whence == VLC_PLAYER_WHENCE_RELATIVE);
(void) speed; (void) whence;
}
void
vlc_player_input_SeekByPos(struct vlc_player_input *input, double position,
enum vlc_player_seek_speed speed,
enum vlc_player_whence whence)
{
vlc_player_t *player = input->player;
vlc_player_assert_seek_params(speed, whence);
/* Assert before we modify position internally, this only check that user
* positions are correct. */
if (whence != VLC_PLAYER_WHENCE_ABSOLUTE)
{
assert(position >= -1 && position <=1);
position += vlc_player_input_GetPos(input, true, vlc_tick_now());
}
else
{
assert(position >= 0 && position <=1);
}
if (position < 0)
position = 0;
else if (position > 1)
position = 1;
vlc_player_UpdateTimerSeekState(player, VLC_TICK_INVALID, position);
int ret = input_ControlPush(input->thread, INPUT_CONTROL_SET_POSITION,
&(input_control_param_t) {
.pos.f_val = position,
.pos.b_fast_seek = speed == VLC_PLAYER_SEEK_FAST,
});
if (ret == VLC_SUCCESS)
vlc_player_osd_Position(player, input, VLC_TICK_INVALID, position);
}
void
vlc_player_input_SeekByTime(struct vlc_player_input *input, vlc_tick_t time,
enum vlc_player_seek_speed speed,
enum vlc_player_whence whence)
{
vlc_player_t *player = input->player;
vlc_player_assert_seek_params(speed, whence);
if (whence != VLC_PLAYER_WHENCE_ABSOLUTE)
time += vlc_player_input_GetTime(input, true, vlc_tick_now());
if (time < VLC_TICK_0)
time = VLC_TICK_0;
vlc_player_UpdateTimerSeekState(player, time, -1);
int ret = input_ControlPush(input->thread, INPUT_CONTROL_SET_TIME,
&(input_control_param_t) {
.time.i_val = time,
.time.b_fast_seek = speed == VLC_PLAYER_SEEK_FAST,
});
if (ret == VLC_SUCCESS)
vlc_player_osd_Position(player, input, time, -1);
}
void
vlc_player_input_UpdateViewpoint(struct vlc_player_input *input,
const vlc_viewpoint_t *viewpoint,
enum vlc_player_whence whence)
{
input_control_param_t param = { .viewpoint = *viewpoint };
if (whence == VLC_PLAYER_WHENCE_ABSOLUTE)
input_ControlPush(input->thread, INPUT_CONTROL_SET_VIEWPOINT,
¶m);
else
input_ControlPush(input->thread, INPUT_CONTROL_UPDATE_VIEWPOINT,
¶m);
}
static bool
vlc_player_WaitRetryDelay(vlc_player_t *player)
{
#define RETRY_TIMEOUT_BASE VLC_TICK_FROM_MS(100)
#define RETRY_TIMEOUT_MAX VLC_TICK_FROM_MS(3200)
#define MAX_EOS_BURST 4
/* Temporize only after a few successive EOS */
if (player->eos_burst_count > MAX_EOS_BURST)
{
unsigned pow = player->eos_burst_count - MAX_EOS_BURST;
/* Delay the next opening to avoid busy loops */
vlc_tick_t delay = RETRY_TIMEOUT_BASE;
for (unsigned i = 1; i < pow && delay < RETRY_TIMEOUT_MAX; ++i)
delay *= 2; /* Wait 100, 200, 400, 800, 1600 and finally 3200ms */
delay += vlc_tick_now();
while (player->eos_burst_count != 0
&& vlc_cond_timedwait(&player->start_delay_cond, &player->lock,
delay) == 0);
if (player->eos_burst_count == 0)
return false; /* canceled */
}
return true;
}
void
vlc_player_input_HandleState(struct vlc_player_input *input,
enum vlc_player_state state, vlc_tick_t state_date)
{
vlc_player_t *player = input->player;
/* The STOPPING state can be set earlier by the player. In that case,
* ignore all future events except the STOPPED one */
if (input->state == VLC_PLAYER_STATE_STOPPING
&& state != VLC_PLAYER_STATE_STOPPED)
return;
enum vlc_player_state last_state = input->state;
input->state = state;
/* Override the global state if the player is still playing and has a next
* media to play */
bool send_event = player->global_state != state;
switch (input->state)
{
case VLC_PLAYER_STATE_STOPPED:
assert(!input->started);
assert(input != player->input);
if (input->titles)
{
vlc_player_title_list_Release(input->titles);
input->titles = NULL;
vlc_player_SendEvent(player, on_titles_changed, NULL);
}
vlc_player_ResetTimer(player);
if (player->last_eos != VLC_TICK_INVALID)
{
vlc_tick_t diff = vlc_tick_now() - player->last_eos;
if (diff < VLC_PLAYER_EOS_BURST_THRESHOLD)
++player->eos_burst_count;
else
player->eos_burst_count = 0;
}
if (player->started)
vlc_player_WaitRetryDelay(player);
/* Assign the current date after the wait */
player->last_eos = vlc_tick_now();
if (!player->deleting)
{
vlc_player_OpenNextMedia(player);
/* It is possible to open several medias in a stopped state */
if (player->input && player->started)
vlc_player_input_Start(player->input);
}
if (!player->input)
player->started = false;
send_event = !player->started && last_state != VLC_PLAYER_STATE_STOPPED;
break;
case VLC_PLAYER_STATE_STOPPING:
{
enum vlc_player_media_stopping_reason reason;
/* The order matters. input->error is only set while the
input->started flag is set, so an error or EOS that follows a
user stop request is ignored and the stop is reported as USER,
whereas an error that occurred before a user stop request takes
precedence and is reported as ERROR. */
if (input->error != VLC_PLAYER_ERROR_NONE)
reason = VLC_PLAYER_MEDIA_STOPPING_ERROR;
else if (!input->started)
reason = VLC_PLAYER_MEDIA_STOPPING_USER;
else
reason = VLC_PLAYER_MEDIA_STOPPING_EOS;
input->started = false;
/* Note: no need to hold the media here, as it is already protected
by the player lock. User can hold the input_item, if they want
to use it beyond the callback scope. */
input_item_t *media = input_GetItem(input->thread);
vlc_player_SendEvent(player, on_stopping_current_media,
media, reason);
vlc_player_UpdateTimerEvent(player, NULL,
VLC_PLAYER_TIMER_EVENT_DISCONTINUITY,
VLC_TICK_INVALID);
vlc_player_UpdateTimerEvent(player, NULL,
VLC_PLAYER_TIMER_EVENT_STOPPING,
VLC_TICK_INVALID);
if (input == player->input)
player->input = NULL;
if (player->started && !player->next_media)
player->started = false;
send_event = !player->started;
break;
}
case VLC_PLAYER_STATE_PLAYING:
input->pause_date = VLC_TICK_INVALID;
vlc_player_SignalAtoBLoop(player);
vlc_player_UpdateTimerEvent(player, NULL,
VLC_PLAYER_TIMER_EVENT_PLAYING,
state_date);
/* fall through */
case VLC_PLAYER_STATE_STARTED:
if (player->started &&
player->global_state == VLC_PLAYER_STATE_PLAYING)
send_event = false;
break;
case VLC_PLAYER_STATE_PAUSED:
/* Can pause from PLAYING or STARTED (deferred pause during buffering) */
assert(player->global_state == VLC_PLAYER_STATE_PLAYING
|| player->global_state == VLC_PLAYER_STATE_STARTED);
assert(state_date != VLC_TICK_INVALID);
input->pause_date = state_date;
vlc_player_SignalAtoBLoop(player);
vlc_player_UpdateTimerEvent(player, NULL,
VLC_PLAYER_TIMER_EVENT_PAUSED,
input->pause_date);
break;
default:
vlc_assert_unreachable();
}
if (send_event)
{
assert(player->global_state != input->state);
player->global_state = input->state;
vlc_player_SendEvent(player, on_state_changed, player->global_state);
}
}
static void
vlc_player_input_HandleStateEvent(struct vlc_player_input *input,
input_state_e state, vlc_tick_t state_date)
{
switch (state)
{
case OPENING_S:
vlc_player_input_HandleState(input, VLC_PLAYER_STATE_STARTED,
VLC_TICK_INVALID);
break;
case PLAYING_S:
input->playing = true;
vlc_player_input_HandleState(input, VLC_PLAYER_STATE_PLAYING,
state_date);
break;
case PAUSE_S:
vlc_player_input_HandleState(input, VLC_PLAYER_STATE_PAUSED,
state_date);
break;
case END_S:
input->playing = false;
vlc_player_input_HandleState(input, VLC_PLAYER_STATE_STOPPING,
VLC_TICK_INVALID);
vlc_player_destructor_AddStoppingInput(input->player, input);
break;
case ERROR_S:
/* Don't send errors if the input is stopped by the user */
if (input->started)
{
/* Contrary to the input_thread_t, an error is not a state */
input->error = VLC_PLAYER_ERROR_GENERIC;
vlc_player_SendEvent(input->player, on_error_changed, input->error);
}
/* input->playing monitor the OPENING_S -> PLAYING_S transition.
* If input->playing is false, we have an error at the opening of
* the input thread and we won't reach END_S. */
if (!input->playing)
{
vlc_player_input_HandleState(input, VLC_PLAYER_STATE_STOPPING,
VLC_TICK_INVALID);
vlc_player_destructor_AddStoppingInput(input->player, input);
}
break;
default:
vlc_assert_unreachable();
}
}
static void
vlc_player_input_HandleProgramEvent(struct vlc_player_input *input,
const struct vlc_input_event_program *ev)
{
vlc_player_t *player = input->player;
struct vlc_player_program *prgm;
vlc_player_program_vector *vec = &input->program_vector;
switch (ev->action)
{
case VLC_INPUT_PROGRAM_ADDED:
prgm = vlc_player_program_New(ev->id, ev->title);
if (!prgm)
break;
if (!vlc_vector_push(vec, prgm))
{
vlc_player_program_Delete(prgm);
break;
}
vlc_player_SendEvent(player, on_program_list_changed,
VLC_PLAYER_LIST_ADDED, prgm);
break;
case VLC_INPUT_PROGRAM_DELETED:
{
size_t idx;
prgm = vlc_player_program_vector_FindById(vec, ev->id, &idx);
if (prgm)
{
vlc_player_SendEvent(player, on_program_list_changed,
VLC_PLAYER_LIST_REMOVED, prgm);
vlc_vector_remove(vec, idx);
vlc_player_program_Delete(prgm);
}
break;
}
case VLC_INPUT_PROGRAM_UPDATED:
case VLC_INPUT_PROGRAM_SCRAMBLED:
prgm = vlc_player_program_vector_FindById(vec, ev->id, NULL);
if (!prgm)
break;
if (ev->action == VLC_INPUT_PROGRAM_UPDATED)
{
if (vlc_player_program_Update(prgm, ev->id, ev->title) != 0)
break;
}
else
prgm->scrambled = ev->scrambled;
vlc_player_SendEvent(player, on_program_list_changed,
VLC_PLAYER_LIST_UPDATED, prgm);
break;
case VLC_INPUT_PROGRAM_SELECTED:
{
int unselected_id = -1, selected_id = -1;
vlc_vector_foreach(prgm, vec)
{
if (prgm->group_id == ev->id)
{
if (!prgm->selected)
{
assert(selected_id == -1);
prgm->selected = true;
selected_id = prgm->group_id;
}
}
else
{
if (prgm->selected)
{
assert(unselected_id == -1);
prgm->selected = false;
unselected_id = prgm->group_id;
}
}
}
if (unselected_id != -1 || selected_id != -1)
vlc_player_SendEvent(player, on_program_selection_changed,
unselected_id, selected_id);
break;
}
default:
vlc_assert_unreachable();
}
}
static const struct vlc_player_track_priv *
vlc_player_FindTeletextSource(const struct vlc_player_input *input,
const struct vlc_player_track_priv *exclude,
bool selected)
{
const struct vlc_player_track_priv *t;
vlc_vector_foreach(t, &input->spu_track_vector)
{
if (t->t.fmt.i_codec == VLC_CODEC_TELETEXT &&
t != exclude &&
t->t.selected == selected)
return t;
}
return NULL;
}
static unsigned
vlc_player_input_TeletextUserPage(const struct vlc_player_track_priv *t)
{
const uint8_t mag = t->t.fmt.subs.teletext.i_magazine;
const uint8_t page = t->t.fmt.subs.teletext.i_page;
return (mag % 10) * 100 +
(page & 0x0F) + ((page >> 4) & 0x0F) * 10;
}
static void
vlc_player_input_HandleTeletextMenu(struct vlc_player_input *input,
const struct vlc_input_event_es *ev,
const struct vlc_player_track_priv *trackpriv)
{
vlc_player_t *player = input->player;
if (ev->fmt->i_cat != SPU_ES ||
ev->fmt->i_codec != VLC_CODEC_TELETEXT)
return;
switch (ev->action)
{
case VLC_INPUT_ES_ADDED:
{
if (!input->teletext_source)
{
input->teletext_source = trackpriv;
vlc_player_SendEvent(player, on_teletext_menu_changed, true);
}
break;
}
case VLC_INPUT_ES_DELETED:
{
if (input->teletext_source == trackpriv)
{
input->teletext_source =
vlc_player_FindTeletextSource(input, trackpriv, true);
if (!input->teletext_source)
input->teletext_source =
vlc_player_FindTeletextSource(input, trackpriv, false);
if (!input->teletext_source) /* no more teletext ES */
{
if (input->teletext_enabled)
{
input->teletext_enabled = false;
vlc_player_SendEvent(player, on_teletext_enabled_changed, false);
}
vlc_player_SendEvent(player, on_teletext_menu_changed, false);
}
else /* another teletext ES was reselected */
{
if (input->teletext_source->t.selected != input->teletext_enabled)
{
input->teletext_enabled = input->teletext_source->t.selected;
vlc_player_SendEvent(player, on_teletext_enabled_changed,
input->teletext_source->t.selected);
}
input->teletext_page =
vlc_player_input_TeletextUserPage(input->teletext_source);
vlc_player_SendEvent(player, on_teletext_page_changed,
input->teletext_page);
}
}
break;
}
case VLC_INPUT_ES_UPDATED:
break;
case VLC_INPUT_ES_SELECTED:
{
if (!input->teletext_enabled) /* we stick with the first selected */
{
input->teletext_source = trackpriv;
input->teletext_enabled = true;
input->teletext_page = vlc_player_input_TeletextUserPage(trackpriv);
vlc_player_SendEvent(player, on_teletext_enabled_changed, true);
vlc_player_SendEvent(player, on_teletext_page_changed,
input->teletext_page);
}
break;
}
case VLC_INPUT_ES_UNSELECTED:
if (input->teletext_source == trackpriv)
{
/* If there's another selected teletext, it needs to become source */
const struct vlc_player_track_priv *other =
vlc_player_FindTeletextSource(input, trackpriv, true);
if (other)
{
input->teletext_source = other;
if (!input->teletext_enabled)
{
input->teletext_enabled = true;
vlc_player_SendEvent(player, on_teletext_enabled_changed, true);
}
input->teletext_page = vlc_player_input_TeletextUserPage(other);
vlc_player_SendEvent(player, on_teletext_page_changed,
input->teletext_page);
}
else
{
input->teletext_enabled = false;
vlc_player_SendEvent(player, on_teletext_enabled_changed, false);
}
}
break;
default:
vlc_assert_unreachable();
}
}
static void
vlc_player_input_HandleEsEvent(struct vlc_player_input *input,
const struct vlc_input_event_es *ev)
{
assert(ev->id && ev->title && ev->fmt);
vlc_player_track_vector *vec =
vlc_player_input_GetTrackVector(input, ev->fmt->i_cat);
if (!vec)
return; /* UNKNOWN_ES or DATA_ES not handled */
vlc_player_t *player = input->player;
struct vlc_player_track_priv *trackpriv;
switch (ev->action)
{
case VLC_INPUT_ES_ADDED:
trackpriv = vlc_player_track_priv_New(ev->id, ev->title, ev->fmt);
if (!trackpriv)
break;
if (ev->fmt->i_cat == VIDEO_ES)
input->ml.has_video_tracks = true;
else if (ev->fmt->i_cat == AUDIO_ES)
input->ml.has_audio_tracks = true;
if (!vlc_vector_push(vec, trackpriv))
{
vlc_player_track_priv_Delete(trackpriv);
break;
}
vlc_player_SendEvent(player, on_track_list_changed,
VLC_PLAYER_LIST_ADDED, &trackpriv->t);
vlc_player_input_HandleTeletextMenu(input, ev, trackpriv);
break;
case VLC_INPUT_ES_DELETED:
{
size_t idx;
trackpriv = vlc_player_track_vector_FindById(vec, ev->id, &idx);
if (trackpriv)
{
vlc_player_input_HandleTeletextMenu(input, ev, trackpriv);
vlc_player_SendEvent(player, on_track_list_changed,
VLC_PLAYER_LIST_REMOVED, &trackpriv->t);
vlc_vector_remove(vec, idx);
vlc_player_track_priv_Delete(trackpriv);
}
break;
}
case VLC_INPUT_ES_UPDATED:
trackpriv = vlc_player_track_vector_FindById(vec, ev->id, NULL);
if (!trackpriv)
break;
if (vlc_player_track_priv_Update(trackpriv, ev->title, ev->fmt) != 0)
break;
vlc_player_SendEvent(player, on_track_list_changed,
VLC_PLAYER_LIST_UPDATED, &trackpriv->t);
vlc_player_input_HandleTeletextMenu(input, ev, trackpriv);
break;
case VLC_INPUT_ES_SELECTED:
trackpriv = vlc_player_track_vector_FindById(vec, ev->id, NULL);
if (trackpriv)
{
trackpriv->t.selected = true;
trackpriv->selected_by_user = ev->forced;
trackpriv->vout_order = ev->vout_order;
vlc_player_SendEvent(player, on_track_selection_changed,
NULL, trackpriv->t.es_id);
vlc_player_input_HandleTeletextMenu(input, ev, trackpriv);
}
break;
case VLC_INPUT_ES_UNSELECTED:
trackpriv = vlc_player_track_vector_FindById(vec, ev->id, NULL);
if (trackpriv)
{
vlc_player_RemoveTimerSource(player, ev->id);
trackpriv->t.selected = false;
trackpriv->selected_by_user = false;
vlc_player_SendEvent(player, on_track_selection_changed,
trackpriv->t.es_id, NULL);
vlc_player_input_HandleTeletextMenu(input, ev, trackpriv);
}
break;
default:
vlc_assert_unreachable();
}
}
static void
vlc_player_input_HandleTitleEvent(struct vlc_player_input *input,
const struct vlc_input_event_title *ev)
{
vlc_player_t *player = input->player;
switch (ev->action)
{
case VLC_INPUT_TITLE_NEW_LIST:
{
input_thread_private_t *input_th = input_priv(input->thread);
const int title_offset = input_th->i_title_offset;
const int chapter_offset = input_th->i_seekpoint_offset;
if (input->titles)
vlc_player_title_list_Release(input->titles);
input->title_selected = input->chapter_selected = 0;
input->titles =
vlc_player_title_list_Create(ev->list.array, ev->list.count,
title_offset, chapter_offset);
vlc_player_SendEvent(player, on_titles_changed, input->titles);
if (input->titles)
{
vlc_player_SendEvent(player, on_title_selection_changed,
&input->titles->array[0], 0);
if (input->ml.restore == VLC_RESTOREPOINT_TITLE &&
(size_t)input->ml.states.current_title < ev->list.count)
{
vlc_player_SelectTitleIdx(player, input->ml.states.current_title);
}
input->ml.restore = VLC_RESTOREPOINT_POSITION;
}
else input->ml.restore = VLC_RESTOREPOINT_NONE;
break;
}
case VLC_INPUT_TITLE_SELECTED:
if (!input->titles)
return; /* a previous VLC_INPUT_TITLE_NEW_LIST failed */
assert(ev->selected_idx < input->titles->count);
input->title_selected = ev->selected_idx;
vlc_player_SendEvent(player, on_title_selection_changed,
&input->titles->array[input->title_selected],
input->title_selected);
if (input->ml.restore == VLC_RESTOREPOINT_POSITION &&
input->ml.states.current_title >= 0 &&
(size_t)input->ml.states.current_title == ev->selected_idx &&
input->ml.pos > .0f)
{
input_SetPosition(input->thread, input->ml.pos, false);
}
/* Reset the wanted title to avoid forcing it or the position
* again during the next title change
*/
input->ml.restore = VLC_RESTOREPOINT_NONE;
break;
default:
vlc_assert_unreachable();
}
}
static void
vlc_player_input_HandleChapterEvent(struct vlc_player_input *input,
const struct vlc_input_event_chapter *ev)
{
vlc_player_t *player = input->player;
if (!input->titles || ev->title < 0 || ev->seekpoint < 0)
return; /* a previous VLC_INPUT_TITLE_NEW_LIST failed */
assert((size_t)ev->title < input->titles->count);
const struct vlc_player_title *title = &input->titles->array[ev->title];
if (!title->chapter_count)
return;
assert(ev->seekpoint < (int)title->chapter_count);
input->title_selected = ev->title;
input->chapter_selected = ev->seekpoint;
const struct vlc_player_chapter *chapter = &title->chapters[ev->seekpoint];
vlc_player_SendEvent(player, on_chapter_selection_changed, title, ev->title,
chapter, ev->seekpoint);
}
static void
vlc_player_input_HandleVoutEvent(struct vlc_player_input *input,
const struct vlc_input_event_vout *ev)
{
assert(ev->vout);
assert(ev->id);
vlc_player_t *player = input->player;
struct vlc_player_track_priv *trackpriv =
vlc_player_input_FindTrackById(input, ev->id, NULL);
if (!trackpriv)
return;
const bool is_video_es = trackpriv->t.fmt.i_cat == VIDEO_ES;
switch (ev->action)
{
case VLC_INPUT_EVENT_VOUT_STARTED:
trackpriv->vout = ev->vout;
vlc_player_SendEvent(player, on_vout_changed,
VLC_PLAYER_VOUT_STARTED, ev->vout,
ev->order, ev->id);
if (is_video_es)
{
/* Register vout callbacks after the vout list event */
vlc_player_vout_AddCallbacks(player, ev->vout);
}
break;
case VLC_INPUT_EVENT_VOUT_STOPPED:
if (is_video_es)
{
/* Un-register vout callbacks before the vout list event */
vlc_player_vout_DelCallbacks(player, ev->vout);
}
trackpriv->vout = NULL;
vlc_player_SendEvent(player, on_vout_changed,
VLC_PLAYER_VOUT_STOPPED, ev->vout,
VLC_VOUT_ORDER_NONE, ev->id);
break;
default:
vlc_assert_unreachable();
}
}
static void
vlc_player_input_NavigationFallback(struct vlc_player_input *input, int nav_type)
{
vlc_player_t *player = input->player;
/* Handle Up/Down/Left/Right if the demux can't navigate */
vlc_viewpoint_t vp = { 0 };
bool viewpoint_updated = true;
float yaw = 0.f, pitch = 0.f, roll = 0.f;
int vol_direction = 0;
int seek_direction = 0;
switch (nav_type)
{
case INPUT_CONTROL_NAV_UP:
vol_direction = 1;
pitch = -1.f;
break;
case INPUT_CONTROL_NAV_DOWN:
vol_direction = -1;
pitch = 1.f;
break;
case INPUT_CONTROL_NAV_LEFT:
seek_direction = -1;
yaw = -1.f;
break;
case INPUT_CONTROL_NAV_RIGHT:
seek_direction = 1;
yaw = 1.f;
break;
case INPUT_CONTROL_NAV_ACTIVATE:
case INPUT_CONTROL_NAV_POPUP:
case INPUT_CONTROL_NAV_MENU:
viewpoint_updated = false;
return;
default:
vlc_assert_unreachable();
}
/* Try to change the viewpoint if possible */
bool viewpoint_ch = false;
size_t vout_count;
vout_thread_t **vouts = vlc_player_vout_HoldAll(input->player, &vout_count);
for (size_t i = 0; i < vout_count; ++i)
{
if (!viewpoint_ch && var_GetBool(vouts[i], "viewpoint-changeable"))
viewpoint_ch = true;
vout_Release(vouts[i]);
}
free(vouts);
if (viewpoint_ch && viewpoint_updated)
{
vlc_viewpoint_from_euler(&vp, yaw, pitch, roll);
vlc_player_input_UpdateViewpoint(input, &vp, VLC_PLAYER_WHENCE_RELATIVE);
}
else if (seek_direction != 0)
{
/* Seek or change volume if the input doesn't have navigation or viewpoint */
vlc_tick_t it = vlc_tick_from_sec(seek_direction
* var_InheritInteger(player, "short-jump-size"));
vlc_player_input_SeekByTime(input, it, VLC_PLAYER_SEEK_PRECISE,
VLC_PLAYER_WHENCE_RELATIVE);
}
else
{
assert(vol_direction != 0);
if (input == player->input)
vlc_player_aout_IncrementVolume(player, vol_direction, NULL);
}
}
static void
vlc_player_input_MouseFallback(struct vlc_player_input *input)
{
vlc_player_t *player = input->player;
vlc_player_TogglePause(player);
}
static void
vlc_player_DisplayFrameError(vlc_player_t *player,
const char *title, const char *title_error,
int status)
{
static const char erange[] = {N_("can't seek back")};
static const char ebusy[] = {N_("no video found")};
static const char enotusp[] = {N_("can't pause/seek/pace")};
static const char einval[] = {N_("invalid state")};
switch (status)
{
case 0:
vlc_player_osd_Message(player, title);
break;
case -EAGAIN:
break;
case -ERANGE:
vlc_dialog_display_error(player, title_error, erange);
break;
case -EBUSY:
vlc_dialog_display_error(player, title_error, ebusy);
break;
case -ENOTSUP:
vlc_dialog_display_error(player, title_error, enotusp);
break;
default:
case -EINVAL:
vlc_dialog_display_error(player, title_error, einval);
break;
}
}
static void
vlc_player_input_FrameNextStatus(struct vlc_player_input *input, int status)
{
vlc_player_t *player = input->player;
unsigned count;
vlc_player_SendEventCount(player, on_next_frame_status, count, status);
/* Don't display errors if status is handled by the player user */
if (count != 0)
return;
vlc_player_DisplayFrameError(player, _("Next frame"),
_("Next frame error"), status);
}
static void
vlc_player_input_FramePreviousStatus(struct vlc_player_input *input, int status)
{
vlc_player_t *player = input->player;
unsigned count;
vlc_player_SendEventCount(player, on_prev_frame_status, count, status);
/* Don't display errors if status is handled by the player user */
if (count != 0)
return;
vlc_player_DisplayFrameError(player, _("Previous frame"),
_("Previous frame error"), status);
}
static bool
input_thread_Events(input_thread_t *input_thread,
const struct vlc_input_event *event, void *user_data)
{
struct vlc_player_input *input = user_data;
vlc_player_t *player = input->player;
input_thread_private_t *priv = input_priv(input_thread);
assert(input_thread == input->thread);
/* No player lock for this event */
if (event->type == INPUT_EVENT_OUTPUT_CLOCK)
{
if (event->output_clock.system_ts != VLC_TICK_INVALID)
{
const struct vlc_player_timer_point point = {
.position = 0,
.rate = event->output_clock.rate,
.ts = event->output_clock.ts,
.length = VLC_TICK_INVALID,
.system_date = event->output_clock.system_ts,
};
vlc_player_UpdateTimer(player, event->output_clock.id,
event->output_clock.master, &point,
VLC_TICK_INVALID,
event->output_clock.frame_rate,
event->output_clock.frame_rate_base, 0);
}
else
{
vlc_player_UpdateTimerEvent(player, event->output_clock.id,
VLC_PLAYER_TIMER_EVENT_DISCONTINUITY,
VLC_TICK_INVALID);
}
return true;
}
bool handled = true;
vlc_mutex_lock(&player->lock);
switch (event->type)
{
case INPUT_EVENT_STATE:
vlc_player_input_HandleStateEvent(input, event->state.value,
event->state.date);
break;
case INPUT_EVENT_EOF:
handled = vlc_player_input_HandleAtoBLoop(input, true);
if (handled)
break;
if (player->play_and_pause)
{
vlc_player_Pause(player);
handled = true;
}
else if (input->repeat > 0)
{
input->repeat--;
handled =
input_ControlPush(input->thread,
INPUT_CONTROL_RESET_POSITION, NULL) == 0;
}
else
handled = false;
break;
case INPUT_EVENT_RATE:
input->rate = event->rate;
vlc_player_SignalAtoBLoop(player);
vlc_player_SendEvent(player, on_rate_changed, input->rate);
break;
case INPUT_EVENT_CAPABILITIES:
{
int old_caps = input->capabilities;
input->capabilities = event->capabilities;
vlc_player_SendEvent(player, on_capabilities_changed,
old_caps, input->capabilities);
break;
}
case INPUT_EVENT_TIMES:
{
bool changed = false;
vlc_tick_t system_date = VLC_TICK_INVALID;
vlc_tick_t duration = input_GetItemDuration(input->thread, event->times.length);
if (event->times.time != VLC_TICK_INVALID
&& (input->time != event->times.time
|| input->position != event->times.position))
{
input->time = event->times.time;
input->position = event->times.position;
system_date = vlc_tick_now();
changed = true;
vlc_player_SendEvent(player, on_position_changed,
input->time, input->position);
vlc_player_input_HandleAtoBLoop(input, false);
}
if (input->length != duration)
{
input->length = duration;
input_item_SetDuration(input_GetItem(input->thread), duration);
vlc_player_SendEvent(player, on_length_changed, input->length);
changed = true;
}
if (input->live != event->times.live)
{
input->live = event->times.live;
changed = true;
}
if (input->normal_time != event->times.normal_time)
{
input->normal_time = event->times.normal_time;
changed = true;
}
if (changed)
{
const struct vlc_player_timer_point point = {
.position = input->position,
.rate = input->rate,
.ts = input->time,
.length = input->length,
.live = input->live,
.system_date = system_date,
};
vlc_player_UpdateTimer(player, NULL, false, &point,
input->normal_time, 0, 0, priv->i_start);
vlc_player_SignalAtoBLoop(player);
}
break;
}
case INPUT_EVENT_PROGRAM:
vlc_player_input_HandleProgramEvent(input, &event->program);
break;
case INPUT_EVENT_ES:
vlc_player_input_HandleEsEvent(input, &event->es);
break;
case INPUT_EVENT_TITLE:
vlc_player_input_HandleTitleEvent(input, &event->title);
break;
case INPUT_EVENT_CHAPTER:
vlc_player_input_HandleChapterEvent(input, &event->chapter);
break;
case INPUT_EVENT_RECORD:
input->recording = event->record;
vlc_player_SendEvent(player, on_recording_changed, input->recording);
break;
case INPUT_EVENT_STATISTICS:
input->stats = *event->stats;
vlc_player_SendEvent(player, on_statistics_changed, &input->stats);
break;
case INPUT_EVENT_SIGNAL:
input->signal_quality = event->signal.quality;
input->signal_strength = event->signal.strength;
vlc_player_SendEvent(player, on_signal_changed,
input->signal_quality, input->signal_strength);
break;
case INPUT_EVENT_CACHE:
if (event->cache == 0.0f)
vlc_player_UpdateTimerEvent(player, NULL,
VLC_PLAYER_TIMER_EVENT_DISCONTINUITY,
VLC_TICK_INVALID);
input->cache = event->cache;
vlc_player_SendEvent(player, on_buffering_changed, event->cache);
break;
case INPUT_EVENT_VOUT:
vlc_player_input_HandleVoutEvent(input, &event->vout);
break;
case INPUT_EVENT_OUTPUT_STATE:
if (event->output_state.action == VLC_INPUT_EVENT_OUTPUT_STATE_PAUSED)
vlc_player_UpdateTimerEvent(player, event->output_state.id,
VLC_PLAYER_TIMER_EVENT_PAUSED,
event->output_state.paused_date);
break;
case INPUT_EVENT_ITEM_META:
case INPUT_EVENT_ITEM_INFO:
vlc_player_SendEvent(player, on_media_meta_changed,
input_GetItem(input->thread));
break;
case INPUT_EVENT_ITEM_EPG:
vlc_player_SendEvent(player, on_media_epg_changed,
input_GetItem(input->thread));
break;
case INPUT_EVENT_SUBITEMS:
vlc_player_SendEvent(player, on_media_subitems_changed,
input_GetItem(input->thread), event->subitems);
input_item_node_Delete(event->subitems);
break;
case INPUT_EVENT_DEAD:
vlc_player_destructor_AddJoinableInput(player, input);
break;
case INPUT_EVENT_VBI_PAGE:
input->teletext_page = event->vbi_page < 999 ? event->vbi_page : 100;
vlc_player_SendEvent(player, on_teletext_page_changed,
input->teletext_page);
break;
case INPUT_EVENT_VBI_TRANSPARENCY:
input->teletext_transparent = event->vbi_transparent;
vlc_player_SendEvent(player, on_teletext_transparency_changed,
input->teletext_transparent);
break;
case INPUT_EVENT_ATTACHMENTS:
vlc_player_SendEvent(player, on_media_attachments_added,
input_GetItem(input->thread),
event->attachments.array,
event->attachments.count);
break;
case INPUT_EVENT_NAV_FAILED:
vlc_player_input_NavigationFallback(input, event->nav_type);
break;
case INPUT_EVENT_MOUSE_LEFT:
vlc_player_input_MouseFallback(input);
break;
case INPUT_EVENT_FRAME_NEXT_STATUS:
vlc_player_input_FrameNextStatus(input,
event->frame_next_status);
break;
case INPUT_EVENT_FRAME_PREVIOUS_STATUS:
vlc_player_input_FramePreviousStatus(input,
event->frame_previous_status);
break;
default:
handled = false;
break;
}
vlc_mutex_unlock(&player->lock);
return handled;
}
void
vlc_player_input_SelectTracksByStringIds(struct vlc_player_input *input,
enum es_format_category_e cat,
const char *str_ids)
{
input_SetEsCatIds(input->thread, cat, str_ids);
}
char *
vlc_player_input_GetSelectedTrackStringIds(struct vlc_player_input *input,
enum es_format_category_e cat)
{
vlc_player_track_vector *vec = vlc_player_input_GetTrackVector(input, cat);
assert(vec);
bool first_track = true;
struct vlc_memstream ms;
struct vlc_player_track_priv* t;
vlc_vector_foreach(t, vec)
{
if (t->selected_by_user && vlc_es_id_IsStrIdStable(t->t.es_id))
{
if (first_track)
{
int ret = vlc_memstream_open(&ms);
if (ret != 0)
return NULL;
}
const char *str_id = vlc_es_id_GetStrId(t->t.es_id);
assert(str_id);
if (!first_track)
vlc_memstream_putc(&ms, ',');
vlc_memstream_puts(&ms, str_id);
first_track = false;
}
}
return !first_track && vlc_memstream_close(&ms) == 0 ? ms.ptr : NULL;
}
struct vlc_player_input *
vlc_player_input_New(vlc_player_t *player, input_item_t *item)
{
struct vlc_player_input *input = malloc(sizeof(*input));
if (!input)
return NULL;
input->player = player;
input->started = false;
input->playing = false;
input->state = VLC_PLAYER_STATE_STOPPED;
input->error = VLC_PLAYER_ERROR_NONE;
input->rate = 1.f;
input->capabilities = 0;
input->length = input->time = VLC_TICK_INVALID;
input->live = false;
input->normal_time = VLC_TICK_0;
input->pause_date = VLC_TICK_INVALID;
input->position = 0.f;
input->recording = false;
input->cache = 0.f;
input->signal_quality = input->signal_strength = -1.f;
memset(&input->stats, 0, sizeof(input->stats));
vlc_vector_init(&input->program_vector);
vlc_vector_init(&input->video_track_vector);
vlc_vector_init(&input->audio_track_vector);
vlc_vector_init(&input->spu_track_vector);
input->teletext_source = NULL;
input->titles = NULL;
input->title_selected = input->chapter_selected = 0;
input->teletext_enabled = input->teletext_transparent = false;
input->teletext_page = 0;
input->abloop_state[0].set = input->abloop_state[1].set = false;
memset(&input->ml.states, 0, sizeof(input->ml.states));
input->ml.states.aspect_ratio = input->ml.states.crop =
input->ml.states.deinterlace = input->ml.states.video_filter = NULL;
input->ml.states.current_title = -1;
input->ml.states.current_video_track =
input->ml.states.current_audio_track =
input->ml.states.current_subtitle_track = NULL;
input->ml.restore = VLC_RESTOREPOINT_NONE;
input->ml.restore_states = false;
input->ml.delay_restore = false;
input->ml.pos = -1.f;
input->ml.has_audio_tracks = input->ml.has_video_tracks = false;
static const struct vlc_input_thread_callbacks cbs = {
.on_event = input_thread_Events,
};
const struct vlc_input_thread_cfg cfg = {
.type = INPUT_TYPE_PLAYBACK,
.hw_dec = INPUT_CFG_HW_DEC_DEFAULT,
.resource = player->resource,
.renderer = player->renderer,
.cbs = &cbs,
.cbs_data = input,
};
input->repeat = player->repeat;
input->thread = input_Create(player, item, &cfg);
if (!input->thread)
{
free(input);
return NULL;
}
vlc_player_input_RestoreMlStates(input, false);
if (player->video_string_ids)
vlc_player_input_SelectTracksByStringIds(input, VIDEO_ES,
player->video_string_ids);
if (player->audio_string_ids)
vlc_player_input_SelectTracksByStringIds(input, AUDIO_ES,
player->audio_string_ids);
if (player->sub_string_ids)
vlc_player_input_SelectTracksByStringIds(input, SPU_ES,
player->sub_string_ids);
/* Initial sub/audio delay */
const vlc_tick_t cat_delays[DATA_ES] = {
[AUDIO_ES] =
VLC_TICK_FROM_MS(var_InheritInteger(player, "audio-desync")),
[SPU_ES] =
vlc_tick_from_samples(var_InheritInteger(player, "sub-delay"), 10),
};
for (enum es_format_category_e i = UNKNOWN_ES; i < DATA_ES; ++i)
{
input->cat_delays[i] = cat_delays[i];
if (cat_delays[i] != 0)
{
int ret = input_SetEsCatDelay(input->thread, i, cat_delays[i]);
if (ret == VLC_SUCCESS)
vlc_player_SendEvent(player, on_category_delay_changed, i,
cat_delays[i]);
}
}
return input;
}
void
vlc_player_input_Delete(struct vlc_player_input *input)
{
assert(input->titles == NULL);
assert(input->program_vector.size == 0);
assert(input->video_track_vector.size == 0);
assert(input->audio_track_vector.size == 0);
assert(input->spu_track_vector.size == 0);
assert(input->teletext_source == NULL);
vlc_vector_destroy(&input->program_vector);
vlc_vector_destroy(&input->video_track_vector);
vlc_vector_destroy(&input->audio_track_vector);
vlc_vector_destroy(&input->spu_track_vector);
input_Close(input->thread);
free(input);
}
¤ Dauer der Verarbeitung: 0.11 Sekunden
(vorverarbeitet am 2026-11-04)
¤
*© Formatika GbR, Deutschland
|
|