568 lines
15 KiB
Lua
568 lines
15 KiB
Lua
|
--[[
|
||
|
This file is part of mfpbar.
|
||
|
|
||
|
mfpbar is free software: you can redistribute it and/or modify it
|
||
|
under the terms of the GNU Affero General Public License as published by the
|
||
|
Free Software Foundation, either version 3 of the License, or (at your
|
||
|
option) any later version.
|
||
|
|
||
|
mfpbar 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 Affero General Public License
|
||
|
for more details.
|
||
|
|
||
|
You should have received a copy of the GNU Affero General Public License
|
||
|
along with mfpbar. If not, see <https://www.gnu.org/licenses/>.
|
||
|
]]
|
||
|
|
||
|
local msg = require('mp.msg')
|
||
|
local utils = require('mp.utils')
|
||
|
local mpopt = require('mp.options')
|
||
|
|
||
|
-- poor man's enum
|
||
|
|
||
|
local pbar_uninit = 0
|
||
|
local pbar_hidden = 1
|
||
|
local pbar_minimized = 2
|
||
|
local pbar_active = 3
|
||
|
|
||
|
-- globals
|
||
|
|
||
|
local state = {
|
||
|
osd = nil,
|
||
|
dpy_w = 0,
|
||
|
dpy_h = 0,
|
||
|
pbar = pbar_uninit,
|
||
|
mouse = nil,
|
||
|
mouse_prev = nil,
|
||
|
cached_ranges = nil,
|
||
|
duration = nil,
|
||
|
chapters = nil,
|
||
|
timeout = nil,
|
||
|
time_observed = false,
|
||
|
press_bounded = false,
|
||
|
fullscreen = false,
|
||
|
thumbfast = {
|
||
|
width = 0,
|
||
|
height = 0,
|
||
|
disabled = true,
|
||
|
available = false
|
||
|
},
|
||
|
}
|
||
|
|
||
|
local opt = {
|
||
|
pbar_height = 2,
|
||
|
pbar_minimized_height = 0.5,
|
||
|
pbar_color = "CCCCCC",
|
||
|
pbar_bg_alpha = "3F",
|
||
|
pbar_fullscreen_hide = true,
|
||
|
cachebar_height = 0.24,
|
||
|
cachebar_color = "1C6C89",
|
||
|
cachebar_uncached_color = "CC3A2A",
|
||
|
cachebar_uncached_alpha = "70",
|
||
|
-- TODO: allow selecting "duration" as well ?
|
||
|
timeline_rhs = "time-remaining",
|
||
|
hover_bar_color = "BDAE93",
|
||
|
font_size = 16,
|
||
|
font_pad = 4, -- TODO: rename this to hpad ?
|
||
|
-- TODO: add a configurable vpad as well ?
|
||
|
font_border_width = 2,
|
||
|
font_border_color = "000000",
|
||
|
proximity = 40,
|
||
|
preview_border_width = 2,
|
||
|
preview_border_color = "BDAE93",
|
||
|
chapter_marker_size = 3,
|
||
|
chapter_marker_color = "BDAE93",
|
||
|
chapter_marker_border_width = 1,
|
||
|
chapter_marker_border_color = "161616",
|
||
|
minimize_timeout = 3,
|
||
|
|
||
|
debug = false,
|
||
|
}
|
||
|
|
||
|
local zassert = function() end
|
||
|
|
||
|
-- function implementation
|
||
|
|
||
|
-- ASS uses BBGGRR format, which fucking sucks
|
||
|
local function rgb_to_ass(color)
|
||
|
if (not string.len(color) == 6) then
|
||
|
msg.error("Invalid color: " .. color)
|
||
|
return "FFFFFF"
|
||
|
end
|
||
|
local r = string.sub(color, 1, 2)
|
||
|
local g = string.sub(color, 3, 4)
|
||
|
local b = string.sub(color, 5, 6)
|
||
|
return string.upper(b .. g .. r)
|
||
|
end
|
||
|
|
||
|
local function grab_chapter_name_at(sec)
|
||
|
zassert(state.chapters)
|
||
|
local name = nil
|
||
|
local psec = -1
|
||
|
for _, c in ipairs(state.chapters) do
|
||
|
if (sec > c.time) then
|
||
|
name = c.title
|
||
|
end
|
||
|
zassert(psec <= c.time)
|
||
|
psec = c.time
|
||
|
end
|
||
|
return name
|
||
|
end
|
||
|
|
||
|
local function format_time(t)
|
||
|
local h = math.floor(t / (60 * 60))
|
||
|
t = t - (h * 60 * 60)
|
||
|
local m = math.floor(t / 60)
|
||
|
local s = t - (m * 60)
|
||
|
return string.format("%.2d:%.2d:%.2d", h, m, s)
|
||
|
end
|
||
|
|
||
|
local function round(n)
|
||
|
zassert(n >= 0)
|
||
|
return math.floor(n + 0.5)
|
||
|
end
|
||
|
|
||
|
local function clamp(n, min, max)
|
||
|
return math.min(math.max(n, min), max)
|
||
|
end
|
||
|
|
||
|
local function hover_to_sec(mx, dw, duration)
|
||
|
zassert(duration)
|
||
|
local n = duration * ((mx + 0.5) / dw)
|
||
|
return clamp(n, 0, duration)
|
||
|
end
|
||
|
|
||
|
local function render()
|
||
|
state.osd:update()
|
||
|
state.osd.data = nil
|
||
|
end
|
||
|
|
||
|
local function draw_append(text)
|
||
|
if (state.osd.data == nil) then
|
||
|
state.osd.data = text
|
||
|
else
|
||
|
state.osd.data = state.osd.data .. '\n' .. text
|
||
|
end
|
||
|
end
|
||
|
|
||
|
local function draw_rect_point(x0, y0, x1, y1, x2, y2, x3, y3, color, opt)
|
||
|
local s = '{\\pos(0, 0)}'
|
||
|
opt = opt or {}
|
||
|
s = s .. '{\\1c&' .. color .. '&}'
|
||
|
s = s .. '{\\1a&' .. (opt.alpha or "00") .. '&}'
|
||
|
s = s .. '{\\bord' .. (opt.bw or '0') .. '}'
|
||
|
s = s .. '{\\3c&' .. (opt.bcolor or "000000") .. '&}'
|
||
|
s = s .. string.format(
|
||
|
'{\\p1}m %d %d l %d %d %d %d %d %d{\\p0}',
|
||
|
x0, y0, x1, y1, x2, y2, x3, y3
|
||
|
)
|
||
|
draw_append(s)
|
||
|
end
|
||
|
|
||
|
local function draw_rect(x, y, w, h, color, opt)
|
||
|
draw_rect_point(
|
||
|
x, y,
|
||
|
x + w, y,
|
||
|
x + w, y + h,
|
||
|
x, y + h,
|
||
|
color, opt
|
||
|
)
|
||
|
end
|
||
|
|
||
|
local function draw_text(x, y, size, text, opt)
|
||
|
local s = string.format('{\\pos(%d, %d)}{\\fs%d}', x, y, size)
|
||
|
opt = opt or {}
|
||
|
s = s .. '{\\bord' .. (opt.bw or '0') .. '}'
|
||
|
s = s .. '{\\3c&' .. (opt.bcolor or "000000") .. '&}'
|
||
|
s = s .. text
|
||
|
draw_append(s)
|
||
|
end
|
||
|
|
||
|
-- TODO: make this less janky.
|
||
|
local function pbar_draw()
|
||
|
local dpy_w = state.dpy_w
|
||
|
local dpy_h = state.dpy_h
|
||
|
local ypos = 0
|
||
|
local play_pos = mp.get_property_native("percent-pos")
|
||
|
local duration = state.duration
|
||
|
local clist = state.chapters
|
||
|
|
||
|
zassert(state.pbar == pbar_minimized or state.pbar == pbar_active)
|
||
|
|
||
|
if (play_pos == nil or dpy_w == 0 or dpy_h == 0) then
|
||
|
return
|
||
|
end
|
||
|
|
||
|
-- L0: playback cursor
|
||
|
local pb_h = state.pbar == pbar_minimized and opt.pbar_minimized_height or opt.pbar_height
|
||
|
zassert(pb_h > 0)
|
||
|
pb_h = dpy_h * (pb_h / 100)
|
||
|
pb_h = math.max(round(pb_h), 4)
|
||
|
local pb_w = dpy_w * (play_pos/100.0)
|
||
|
local pb_y = dpy_h - (pb_h + ypos)
|
||
|
draw_rect(0, pb_y, pb_w, pb_h, opt.pbar_color)
|
||
|
draw_rect(pb_w, pb_y, dpy_w - pb_w, pb_h, "000000", { alpha = opt.pbar_bg_alpha })
|
||
|
ypos = ypos + pb_h
|
||
|
|
||
|
if (duration) then
|
||
|
-- L1: cache cusor
|
||
|
if (state.cached_ranges and opt.cachebar_height > 0) then
|
||
|
zassert(#state.cached_ranges > 0)
|
||
|
local ch = dpy_h * (opt.cachebar_height / 100)
|
||
|
ch = math.max(round(ch), 2)
|
||
|
draw_rect(
|
||
|
0, dpy_h - (ch + ypos), dpy_w, ch,
|
||
|
opt.cachebar_uncached_color,
|
||
|
{ alpha = opt.cachebar_uncached_alpha }
|
||
|
)
|
||
|
for _, range in ipairs(state.cached_ranges) do
|
||
|
local s = range['start']
|
||
|
local e = range['end']
|
||
|
local sp = dpy_w * (s / duration)
|
||
|
local ep = (dpy_w * (e / duration)) - sp
|
||
|
|
||
|
draw_rect(sp, dpy_h - (ch + ypos), ep, ch, opt.cachebar_color)
|
||
|
end
|
||
|
ypos = ypos + ch
|
||
|
end
|
||
|
|
||
|
-- L0-???: chapters
|
||
|
if (clist and opt.chapter_marker_size > 0) then
|
||
|
zassert(#clist > 0)
|
||
|
local bw = opt.chapter_marker_border_width
|
||
|
local tw = opt.chapter_marker_size
|
||
|
local miny = tw + bw + 1 -- +1 for pad
|
||
|
local y = dpy_h - math.max(pb_h / 2, miny)
|
||
|
for _, c in ipairs(clist) do
|
||
|
local x = dpy_w * (c.time / duration)
|
||
|
draw_rect_point(
|
||
|
x - tw, y,
|
||
|
x, y - tw,
|
||
|
x + tw, y,
|
||
|
x, y + tw,
|
||
|
opt.chapter_marker_color,
|
||
|
{ bw = bw, bcolor = opt.chapter_marker_border_color }
|
||
|
)
|
||
|
end
|
||
|
ypos = math.max(ypos, dpy_h - (y + tw + bw))
|
||
|
end
|
||
|
end
|
||
|
|
||
|
if (state.pbar == pbar_active) then
|
||
|
local fs = opt.font_size
|
||
|
local pad = opt.font_pad
|
||
|
local fopt = { bw = opt.font_border_width, bcolor = opt.font_border_color }
|
||
|
|
||
|
-- L2: timeline
|
||
|
-- LHS: current playback position
|
||
|
local time = mp.get_property_osd("time-pos", "00:00:00")
|
||
|
draw_text(pad, dpy_h - (ypos + fs), fs, time, fopt)
|
||
|
-- RHS: time/playback remaining
|
||
|
local rem = "-" .. mp.get_property_osd(opt.timeline_rhs, "99:99:99")
|
||
|
draw_text(dpy_w - pad, dpy_h - (ypos + fs), fs, "{\\an9}" .. rem, fopt)
|
||
|
ypos = ypos + fs + (fopt.bw * 2)
|
||
|
|
||
|
if (duration) then
|
||
|
zassert(state.mouse)
|
||
|
|
||
|
-- L0-2: hovered timeline
|
||
|
local hover_sec = hover_to_sec(state.mouse.x, dpy_w, duration)
|
||
|
local hover_text = format_time(hover_sec)
|
||
|
draw_rect(
|
||
|
math.max(state.mouse.x - 1, 0), dpy_h - ypos,
|
||
|
2, ypos, opt.hover_bar_color
|
||
|
)
|
||
|
local fw = fs * 2 -- guesstimate ¯\_(ツ)_/¯
|
||
|
local x = clamp(state.mouse.x, pad + fw, dpy_w - (pad + fw))
|
||
|
draw_text(
|
||
|
x, dpy_h - (ypos + fs), fs,
|
||
|
"{\\an8}" .. hover_text, fopt
|
||
|
)
|
||
|
ypos = ypos + fs + (fopt.bw * 2)
|
||
|
|
||
|
-- L3: chapter name
|
||
|
local cname = clist and grab_chapter_name_at(hover_sec) or nil
|
||
|
if cname then
|
||
|
zassert(cname)
|
||
|
local fw = string.len(cname) * fs * 0.28 -- guesstimate again
|
||
|
local x = clamp(state.mouse.x, pad + fw, dpy_w - (pad + fw))
|
||
|
draw_text(
|
||
|
x, dpy_h - (ypos + fs),
|
||
|
fs, "{\\an8}" .. cname, fopt
|
||
|
)
|
||
|
ypos = ypos + fs + (fopt.bw * 2)
|
||
|
end
|
||
|
|
||
|
-- L4: preview thumbnail
|
||
|
if not state.thumbfast.disabled then
|
||
|
local pw = opt.preview_border_width
|
||
|
local hpad = 4 + pw
|
||
|
local tw = state.thumbfast.width
|
||
|
local th = state.thumbfast.height
|
||
|
local y = dpy_h - (ypos + th + pw)
|
||
|
local x = state.mouse.x - (tw / 2)
|
||
|
x = clamp(x, hpad, dpy_w - (hpad + tw))
|
||
|
mp.commandv(
|
||
|
"script-message-to", "thumbfast", "thumb",
|
||
|
hover_sec, x, y
|
||
|
)
|
||
|
ypos = ypos + th + pw
|
||
|
|
||
|
-- L4: preview border
|
||
|
if pw > 0 then
|
||
|
local c = opt.preview_border_color
|
||
|
draw_rect(
|
||
|
x, y, tw, th, "161616",
|
||
|
{ alpha = "7F", bw = pw, bcolor = c }
|
||
|
)
|
||
|
ypos = ypos + pw
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
|
||
|
render()
|
||
|
end
|
||
|
|
||
|
local function pbar_pressed()
|
||
|
zassert(state.mouse.hover)
|
||
|
zassert(state.pbar == pbar_active)
|
||
|
if (state.duration) then
|
||
|
mp.set_property("time-pos", hover_to_sec(
|
||
|
state.mouse.x, state.dpy_w, state.duration
|
||
|
));
|
||
|
end
|
||
|
end
|
||
|
|
||
|
local function pbar_update(next_state)
|
||
|
local dpy_w = state.dpy_w
|
||
|
local dpy_h = state.dpy_h
|
||
|
|
||
|
if (dpy_w == 0 or dpy_h == 0 or
|
||
|
state.pbar == next_state or state.pbar == pbar_uninit)
|
||
|
then
|
||
|
return
|
||
|
end
|
||
|
|
||
|
zassert(dpy_w > 0)
|
||
|
zassert(dpy_h > 0)
|
||
|
|
||
|
local statestr = {
|
||
|
[pbar_uninit] = "uninit", [pbar_active] = "active",
|
||
|
[pbar_minimized] = "minimized", [pbar_hidden] = "hidden"
|
||
|
}
|
||
|
msg.debug('[UPDATE]: ', statestr[state.pbar], '=> ', statestr[next_state]);
|
||
|
|
||
|
-- TODO: reduce latency when pbar is active
|
||
|
if (next_state == pbar_active) then
|
||
|
state.pbar = pbar_active
|
||
|
pbar_draw()
|
||
|
if (not state.press_bounded) then
|
||
|
mp.add_forced_key_binding('mbtn_left', 'pbar_pressed', pbar_pressed)
|
||
|
state.press_bounded = true
|
||
|
end
|
||
|
if (not state.time_observed) then
|
||
|
mp.observe_property("time-pos", nil, pbar_draw)
|
||
|
state.time_observed = true
|
||
|
end
|
||
|
else
|
||
|
if (next_state == pbar_minimized) then
|
||
|
zassert(opt.pbar_minimized_height > 0)
|
||
|
state.pbar = pbar_minimized
|
||
|
pbar_draw()
|
||
|
if (not state.time_observed) then
|
||
|
mp.observe_property("time-pos", nil, pbar_draw)
|
||
|
state.time_observed = true
|
||
|
end
|
||
|
elseif (next_state == pbar_hidden) then
|
||
|
zassert(state.pbar ~= pbar_hidden)
|
||
|
state.pbar = pbar_hidden
|
||
|
state.osd.data = '' -- clear everything
|
||
|
render()
|
||
|
zassert(state.time_observed)
|
||
|
mp.unobserve_property(pbar_draw)
|
||
|
state.time_observed = false
|
||
|
else
|
||
|
zassert(false, "unreachable")
|
||
|
end
|
||
|
|
||
|
if (state.press_bounded) then
|
||
|
mp.remove_key_binding('pbar_pressed')
|
||
|
state.press_bounded = false
|
||
|
end
|
||
|
state.mouse = nil
|
||
|
if (state.thumbfast.available) then
|
||
|
mp.commandv("script-message-to", "thumbfast", "clear")
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
|
||
|
local function pbar_minimize_or_hide()
|
||
|
msg.debug("[MIN-HIDE]")
|
||
|
if (opt.pbar_minimized_height > 0 and not (opt.pbar_fullscreen_hide and state.fullscreen)) then
|
||
|
pbar_update(pbar_minimized)
|
||
|
else
|
||
|
pbar_update(pbar_hidden)
|
||
|
end
|
||
|
end
|
||
|
|
||
|
local function mouse_isactive(m)
|
||
|
return m.hover and math.abs(m.y - state.dpy_h) < opt.proximity
|
||
|
end
|
||
|
|
||
|
local function update_mouse_pos(kind, mouse)
|
||
|
zassert(kind == "mouse-pos")
|
||
|
state.mouse_prev = state.mouse or { hover = false }
|
||
|
state.mouse = mouse
|
||
|
msg.debug('[MOUSE] hover = ', mouse.hover, ' x = ', mouse.x, ' y = ', mouse.y)
|
||
|
|
||
|
local dpy_w = state.dpy_w
|
||
|
local dpy_h = state.dpy_h
|
||
|
|
||
|
if (dpy_w == 0 or dpy_h == 0) then
|
||
|
return
|
||
|
end
|
||
|
|
||
|
zassert(dpy_w > 0)
|
||
|
zassert(dpy_h > 0)
|
||
|
zassert(mouse)
|
||
|
|
||
|
-- TODO: ensure there's enough height to draw our stuff ?
|
||
|
if (mouse_isactive(state.mouse_prev) and mouse_isactive(mouse)) then
|
||
|
-- TODO: a better way to do this without killing/resuming a
|
||
|
-- timer on each mouse update?
|
||
|
state.timeout:kill()
|
||
|
state.timeout:resume()
|
||
|
pbar_update(pbar_active)
|
||
|
else
|
||
|
state.timeout:kill()
|
||
|
pbar_minimize_or_hide()
|
||
|
end
|
||
|
end
|
||
|
|
||
|
local function update_fullscreen(kind, fs)
|
||
|
zassert(kind == "fullscreen")
|
||
|
state.fullscreen = fs
|
||
|
msg.debug('[FULLSCREEN] fs = ', fs)
|
||
|
pbar_minimize_or_hide()
|
||
|
end
|
||
|
|
||
|
local function update_focus(kind, foc)
|
||
|
zassert(kind == "focused")
|
||
|
msg.debug('[FOCUS] focus = ', foc)
|
||
|
state.mouse_prev = { hover = false, x = 0, y = 0 }
|
||
|
end
|
||
|
|
||
|
local function set_dpy_size(kind, osd)
|
||
|
zassert(kind == "osd-dimensions")
|
||
|
state.dpy_w = osd.w
|
||
|
state.osd.res_x = osd.w
|
||
|
state.dpy_h = osd.h
|
||
|
state.osd.res_y = osd.h
|
||
|
msg.debug('[DPY] w = ', osd.w, ' h = ', osd.h)
|
||
|
|
||
|
-- HACK: ensure we don't obstruct the console (excluding the preview and hovered timeline)
|
||
|
-- the shared_script_property_* functions are documented as undocumented :)
|
||
|
-- and users are discouraged to use them, but whatever...
|
||
|
local b = (opt.font_size + (opt.font_border_width * 2) + 8) / state.dpy_h -- +8 padding
|
||
|
b = b + ((opt.pbar_minimized_height + opt.cachebar_height) / 100.0)
|
||
|
utils.shared_script_property_set(
|
||
|
'osc-margins',
|
||
|
string.format('%f,%f,%f,%f', 0, 0, 0, b)
|
||
|
)
|
||
|
end
|
||
|
|
||
|
local function set_cache_state(kind, c)
|
||
|
zassert(kind == "demuxer-cache-state")
|
||
|
if (c == nil) then
|
||
|
state.cached_ranges = nil
|
||
|
else
|
||
|
local r = c['seekable-ranges']
|
||
|
if #r > 0 then
|
||
|
state.cached_ranges = r
|
||
|
else
|
||
|
state.cached_ranges = nil
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
|
||
|
local function set_duration(kind, d)
|
||
|
zassert(kind == "duration")
|
||
|
state.duration = d
|
||
|
end
|
||
|
|
||
|
local function set_chapter_list(kind, c)
|
||
|
zassert(kind == "chapter-list")
|
||
|
if (c and #c > 0) then
|
||
|
state.chapters = c
|
||
|
table.sort(state.chapters, function(a, b) return a.time < b.time end)
|
||
|
else
|
||
|
state.chapters = nil
|
||
|
end
|
||
|
end
|
||
|
|
||
|
local function set_thumbfast(json)
|
||
|
local data = utils.parse_json(json)
|
||
|
if (type(data) ~= "table" or not data.width or not data.height) then
|
||
|
msg.error("thumbfast-info: received json didn't produce a table with thumbnail information")
|
||
|
else
|
||
|
state.thumbfast = data
|
||
|
end
|
||
|
end
|
||
|
|
||
|
local function pbar_init(kind, thing)
|
||
|
zassert(kind == 'vo-configured')
|
||
|
msg.debug("[VO-CONFIGURED]", thing, state.pbar)
|
||
|
|
||
|
if thing then
|
||
|
zassert(state.pbar == pbar_uninit)
|
||
|
state.pbar = pbar_hidden
|
||
|
if (opt.pbar_minimized_height > 0) then
|
||
|
pbar_update(pbar_minimized)
|
||
|
end
|
||
|
mp.unobserve_property(pbar_init)
|
||
|
end
|
||
|
end
|
||
|
|
||
|
local function init()
|
||
|
mpopt.read_options(opt, "mfpbar")
|
||
|
for k,v in pairs(opt) do
|
||
|
if string.find(k, "_color$") then
|
||
|
opt[k] = rgb_to_ass(v)
|
||
|
end
|
||
|
end
|
||
|
|
||
|
if opt.debug then
|
||
|
msg.debug("[ASSERTIONS] enabled")
|
||
|
zassert = assert
|
||
|
else
|
||
|
zassert(false)
|
||
|
end
|
||
|
|
||
|
state.osd = mp.create_osd_overlay("ass-events")
|
||
|
mp.observe_property("osd-dimensions", "native", set_dpy_size)
|
||
|
mp.observe_property('demuxer-cache-state', 'native', set_cache_state)
|
||
|
mp.observe_property('duration', 'native', set_duration)
|
||
|
mp.observe_property('chapter-list', 'native', set_chapter_list)
|
||
|
mp.register_script_message("thumbfast-info", set_thumbfast)
|
||
|
mp.observe_property('fullscreen', 'native', update_fullscreen)
|
||
|
mp.observe_property('focused', 'native', update_focus)
|
||
|
|
||
|
-- NOTE: mouse-pos doesn't work mpv versions older than v33
|
||
|
mp.observe_property("mouse-pos", "native", update_mouse_pos)
|
||
|
|
||
|
if (opt.minimize_timeout > 0) then
|
||
|
state.timeout = mp.add_timeout(opt.minimize_timeout, pbar_minimize_or_hide)
|
||
|
state.timeout:kill() -- update_mouse_pos() will kill/resume this as needed
|
||
|
else
|
||
|
state.timeout = { kill = function() end, resume = function() end }
|
||
|
end
|
||
|
|
||
|
-- HACK: mpv doesn't open the window instantly by default.
|
||
|
-- so wait for 'vo-configured' as a hook for when the window opens.
|
||
|
mp.observe_property('vo-configured', 'native', pbar_init)
|
||
|
end
|
||
|
|
||
|
init()
|