[vlc-devel] [PATCH V2 08/13] player: add the timer API

Thomas Guillem thomas at gllm.fr
Fri Aug 23 16:45:13 CEST 2019


Any interface or control modules could request a timer from the player. This
player timer is like the player event listener except that:

 - It is only used to receive time update

 - The timer is not locked by the player lock. Indeed the player lock can be
   too "slow" (it can be recursive, it is used by the playlist, and is it held
   when sending all events). So it's not a good idea to hold this lock for
   every frame/sample updates.

 - The minimum delay between each updates can be configured: it avoids to flood
   the UI when playing a media file with very high fps or very low audio sample
   size.

The time updated is the output time, unlike the on_position_changed event that
use the input time. It can fixes a very big delay between the UI time widgets
and the outputted content (depending on the audio output module, this delay
could be close to 2seconds).

The vlc_player_timer_value struct is used by timer update callbacks. This
public struct hold all the informations to interpolate a time at a given date.
It could be done with the vlc_player_timer_value_Interpolate() helper. In this
way, it is now possible to get the last player time without holding any locks.

There are two timer types:

 - Source: update are sent only when a frame or a sample is outputted. Users of
   this timer should take into account that the delay between each updates is
   not regular and can be up to 1seconds (depending of the input). In that
   case, they should use their own timer (from their mainloop) and use
   vlc_player_timer_value_Interpolate() to get the last time.

 - Regular: The player will spawn a vlc timer (likely an other thread) that
   will send regular time updates. It can be used by control/interfaces that
   don't have any mainloop.
---
 include/vlc_player.h | 134 ++++++++++++++++++++++++
 src/Makefile.am      |   1 +
 src/libvlccore.sym   |   3 +
 src/player/input.c   |  55 +++++++++-
 src/player/player.c  |  10 ++
 src/player/player.h  |  41 ++++++++
 src/player/timer.c   | 240 +++++++++++++++++++++++++++++++++++++++++++
 7 files changed, 483 insertions(+), 1 deletion(-)
 create mode 100644 src/player/timer.c

diff --git a/include/vlc_player.h b/include/vlc_player.h
index 401eafd047..201f6407ba 100644
--- a/include/vlc_player.h
+++ b/include/vlc_player.h
@@ -3055,6 +3055,140 @@ vlc_player_RemoveListener(vlc_player_t *player,
 
 /** @} vlc_player__events */
 
+/**
+ * @defgroup vlc_player__timer Player timer
+ * @{
+ */
+
+/**
+ * Player timer opaque structure.
+ */
+typedef struct vlc_player_timer_id vlc_player_timer_id;
+
+/*
+ * Player timer creation type
+ *
+ * @see vlc_player_AddTimer()
+ */
+enum vlc_player_timer_type
+{
+    /**
+     * Get notified only when the time is updated by the input or output
+     * source. The input source is the 'demux' or the 'access_demux'. The
+     * output source are audio and video outputs: an update is received each
+     * time a video frame is displayed or an audio sample is written. The delay
+     * between each updates may depend on the input and source type (it can be
+     * every 5ms, 30ms, 1s or 10s...). The user of this timer may need to
+     * update the position at a higher frequency from its own mainloop via
+     * vlc_player_timer_value_Interpolate().
+     */
+    VLC_PLAYER_TIMER_TYPE_SOURCE,
+};
+
+/**
+ * Player timer state
+ *
+ * @see vlc_player_timer_cbs.on_update
+ */
+enum vlc_player_timer_state
+{
+    /* Normal state, the player is playing */
+    VLC_PLAYER_TIMER_STATE_PLAYING,
+    /* The player is paused */
+    VLC_PLAYER_TIMER_STATE_PAUSED,
+    /* A discontinuity occurred, likely caused by seek from the user. */
+    VLC_PLAYER_TIMER_STATE_DISCONTINUITY,
+};
+
+/**
+ * Player timer value
+ *
+ * @see vlc_player_timer_cbs.on_update
+ */
+struct vlc_player_timer_value
+{
+    /** Position in the range [0.0f;1.0] */
+    float position;
+    /** Rate of the player */
+    double rate;
+    /** Valid time > 0 or VLC_TICK_INVALID */
+    vlc_tick_t ts;
+    /** Valid length > 0 or VLC_TICK_INVALID */
+    vlc_tick_t length;
+    /** System date of this record (always valid), this date can be in the
+     * future. */
+    vlc_tick_t system_date;
+};
+
+/**
+ * Player timer callbacks
+ *
+ * @see vlc_player_AddTimer
+ */
+struct vlc_player_timer_cbs
+{
+    /**
+     * Called when the state or the time changed.
+     *
+     * @warning The player is not locked from this callback. It is forbidden
+     * to call any player functions from here.
+     *
+     * @param timer the timer created by vlc_player_AddTimer()
+     * @param state PLAYING, PAUSED or DISCONTINUITY
+     * @param value always valid, the time corresponding to the state
+     * @param data opaque pointer set by vlc_player_AddTimer()
+     */
+    void (*on_update)(enum vlc_player_timer_state state,
+                      const struct vlc_player_timer_value *value, void *data);
+};
+
+/**
+ * Add a timer in order to get times updates
+ *
+ * @param player player instance (locked or not)
+ * @param type SOURCE, see vlc_player_timer_type
+ * @param min_period corresponds to the minimum period between each updates,
+ * use it to avoid flood from too many source updates, set it to
+ * VLC_TICK_INVALID to receive all updates.
+ * @param cbs pointer to a vlc_player_timer_cbs structure, the structure must
+ * be valid during the lifetime of the player
+ * @param cbs_data opaque pointer used by the callbacks
+ * @return a valid vlc_player_timer_id or NULL in case of memory allocation
+ * error
+ */
+VLC_API vlc_player_timer_id *
+vlc_player_AddTimer(vlc_player_t *player, enum vlc_player_timer_type type,
+                    vlc_tick_t min_period,
+                    const struct vlc_player_timer_cbs *cbs, void *data);
+
+/**
+ * Remove a player timer
+ *
+ * @param player player instance (locked or not)
+ * @param timer timer created by vlc_player_AddTimer()
+ */
+VLC_API void
+vlc_player_RemoveTimer(vlc_player_t *player, vlc_player_timer_id *timer);
+
+/**
+ * Interpolate the last timer value to now
+ *
+ * @param value time update obtained via the vlc_player_timer_cbs.on_update()
+ * callback
+ * @param system_now current system date
+ * @param player_rate rate of the player
+ * @param out_ts pointer where to set the interpolated ts
+ * @param out_pos pointer where to set the interpolated position
+ * @return VLC_SUCCESS in case of success, an error in the interpolated ts is
+ * negative (could happen during the buffering step)
+ */
+VLC_API int
+vlc_player_timer_value_Interpolate(const struct vlc_player_timer_value *value,
+                                   vlc_tick_t system_now,
+                                   vlc_tick_t *out_ts, float *out_pos);
+
+/** @} vlc_player__timer */
+
 /** @} vlc_player */
 
 #endif
diff --git a/src/Makefile.am b/src/Makefile.am
index 9f0b6df13a..e91fff6a3f 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -265,6 +265,7 @@ libvlccore_la_SOURCES = \
 	player/player.c \
 	player/player.h \
 	player/input.c \
+	player/timer.c \
 	player/track.c \
 	player/title.c \
 	player/aout.c \
diff --git a/src/libvlccore.sym b/src/libvlccore.sym
index f278fe6e9d..8e266b41df 100644
--- a/src/libvlccore.sym
+++ b/src/libvlccore.sym
@@ -766,6 +766,7 @@ vlc_thumbnailer_Cancel
 vlc_thumbnailer_Release
 vlc_player_AddAssociatedMedia
 vlc_player_AddListener
+vlc_player_AddTimer
 vlc_player_aout_AddListener
 vlc_player_aout_EnableFilter
 vlc_player_aout_GetVolume
@@ -824,6 +825,7 @@ vlc_player_Pause
 vlc_player_program_Delete
 vlc_player_program_Dup
 vlc_player_RemoveListener
+vlc_player_RemoveTimer
 vlc_player_RestartEsId
 vlc_player_Resume
 vlc_player_SeekByPos
@@ -860,6 +862,7 @@ vlc_player_SetTeletextTransparency
 vlc_player_SetTrackCategoryEnabled
 vlc_player_Start
 vlc_player_Stop
+vlc_player_timer_value_Interpolate
 vlc_player_title_list_GetAt
 vlc_player_title_list_GetCount
 vlc_player_title_list_Hold
diff --git a/src/player/input.c b/src/player/input.c
index d63b2ff59c..4ffe59522c 100644
--- a/src/player/input.c
+++ b/src/player/input.c
@@ -60,12 +60,24 @@ vlc_player_input_HandleAtoBLoop(struct vlc_player_input *input, vlc_tick_t time,
 vlc_tick_t
 vlc_player_input_GetTime(struct vlc_player_input *input)
 {
+    vlc_player_t *player = input->player;
+    vlc_tick_t ts;
+
+    if (input == player->input
+     && vlc_player_GetTimerPoint(player, vlc_tick_now(), &ts, NULL) == 0)
+        return ts;
     return input->time;
 }
 
 float
 vlc_player_input_GetPos(struct vlc_player_input *input)
 {
+    vlc_player_t *player = input->player;
+    float pos;
+
+    if (input == player->input
+     && vlc_player_GetTimerPoint(player, vlc_tick_now(), NULL, &pos) == 0)
+        return pos;
     return input->position;
 }
 
@@ -172,6 +184,11 @@ vlc_player_input_HandleState(struct vlc_player_input *input,
             break;
         case VLC_PLAYER_STATE_STOPPING:
             input->started = false;
+
+            vlc_player_UpdateTimerSource(player, NULL, false, -1, -1,
+                                         VLC_TICK_INVALID, VLC_TICK_INVALID,
+                                         VLC_TICK_INVALID);
+
             if (input == player->input)
                 player->input = NULL;
 
@@ -183,8 +200,17 @@ vlc_player_input_HandleState(struct vlc_player_input *input,
             }
             send_event = !player->started;
             break;
-        case VLC_PLAYER_STATE_STARTED:
         case VLC_PLAYER_STATE_PLAYING:
+            assert(state_date != VLC_TICK_INVALID);
+            if (input->pause_date != VLC_TICK_INVALID)
+            {
+                vlc_player_UpdateTimerState(player,
+                                            VLC_PLAYER_TIMER_STATE_PLAYING,
+                                            input->pause_date, state_date);
+                input->pause_date = VLC_TICK_INVALID;
+            }
+            /* fallthrough */
+        case VLC_PLAYER_STATE_STARTED:
             if (player->started &&
                 player->global_state == VLC_PLAYER_STATE_PLAYING)
                 send_event = false;
@@ -192,6 +218,11 @@ vlc_player_input_HandleState(struct vlc_player_input *input,
 
         case VLC_PLAYER_STATE_PAUSED:
             assert(player->started && input->started);
+            assert(state_date != VLC_TICK_INVALID);
+            input->pause_date = state_date;
+
+            vlc_player_UpdateTimerState(player, VLC_PLAYER_TIMER_STATE_PAUSED,
+                                        input->pause_date, state_date);
             break;
         default:
             vlc_assert_unreachable();
@@ -573,6 +604,17 @@ input_thread_Events(input_thread_t *input_thread,
 
     assert(input_thread == input->thread);
 
+    /* No player lock for this event */
+    if (event->type == INPUT_EVENT_OUTPUT_CLOCK)
+    {
+        vlc_player_UpdateTimerSource(player, event->output_clock.id,
+                                     event->output_clock.master, -1,
+                                     event->output_clock.rate,
+                                     event->output_clock.ts, VLC_TICK_INVALID,
+                                     event->output_clock.system_ts);
+        return;
+    }
+
     vlc_mutex_lock(&player->lock);
 
     switch (event->type)
@@ -594,12 +636,15 @@ input_thread_Events(input_thread_t *input_thread,
             break;
         }
         case INPUT_EVENT_TIMES:
+        {
+            bool changed = false;
             if (event->times.ms != VLC_TICK_INVALID
              && (input->time != event->times.ms
               || input->position != event->times.percentage))
             {
                 input->time = event->times.ms;
                 input->position = event->times.percentage;
+                changed = true;
                 vlc_player_SendEvent(player, on_position_changed,
                                      input->time, input->position);
 
@@ -609,8 +654,15 @@ input_thread_Events(input_thread_t *input_thread,
             {
                 input->length = event->times.length;
                 vlc_player_SendEvent(player, on_length_changed, input->length);
+                changed = true;
             }
+            if (changed)
+                vlc_player_UpdateTimerSource(player, NULL, false,
+                                             input->position, input->rate,
+                                             input->time, input->length,
+                                             vlc_tick_now());
             break;
+        }
         case INPUT_EVENT_PROGRAM:
             vlc_player_input_HandleProgramEvent(input, &event->program);
             break;
@@ -694,6 +746,7 @@ vlc_player_input_New(vlc_player_t *player, input_item_t *item)
     input->rate = 1.f;
     input->capabilities = 0;
     input->length = input->time = VLC_TICK_INVALID;
+    input->pause_date = VLC_TICK_INVALID;
     input->position = 0.f;
 
     input->recording = false;
diff --git a/src/player/player.c b/src/player/player.c
index b23b7ae212..b8d2963a5f 100644
--- a/src/player/player.c
+++ b/src/player/player.c
@@ -893,6 +893,10 @@ vlc_player_SelectPrevChapter(vlc_player_t *player)
 void
 vlc_player_Lock(vlc_player_t *player)
 {
+    /* The timer lock should not be held (possible lock-order-inversion), cf.
+     * vlc_player_timer_cbs.on_update documentation */
+    assert(!vlc_mutex_marked(&player->timer_lock));
+
     vlc_mutex_lock(&player->lock);
 }
 
@@ -1788,6 +1792,7 @@ vlc_player_InitLocks(vlc_player_t *player, enum vlc_player_lock_type lock_type)
     else
         vlc_mutex_init(&player->lock);
 
+    vlc_mutex_init(&player->timer_lock);
     vlc_mutex_init(&player->vout_listeners_lock);
     vlc_mutex_init(&player->aout_listeners_lock);
     vlc_cond_init(&player->start_delay_cond);
@@ -1798,6 +1803,7 @@ static void
 vlc_player_DestroyLocks(vlc_player_t *player)
 {
     vlc_mutex_destroy(&player->lock);
+    vlc_mutex_destroy(&player->timer_lock);
     vlc_mutex_destroy(&player->vout_listeners_lock);
     vlc_mutex_destroy(&player->aout_listeners_lock);
     vlc_cond_destroy(&player->start_delay_cond);
@@ -1818,6 +1824,7 @@ vlc_player_Delete(vlc_player_t *player)
     assert(vlc_list_is_empty(&player->listeners));
     assert(vlc_list_is_empty(&player->vout_listeners));
     assert(vlc_list_is_empty(&player->aout_listeners));
+    assert(vlc_list_is_empty(&player->source_timers));
 
     vlc_mutex_unlock(&player->lock);
 
@@ -1855,6 +1862,7 @@ vlc_player_New(vlc_object_t *parent, enum vlc_player_lock_type lock_type,
     vlc_list_init(&player->listeners);
     vlc_list_init(&player->vout_listeners);
     vlc_list_init(&player->aout_listeners);
+    vlc_list_init(&player->source_timers);
     vlc_list_init(&player->destructor.inputs);
     vlc_list_init(&player->destructor.stopping_inputs);
     vlc_list_init(&player->destructor.joinable_inputs);
@@ -1876,6 +1884,8 @@ vlc_player_New(vlc_object_t *parent, enum vlc_player_lock_type lock_type,
     player->next_media_requested = false;
     player->next_media = NULL;
 
+    player->timer_value.system_date = VLC_TICK_INVALID;
+
 #define VAR_CREATE(var, flag) do { \
     if (var_Create(player, var, flag) != VLC_SUCCESS) \
         goto error; \
diff --git a/src/player/player.h b/src/player/player.h
index 576bc57110..46a50b2526 100644
--- a/src/player/player.h
+++ b/src/player/player.h
@@ -65,6 +65,8 @@ struct vlc_player_input
     float position;
     vlc_tick_t time;
 
+    vlc_tick_t pause_date;
+
     bool recording;
 
     float signal_quality;
@@ -107,6 +109,18 @@ struct vlc_player_listener_id
     struct vlc_list node;
 };
 
+struct vlc_player_timer_id
+{
+    enum vlc_player_timer_type type;
+    vlc_tick_t period;
+    vlc_tick_t last_update_date;
+
+    const struct vlc_player_timer_cbs *cbs;
+    void *data;
+
+    struct vlc_list node;
+};
+
 struct vlc_player_vout_listener_id
 {
     const struct vlc_player_vout_cbs *cbs;
@@ -125,6 +139,7 @@ struct vlc_player_t
 {
     struct vlc_object_t obj;
     vlc_mutex_t lock;
+    vlc_mutex_t timer_lock;
     vlc_mutex_t aout_listeners_lock;
     vlc_mutex_t vout_listeners_lock;
     vlc_cond_t start_delay_cond;
@@ -142,6 +157,10 @@ struct vlc_player_t
     struct vlc_list aout_listeners;
     struct vlc_list vout_listeners;
 
+    struct vlc_list source_timers;
+    void *timer_source;
+    struct vlc_player_timer_value timer_value;
+
     input_resource_t *resource;
     vlc_renderer_item_t *renderer;
 
@@ -312,6 +331,28 @@ void
 vlc_player_input_HandleState(struct vlc_player_input *, enum vlc_player_state,
                              vlc_tick_t state_date);
 
+struct vlc_player_timer_value
+vlc_player_input_GetTimerValue(struct vlc_player_input *input);
+
+/*
+ * player_timer.c
+*/
+
+void
+vlc_player_UpdateTimerState(vlc_player_t *player,
+                            enum vlc_player_timer_state state,
+                            vlc_tick_t interpolate_date, vlc_tick_t system_date);
+
+void
+vlc_player_UpdateTimerSource(vlc_player_t *player, void *source,
+                             bool source_is_master, float position, double rate,
+                             vlc_tick_t ts, vlc_tick_t length,
+                             vlc_tick_t system_date);
+
+int
+vlc_player_GetTimerPoint(vlc_player_t *player, vlc_tick_t system_now,
+                         vlc_tick_t *out_ts, float *out_pos);
+
 /*
  * player_vout.c
  */
diff --git a/src/player/timer.c b/src/player/timer.c
new file mode 100644
index 0000000000..198bf7db64
--- /dev/null
+++ b/src/player/timer.c
@@ -0,0 +1,240 @@
+/*****************************************************************************
+ * player.c: Player interface
+ *****************************************************************************
+ * Copyright © 2018 VLC authors and VideoLAN
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2.1 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
+ *****************************************************************************/
+
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif
+
+#include <limits.h>
+
+#include "player.h"
+
+static void
+vlc_player_UpdateTimersLocked(vlc_player_t *player, bool force_update,
+                              enum vlc_player_timer_state state,
+                              const struct vlc_player_timer_value *value)
+{
+    vlc_player_timer_id *timer;
+
+    vlc_list_foreach(timer, &player->source_timers, node)
+    {
+        /* Respect refresh delay of the timer */
+        if (!force_update && timer->period != VLC_TICK_INVALID
+         && timer->last_update_date != VLC_TICK_INVALID
+         && value->system_date - timer->last_update_date < timer->period)
+            continue;
+        timer->cbs->on_update(state, value, timer->data);
+        timer->last_update_date = value->system_date;
+    }
+}
+
+static void
+vlc_player_UpdateTimerStateLocked(vlc_player_t *player,
+                                  enum vlc_player_timer_state state,
+                                  vlc_tick_t interpolate_date,
+                                  vlc_tick_t system_date)
+{
+    assert(interpolate_date != VLC_TICK_INVALID
+        && system_date != VLC_TICK_INVALID);
+
+    vlc_tick_t new_ts = VLC_TICK_0; /* Fallback to 0 in case of failure */
+    float new_pos = 0;
+    vlc_player_timer_value_Interpolate(&player->timer_value, interpolate_date,
+                                       &new_ts, &new_pos);
+
+    const struct vlc_player_timer_value value =
+    {
+        .position = new_pos,
+        .rate = player->timer_value.rate,
+        .ts = new_ts,
+        .length = player->timer_value.length,
+        .system_date = system_date,
+    };
+
+    vlc_player_UpdateTimersLocked(player, true, state, &value);
+}
+
+void
+vlc_player_UpdateTimerState(vlc_player_t *player,
+                            enum vlc_player_timer_state state,
+                            vlc_tick_t pause_date, vlc_tick_t system_date)
+{
+    vlc_mutex_lock(&player->timer_lock);
+    vlc_player_UpdateTimerStateLocked(player, state, pause_date, system_date);
+    vlc_mutex_unlock(&player->timer_lock);
+}
+
+void
+vlc_player_UpdateTimerSource(vlc_player_t *player, void *source,
+                             bool source_is_master, float position, double rate,
+                             vlc_tick_t ts, vlc_tick_t length,
+                             vlc_tick_t system_date)
+{
+    vlc_mutex_lock(&player->timer_lock);
+
+    if (system_date != VLC_TICK_INVALID)
+    {
+        /* Source priority:
+         * 1/ source != NULL + master (from the master ES track)
+         * 2/ source != NULL (from the first ES track updated)
+         * 3/ source == NULL (from the input)
+         */
+        if (player->timer_source == NULL)
+            player->timer_source = source;
+        else if (source_is_master)
+            player->timer_source = source;
+
+        bool update = false, force_update = false;
+
+        if (source == NULL)
+        {
+            /* Only valid for input sources */
+            if (player->timer_value.length != length)
+            {
+                player->timer_value.length = length;
+                update = force_update = true;
+            }
+            /* Will likely be overridden by non input source */
+            player->timer_value.position = position;
+        }
+
+        if (player->timer_source == source)
+        {
+            update = ts != VLC_TICK_INVALID;
+            force_update = player->timer_value.rate != rate;
+
+            player->timer_value.rate = rate;
+            player->timer_value.ts = ts;
+            player->timer_value.system_date = system_date;
+
+            if (player->timer_value.length != VLC_TICK_INVALID)
+                player->timer_value.position = player->timer_value.ts
+                                             / (double) player->timer_value.length;
+        }
+
+        if (update)
+            vlc_player_UpdateTimersLocked(player, force_update,
+                                          VLC_PLAYER_TIMER_STATE_PLAYING,
+                                          &player->timer_value);
+    }
+    else if (player->timer_source == source
+          && player->timer_value.system_date != VLC_TICK_INVALID)
+    {
+        const vlc_tick_t system_now = vlc_tick_now();
+        vlc_player_UpdateTimerStateLocked(player,
+                                          VLC_PLAYER_TIMER_STATE_DISCONTINUITY,
+                                          system_now, system_now);
+
+        player->timer_source = NULL;
+        player->timer_value.system_date = VLC_TICK_INVALID;
+    }
+
+    vlc_mutex_unlock(&player->timer_lock);
+}
+
+int
+vlc_player_GetTimerPoint(vlc_player_t *player, vlc_tick_t system_now,
+                         vlc_tick_t *out_ts, float *out_pos)
+{
+    vlc_mutex_lock(&player->timer_lock);
+    if (player->timer_value.system_date == VLC_TICK_INVALID)
+    {
+        vlc_mutex_unlock(&player->timer_lock);
+        return VLC_EGENERIC;
+    }
+    int ret = vlc_player_timer_value_Interpolate(&player->timer_value,
+                                                 system_now, out_ts, out_pos);
+
+    vlc_mutex_unlock(&player->timer_lock);
+    return ret;
+}
+
+vlc_player_timer_id *
+vlc_player_AddTimer(vlc_player_t *player, enum vlc_player_timer_type type,
+                    vlc_tick_t min_period,
+                    const struct vlc_player_timer_cbs *cbs, void *data)
+{
+    assert(min_period >= VLC_TICK_0 || min_period == VLC_TICK_INVALID);
+    assert(cbs && cbs->on_update);
+    assert(type == VLC_PLAYER_TIMER_TYPE_SOURCE);
+
+    struct vlc_player_timer_id *timer = malloc(sizeof(*timer));
+    if (!timer)
+        return NULL;
+    timer->type = type;
+    timer->period = min_period;
+    timer->last_update_date = VLC_TICK_INVALID;
+    timer->cbs = cbs;
+    timer->data = data;
+
+    vlc_mutex_lock(&player->timer_lock);
+    vlc_list_append(&timer->node, &player->source_timers);
+    vlc_mutex_unlock(&player->timer_lock);
+
+    return timer;
+}
+
+void
+vlc_player_RemoveTimer(vlc_player_t *player, vlc_player_timer_id *timer)
+{
+    assert(timer);
+
+    vlc_mutex_lock(&player->timer_lock);
+    vlc_list_remove(&timer->node);
+    vlc_mutex_unlock(&player->timer_lock);
+
+    free(timer);
+}
+
+int
+vlc_player_timer_value_Interpolate(const struct vlc_player_timer_value *value,
+                                   vlc_tick_t system_now,
+                                   vlc_tick_t *out_ts, float *out_pos)
+{
+    assert(value);
+    assert(system_now > 0);
+    assert(out_ts || out_pos);
+
+    const vlc_tick_t drift = (system_now - value->system_date) * value->rate;
+    vlc_tick_t ts = value->ts;
+    float pos = value->position;
+
+    if (ts != VLC_TICK_INVALID)
+    {
+        ts += drift;
+        if (ts < 0)
+            return VLC_EGENERIC;
+    }
+    if (value->length != VLC_TICK_INVALID)
+    {
+        pos += drift / (float) value->length;
+        if (pos > 1.f)
+            pos = 1.f;
+        if (ts > value->length)
+            ts = value->length;
+    }
+
+    if (out_ts)
+        *out_ts = ts;
+    if (out_pos)
+        *out_pos = pos;
+
+    return VLC_SUCCESS;
+}
-- 
2.20.1



More information about the vlc-devel mailing list