update reload and add config file

This commit is contained in:
sudacode 2025-01-18 15:37:11 -08:00
parent a9b562f8e8
commit 0acea11d35
2 changed files with 272 additions and 240 deletions

34
script-opts/reload.conf Normal file
View File

@ -0,0 +1,34 @@
# enable automatic reload on timeout
# when paused-for-cache event fired, we will wait
# paused_for_cache_timer_timeout sedonds and then reload the video
paused_for_cache_timer_enabled=yes
# checking paused_for_cache property interval in seconds,
# can not be less than 0.05 (50 ms)
paused_for_cache_timer_interval=1
# time in seconds to wait until reload
paused_for_cache_timer_timeout=10
# enable automatic reload based on demuxer cache
# if demuxer-cache-time property didn't change in demuxer_cache_timer_timeout
# time interval, the video will be reloaded as soon as demuxer cache depleated
demuxer_cache_timer_enabled=yes
# checking demuxer-cache-time property interval in seconds,
# can not be less than 0.05 (50 ms)
demuxer_cache_timer_interval=2
# if demuxer cache didn't receive any data during demuxer_cache_timer_timeout
# we decide that it has no progress and will reload the stream when
# paused_for_cache event happens
demuxer_cache_timer_timeout=20
# when the end-of-file is reached, reload the stream to check
# if there is more content available.
reload_eof_enabled=no
# keybinding to reload stream from current time position
# you can disable keybinding by setting it to empty value
# reload_key_binding=
reload_key_binding=Ctrl+r

View File

@ -57,12 +57,10 @@
-- `--msg-level='reload=debug'`. You may also need to add the `--no-msg-color` -- `--msg-level='reload=debug'`. You may also need to add the `--no-msg-color`
-- option to make the debug logs visible if you are using a dark colorscheme -- option to make the debug logs visible if you are using a dark colorscheme
-- in terminal. -- in terminal.
local msg = require 'mp.msg' local msg = require 'mp.msg'
local options = require 'mp.options' local options = require 'mp.options'
local utils = require 'mp.utils' local utils = require 'mp.utils'
local settings = { local settings = {
paused_for_cache_timer_enabled = true, paused_for_cache_timer_enabled = true,
paused_for_cache_timer_interval = 1, paused_for_cache_timer_interval = 1,
@ -71,7 +69,7 @@ local settings = {
demuxer_cache_timer_interval = 2, demuxer_cache_timer_interval = 2,
demuxer_cache_timer_timeout = 20, demuxer_cache_timer_timeout = 20,
reload_eof_enabled = false, reload_eof_enabled = false,
reload_key_binding = "Ctrl+r", reload_key_binding = "Ctrl+r"
} }
-- global state stores properties between reloads -- global state stores properties between reloads
@ -99,11 +97,7 @@ local property_keep_open = nil
local demuxer_cache = { local demuxer_cache = {
timer = nil, timer = nil,
state = { state = { name = 'uninitialized', demuxer_cache_time = 0, in_state_time = 0 },
name = 'uninitialized',
demuxer_cache_time = 0,
in_state_time = 0,
},
events = { events = {
continue_fetch = { name = 'continue_fetch', from = 'fetch', to = 'fetch' }, continue_fetch = { name = 'continue_fetch', from = 'fetch', to = 'fetch' },
@ -112,8 +106,8 @@ local demuxer_cache = {
fetch_to_stale = { name = 'fetch_to_stale', from = 'fetch', to = 'stale' }, fetch_to_stale = { name = 'fetch_to_stale', from = 'fetch', to = 'stale' },
stale_to_fetch = { name = 'stale_to_fetch', from = 'stale', to = 'fetch' }, stale_to_fetch = { name = 'stale_to_fetch', from = 'stale', to = 'fetch' },
stale_to_stuck = { name = 'stale_to_stuck', from = 'stale', to = 'stuck' }, stale_to_stuck = { name = 'stale_to_stuck', from = 'stale', to = 'stuck' },
stuck_to_fetch = { name = 'stuck_to_fetch', from = 'stuck', to = 'fetch' }, stuck_to_fetch = { name = 'stuck_to_fetch', from = 'stuck', to = 'fetch' }
}, }
} }
@ -122,7 +116,7 @@ function demuxer_cache.reset_state()
demuxer_cache.state = { demuxer_cache.state = {
name = demuxer_cache.events.continue_fetch.to, name = demuxer_cache.events.continue_fetch.to,
demuxer_cache_time = 0, demuxer_cache_time = 0,
in_state_time = 0, in_state_time = 0
} }
end end
@ -145,16 +139,21 @@ end
function demuxer_cache.transition(event) function demuxer_cache.transition(event)
if demuxer_cache.state.name == event.from then if demuxer_cache.state.name == event.from then
-- state setup -- state setup
demuxer_cache.state.demuxer_cache_time = event.demuxer_cache_time demuxer_cache.state.demuxer_cache_time = event.demuxer_cache_time
if event.name == 'continue_fetch' then if event.name == 'continue_fetch' then
demuxer_cache.state.in_state_time = demuxer_cache.state.in_state_time + event.interval demuxer_cache.state.in_state_time = demuxer_cache.state
.in_state_time +
event.interval
elseif event.name == 'continue_stale' then elseif event.name == 'continue_stale' then
demuxer_cache.state.in_state_time = demuxer_cache.state.in_state_time + event.interval demuxer_cache.state.in_state_time = demuxer_cache.state
.in_state_time +
event.interval
elseif event.name == 'continue_stuck' then elseif event.name == 'continue_stuck' then
demuxer_cache.state.in_state_time = demuxer_cache.state.in_state_time + event.interval demuxer_cache.state.in_state_time = demuxer_cache.state
.in_state_time +
event.interval
elseif event.name == 'fetch_to_stale' then elseif event.name == 'fetch_to_stale' then
demuxer_cache.state.in_state_time = 0 demuxer_cache.state.in_state_time = 0
elseif event.name == 'stale_to_fetch' then elseif event.name == 'stale_to_fetch' then
@ -168,33 +167,32 @@ function demuxer_cache.transition(event)
-- state transition -- state transition
demuxer_cache.state.name = event.to demuxer_cache.state.name = event.to
msg.debug('demuxer_cache.transition', event.name, utils.to_string(demuxer_cache.state)) msg.debug('demuxer_cache.transition', event.name,
utils.to_string(demuxer_cache.state))
else else
msg.error( msg.error('demuxer_cache.transition', 'illegal transition', event.name,
'demuxer_cache.transition',
'illegal transition', event.name,
'from state', demuxer_cache.state.name) 'from state', demuxer_cache.state.name)
end end
end end
function demuxer_cache.initialize(demuxer_cache_timer_interval) function demuxer_cache.initialize(demuxer_cache_timer_interval)
demuxer_cache.reset_state() demuxer_cache.reset_state()
demuxer_cache.timer = mp.add_periodic_timer( demuxer_cache.timer = mp.add_periodic_timer(demuxer_cache_timer_interval,
demuxer_cache_timer_interval,
function() function()
demuxer_cache.demuxer_cache_timer_tick( demuxer_cache.demuxer_cache_timer_tick(
mp.get_property_native('demuxer-cache-time'), mp.get_property_native('demuxer-cache-time'),
demuxer_cache_timer_interval) demuxer_cache_timer_interval)
end end)
)
end end
-- If there is no progress of demuxer_cache_time in -- If there is no progress of demuxer_cache_time in
-- settings.demuxer_cache_timer_timeout time interval switch state to -- settings.demuxer_cache_timer_timeout time interval switch state to
-- 'stuck' and switch back to 'fetch' as soon as any progress is made -- 'stuck' and switch back to 'fetch' as soon as any progress is made
function demuxer_cache.demuxer_cache_timer_tick(demuxer_cache_time, demuxer_cache_timer_interval) function demuxer_cache.demuxer_cache_timer_tick(demuxer_cache_time,
demuxer_cache_timer_interval)
local event = nil local event = nil
local cache_has_progress = demuxer_cache.has_progress_since(demuxer_cache_time) local cache_has_progress = demuxer_cache.has_progress_since(
demuxer_cache_time)
-- I miss pattern matching so much -- I miss pattern matching so much
if demuxer_cache.is_state_fetch() then if demuxer_cache.is_state_fetch() then
@ -206,7 +204,8 @@ function demuxer_cache.demuxer_cache_timer_tick(demuxer_cache_time, demuxer_cach
elseif demuxer_cache.is_state_stale() then elseif demuxer_cache.is_state_stale() then
if cache_has_progress then if cache_has_progress then
event = demuxer_cache.events.stale_to_fetch event = demuxer_cache.events.stale_to_fetch
elseif demuxer_cache.state.in_state_time < settings.demuxer_cache_timer_timeout then elseif demuxer_cache.state.in_state_time <
settings.demuxer_cache_timer_timeout then
event = demuxer_cache.events.continue_stale event = demuxer_cache.events.continue_stale
else else
event = demuxer_cache.events.stale_to_stuck event = demuxer_cache.events.stale_to_stuck
@ -224,11 +223,7 @@ function demuxer_cache.demuxer_cache_timer_tick(demuxer_cache_time, demuxer_cach
demuxer_cache.transition(event) demuxer_cache.transition(event)
end end
local paused_for_cache = { timer = nil, time = 0 }
local paused_for_cache = {
timer = nil,
time = 0,
}
function paused_for_cache.reset_timer() function paused_for_cache.reset_timer()
msg.debug('paused_for_cache.reset_timer', paused_for_cache.time) msg.debug('paused_for_cache.reset_timer', paused_for_cache.time)
@ -242,8 +237,7 @@ end
function paused_for_cache.start_timer(interval_seconds, timeout_seconds) function paused_for_cache.start_timer(interval_seconds, timeout_seconds)
msg.debug('paused_for_cache.start_timer', paused_for_cache.time) msg.debug('paused_for_cache.start_timer', paused_for_cache.time)
if not paused_for_cache.timer then if not paused_for_cache.timer then
paused_for_cache.timer = mp.add_periodic_timer( paused_for_cache.timer = mp.add_periodic_timer(interval_seconds,
interval_seconds,
function() function()
paused_for_cache.time = paused_for_cache.time + interval_seconds paused_for_cache.time = paused_for_cache.time + interval_seconds
if paused_for_cache.time >= timeout_seconds then if paused_for_cache.time >= timeout_seconds then
@ -251,14 +245,12 @@ function paused_for_cache.start_timer(interval_seconds, timeout_seconds)
reload_resume() reload_resume()
end end
msg.debug('paused_for_cache', 'tick', paused_for_cache.time) msg.debug('paused_for_cache', 'tick', paused_for_cache.time)
end end)
)
end end
end end
function paused_for_cache.handler(property, is_paused) function paused_for_cache.handler(property, is_paused)
if is_paused then if is_paused then
if demuxer_cache.is_state_stuck() then if demuxer_cache.is_state_stuck() then
msg.info("demuxer cache has no progress") msg.info("demuxer cache has no progress")
-- reset demuxer state to avoid immediate reload if -- reset demuxer state to avoid immediate reload if
@ -267,8 +259,7 @@ function paused_for_cache.handler(property, is_paused)
reload_resume() reload_resume()
end end
paused_for_cache.start_timer( paused_for_cache.start_timer(settings.paused_for_cache_timer_interval,
settings.paused_for_cache_timer_interval,
settings.paused_for_cache_timer_timeout) settings.paused_for_cache_timer_timeout)
else else
paused_for_cache.reset_timer() paused_for_cache.reset_timer()
@ -285,8 +276,15 @@ function reload(path, time_pos)
if time_pos == nil then if time_pos == nil then
mp.commandv("loadfile", path, "replace") mp.commandv("loadfile", path, "replace")
else else
local success = mp.commandv("loadfile", path, "replace", -1,
"start=+" .. time_pos)
-- fallback to old syntax of loadfile for compatibility
if success == nil then
msg.warn(
"old loadfile syntax detected. falling back to using old syntax. update mpv to remove this warning")
mp.commandv("loadfile", path, "replace", "start=+" .. time_pos) mp.commandv("loadfile", path, "replace", "start=+" .. time_pos)
end end
end
end end
function reload_resume() function reload_resume()
@ -297,7 +295,7 @@ function reload_resume()
local playlist_count = mp.get_property_number("playlist/count") local playlist_count = mp.get_property_number("playlist/count")
local playlist_pos = mp.get_property_number("playlist-pos") local playlist_pos = mp.get_property_number("playlist-pos")
local playlist = {} local playlist = {}
for i = 0, playlist_count-1 do for i = 0, playlist_count - 1 do
playlist[i] = mp.get_property("playlist/" .. i .. "/filename") playlist[i] = mp.get_property("playlist/" .. i .. "/filename")
end end
-- Tries to determine live stream vs. pre-recordered VOD. VOD has non-zero -- Tries to determine live stream vs. pre-recordered VOD. VOD has non-zero
@ -319,12 +317,12 @@ function reload_resume()
msg.info("reloading stream") msg.info("reloading stream")
reload(path, nil) reload(path, nil)
end end
msg.info("file ", playlist_pos+1, "of", playlist_count, "in playlist") msg.info("file", playlist_pos + 1, "of", playlist_count, "in playlist")
for i = 0, playlist_pos-1 do for i = 0, playlist_pos - 1 do
mp.commandv("loadfile", playlist[i], "append") mp.commandv("loadfile", playlist[i], "append")
end end
mp.commandv("playlist-move", 0, playlist_pos+1) mp.commandv("playlist-move", 0, playlist_pos + 1)
for i = playlist_pos+1, playlist_count-1 do for i = playlist_pos + 1, playlist_count - 1 do
mp.commandv("loadfile", playlist[i], "append") mp.commandv("loadfile", playlist[i], "append")
end end
end end
@ -334,15 +332,14 @@ function reload_eof(property, eof_reached)
local time_pos = mp.get_property_number("time-pos") local time_pos = mp.get_property_number("time-pos")
local duration = mp.get_property_number("duration") local duration = mp.get_property_number("duration")
if eof_reached and math.floor(time_pos) == math.floor(duration) then if eof_reached and round(time_pos) == round(duration) then
msg.debug("property_time_pos", property_time_pos, "time_pos", time_pos) msg.debug("property_time_pos", property_time_pos, "time_pos", time_pos)
-- Check that playback time_pos made progress after the last reload. When -- Check that playback time_pos made progress after the last reload. When
-- eof is reached we try to reload video, in case there is more content -- eof is reached we try to reload the video, in case there is more content
-- available. If time_pos stayed the same after reload, it means that vidkk -- available. If time_pos stayed the same after reload, it means that the
-- to avoid infinite reload loop when playback ended -- video length stayed the same, and we can end the playback.
-- math.floor function rounds time_pos to a second, to avoid inane reloads if round(property_time_pos) == round(time_pos) then
if math.floor(property_time_pos) == math.floor(time_pos) then
msg.info("eof reached, playback ended") msg.info("eof reached, playback ended")
mp.set_property("keep-open", property_keep_open) mp.set_property("keep-open", property_keep_open)
else else
@ -364,7 +361,7 @@ function on_file_loaded(event)
seekable = mp.get_property("seekable"), seekable = mp.get_property("seekable"),
pause = mp.get_property("pause"), pause = mp.get_property("pause"),
paused_for_cache = mp.get_property("paused-for-cache"), paused_for_cache = mp.get_property("paused-for-cache"),
cache_buffering_state = mp.get_property("cache-buffering-state"), cache_buffering_state = mp.get_property("cache-buffering-state")
} }
msg.debug("debug_info", utils.to_string(debug_info)) msg.debug("debug_info", utils.to_string(debug_info))
@ -380,12 +377,16 @@ function on_file_loaded(event)
mp.commandv("keypress", 'SPACE') mp.commandv("keypress", 'SPACE')
end end
-- Round positive numbers.
function round(num) return math.floor(num + 0.5) end
-- main -- main
read_settings() read_settings()
if settings.reload_key_binding ~= "" then if settings.reload_key_binding ~= "" then
mp.add_key_binding(settings.reload_key_binding, "reload_resume", reload_resume) mp.add_key_binding(settings.reload_key_binding, "reload_resume",
reload_resume)
end end
if settings.paused_for_cache_timer_enabled then if settings.paused_for_cache_timer_enabled then
@ -398,10 +399,7 @@ end
if settings.reload_eof_enabled then if settings.reload_eof_enabled then
-- vo-configured == video output created && its configuration went ok -- vo-configured == video output created && its configuration went ok
mp.observe_property( mp.observe_property("vo-configured", "bool", function(name, vo_configured)
"vo-configured",
"bool",
function(name, vo_configured)
msg.debug(name, vo_configured) msg.debug(name, vo_configured)
if vo_configured then if vo_configured then
property_path = mp.get_property("path") property_path = mp.get_property("path")
@ -409,10 +407,10 @@ if settings.reload_eof_enabled then
mp.set_property("keep-open", "yes") mp.set_property("keep-open", "yes")
mp.set_property("keep-open-pause", "no") mp.set_property("keep-open-pause", "no")
end end
end end)
)
mp.observe_property("eof-reached", "bool", reload_eof) mp.observe_property("eof-reached", "bool", reload_eof)
end end
mp.register_event("file-loaded", on_file_loaded) mp.register_event("file-loaded", on_file_loaded)