summaryrefslogtreecommitdiffstats
path: root/player/lua/osc.lua
diff options
context:
space:
mode:
Diffstat (limited to 'player/lua/osc.lua')
-rw-r--r--player/lua/osc.lua142
1 files changed, 124 insertions, 18 deletions
diff --git a/player/lua/osc.lua b/player/lua/osc.lua
index af59470335..5259b5873e 100644
--- a/player/lua/osc.lua
+++ b/player/lua/osc.lua
@@ -48,6 +48,10 @@ local user_opts = {
windowcontrols = "auto", -- whether to show window controls
windowcontrols_alignment = "right", -- which side to show window controls on
greenandgrumpy = false, -- disable santa hat
+ livemarkers = true, -- update seekbar chapter markers on duration change
+ chapters_osd = true, -- whether to show chapters OSD on next/prev
+ playlist_osd = true, -- whether to show playlist OSD on next/prev
+ chapter_fmt = "Chapter: %s", -- chapter print format for seekbar-hover. "no" to disable
}
-- read options from config and command-line
@@ -101,6 +105,7 @@ local state = {
tc_ms = user_opts.timems, -- Should the timecodes display their time with milliseconds
mp_screen_sizeX, mp_screen_sizeY, -- last screen-resolution, to detect resolution changes to issue reINITs
initREQ = false, -- is a re-init request pending?
+ marginsREQ = false, -- is a margins update pending?
last_mouseX, last_mouseY, -- last mouse position, to detect significant mouse movement
mouse_in_window = false,
message_text,
@@ -119,6 +124,7 @@ local state = {
border = true,
maximized = false,
osd = mp.create_osd_overlay("ass-events"),
+ chapter_list = {}, -- sorted by time
}
local window_control_box_width = 80
@@ -130,6 +136,12 @@ local is_december = os.date("*t").month == 12
-- Helperfunctions
--
+function kill_animation()
+ state.anistart = nil
+ state.animation = nil
+ state.anitype = nil
+end
+
function set_osd(res_x, res_y, text)
if state.osd.res_x == res_x and
state.osd.res_y == res_y and
@@ -591,8 +603,35 @@ end
-- Element Rendering
--
+-- returns nil or a chapter element from the native property chapter-list
+function get_chapter(possec)
+ local cl = state.chapter_list -- sorted, get latest before possec, if any
+
+ for n=#cl,1,-1 do
+ if possec >= cl[n].time then
+ return cl[n]
+ end
+ end
+end
+
function render_elements(master_ass)
+ -- when the slider is dragged or hovered and we have a target chapter name
+ -- then we use it instead of the normal title. we calculate it before the
+ -- render iterations because the title may be rendered before the slider.
+ state.forced_title = nil
+ local se, ae = state.slider_element, elements[state.active_element]
+ if user_opts.chapter_fmt ~= "no" and se and (ae == se or (not ae and mouse_hit(se))) then
+ local dur = mp.get_property_number("duration", 0)
+ if dur > 0 then
+ local possec = get_slider_value(se) * dur / 100 -- of mouse pos
+ local ch = get_chapter(possec)
+ if ch and ch.title and ch.title ~= "" then
+ state.forced_title = string.format(user_opts.chapter_fmt, ch.title)
+ end
+ end
+ end
+
for n=1, #elements do
local element = elements[n]
@@ -1687,6 +1726,7 @@ function update_options(list)
validate_user_opts()
request_tick()
visibility_mode(user_opts.visibility, true)
+ update_duration_watch()
request_init()
end
@@ -1738,7 +1778,8 @@ function osc_init()
ne = new_element("title", "button")
ne.content = function ()
- local title = mp.command_native({"expand-text", user_opts.title})
+ local title = state.forced_title or
+ mp.command_native({"expand-text", user_opts.title})
-- escape ASS, and strip newlines and trailing slashes
title = title:gsub("\\n", " "):gsub("\\$", ""):gsub("{","\\{")
return not (title == "") and title or "mpv"
@@ -1766,7 +1807,9 @@ function osc_init()
ne.eventresponder["mbtn_left_up"] =
function ()
mp.commandv("playlist-prev", "weak")
- show_message(get_playlist(), 3)
+ if user_opts.playlist_osd then
+ show_message(get_playlist(), 3)
+ end
end
ne.eventresponder["shift+mbtn_left_up"] =
function () show_message(get_playlist(), 3) end
@@ -1781,7 +1824,9 @@ function osc_init()
ne.eventresponder["mbtn_left_up"] =
function ()
mp.commandv("playlist-next", "weak")
- show_message(get_playlist(), 3)
+ if user_opts.playlist_osd then
+ show_message(get_playlist(), 3)
+ end
end
ne.eventresponder["shift+mbtn_left_up"] =
function () show_message(get_playlist(), 3) end
@@ -1836,7 +1881,9 @@ function osc_init()
ne.eventresponder["mbtn_left_up"] =
function ()
mp.commandv("add", "chapter", -1)
- show_message(get_chapterlist(), 3)
+ if user_opts.chapters_osd then
+ show_message(get_chapterlist(), 3)
+ end
end
ne.eventresponder["shift+mbtn_left_up"] =
function () show_message(get_chapterlist(), 3) end
@@ -1851,7 +1898,9 @@ function osc_init()
ne.eventresponder["mbtn_left_up"] =
function ()
mp.commandv("add", "chapter", 1)
- show_message(get_chapterlist(), 3)
+ if user_opts.chapters_osd then
+ show_message(get_chapterlist(), 3)
+ end
end
ne.eventresponder["shift+mbtn_left_up"] =
function () show_message(get_chapterlist(), 3) end
@@ -1915,6 +1964,7 @@ function osc_init()
ne = new_element("seekbar", "slider")
ne.enabled = not (mp.get_property("percent-pos") == nil)
+ state.slider_element = ne.enabled and ne or nil -- used for forced_title
ne.slider.markerF = function ()
local duration = mp.get_property_number("duration", nil)
if not (duration == nil) then
@@ -2043,10 +2093,10 @@ function osc_init()
dmx_cache = state.dmx_cache
end
local min = math.floor(dmx_cache / 60)
- local sec = dmx_cache % 60
+ local sec = math.floor(dmx_cache % 60) -- don't round e.g. 59.9 to 60
return "Cache: " .. (min > 0 and
string.format("%sm%02.0fs", min, sec) or
- string.format("%3.0fs", dmx_cache))
+ string.format("%3.0fs", sec))
end
-- volume
@@ -2099,7 +2149,10 @@ function update_margins()
local margins = osc_param.video_margins
-- Don't use margins if it's visible only temporarily.
- if (not state.osc_visible) or (get_hidetimeout() >= 0) then
+ if (not state.osc_visible) or (get_hidetimeout() >= 0) or
+ (state.fullscreen and not user_opts.showfullscreen) or
+ (not state.fullscreen and not user_opts.showwindowed)
+ then
margins = {l = 0, r = 0, t = 0, b = 0}
end
@@ -2236,6 +2289,7 @@ end
function render_wipe()
msg.trace("render_wipe()")
+ state.osd.data = "" -- allows set_osd to immediately update on enable
state.osd:remove()
end
@@ -2256,7 +2310,14 @@ function render()
end
-- init management
- if state.initREQ then
+ if state.active_element then
+ -- mouse is held down on some element - keep ticking and igore initReq
+ -- till it's released, or else the mouse-up (click) will misbehave or
+ -- get ignored. that's because osc_init() recreates the osc elements,
+ -- but mouse handling depends on the elements staying unmodified
+ -- between mouse-down and mouse-up (using the index active_element).
+ request_tick()
+ elseif state.initREQ then
osc_init()
state.initREQ = false
@@ -2293,14 +2354,10 @@ function render()
if (state.anitype == "out") then
osc_visible(false)
end
- state.anistart = nil
- state.animation = nil
- state.anitype = nil
+ kill_animation()
end
else
- state.anistart = nil
- state.animation = nil
- state.anitype = nil
+ kill_animation()
end
--mouse show/hide area
@@ -2472,8 +2529,10 @@ function process_event(source, what)
if element_has_action(elements[n], action) then
elements[n].eventresponder[action](elements[n])
end
- request_tick()
end
+
+ -- ensure rendering after any (mouse) event - icons could change etc
+ request_tick()
end
@@ -2504,6 +2563,11 @@ local santa_hat_lines = {
-- called by mpv on every frame
function tick()
+ if state.marginsREQ == true then
+ update_margins()
+ state.marginsREQ = false
+ end
+
if (not state.enabled) then return end
if (state.idle) then
@@ -2548,13 +2612,23 @@ function tick()
render()
else
-- Flush OSD
- set_osd(osc_param.playresy, osc_param.playresy, "")
+ render_wipe()
end
state.tick_last_time = mp.get_time()
if state.anitype ~= nil then
- request_tick()
+ -- state.anistart can be nil - animation should now start, or it can
+ -- be a timestamp when it started. state.idle has no animation.
+ if not state.idle and
+ (not state.anistart or
+ mp.get_time() < 1 + state.anistart + user_opts.fadeduration/1000)
+ then
+ -- animating or starting, or still within 1s past the deadline
+ request_tick()
+ else
+ kill_animation()
+ end
end
end
@@ -2582,12 +2656,42 @@ function enable_osc(enable)
end
end
+-- duration is observed for the sole purpose of updating chapter markers
+-- positions. live streams with chapters are very rare, and the update is also
+-- expensive (with request_init), so it's only observed when we have chapters
+-- and the user didn't disable the livemarkers option (update_duration_watch).
+function on_duration() request_init() end
+
+local duration_watched = false
+function update_duration_watch()
+ local want_watch = user_opts.livemarkers and
+ (mp.get_property_number("chapters", 0) or 0) > 0 and
+ true or false -- ensure it's a boolean
+
+ if (want_watch ~= duration_watched) then
+ if want_watch then
+ mp.observe_property("duration", nil, on_duration)
+ else
+ mp.unobserve_property(on_duration)
+ end
+ duration_watched = want_watch
+ end
+end
+
validate_user_opts()
+update_duration_watch()
mp.register_event("shutdown", shutdown)
mp.register_event("start-file", request_init)
mp.observe_property("track-list", nil, request_init)
mp.observe_property("playlist", nil, request_init)
+mp.observe_property("chapter-list", "native", function(_, list)
+ list = list or {} -- safety, shouldn't return nil
+ table.sort(list, function(a, b) return a.time < b.time end)
+ state.chapter_list = list
+ update_duration_watch()
+ request_init()
+end)
mp.register_script_message("osc-message", show_message)
mp.register_script_message("osc-chapterlist", function(dur)
@@ -2607,6 +2711,7 @@ end)
mp.observe_property("fullscreen", "bool",
function(name, val)
state.fullscreen = val
+ state.marginsREQ = true
request_init_resize()
end
)
@@ -2722,6 +2827,7 @@ function visibility_mode(mode, no_osd)
end
user_opts.visibility = mode
+ utils.shared_script_property_set("osc-visibility", mode)
if not no_osd and tonumber(mp.get_property("osd-level")) >= 1 then
mp.osd_message("OSC visibility: " .. mode)