refactor: use cicular buffer for cursor.history
Circular buffers are more efficient then moving the entries around. Cursor velocity is now calculated on the fly, with from the current cursor position and time as well a cursor position that is either the youngest sample >100ms old, or the oldest sample in the buffer, which is 10 elements in size. Restricting the sample selection based on time prevents the filter window from becomming too long in case of setups with low cursor update frequency, while still bring more responsive velocity measurements to systems with higher cursor update frequencies. Removed the timer for exact seeking after fast seeks, as fast/exact seeks can now be controlled with cursor speed and thus there is no need for this anymore.
This commit is contained in:

committed by
Tomas Klaen

parent
776ca17f66
commit
3f21df1a9d
@@ -16,11 +16,6 @@ function Timeline:init()
|
||||
self.top_border = options.timeline_border
|
||||
self.is_hovered = false
|
||||
self.has_thumbnail = false
|
||||
self.pixels_per_frame = 1
|
||||
|
||||
-- Delayed seeking timer
|
||||
self.seek_timer = mp.add_timeout(0.05, function() self:set_from_cursor() end)
|
||||
self.seek_timer:kill()
|
||||
|
||||
-- Release any dragging when file gets unloaded
|
||||
mp.register_event('end-file', function() self.pressed = false end)
|
||||
@@ -53,13 +48,6 @@ end
|
||||
|
||||
function Timeline:get_is_hovered() return self.enabled and self.is_hovered end
|
||||
|
||||
function Timeline:update_pixels_per_frame()
|
||||
if state.is_video and state.framerate and state.duration then
|
||||
self.pixels_per_frame = self.width / (state.duration * state.framerate)
|
||||
else
|
||||
self.pixels_per_frame = 1
|
||||
end
|
||||
end
|
||||
function Timeline:update_dimensions()
|
||||
if state.fullormaxed then
|
||||
self.size_min = options.timeline_size_min_fullscreen
|
||||
@@ -82,7 +70,6 @@ function Timeline:update_dimensions()
|
||||
if Elements.top_bar.enabled then available_space = available_space - Elements.top_bar.size end
|
||||
self.obstructed = available_space < self.size_max + 10
|
||||
self:decide_enabled()
|
||||
self:update_pixels_per_frame()
|
||||
end
|
||||
|
||||
function Timeline:get_time_at_x(x)
|
||||
@@ -116,14 +103,12 @@ function Timeline:handle_cursor_down()
|
||||
self:set_from_cursor()
|
||||
cursor.on_primary_up = function() self:handle_cursor_up() end
|
||||
end
|
||||
function Timeline:on_prop_duration() self:decide_enabled() self:update_pixels_per_frame() end
|
||||
function Timeline:on_prop_framerate() self:update_pixels_per_frame() end
|
||||
function Timeline:on_prop_duration() self:decide_enabled() end
|
||||
function Timeline:on_prop_time() self:decide_enabled() end
|
||||
function Timeline:on_prop_border() self:update_dimensions() end
|
||||
function Timeline:on_prop_fullormaxed() self:update_dimensions() end
|
||||
function Timeline:on_display() self:update_dimensions() end
|
||||
function Timeline:handle_cursor_up()
|
||||
self.seek_timer:kill()
|
||||
if self.pressed then
|
||||
mp.set_property_native('pause', self.pressed.pause)
|
||||
self.pressed = false
|
||||
@@ -137,10 +122,8 @@ function Timeline:on_global_mouse_move()
|
||||
if self.pressed then
|
||||
self.pressed.distance = self.pressed.distance + get_point_to_point_proximity(self.pressed.last, cursor)
|
||||
self.pressed.last.x, self.pressed.last.y = cursor.x, cursor.y
|
||||
if not state.is_video or math.abs(cursor.velocity.x) > 80 * math.max(1, self.pixels_per_frame) then
|
||||
if state.is_video and math.abs(cursor.get_velocity().x) / self.width * state.duration > 30 then
|
||||
self:set_from_cursor(true)
|
||||
self.seek_timer:kill()
|
||||
self.seek_timer:resume()
|
||||
else self:set_from_cursor() end
|
||||
end
|
||||
end
|
||||
|
@@ -202,3 +202,59 @@ function Class:init() end
|
||||
function Class:destroy() end
|
||||
|
||||
function class(parent) return setmetatable({}, {__index = parent or Class}) end
|
||||
|
||||
---@class CircularBuffer<T> : Class
|
||||
CircularBuffer = class()
|
||||
|
||||
function CircularBuffer:new(max_size) return Class.new(self, max_size) --[[@as CircularBuffer]] end
|
||||
function CircularBuffer:init(max_size)
|
||||
self.max_size = max_size
|
||||
self.size = 0
|
||||
self.pos = 0
|
||||
self.data = {}
|
||||
end
|
||||
|
||||
function CircularBuffer:insert(item)
|
||||
self.pos = self.pos % self.max_size + 1
|
||||
self.data[self.pos] = item
|
||||
if self.size < self.max_size then self.size = self.size + 1 end
|
||||
end
|
||||
|
||||
function CircularBuffer:get(i)
|
||||
return i <= self.size and self.data[(self.pos + i - 1) % self.size + 1] or nil
|
||||
end
|
||||
|
||||
local function iter(self, i)
|
||||
if i == self.size then return nil end
|
||||
i = i + 1
|
||||
return i, self:get(i)
|
||||
end
|
||||
|
||||
function CircularBuffer:iter()
|
||||
return iter, self, 0
|
||||
end
|
||||
|
||||
local function iter_rev(self, i)
|
||||
if i == 1 then return nil end
|
||||
i = i - 1
|
||||
return i, self:get(i)
|
||||
end
|
||||
|
||||
function CircularBuffer:iter_rev()
|
||||
return iter_rev, self, self.size + 1
|
||||
end
|
||||
|
||||
function CircularBuffer:head()
|
||||
return self.data[self.pos]
|
||||
end
|
||||
|
||||
function CircularBuffer:tail()
|
||||
if self.size < 1 then return nil end
|
||||
return self.data[self.pos % self.size + 1]
|
||||
end
|
||||
|
||||
function CircularBuffer:clear()
|
||||
for i = self.size, 1, -1 do self.data[i] = nil end
|
||||
self.size = 0
|
||||
self.pos = 0
|
||||
end
|
||||
|
@@ -316,10 +316,7 @@ cursor = {
|
||||
on_wheel_down = nil,
|
||||
on_wheel_up = nil,
|
||||
allow_dragging = false,
|
||||
---@type {x: number, y: number, time: number}[]
|
||||
history = {},
|
||||
history_size = 10,
|
||||
velocity = {x = 0, y = 0},
|
||||
history = CircularBuffer:new(10),
|
||||
-- Called at the beginning of each render
|
||||
reset_handlers = function()
|
||||
cursor.on_primary_down, cursor.on_primary_up = nil, nil
|
||||
@@ -342,6 +339,26 @@ cursor = {
|
||||
cursor.wheel_enabled = enable_wheel
|
||||
end
|
||||
end,
|
||||
find_history_sample = function()
|
||||
local time = mp.get_time()
|
||||
for _, e in cursor.history:iter_rev() do
|
||||
if time - e.time > 0.1 then
|
||||
return e
|
||||
end
|
||||
end
|
||||
return cursor.history:tail()
|
||||
end,
|
||||
get_velocity = function()
|
||||
local snap = cursor.find_history_sample()
|
||||
if snap then
|
||||
local x, y, time = cursor.x - snap.x, cursor.y - snap.y, mp.get_time()
|
||||
local time_diff = time - snap.time
|
||||
if time_diff > 0.001 then
|
||||
return { x = x / time_diff, y = y / time_diff }
|
||||
end
|
||||
end
|
||||
return { x = 0, y = 0 }
|
||||
end,
|
||||
move = function(x, y)
|
||||
local old_x, old_y = cursor.x, cursor.y
|
||||
|
||||
@@ -360,8 +377,8 @@ cursor = {
|
||||
Elements:update_proximities()
|
||||
|
||||
if cursor.x == INFINITY or cursor.y == INFINITY then
|
||||
cursor.hidden, cursor.history = true, {}
|
||||
cursor.velocity.x, cursor.velocity.y = 0, 0
|
||||
cursor.hidden = true
|
||||
cursor.history:clear()
|
||||
|
||||
-- Slowly fadeout elements that are currently visible
|
||||
for _, element_name in ipairs({'timeline', 'volume', 'top_bar'}) do
|
||||
@@ -375,28 +392,12 @@ cursor = {
|
||||
|
||||
Elements:trigger('global_mouse_leave')
|
||||
elseif cursor.hidden then
|
||||
cursor.hidden, cursor.history = false, {}
|
||||
cursor.velocity.x, cursor.velocity.y = 0, 0
|
||||
cursor.hidden = false
|
||||
cursor.history:clear()
|
||||
Elements:trigger('global_mouse_enter')
|
||||
else
|
||||
-- Update history
|
||||
local new_index = #cursor.history + 1
|
||||
local last = {x = x, y = y, time = mp.get_time()}
|
||||
if #cursor.history >= cursor.history_size then
|
||||
new_index = #cursor.history
|
||||
for i = 1, #cursor.history, 1 do cursor.history[i] = cursor.history[i + 1] end
|
||||
end
|
||||
cursor.history[new_index] = last
|
||||
|
||||
-- Update velocity
|
||||
if #cursor.history > 5 then
|
||||
local first = cursor.history[1]
|
||||
local time_delta = (last.time - first.time)
|
||||
cursor.velocity.x = (last.x - first.x) / time_delta
|
||||
cursor.velocity.y = (last.y - first.y) / time_delta
|
||||
else
|
||||
cursor.velocity.x, cursor.velocity.y = 0, 0
|
||||
end
|
||||
cursor.history:insert({x = cursor.x, y = cursor.y, time = mp.get_time()})
|
||||
end
|
||||
|
||||
Elements:proximity_trigger('mouse_move')
|
||||
@@ -424,11 +425,8 @@ cursor = {
|
||||
-- Calculates distance in which cursor reaches rectangle if it continues moving on the same path.
|
||||
-- Returns `nil` if cursor is not moving towards the rectangle.
|
||||
direction_to_rectangle_distance = function(rect)
|
||||
if cursor.hidden or #cursor.history < 10 then
|
||||
return false
|
||||
end
|
||||
|
||||
local prev = cursor.history[#cursor.history - 9]
|
||||
local prev = cursor.find_history_sample()
|
||||
if not prev then return false end
|
||||
local end_x, end_y = cursor.x + (cursor.x - prev.x) * 1e10, cursor.y + (cursor.y - prev.y) * 1e10
|
||||
return get_ray_to_rectangle_distance(cursor.x, cursor.y, end_x, end_y, rect)
|
||||
end
|
||||
@@ -455,7 +453,6 @@ state = {
|
||||
duration = nil, -- current media duration
|
||||
time_human = nil, -- current playback time in human format
|
||||
destination_time_human = nil, -- depends on options.destination_time
|
||||
framerate = 1,
|
||||
pause = mp.get_property_native('pause'),
|
||||
chapters = {},
|
||||
current_chapter = nil,
|
||||
@@ -829,7 +826,6 @@ end)
|
||||
mp.observe_property('display-hidpi-scale', 'native', create_state_setter('hidpi_scale', update_display_dimensions))
|
||||
mp.observe_property('cache', 'string', create_state_setter('cache'))
|
||||
mp.observe_property('cache-buffering-state', 'number', create_state_setter('cache_buffering'))
|
||||
mp.observe_property('demuxer-rawvideo-fps', 'number', create_state_setter('framerate'))
|
||||
mp.observe_property('demuxer-via-network', 'native', create_state_setter('is_stream', function()
|
||||
Elements:trigger('dispositions')
|
||||
end))
|
||||
|
Reference in New Issue
Block a user