Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- local assdraw = require 'mp.assdraw'
- local msg = require 'mp.msg'
- local opt = require 'mp.options'
- local utils = require 'mp.utils'
- --
- -- Parameters
- --
- -- default user option values
- -- do not touch, change them in osc.conf
- local user_opts = {
- showwindowed = true, -- show OSC when windowed?
- showfullscreen = true, -- show OSC when fullscreen?
- scalewindowed = 1, -- scaling of the controller when windowed
- scalefullscreen = 1, -- scaling of the controller when fullscreen
- scaleforcedwindow = 2, -- scaling when rendered on a forced window
- vidscale = true, -- scale the controller with the video?
- valign = 0.8, -- vertical alignment, -1 (top) to 1 (bottom)
- halign = 0, -- horizontal alignment, -1 (left) to 1 (right)
- barmargin = 0, -- vertical margin of top/bottombar
- boxalpha = 150, -- alpha of the background box,
- -- 0 (opaque) to 255 (fully transparent)
- hidetimeout = 30000, -- duration in ms until the OSC hides if no
- -- mouse movement. enforced non-negative for the
- -- user, but internally negative is "always-on".
- fadeduration = 200, -- duration of fade out in ms, 0 = no fade
- deadzonesize = 0.9, -- size of deadzone
- minmousemove = 0, -- minimum amount of pixels the mouse has to
- -- move between ticks to make the OSC show up
- iamaprogrammer = false, -- use native mpv values and disable OSC
- -- internal track list management (and some
- -- functions that depend on it)
- layout = "bottombar",
- seekbarstyle = "bar", -- bar, diamond or knob
- seekbarhandlesize = 0.6, -- size ratio of the diamond and knob handle
- seekrangestyle = "inverted", -- bar, line, slider, inverted or none
- seekrangeseparate = true, -- wether the seekranges overlay on the bar-style seekbar
- seekrangealpha = 200, -- transparency of seekranges
- seekbarkeyframes = true, -- use keyframes when dragging the seekbar
- title = "${media-title}", -- string compatible with property-expansion
- -- to be shown as OSC title
- tooltipborder = 1, -- border of tooltip in bottom/topbar
- timetotal = true, -- display total time instead of remaining time?
- timems = false, -- display timecodes with milliseconds?
- visibility = "auto", -- only used at init to set visibility_mode(...)
- boxmaxchars = 80 -- title crop threshold for box layout
- }
- -- read_options may modify hidetimeout, so save the original default value in
- -- case the user set hidetimeout < 0 and we need the default instead.
- local hidetimeout_def = user_opts.hidetimeout
- -- read options from config and command-line
- opt.read_options(user_opts, "osc")
- if user_opts.hidetimeout < 0 then
- user_opts.hidetimeout = hidetimeout_def
- msg.warn("hidetimeout cannot be negative. Using " .. user_opts.hidetimeout)
- end
- local osc_param = { -- calculated by osc_init()
- playresy = 0, -- canvas size Y
- playresx = 0, -- canvas size X
- display_aspect = 1,
- unscaled_y = 0,
- areas = {}
- }
- local osc_styles = {
- bigButtons = "{\\blur0\\bord0\\1c&HFFFFFF\\3c&HFFFFFF\\fs50\\fnmpv-osd-symbols}",
- smallButtonsL = "{\\blur0\\bord0\\1c&HFFFFFF\\3c&HFFFFFF\\fs19\\fnmpv-osd-symbols}",
- smallButtonsLlabel = "{\\fscx105\\fscy105\\fn" ..
- mp.get_property("options/osd-font") .. "}",
- smallButtonsR = "{\\blur0\\bord0\\1c&HFFFFFF\\3c&HFFFFFF\\fs30\\fnmpv-osd-symbols}",
- topButtons = "{\\blur0\\bord0\\1c&HFFFFFF\\3c&HFFFFFF\\fs12\\fnmpv-osd-symbols}",
- elementDown = "{\\1c&H999999}",
- timecodes = "{\\blur0\\bord0\\1c&HFFFFFF\\3c&HFFFFFF\\fs20}",
- vidtitle = "{\\blur0\\bord0\\1c&HFFFFFF\\3c&HFFFFFF\\fs12\\q2}",
- box = "{\\rDefault\\blur0\\bord1\\1c&H000000\\3c&HFFFFFF}",
- topButtonsBar = "{\\blur0\\bord0\\1c&HFFFFFF\\3c&HFFFFFF\\fs18\\fnmpv-osd-symbols}",
- smallButtonsBar = "{\\blur0\\bord0\\1c&HFFFFFF\\3c&HFFFFFF\\fs28\\fnmpv-osd-symbols}",
- smallButtonsBarS = "{\\blur0\\bord0\\1c&HFFFFFF\\3c&HFFFFFF\\fs18\\fnmpv-osd-symbols}",
- smallButtonsBarF = "{\\blur0\\bord0\\1c&HFFFFFF\\3c&HFFFFFF\\fs26\\fnmpv-osd-symbols}",
- timecodesBar = "{\\blur0\\bord0\\1c&HFFFFFF\\3c&HFFFFFF\\fs22}",
- timecodesBack = "{\\blur0\\bord0\\1c&H979797\\3c&HFFFFFF\\fs18\\q2}",
- timePosBar = "{\\blur0\\bord" .. user_opts.tooltipborder ..
- "\\1c&HFFFFFF\\3c&H000000\\fs22}",
- vidtitleBar = "{\\blur0\\bord0\\1c&HFFFFFF\\3c&HFFFFFF\\fs18\\q2}"
- }
- -- internal states, do not touch
- local state = {
- showtime, -- time of last invocation (last mouse move)
- osc_visible = false,
- anistart, -- time when the animation started
- anitype, -- current type of animation
- animation, -- current animation alpha
- mouse_down_counter = 0, -- used for softrepeat
- active_element = nil, -- nil = none, 0 = background, 1+ = see elements[]
- active_event_source = nil, -- the "button" that issued the current event
- rightTC_trem = not user_opts.timetotal, -- if the right timecode should display total or remaining time
- 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?
- last_mouseX,
- last_mouseY, -- last mouse position, to detect significant mouse movement
- message_text,
- message_timeout,
- fullscreen = false,
- timer = nil,
- cache_idle = false,
- idle = false,
- enabled = true,
- input_enabled = true,
- showhide_enabled = false,
- dmx_cache = 0
- }
- --
- -- Helperfunctions
- --
- -- scale factor for translating between real and virtual ASS coordinates
- function get_virt_scale_factor()
- local w, h = mp.get_osd_size()
- if w <= 0 or h <= 0 then return 0, 0 end
- return osc_param.playresx / w, osc_param.playresy / h
- end
- local thumbfast = {
- width = 0,
- height = 0,
- disabled = false
- }
- -- return mouse position in virtual ASS coordinates (playresx/y)
- function get_virt_mouse_pos()
- local sx, sy = get_virt_scale_factor()
- local x, y = mp.get_mouse_pos()
- return x * sx, y * sy
- end
- function set_virt_mouse_area(x0, y0, x1, y1, name)
- local sx, sy = get_virt_scale_factor()
- mp.set_mouse_area(x0 / sx, y0 / sy, x1 / sx, y1 / sy, name)
- end
- function scale_value(x0, x1, y0, y1, val)
- local m = (y1 - y0) / (x1 - x0)
- local b = y0 - (m * x0)
- return (m * val) + b
- end
- -- returns hitbox spanning coordinates (top left, bottom right corner)
- -- according to alignment
- function get_hitbox_coords(x, y, an, w, h)
- local alignments = {
- [1] = function() return x, y - h, x + w, y end,
- [2] = function() return x - (w / 2), y - h, x + (w / 2), y end,
- [3] = function() return x - w, y - h, x, y end,
- [4] = function() return x, y - (h / 2), x + w, y + (h / 2) end,
- [5] = function()
- return x - (w / 2), y - (h / 2), x + (w / 2), y + (h / 2)
- end,
- [6] = function() return x - w, y - (h / 2), x, y + (h / 2) end,
- [7] = function() return x, y, x + w, y + h end,
- [8] = function() return x - (w / 2), y, x + (w / 2), y + h end,
- [9] = function() return x - w, y, x, y + h end
- }
- return alignments[an]()
- end
- function get_hitbox_coords_geo(geometry)
- return get_hitbox_coords(geometry.x, geometry.y, geometry.an, geometry.w,
- geometry.h)
- end
- function get_element_hitbox(element)
- return element.hitbox.x1, element.hitbox.y1, element.hitbox.x2,
- element.hitbox.y2
- end
- function mouse_hit(element) return mouse_hit_coords(get_element_hitbox(element)) end
- function mouse_hit_coords(bX1, bY1, bX2, bY2)
- local mX, mY = get_virt_mouse_pos()
- return (mX >= bX1 and mX <= bX2 and mY >= bY1 and mY <= bY2)
- end
- function limit_range(min, max, val)
- if val > max then
- val = max
- elseif val < min then
- val = min
- end
- return val
- end
- -- translate value into element coordinates
- function get_slider_ele_pos_for(element, val)
- local ele_pos = scale_value(element.slider.min.value,
- element.slider.max.value,
- element.slider.min.ele_pos,
- element.slider.max.ele_pos, val)
- return limit_range(element.slider.min.ele_pos, element.slider.max.ele_pos,
- ele_pos)
- end
- -- translates global (mouse) coordinates to value
- function get_slider_value_at(element, glob_pos)
- local val = scale_value(element.slider.min.glob_pos,
- element.slider.max.glob_pos,
- element.slider.min.value, element.slider.max.value,
- glob_pos)
- return limit_range(element.slider.min.value, element.slider.max.value, val)
- end
- -- get value at current mouse position
- function get_slider_value(element)
- return get_slider_value_at(element, get_virt_mouse_pos())
- end
- function countone(val)
- if not (user_opts.iamaprogrammer) then val = val + 1 end
- return val
- end
- -- align: -1 .. +1
- -- frame: size of the containing area
- -- obj: size of the object that should be positioned inside the area
- -- margin: min. distance from object to frame (as long as -1 <= align <= +1)
- function get_align(align, frame, obj, margin)
- return (frame / 2) + (((frame / 2) - margin - (obj / 2)) * align)
- end
- -- multiplies two alpha values, formular can probably be improved
- function mult_alpha(alphaA, alphaB)
- return 255 - (((1 - (alphaA / 255)) * (1 - (alphaB / 255))) * 255)
- end
- function add_area(name, x1, y1, x2, y2)
- -- create area if needed
- if (osc_param.areas[name] == nil) then osc_param.areas[name] = {} end
- table.insert(osc_param.areas[name], {x1 = x1, y1 = y1, x2 = x2, y2 = y2})
- end
- function ass_append_alpha(ass, alpha, modifier)
- local ar = {}
- for ai, av in pairs(alpha) do
- av = mult_alpha(av, modifier)
- if state.animation then av = mult_alpha(av, state.animation) end
- ar[ai] = av
- end
- ass:append(string.format("{\\1a&H%X&\\2a&H%X&\\3a&H%X&\\4a&H%X&}", ar[1],
- ar[2], ar[3], ar[4]))
- end
- function ass_draw_rr_h_cw(ass, x0, y0, x1, y1, r1, hexagon, r2)
- if hexagon then
- ass:hexagon_cw(x0, y0, x1, y1, r1, r2)
- else
- ass:round_rect_cw(x0, y0, x1, y1, r1, r2)
- end
- end
- function ass_draw_rr_h_ccw(ass, x0, y0, x1, y1, r1, hexagon, r2)
- if hexagon then
- ass:hexagon_ccw(x0, y0, x1, y1, r1, r2)
- else
- ass:round_rect_ccw(x0, y0, x1, y1, r1, r2)
- end
- end
- --
- -- Tracklist Management
- --
- local nicetypes = {video = "Video", audio = "Audio", sub = "Subtitle"}
- -- updates the OSC internal playlists, should be run each time the track-layout changes
- function update_tracklist()
- local tracktable = mp.get_property_native("track-list", {})
- -- by osc_id
- tracks_osc = {}
- tracks_osc.video, tracks_osc.audio, tracks_osc.sub = {}, {}, {}
- -- by mpv_id
- tracks_mpv = {}
- tracks_mpv.video, tracks_mpv.audio, tracks_mpv.sub = {}, {}, {}
- for n = 1, #tracktable do
- if not (tracktable[n].type == "unknown") then
- local type = tracktable[n].type
- local mpv_id = tonumber(tracktable[n].id)
- -- by osc_id
- table.insert(tracks_osc[type], tracktable[n])
- -- by mpv_id
- tracks_mpv[type][mpv_id] = tracktable[n]
- tracks_mpv[type][mpv_id].osc_id = #tracks_osc[type]
- end
- end
- end
- -- return a nice list of tracks of the given type (video, audio, sub)
- function get_tracklist(type)
- local msg = "Available " .. nicetypes[type] .. " Tracks: "
- if #tracks_osc[type] == 0 then
- msg = msg .. "none"
- else
- for n = 1, #tracks_osc[type] do
- local track = tracks_osc[type][n]
- local lang, title, selected = "unknown", "", "○"
- if not (track.lang == nil) then lang = track.lang end
- if not (track.title == nil) then title = track.title end
- if (track.id == tonumber(mp.get_property(type))) then
- selected = "●"
- end
- msg =
- msg .. "\n" .. selected .. " " .. n .. ": [" .. lang .. "] " ..
- title
- end
- end
- return msg
- end
- -- relatively change the track of given <type> by <next> tracks
- -- (+1 -> next, -1 -> previous)
- function set_track(type, next)
- local current_track_mpv, current_track_osc
- if (mp.get_property(type) == "no") then
- current_track_osc = 0
- else
- current_track_mpv = tonumber(mp.get_property(type))
- current_track_osc = tracks_mpv[type][current_track_mpv].osc_id
- end
- local new_track_osc = (current_track_osc + next) % (#tracks_osc[type] + 1)
- local new_track_mpv
- if new_track_osc == 0 then
- new_track_mpv = "no"
- else
- new_track_mpv = tracks_osc[type][new_track_osc].id
- end
- mp.commandv("set", type, new_track_mpv)
- if (new_track_osc == 0) then
- show_message(nicetypes[type] .. " Track: none")
- else
- show_message(nicetypes[type] .. " Track: " .. new_track_osc .. "/" ..
- #tracks_osc[type] .. " [" ..
- (tracks_osc[type][new_track_osc].lang or "unknown") ..
- "] " .. (tracks_osc[type][new_track_osc].title or ""))
- end
- end
- -- get the currently selected track of <type>, OSC-style counted
- function get_track(type)
- local track = mp.get_property(type)
- if track ~= "no" and track ~= nil then
- local tr = tracks_mpv[type][tonumber(track)]
- if tr then return tr.osc_id end
- end
- return 0
- end
- --
- -- Element Management
- --
- local elements = {}
- function prepare_elements()
- -- remove elements without layout or invisble
- local elements2 = {}
- for n, element in pairs(elements) do
- if not (element.layout == nil) and (element.visible) then
- table.insert(elements2, element)
- end
- end
- elements = elements2
- function elem_compare(a, b) return a.layout.layer < b.layout.layer end
- table.sort(elements, elem_compare)
- for _, element in pairs(elements) do
- local elem_geo = element.layout.geometry
- -- Calculate the hitbox
- local bX1, bY1, bX2, bY2 = get_hitbox_coords_geo(elem_geo)
- element.hitbox = {x1 = bX1, y1 = bY1, x2 = bX2, y2 = bY2}
- local style_ass = assdraw.ass_new()
- -- prepare static elements
- style_ass:append("{}") -- hack to troll new_event into inserting a \n
- style_ass:new_event()
- style_ass:pos(elem_geo.x, elem_geo.y)
- style_ass:an(elem_geo.an)
- style_ass:append(element.layout.style)
- element.style_ass = style_ass
- local static_ass = assdraw.ass_new()
- if (element.type == "box") then
- -- draw box
- static_ass:draw_start()
- ass_draw_rr_h_cw(static_ass, 0, 0, elem_geo.w, elem_geo.h,
- element.layout.box.radius,
- element.layout.box.hexagon)
- static_ass:draw_stop()
- elseif (element.type == "slider") then
- -- draw static slider parts
- local r1 = 0
- local r2 = 0
- local slider_lo = element.layout.slider
- -- offset between element outline and drag-area
- local foV = slider_lo.border + slider_lo.gap
- -- calculate positions of min and max points
- if (slider_lo.stype ~= "bar") then
- r1 = elem_geo.h / 2
- element.slider.min.ele_pos = elem_geo.h / 2
- element.slider.max.ele_pos = elem_geo.w - (elem_geo.h / 2)
- if (slider_lo.stype == "diamond") then
- r2 = (elem_geo.h - 2 * slider_lo.border) / 2
- elseif (slider_lo.stype == "knob") then
- r2 = r1
- end
- else
- element.slider.min.ele_pos = slider_lo.border + slider_lo.gap
- element.slider.max.ele_pos =
- elem_geo.w - (slider_lo.border + slider_lo.gap)
- end
- element.slider.min.glob_pos =
- element.hitbox.x1 + element.slider.min.ele_pos
- element.slider.max.glob_pos =
- element.hitbox.x1 + element.slider.max.ele_pos
- -- -- --
- static_ass:draw_start()
- -- the box
- ass_draw_rr_h_cw(static_ass, 0, 0, elem_geo.w, elem_geo.h, r1,
- slider_lo.stype == "diamond")
- -- the "hole"
- ass_draw_rr_h_ccw(static_ass, slider_lo.border, slider_lo.border,
- elem_geo.w - slider_lo.border,
- elem_geo.h - slider_lo.border, r2,
- slider_lo.stype == "diamond")
- -- marker nibbles
- if not (element.slider.markerF == nil) and (slider_lo.gap > 0) then
- local markers = element.slider.markerF()
- for _, marker in pairs(markers) do
- if (marker > element.slider.min.value) and
- (marker < element.slider.max.value) then
- local s = get_slider_ele_pos_for(element, marker)
- if (slider_lo.gap > 1) then -- draw triangles
- local a = slider_lo.gap / 0.5 -- 0.866
- -- top
- if (slider_lo.nibbles_top) then
- static_ass:move_to(s - (a / 2), slider_lo.border)
- static_ass:line_to(s + (a / 2), slider_lo.border)
- static_ass:line_to(s, foV)
- end
- -- bottom
- if (slider_lo.nibbles_bottom) then
- static_ass:move_to(s - (a / 2), elem_geo.h -
- slider_lo.border)
- static_ass:line_to(s, elem_geo.h - foV)
- static_ass:line_to(s + (a / 2), elem_geo.h -
- slider_lo.border)
- end
- else -- draw 2x1px nibbles
- -- top
- if (slider_lo.nibbles_top) then
- static_ass:rect_cw(s - 1, slider_lo.border,
- s + 1, slider_lo.border +
- slider_lo.gap)
- end
- -- bottom
- if (slider_lo.nibbles_bottom) then
- static_ass:rect_cw(s - 1, elem_geo.h -
- slider_lo.border -
- slider_lo.gap, s + 1,
- elem_geo.h - slider_lo.border)
- end
- end
- end
- end
- end
- end
- element.static_ass = static_ass
- -- if the element is supposed to be disabled,
- -- style it accordingly and kill the eventresponders
- if not (element.enabled) then
- element.layout.alpha[1] = 136
- element.eventresponder = nil
- end
- end
- end
- --
- -- Element Rendering
- --
- function render_elements(master_ass)
- for n = 1, #elements do
- local element = elements[n]
- local style_ass = assdraw.ass_new()
- style_ass:merge(element.style_ass)
- ass_append_alpha(style_ass, element.layout.alpha, 0)
- if element.eventresponder and (state.active_element == n) then
- -- run render event functions
- if not (element.eventresponder.render == nil) then
- element.eventresponder.render(element)
- end
- if mouse_hit(element) then
- -- mouse down styling
- if (element.styledown) then
- style_ass:append(osc_styles.elementDown)
- end
- if (element.softrepeat) and
- (state.mouse_down_counter >= 15 and state.mouse_down_counter %
- 5 == 0) then
- element.eventresponder[state.active_event_source .. "_down"](
- element)
- end
- state.mouse_down_counter = state.mouse_down_counter + 1
- end
- end
- local elem_ass = assdraw.ass_new()
- elem_ass:merge(style_ass)
- if not (element.type == "button") then
- elem_ass:merge(element.static_ass)
- end
- if (element.type == "slider") then
- local slider_lo = element.layout.slider
- local elem_geo = element.layout.geometry
- local s_min = element.slider.min.value
- local s_max = element.slider.max.value
- -- draw pos marker
- local foH, xp
- local pos = element.slider.posF()
- local foV = slider_lo.border + slider_lo.gap
- local innerH = elem_geo.h - (2 * foV)
- local seekRanges = element.slider.seekRangesF()
- local seekRangeLineHeight = innerH / 5
- if slider_lo.stype ~= "bar" then
- foH = elem_geo.h / 2
- else
- foH = slider_lo.border + slider_lo.gap
- end
- if pos then
- xp = get_slider_ele_pos_for(element, pos)
- if slider_lo.stype ~= "bar" then
- local r = (user_opts.seekbarhandlesize * innerH) / 2
- ass_draw_rr_h_cw(elem_ass, xp - r, foH - r, xp + r, foH + r,
- r, slider_lo.stype == "diamond")
- else
- local h = 0
- if seekRanges and user_opts.seekrangeseparate and
- slider_lo.rtype ~= "inverted" then
- h = seekRangeLineHeight
- end
- elem_ass:rect_cw(foH, foV, xp, elem_geo.h - foV - h)
- if seekRanges and not user_opts.seekrangeseparate and
- slider_lo.rtype ~= "inverted" then
- -- Punch holes for the seekRanges to be drawn later
- for _, range in pairs(seekRanges) do
- if range["start"] < pos then
- local pstart =
- get_slider_ele_pos_for(element,
- range["start"])
- local pend = xp
- if pos > range["end"] then
- pend =
- get_slider_ele_pos_for(element,
- range["end"])
- end
- elem_ass:rect_ccw(pstart, elem_geo.h - foV -
- seekRangeLineHeight, pend,
- elem_geo.h - foV)
- end
- end
- end
- end
- if slider_lo.rtype == "slider" then
- ass_draw_rr_h_cw(elem_ass, foH - innerH / 6,
- foH - innerH / 6, xp, foH + innerH / 6,
- innerH / 6, slider_lo.stype == "diamond", 0)
- ass_draw_rr_h_cw(elem_ass, xp, foH - innerH / 15,
- elem_geo.w - foH + innerH / 15,
- foH + innerH / 15, 0,
- slider_lo.stype == "diamond", innerH / 15)
- for _, range in pairs(seekRanges or {}) do
- local pstart = get_slider_ele_pos_for(element,
- range["start"])
- local pend = get_slider_ele_pos_for(element,
- range["end"])
- ass_draw_rr_h_ccw(elem_ass, pstart, foH - innerH / 21,
- pend, foH + innerH / 21, innerH / 21,
- slider_lo.stype == "diamond")
- end
- end
- end
- if seekRanges then
- if slider_lo.rtype ~= "inverted" then
- elem_ass:draw_stop()
- elem_ass:merge(element.style_ass)
- ass_append_alpha(elem_ass, element.layout.alpha,
- user_opts.seekrangealpha)
- elem_ass:merge(element.static_ass)
- end
- for _, range in pairs(seekRanges) do
- local pstart = get_slider_ele_pos_for(element,
- range["start"])
- local pend = get_slider_ele_pos_for(element, range["end"])
- if slider_lo.rtype == "slider" then
- ass_draw_rr_h_cw(elem_ass, pstart, foH - innerH / 21,
- pend, foH + innerH / 21, innerH / 21,
- slider_lo.stype == "diamond")
- elseif slider_lo.rtype == "line" then
- if slider_lo.stype == "bar" then
- elem_ass:rect_cw(pstart, elem_geo.h - foV -
- seekRangeLineHeight, pend,
- elem_geo.h - foV)
- else
- ass_draw_rr_h_cw(elem_ass, pstart - innerH / 8,
- foH - innerH / 8,
- pend + innerH / 8,
- foH + innerH / 8, innerH / 8,
- slider_lo.stype == "diamond")
- end
- elseif slider_lo.rtype == "bar" then
- if slider_lo.stype ~= "bar" then
- ass_draw_rr_h_cw(elem_ass, pstart - innerH / 2, foV,
- pend + innerH / 2, foV + innerH,
- innerH / 2,
- slider_lo.stype == "diamond")
- elseif range["end"] >= (pos or 0) then
- elem_ass:rect_cw(pstart, foV, pend, elem_geo.h - foV)
- else
- elem_ass:rect_cw(pstart, elem_geo.h - foV -
- seekRangeLineHeight, pend,
- elem_geo.h - foV)
- end
- elseif slider_lo.rtype == "inverted" then
- if slider_lo.stype ~= "bar" then
- ass_draw_rr_h_ccw(elem_ass, pstart,
- (elem_geo.h / 2) - 1, pend,
- (elem_geo.h / 2) + 1, 1,
- slider_lo.stype == "diamond")
- else
- elem_ass:rect_ccw(pstart, (elem_geo.h / 2) - 1,
- pend, (elem_geo.h / 2) + 1)
- end
- end
- end
- end
- elem_ass:draw_stop()
- -- add tooltip
- if not (element.slider.tooltipF == nil) then
- if mouse_hit(element) then
- local sliderpos = get_slider_value(element)
- local tooltiplabel = element.slider.tooltipF(sliderpos)
- local an = slider_lo.tooltip_an
- local ty
- if (an == 2) then
- ty = element.hitbox.y1 - slider_lo.border
- else
- ty = element.hitbox.y1 + elem_geo.h / 2
- end
- local tx = get_virt_mouse_pos()
- local thumb_tx = tx
- if (slider_lo.adjust_tooltip) then
- if (an == 2) then
- if (sliderpos < (s_min + 3)) then
- an = an - 1
- elseif (sliderpos > (s_max - 3)) then
- an = an + 1
- end
- elseif (sliderpos > (s_max - s_min) / 2) then
- an = an + 1
- tx = tx - 5
- else
- an = an - 1
- tx = tx + 10
- end
- end
- -- tooltip label
- elem_ass:new_event()
- elem_ass:pos(tx, ty)
- elem_ass:an(an)
- elem_ass:append(slider_lo.tooltip_style)
- ass_append_alpha(elem_ass, slider_lo.alpha, 0)
- elem_ass:append(tooltiplabel)
- -- thumbnail
- if not thumbfast.disabled and thumbfast.width ~= 0 and thumbfast.height ~= 0 then
- local osd_w = mp.get_property_number("osd-width")
- if osd_w then
- local r_w, r_h = get_virt_scale_factor()
- local tooltip_font_size = (user_opts.layout == "box" or user_opts.layout == "slimbox") and 2 or 12
- local thumb_ty = user_opts.layout ~= "topbar" and element.hitbox.y1 - 8 or element.hitbox.y2 + tooltip_font_size + 8
- local thumb_pad = 2
- local thumb_margin_x = 20 / r_w
- local thumb_margin_y = (4 + user_opts.tooltipborder) / r_h + thumb_pad
- local thumb_x = math.min(osd_w - thumbfast.width - thumb_margin_x, math.max(thumb_margin_x, thumb_tx / r_w - thumbfast.width / 2))
- local thumb_y = user_opts.layout ~= "topbar" and thumb_ty / r_h - thumbfast.height - tooltip_font_size / r_h - thumb_margin_y or thumb_ty / r_h + thumb_margin_y
- thumb_x = math.floor(thumb_x + 0.5)
- thumb_y = math.floor(thumb_y + 0.5)
- elem_ass:new_event()
- elem_ass:pos(thumb_x * r_w, thumb_y * r_h)
- elem_ass:append(osc_styles.timePosBar)
- elem_ass:append("{\\1a&H20&}")
- elem_ass:draw_start()
- elem_ass:rect_cw(-thumb_pad * r_w, -thumb_pad * r_h, (thumbfast.width + thumb_pad) * r_w, (thumbfast.height + thumb_pad) * r_h)
- elem_ass:draw_stop()
- mp.commandv("script-message-to", "thumbfast", "thumb",
- mp.get_property_number("duration", 0) * (sliderpos / 100),
- thumb_x,
- thumb_y
- )
- end
- end
- else
- if thumbfast.width ~= 0 and thumbfast.height ~= 0 then
- mp.commandv("script-message-to", "thumbfast", "clear")
- end
- end
- end
- elseif (element.type == "button") then
- local buttontext
- if type(element.content) == "function" then
- buttontext = element.content() -- function objects
- elseif not (element.content == nil) then
- buttontext = element.content -- text objects
- end
- local maxchars = element.layout.button.maxchars
- if not (maxchars == nil) and (#buttontext > maxchars) then
- local max_ratio = 1.25 -- up to 25% more chars while shrinking
- local limit = math.max(0, math.floor(maxchars * max_ratio) - 3)
- if (#buttontext > limit) then
- while (#buttontext > limit) do
- buttontext = buttontext:gsub(".[\128-\191]*$", "")
- end
- buttontext = buttontext .. "..."
- end
- local _, nchars2 = buttontext:gsub(".[\128-\191]*", "")
- local stretch = (maxchars / #buttontext) * 100
- buttontext = string.format("{\\fscx%f}",
- (maxchars / #buttontext) * 100) ..
- buttontext
- end
- elem_ass:append(buttontext)
- end
- master_ass:merge(elem_ass)
- end
- end
- --
- -- Message display
- --
- -- pos is 1 based
- function limited_list(prop, pos)
- local proplist = mp.get_property_native(prop, {})
- local count = #proplist
- if count == 0 then return count, proplist end
- local fs = tonumber(mp.get_property('options/osd-font-size'))
- local max = math.ceil(osc_param.unscaled_y * 0.75 / fs)
- if max % 2 == 0 then max = max - 1 end
- local delta = math.ceil(max / 2) - 1
- local begi = math.max(math.min(pos - delta, count - max + 1), 1)
- local endi = math.min(begi + max - 1, count)
- local reslist = {}
- for i = begi, endi do
- local item = proplist[i]
- item.current = (i == pos) and true or nil
- table.insert(reslist, item)
- end
- return count, reslist
- end
- function get_playlist()
- local pos = mp.get_property_number('playlist-pos', 0) + 1
- local count, limlist = limited_list('playlist', pos)
- if count == 0 then return 'Empty playlist.' end
- local message = string.format('Playlist [%d/%d]:\n', pos, count)
- for i, v in ipairs(limlist) do
- local title = v.title
- local _, filename = utils.split_path(v.filename)
- if title == nil then title = filename end
- message = string.format('%s %s %s\n', message,
- (v.current and '●' or '○'), title)
- end
- return message
- end
- function get_chapterlist()
- local pos = mp.get_property_number('chapter', 0) + 1
- local count, limlist = limited_list('chapter-list', pos)
- if count == 0 then return 'No chapters.' end
- local message = string.format('Chapters [%d/%d]:\n', pos, count)
- for i, v in ipairs(limlist) do
- local time = mp.format_time(v.time)
- local title = v.title
- if title == nil then title = string.format('Chapter %02d', i) end
- message = string.format('%s[%s] %s %s\n', message, time,
- (v.current and '●' or '○'), title)
- end
- return message
- end
- function show_message(text, duration)
- -- print("text: "..text.." duration: " .. duration)
- if duration == nil then
- duration = tonumber(mp.get_property("options/osd-duration")) / 1000
- elseif not type(duration) == "number" then
- print("duration: " .. duration)
- end
- -- cut the text short, otherwise the following functions
- -- may slow down massively on huge input
- text = string.sub(text, 0, 4000)
- -- replace actual linebreaks with ASS linebreaks
- text = string.gsub(text, "\n", "\\N")
- state.message_text = text
- state.message_timeout = mp.get_time() + duration
- end
- function render_message(ass)
- if not (state.message_timeout == nil) and not (state.message_text == nil) and
- state.message_timeout > mp.get_time() then
- local _, lines = string.gsub(state.message_text, "\\N", "")
- local fontsize = tonumber(mp.get_property("options/osd-font-size"))
- local outline = tonumber(mp.get_property("options/osd-border-size"))
- local maxlines = math.ceil(osc_param.unscaled_y * 0.75 / fontsize)
- local counterscale = osc_param.playresy / osc_param.unscaled_y
- fontsize = fontsize * counterscale /
- math.max(0.65 + math.min(lines / maxlines, 1), 1)
- outline = outline * counterscale /
- math.max(0.75 + math.min(lines / maxlines, 1) / 2, 1)
- local style = "{\\bord" .. outline .. "\\fs" .. fontsize .. "}"
- ass:new_event()
- ass:append(style .. state.message_text)
- else
- state.message_text = nil
- state.message_timeout = nil
- end
- end
- --
- -- Initialisation and Layout
- --
- function new_element(name, type)
- elements[name] = {}
- elements[name].type = type
- -- add default stuff
- elements[name].eventresponder = {}
- elements[name].visible = true
- elements[name].enabled = true
- elements[name].softrepeat = false
- elements[name].styledown = (type == "button")
- elements[name].state = {}
- if (type == "slider") then
- elements[name].slider = {min = {value = 0}, max = {value = 100}}
- end
- return elements[name]
- end
- function add_layout(name)
- if not (elements[name] == nil) then
- -- new layout
- elements[name].layout = {}
- -- set layout defaults
- elements[name].layout.layer = 50
- elements[name].layout.alpha = {[1] = 0, [2] = 255, [3] = 255, [4] = 255}
- if (elements[name].type == "button") then
- elements[name].layout.button = {maxchars = nil}
- elseif (elements[name].type == "slider") then
- -- slider defaults
- elements[name].layout.slider =
- {
- border = 1,
- gap = 1,
- nibbles_top = true,
- nibbles_bottom = true,
- stype = "slider",
- adjust_tooltip = true,
- tooltip_style = "",
- tooltip_an = 2,
- alpha = {[1] = 0, [2] = 255, [3] = 88, [4] = 255}
- }
- elseif (elements[name].type == "box") then
- elements[name].layout.box = {radius = 0, hexagon = false}
- end
- return elements[name].layout
- else
- msg.error("Can't add_layout to element \"" .. name ..
- "\", doesn't exist.")
- end
- end
- --
- -- Layouts
- --
- local layouts = {}
- -- Classic box layout
- layouts["box"] = function()
- local osc_geo = {
- w = 550, -- width
- h = 138, -- height
- r = 10, -- corner-radius
- p = 15 -- padding
- }
- -- make sure the OSC actually fits into the video
- if (osc_param.playresx < (osc_geo.w + (2 * osc_geo.p))) then
- osc_param.playresy = (osc_geo.w + (2 * osc_geo.p)) /
- osc_param.display_aspect
- osc_param.playresx = osc_param.playresy * osc_param.display_aspect
- end
- -- position of the controller according to video aspect and valignment
- local posX = math.floor(get_align(user_opts.halign, osc_param.playresx,
- osc_geo.w, 0))
- local posY = math.floor(get_align(user_opts.valign, osc_param.playresy,
- osc_geo.h, 0))
- -- position offset for contents aligned at the borders of the box
- local pos_offsetX = (osc_geo.w - (2 * osc_geo.p)) / 2
- local pos_offsetY = (osc_geo.h - (2 * osc_geo.p)) / 2
- osc_param.areas = {} -- delete areas
- -- area for active mouse input
- add_area("input", get_hitbox_coords(posX, posY, 5, osc_geo.w, osc_geo.h))
- -- area for show/hide
- local sh_area_y0, sh_area_y1
- if user_opts.valign > 0 then
- -- deadzone above OSC
- sh_area_y0 = get_align(-1 + (2 * user_opts.deadzonesize),
- posY - (osc_geo.h / 2), 0, 0)
- sh_area_y1 = osc_param.playresy
- else
- -- deadzone below OSC
- sh_area_y0 = 0
- sh_area_y1 = (posY + (osc_geo.h / 2)) +
- get_align(1 - (2 * user_opts.deadzonesize),
- osc_param.playresy - (posY + (osc_geo.h / 2)),
- 0, 0)
- end
- add_area("showhide", 0, sh_area_y0, osc_param.playresx, sh_area_y1)
- -- fetch values
- local osc_w, osc_h, osc_r, osc_p = osc_geo.w, osc_geo.h, osc_geo.r,
- osc_geo.p
- local lo
- --
- -- Background box
- --
- new_element("bgbox", "box")
- lo = add_layout("bgbox")
- lo.geometry = {x = posX, y = posY, an = 5, w = osc_w, h = osc_h}
- lo.layer = 10
- lo.style = osc_styles.box
- lo.alpha[1] = user_opts.boxalpha
- lo.alpha[3] = user_opts.boxalpha
- lo.box.radius = osc_r
- --
- -- Title row
- --
- local titlerowY = posY - pos_offsetY - 10
- lo = add_layout("title")
- lo.geometry = {x = posX, y = titlerowY, an = 8, w = 496, h = 12}
- lo.style = osc_styles.vidtitle
- lo.button.maxchars = user_opts.boxmaxchars
- lo = add_layout("pl_prev")
- lo.geometry = {
- x = (posX - pos_offsetX),
- y = titlerowY,
- an = 7,
- w = 12,
- h = 12
- }
- lo.style = osc_styles.topButtons
- lo = add_layout("pl_next")
- lo.geometry = {
- x = (posX + pos_offsetX),
- y = titlerowY,
- an = 9,
- w = 12,
- h = 12
- }
- lo.style = osc_styles.topButtons
- --
- -- Big buttons
- --
- local bigbtnrowY = posY - pos_offsetY + 35
- local bigbtndist = 60
- lo = add_layout("playpause")
- lo.geometry = {x = posX, y = bigbtnrowY, an = 5, w = 40, h = 40}
- lo.style = osc_styles.bigButtons
- lo = add_layout("skipback")
- lo.geometry = {
- x = posX - bigbtndist,
- y = bigbtnrowY,
- an = 5,
- w = 40,
- h = 40
- }
- lo.style = osc_styles.bigButtons
- lo = add_layout("skipfrwd")
- lo.geometry = {
- x = posX + bigbtndist,
- y = bigbtnrowY,
- an = 5,
- w = 40,
- h = 40
- }
- lo.style = osc_styles.bigButtons
- lo = add_layout("ch_prev")
- lo.geometry = {
- x = posX - (bigbtndist * 2),
- y = bigbtnrowY,
- an = 5,
- w = 40,
- h = 40
- }
- lo.style = osc_styles.bigButtons
- lo = add_layout("ch_next")
- lo.geometry = {
- x = posX + (bigbtndist * 2),
- y = bigbtnrowY,
- an = 5,
- w = 40,
- h = 40
- }
- lo.style = osc_styles.bigButtons
- lo = add_layout("cy_audio")
- lo.geometry = {
- x = posX - pos_offsetX,
- y = bigbtnrowY,
- an = 1,
- w = 70,
- h = 18
- }
- lo.style = osc_styles.smallButtonsL
- lo = add_layout("cy_sub")
- lo.geometry = {
- x = posX - pos_offsetX,
- y = bigbtnrowY,
- an = 7,
- w = 70,
- h = 18
- }
- lo.style = osc_styles.smallButtonsL
- lo = add_layout("tog_fs")
- lo.geometry = {
- x = posX + pos_offsetX - 25,
- y = bigbtnrowY,
- an = 4,
- w = 25,
- h = 25
- }
- lo.style = osc_styles.smallButtonsR
- lo = add_layout("volume")
- lo.geometry = {
- x = posX + pos_offsetX - (25 * 2) - osc_geo.p,
- y = bigbtnrowY,
- an = 4,
- w = 25,
- h = 25
- }
- lo.style = osc_styles.smallButtonsR
- --
- -- Seekbar
- --
- lo = add_layout("seekbar")
- lo.geometry = {
- x = posX,
- y = posY + pos_offsetY - 22,
- an = 2,
- w = pos_offsetX * 2,
- h = 15
- }
- lo.style = osc_styles.timecodes
- lo.slider.tooltip_style = osc_styles.vidtitle
- lo.slider.stype = user_opts["seekbarstyle"]
- lo.slider.rtype = user_opts["seekrangestyle"]
- --
- -- Timecodes + Cache
- --
- local bottomrowY = posY + pos_offsetY - 5
- lo = add_layout("tc_left")
- lo.geometry = {
- x = posX - pos_offsetX,
- y = bottomrowY,
- an = 4,
- w = 110,
- h = 18
- }
- lo.style = osc_styles.timecodes
- lo = add_layout("tc_right")
- lo.geometry = {
- x = posX + pos_offsetX,
- y = bottomrowY,
- an = 6,
- w = 110,
- h = 18
- }
- lo.style = osc_styles.timecodes
- lo = add_layout("cache")
- lo.geometry = {x = posX, y = bottomrowY, an = 5, w = 110, h = 18}
- lo.style = osc_styles.timecodes
- end
- -- slim box layout
- layouts["slimbox"] = function()
- local osc_geo = {
- w = 660, -- width
- h = 70, -- height
- r = 10 -- corner-radius
- }
- -- make sure the OSC actually fits into the video
- if (osc_param.playresx < (osc_geo.w)) then
- osc_param.playresy = (osc_geo.w) / osc_param.display_aspect
- osc_param.playresx = osc_param.playresy * osc_param.display_aspect
- end
- -- position of the controller according to video aspect and valignment
- local posX = math.floor(get_align(user_opts.halign, osc_param.playresx,
- osc_geo.w, 0))
- local posY = math.floor(get_align(user_opts.valign, osc_param.playresy,
- osc_geo.h, 0))
- osc_param.areas = {} -- delete areas
- -- area for active mouse input
- add_area("input", get_hitbox_coords(posX, posY, 5, osc_geo.w, osc_geo.h))
- -- area for show/hide
- local sh_area_y0, sh_area_y1
- if user_opts.valign > 0 then
- -- deadzone above OSC
- sh_area_y0 = get_align(-1 + (2 * user_opts.deadzonesize),
- posY - (osc_geo.h / 2), 0, 0)
- sh_area_y1 = osc_param.playresy
- else
- -- deadzone below OSC
- sh_area_y0 = 0
- sh_area_y1 = (posY + (osc_geo.h / 2)) +
- get_align(1 - (2 * user_opts.deadzonesize),
- osc_param.playresy - (posY + (osc_geo.h / 2)),
- 0, 0)
- end
- add_area("showhide", 0, sh_area_y0, osc_param.playresx, sh_area_y1)
- local lo
- local tc_w, ele_h, inner_w = 100, 20, osc_geo.w - 100
- -- styles
- local styles = {
- box = "{\\rDefault\\blur0\\bord1\\1c&H000000\\3c&HFFFFFF}",
- timecodes = "{\\1c&HFFFFFF\\3c&H000000\\fs20\\bord2\\blur1}",
- tooltip = "{\\1c&HFFFFFF\\3c&H000000\\fs12\\bord1\\blur0.5}"
- }
- new_element("bgbox", "box")
- lo = add_layout("bgbox")
- lo.geometry = {x = posX, y = posY - 1, an = 2, w = inner_w, h = ele_h}
- lo.layer = 10
- lo.style = osc_styles.box
- lo.alpha[1] = user_opts.boxalpha
- lo.alpha[3] = 0
- if not (user_opts["seekbarstyle"] == "bar") then
- lo.box.radius = osc_geo.r
- lo.box.hexagon = user_opts["seekbarstyle"] == "diamond"
- end
- lo = add_layout("seekbar")
- lo.geometry = {x = posX, y = posY - 1, an = 2, w = inner_w, h = ele_h}
- lo.style = osc_styles.timecodes
- lo.slider.border = 0
- lo.slider.gap = 1.5
- lo.slider.tooltip_style = styles.tooltip
- lo.slider.stype = user_opts["seekbarstyle"]
- lo.slider.rtype = user_opts["seekrangestyle"]
- lo.slider.adjust_tooltip = false
- --
- -- Timecodes
- --
- lo = add_layout("tc_left")
- lo.geometry = {
- x = posX - (inner_w / 2) + osc_geo.r,
- y = posY + 1,
- an = 7,
- w = tc_w,
- h = ele_h
- }
- lo.style = styles.timecodes
- lo.alpha[3] = user_opts.boxalpha
- lo = add_layout("tc_right")
- lo.geometry = {
- x = posX + (inner_w / 2) - osc_geo.r,
- y = posY + 1,
- an = 9,
- w = tc_w,
- h = ele_h
- }
- lo.style = styles.timecodes
- lo.alpha[3] = user_opts.boxalpha
- -- Cache
- lo = add_layout("cache")
- lo.geometry = {x = posX, y = posY + 1, an = 8, w = tc_w, h = ele_h}
- lo.style = styles.timecodes
- lo.alpha[3] = user_opts.boxalpha
- end
- layouts["bottombar"] = function()
- local osc_geo = {
- x = -2,
- y = osc_param.playresy - 54 - user_opts.barmargin,
- an = 7,
- w = osc_param.playresx + 4,
- h = 56
- }
- local padX = 9
- local padY = 3
- local buttonW = 27
- local tcW = (state.tc_ms) and 118 or 80 -- 170 or 110
- local tsW = 40 -- 90
- local minW = (buttonW + padX) * 5 + (tcW + padX) * 4 + (tsW + padX) * 2
- if ((osc_param.display_aspect > 0) and (osc_param.playresx < minW)) then
- osc_param.playresy = minW / osc_param.display_aspect
- osc_param.playresx = osc_param.playresy * osc_param.display_aspect
- osc_geo.y = osc_param.playresy - 54 - user_opts.barmargin
- osc_geo.w = osc_param.playresx + 4
- end
- local line1 = osc_geo.y + 9 + padY
- local line2 = osc_geo.y + 36 + padY
- osc_param.areas = {}
- add_area("input", get_hitbox_coords(osc_geo.x, osc_geo.y, osc_geo.an,
- osc_geo.w, osc_geo.h))
- local sh_area_y0, sh_area_y1
- sh_area_y0 = get_align(-1 + (2 * user_opts.deadzonesize),
- osc_geo.y - (osc_geo.h / 2), 0, 0)
- sh_area_y1 = osc_param.playresy - user_opts.barmargin
- add_area("showhide", 0, sh_area_y0, osc_param.playresx, sh_area_y1)
- local lo, geo
- -- Background bar
- new_element("bgbox", "box")
- lo = add_layout("bgbox")
- lo.geometry = osc_geo
- lo.layer = 10
- lo.style = osc_styles.box
- lo.alpha[1] = user_opts.boxalpha
- -- Playlist prev/next
- geo = {x = osc_geo.x + padX, y = line1, an = 4, w = 18, h = 18 - padY}
- lo = add_layout("pl_prev")
- lo.geometry = geo
- lo.style = osc_styles.topButtonsBar
- geo = {
- x = geo.x + geo.w + padX,
- y = geo.y,
- an = geo.an,
- w = geo.w,
- h = geo.h
- }
- lo = add_layout("pl_next")
- lo.geometry = geo
- lo.style = osc_styles.topButtonsBar
- local t_l = geo.x + geo.w + padX
- -- Track selection buttons
- geo = {
- x = osc_geo.x + osc_geo.w - padX,
- y = geo.y,
- an = 6,
- w = tsW,
- h = geo.h
- } -- { x = geo.x - tsW - padX, y = geo.y, an = geo.an, w = tsW, h = geo.h }
- lo = add_layout("cy_sub")
- lo.geometry = geo
- lo.style = osc_styles.smallButtonsBarS
- geo = {
- x = geo.x - geo.w - padX,
- y = geo.y,
- an = geo.an,
- w = geo.w,
- h = geo.h
- }
- lo = add_layout("cy_audio")
- lo.geometry = geo
- lo.style = osc_styles.smallButtonsBarS
- -- Cache
- geo = {x = geo.x - geo.w - padX, y = geo.y, an = geo.an, w = 50, h = geo.h}
- lo = add_layout("cache")
- lo.geometry = geo
- lo.style = osc_styles.vidtitleBar
- local t_r = geo.x - geo.w - padX
- -- Title
- geo = {x = t_l, y = geo.y, an = 4, w = t_r - t_l, h = geo.h}
- lo = add_layout("title")
- lo.geometry = geo
- lo.style = string.format("%s{\\clip(%f,%f,%f,%f)}", osc_styles.vidtitleBar,
- geo.x, geo.y - geo.h, geo.w, geo.y + geo.h)
- -- Playback control buttons
- geo = {
- x = osc_geo.x + padX,
- y = line2,
- an = 4,
- w = buttonW,
- h = 36 - padY * 2
- }
- lo = add_layout("ch_prev")
- lo.geometry = geo
- lo.style = osc_styles.smallButtonsBar
- geo = {
- x = geo.x + geo.w + padX,
- y = geo.y,
- an = geo.an,
- w = geo.w,
- h = geo.h
- }
- lo = add_layout("playpause")
- lo.geometry = geo
- lo.style = osc_styles.smallButtonsBar
- geo = {
- x = geo.x + geo.w + padX,
- y = geo.y,
- an = geo.an,
- w = geo.w,
- h = geo.h
- }
- lo = add_layout("ch_next")
- lo.geometry = geo
- lo.style = osc_styles.smallButtonsBar
- -- Left timecode
- geo = {
- x = geo.x + geo.w + padX + tcW,
- y = geo.y,
- an = 6,
- w = tcW,
- h = geo.h
- }
- lo = add_layout("tc_left")
- lo.geometry = geo
- lo.style = osc_styles.timecodesBar
- local sb_l = geo.x + padX
- -- -- Fullscreen button
- -- geo = { x = osc_geo.x + osc_geo.w - buttonW - padX, y = geo.y, an = 4,
- -- w = buttonW, h = geo.h }
- -- lo = add_layout("tog_fs")
- -- lo.geometry = geo
- -- lo.style = osc_styles.smallButtonsBarF
- -- -- Volume
- -- geo = { x = geo.x - geo.w - padX, y = geo.y, an = geo.an, w = geo.w, h = geo.h }
- -- lo = add_layout("volume")
- -- lo.geometry = geo
- -- lo.style = osc_styles.smallButtonsBarF
- -- -- Track selection buttons
- -- geo = { x = geo.x - tsW - padX, y = geo.y, an = geo.an, w = tsW, h = geo.h }
- -- lo = add_layout("cy_sub")
- -- lo.geometry = geo
- -- lo.style = osc_styles.smallButtonsBarS
- -- geo = { x = geo.x - geo.w - padX, y = geo.y, an = geo.an, w = geo.w, h = geo.h }
- -- lo = add_layout("cy_audio")
- -- lo.geometry = geo
- -- lo.style = osc_styles.smallButtonsBarS
- -- Right timecode
- geo = {
- x = osc_geo.x + osc_geo.w - padX,
- y = geo.y,
- an = 6, -- geo.an,
- w = tcW,
- h = geo.h
- }
- lo = add_layout("tc_right")
- lo.geometry = geo
- lo.style = osc_styles.timecodesBar
- local sb_r = geo.x - padX - tcW
- -- Seekbar
- geo = {
- x = sb_l,
- y = geo.y,
- an = 4,
- w = math.max(0, sb_r - sb_l),
- h = geo.h - 15
- }
- new_element("bgbar1", "box")
- lo = add_layout("bgbar1")
- lo.geometry = geo
- lo.layer = 15
- lo.style = osc_styles.timecodesBar
- lo.alpha[1] = math.min(255,
- user_opts.boxalpha + (255 - user_opts.boxalpha) * 0.2)
- if not (user_opts["seekbarstyle"] == "bar") then
- lo.box.radius = geo.h / 2
- lo.box.hexagon = user_opts["seekbarstyle"] == "diamond"
- end
- lo = add_layout("seekbar")
- lo.geometry = geo
- lo.style = osc_styles.timecodes
- lo.slider.border = 0
- lo.slider.gap = 3
- lo.slider.tooltip_style = osc_styles.timePosBar
- lo.slider.tooltip_an = 2
- lo.slider.stype = user_opts["seekbarstyle"]
- lo.slider.rtype = user_opts["seekrangestyle"]
- end
- layouts["topbar"] = function()
- local osc_geo = {
- x = -2,
- y = 54 + user_opts.barmargin,
- an = 1,
- w = osc_param.playresx + 4,
- h = 56
- }
- local padX = 9
- local padY = 3
- local buttonW = 27
- local tcW = (state.tc_ms) and 170 or 110
- local tsW = 90
- local minW = (buttonW + padX) * 5 + (tcW + padX) * 4 + (tsW + padX) * 2
- if ((osc_param.display_aspect > 0) and (osc_param.playresx < minW)) then
- osc_param.playresy = minW / osc_param.display_aspect
- osc_param.playresx = osc_param.playresy * osc_param.display_aspect
- osc_geo.y = 54 + user_opts.barmargin
- osc_geo.w = osc_param.playresx + 4
- end
- local line1 = osc_geo.y - 36 - padY
- local line2 = osc_geo.y - 9 - padY
- osc_param.areas = {}
- add_area("input", get_hitbox_coords(osc_geo.x, osc_geo.y, osc_geo.an,
- osc_geo.w, osc_geo.h))
- local sh_area_y0, sh_area_y1
- sh_area_y0 = user_opts.barmargin
- sh_area_y1 = (osc_geo.y + (osc_geo.h / 2)) +
- get_align(1 - (2 * user_opts.deadzonesize),
- osc_param.playresy -
- (osc_geo.y + (osc_geo.h / 2)), 0, 0)
- add_area("showhide", 0, sh_area_y0, osc_param.playresx, sh_area_y1)
- local lo, geo
- -- Background bar
- new_element("bgbox", "box")
- lo = add_layout("bgbox")
- lo.geometry = osc_geo
- lo.layer = 10
- lo.style = osc_styles.box
- lo.alpha[1] = user_opts.boxalpha
- -- Playback control buttons
- geo = {
- x = osc_geo.x + padX,
- y = line1,
- an = 4,
- w = buttonW,
- h = 36 - padY * 2
- }
- lo = add_layout("playpause")
- lo.geometry = geo
- lo.style = osc_styles.smallButtonsBar
- geo = {
- x = geo.x + geo.w + padX,
- y = geo.y,
- an = geo.an,
- w = geo.w,
- h = geo.h
- }
- lo = add_layout("ch_prev")
- lo.geometry = geo
- lo.style = osc_styles.smallButtonsBar
- geo = {
- x = geo.x + geo.w + padX,
- y = geo.y,
- an = geo.an,
- w = geo.w,
- h = geo.h
- }
- lo = add_layout("ch_next")
- lo.geometry = geo
- lo.style = osc_styles.smallButtonsBar
- -- Left timecode
- geo = {
- x = geo.x + geo.w + padX + tcW,
- y = geo.y,
- an = 6,
- w = tcW,
- h = geo.h
- }
- lo = add_layout("tc_left")
- lo.geometry = geo
- lo.style = osc_styles.timecodesBar
- local sb_l = geo.x + padX
- -- Fullscreen button
- geo = {
- x = osc_geo.x + osc_geo.w - buttonW - padX,
- y = geo.y,
- an = 4,
- w = buttonW,
- h = geo.h
- }
- lo = add_layout("tog_fs")
- lo.geometry = geo
- lo.style = osc_styles.smallButtonsBar
- -- Volume
- geo = {
- x = geo.x - geo.w - padX,
- y = geo.y,
- an = geo.an,
- w = geo.w,
- h = geo.h
- }
- lo = add_layout("volume")
- lo.geometry = geo
- lo.style = osc_styles.smallButtonsBar
- -- Track selection buttons
- geo = {x = geo.x - tsW - padX, y = geo.y, an = geo.an, w = tsW, h = geo.h}
- lo = add_layout("cy_sub")
- lo.geometry = geo
- lo.style = osc_styles.smallButtonsBar
- geo = {
- x = geo.x - geo.w - padX,
- y = geo.y,
- an = geo.an,
- w = geo.w,
- h = geo.h
- }
- lo = add_layout("cy_audio")
- lo.geometry = geo
- lo.style = osc_styles.smallButtonsBar
- -- Right timecode
- geo = {
- x = geo.x - geo.w - padX - tcW - 10,
- y = geo.y,
- an = 4,
- w = tcW,
- h = geo.h
- }
- lo = add_layout("tc_right")
- lo.geometry = geo
- lo.style = osc_styles.timecodesBar
- local sb_r = geo.x - padX
- -- Seekbar
- geo = {
- x = sb_l,
- y = user_opts.barmargin,
- an = 7,
- w = math.max(0, sb_r - sb_l),
- h = geo.h
- }
- new_element("bgbar1", "box")
- lo = add_layout("bgbar1")
- lo.geometry = geo
- lo.layer = 15
- lo.style = osc_styles.timecodesBar
- lo.alpha[1] = math.min(255,
- user_opts.boxalpha + (255 - user_opts.boxalpha) * 0.8)
- if not (user_opts["seekbarstyle"] == "bar") then
- lo.box.radius = geo.h / 2
- lo.box.hexagon = user_opts["seekbarstyle"] == "diamond"
- end
- lo = add_layout("seekbar")
- lo.geometry = geo
- lo.style = osc_styles.timecodesBar
- lo.slider.border = 0
- lo.slider.gap = 2
- lo.slider.tooltip_style = osc_styles.timePosBar
- lo.slider.stype = user_opts["seekbarstyle"]
- lo.slider.rtype = user_opts["seekrangestyle"]
- lo.slider.tooltip_an = 5
- -- Playlist prev/next
- geo = {x = osc_geo.x + padX, y = line2, an = 4, w = 18, h = 18 - padY}
- lo = add_layout("pl_prev")
- lo.geometry = geo
- lo.style = osc_styles.topButtonsBar
- geo = {
- x = geo.x + geo.w + padX,
- y = geo.y,
- an = geo.an,
- w = geo.w,
- h = geo.h
- }
- lo = add_layout("pl_next")
- lo.geometry = geo
- lo.style = osc_styles.topButtonsBar
- local t_l = geo.x + geo.w + padX
- -- Cache
- geo = {
- x = osc_geo.x + osc_geo.w - padX,
- y = geo.y,
- an = 6,
- w = 150,
- h = geo.h
- }
- lo = add_layout("cache")
- lo.geometry = geo
- lo.style = osc_styles.vidtitleBar
- local t_r = geo.x - geo.w - padX * 2
- -- Title
- geo = {x = t_l, y = geo.y, an = 4, w = t_r - t_l, h = geo.h}
- lo = add_layout("title")
- lo.geometry = geo
- lo.style = string.format("%s{\\clip(%f,%f,%f,%f)}", osc_styles.vidtitleBar,
- geo.x, geo.y - geo.h, geo.w, geo.y + geo.h)
- end
- -- Validate string type user options
- function validate_user_opts()
- if layouts[user_opts.layout] == nil then
- msg.warn("Invalid setting \"" .. user_opts.layout .. "\" for layout")
- user_opts.layout = "bottombar"
- end
- if user_opts.seekbarstyle ~= "bar" and user_opts.seekbarstyle ~= "diamond" and
- user_opts.seekbarstyle ~= "knob" then
- msg.warn("Invalid setting \"" .. user_opts.seekbarstyle ..
- "\" for seekbarstyle")
- user_opts.seekbarstyle = "bar"
- end
- if user_opts.seekrangestyle ~= "bar" and user_opts.seekrangestyle ~= "line" and
- user_opts.seekrangestyle ~= "slider" and user_opts.seekrangestyle ~=
- "inverted" and user_opts.seekrangestyle ~= "none" then
- msg.warn("Invalid setting \"" .. user_opts.seekrangestyle ..
- "\" for seekrangestyle")
- user_opts.seekrangestyle = "inverted"
- end
- if user_opts.seekrangestyle == "slider" and user_opts.seekbarstyle == "bar" then
- msg.warn(
- "Using \"slider\" seekrangestyle together with \"bar\" seekbarstyle is not supported")
- user_opts.seekrangestyle = "inverted"
- end
- end
- -- OSC INIT
- function osc_init()
- msg.debug("osc_init")
- -- set canvas resolution according to display aspect and scaling setting
- local baseResY = 720
- local display_w, display_h, display_aspect = mp.get_osd_size()
- local scale = 1
- if (mp.get_property("video") == "no") then -- dummy/forced window
- scale = user_opts.scaleforcedwindow
- elseif state.fullscreen then
- scale = user_opts.scalefullscreen
- else
- scale = user_opts.scalewindowed
- end
- if user_opts.vidscale then
- osc_param.unscaled_y = baseResY
- else
- osc_param.unscaled_y = display_h
- end
- osc_param.playresy = osc_param.unscaled_y / scale
- if (display_aspect > 0) then osc_param.display_aspect = display_aspect end
- osc_param.playresx = osc_param.playresy * osc_param.display_aspect
- -- stop seeking with the slider to prevent skipping files
- state.active_element = nil
- elements = {}
- -- some often needed stuff
- local pl_count = mp.get_property_number("playlist-count", 0)
- local have_pl = (pl_count > 1)
- local pl_pos = mp.get_property_number("playlist-pos", 0) + 1
- local have_ch = (mp.get_property_number("chapters", 0) > 0)
- local loop = mp.get_property("loop-playlist", "no")
- local ne
- -- title
- ne = new_element("title", "button")
- ne.content = function()
- local title = 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"
- end
- ne.eventresponder["mbtn_left_up"] = function()
- local title = mp.get_property_osd("media-title")
- if (have_pl) then
- title = string.format("[%d/%d] %s", countone(pl_pos - 1), pl_count,
- title)
- end
- show_message(title)
- end
- ne.eventresponder["mbtn_right_up"] =
- function() show_message(mp.get_property_osd("filename")) end
- -- playlist buttons
- -- prev
- ne = new_element("pl_prev", "button")
- ne.content = "\238\132\144"
- ne.enabled = (pl_pos > 1) or (loop ~= "no")
- ne.eventresponder["mbtn_left_up"] = function()
- mp.commandv("playlist-prev", "weak")
- show_message(get_playlist(), 3)
- end
- ne.eventresponder["shift+mbtn_left_up"] =
- function() show_message(get_playlist(), 3) end
- ne.eventresponder["mbtn_right_up"] =
- function() show_message(get_playlist(), 3) end
- -- next
- ne = new_element("pl_next", "button")
- ne.content = "\238\132\129"
- ne.enabled = (have_pl and (pl_pos < pl_count)) or (loop ~= "no")
- ne.eventresponder["mbtn_left_up"] = function()
- mp.commandv("playlist-next", "weak")
- show_message(get_playlist(), 3)
- end
- ne.eventresponder["shift+mbtn_left_up"] =
- function() show_message(get_playlist(), 3) end
- ne.eventresponder["mbtn_right_up"] =
- function() show_message(get_playlist(), 3) end
- -- big buttons
- -- playpause
- ne = new_element("playpause", "button")
- ne.content = function()
- if mp.get_property("pause") == "yes" then
- return ("\238\132\129")
- else
- return ("\238\128\130")
- end
- end
- ne.eventresponder["mbtn_left_up"] = function()
- mp.commandv("cycle", "pause")
- end
- -- skipback
- ne = new_element("skipback", "button")
- ne.softrepeat = true
- ne.content = "\238\128\132"
- ne.eventresponder["mbtn_left_down"] =
- function() mp.commandv("seek", -5, "relative", "keyframes") end
- ne.eventresponder["shift+mbtn_left_down"] =
- function() mp.commandv("frame-back-step") end
- ne.eventresponder["mbtn_right_down"] =
- function() mp.commandv("seek", -30, "relative", "keyframes") end
- -- skipfrwd
- ne = new_element("skipfrwd", "button")
- ne.softrepeat = true
- ne.content = "\238\128\133"
- ne.eventresponder["mbtn_left_down"] =
- function() mp.commandv("seek", 10, "relative", "keyframes") end
- ne.eventresponder["shift+mbtn_left_down"] =
- function() mp.commandv("frame-step") end
- ne.eventresponder["mbtn_right_down"] =
- function() mp.commandv("seek", 60, "relative", "keyframes") end
- -- ch_prev
- ne = new_element("ch_prev", "button")
- ne.enabled = have_ch
- ne.content = "\238\132\132"
- ne.eventresponder["mbtn_left_up"] = function()
- mp.commandv("add", "chapter", -1)
- show_message(get_chapterlist(), 3)
- end
- ne.eventresponder["shift+mbtn_left_up"] =
- function() show_message(get_chapterlist(), 3) end
- ne.eventresponder["mbtn_right_up"] =
- function() show_message(get_chapterlist(), 3) end
- -- ch_next
- ne = new_element("ch_next", "button")
- ne.enabled = have_ch
- ne.content = "\238\132\133"
- ne.eventresponder["mbtn_left_up"] = function()
- mp.commandv("add", "chapter", 1)
- show_message(get_chapterlist(), 3)
- end
- ne.eventresponder["shift+mbtn_left_up"] =
- function() show_message(get_chapterlist(), 3) end
- ne.eventresponder["mbtn_right_up"] =
- function() show_message(get_chapterlist(), 3) end
- --
- update_tracklist()
- -- cy_audio
- ne = new_element("cy_audio", "button")
- ne.enabled = (#tracks_osc.audio > 0)
- ne.content = function()
- local aid = "–"
- if not (get_track("audio") == 0) then aid = get_track("audio") end
- return ("\238\132\134" .. osc_styles.smallButtonsLlabel .. " " .. aid ..
- "/" .. #tracks_osc.audio)
- end
- ne.eventresponder["mbtn_left_up"] = function() set_track("audio", 1) end
- ne.eventresponder["mbtn_right_up"] = function() set_track("audio", -1) end
- ne.eventresponder["shift+mbtn_left_down"] =
- function() show_message(get_tracklist("audio"), 2) end
- -- cy_sub
- ne = new_element("cy_sub", "button")
- ne.enabled = (#tracks_osc.sub > 0)
- ne.content = function()
- local sid = "–"
- if not (get_track("sub") == 0) then sid = get_track("sub") end
- return ("\238\132\135" .. osc_styles.smallButtonsLlabel .. " " .. sid ..
- "/" .. #tracks_osc.sub)
- end
- ne.eventresponder["mbtn_left_up"] = function() set_track("sub", 1) end
- ne.eventresponder["mbtn_right_up"] = function() set_track("sub", -1) end
- ne.eventresponder["shift+mbtn_left_down"] =
- function() show_message(get_tracklist("sub"), 2) end
- -- tog_fs
- ne = new_element("tog_fs", "button")
- ne.content = function()
- if (state.fullscreen) then
- return ("\238\132\137")
- else
- return ("\238\132\136")
- end
- end
- ne.eventresponder["mbtn_left_up"] = function()
- mp.commandv("cycle", "fullscreen")
- end
- -- seekbar
- ne = new_element("seekbar", "slider")
- ne.enabled = not (mp.get_property("percent-pos") == nil)
- ne.slider.markerF = function()
- local duration = mp.get_property_number("duration", nil)
- if not (duration == nil) then
- local chapters = mp.get_property_native("chapter-list", {})
- local markers = {}
- for n = 1, #chapters do
- markers[n] = (chapters[n].time / duration * 100)
- end
- return markers
- else
- return {}
- end
- end
- ne.slider.posF = function()
- return mp.get_property_number("percent-pos", nil)
- end
- ne.slider.tooltipF = function(pos)
- local duration = mp.get_property_number("duration", nil)
- if not ((duration == nil) or (pos == nil)) then
- possec = duration * (pos / 100)
- return mp.format_time(possec)
- else
- return ""
- end
- end
- ne.slider.seekRangesF = function()
- if user_opts.seekrangestyle == "none" then return nil end
- local cache_state = mp.get_property_native("demuxer-cache-state", nil)
- if not cache_state then return nil end
- local duration = mp.get_property_number("duration", nil)
- if (duration == nil) or duration <= 0 then return nil end
- local ranges = cache_state["seekable-ranges"]
- for _, range in pairs(ranges) do
- range["start"] = 100 * range["start"] / duration
- range["end"] = 100 * range["end"] / duration
- end
- if #ranges == 0 then return nil end
- return ranges
- end
- ne.eventresponder["mouse_move"] = -- keyframe seeking when mouse is dragged
- function(element)
- -- mouse move events may pile up during seeking and may still get
- -- sent when the user is done seeking, so we need to throw away
- -- identical seeks
- local seekto = get_slider_value(element)
- if (element.state.lastseek == nil) or
- (not (element.state.lastseek == seekto)) then
- mp.commandv("seek", seekto, "absolute-percent",
- user_opts.seekbarkeyframes and "keyframes" or "exact")
- element.state.lastseek = seekto
- end
- end
- ne.eventresponder["mbtn_left_down"] = -- exact seeks on single clicks
- function(element)
- mp.commandv("seek", get_slider_value(element), "absolute-percent",
- "exact")
- end
- ne.eventresponder["reset"] = function(element)
- element.state.lastseek = nil
- end
- -- tc_left (current pos)
- ne = new_element("tc_left", "button")
- ne.content = function()
- if (state.tc_ms) then
- return (mp.get_property_osd("playback-time/full"))
- else
- return (mp.get_property_osd("playback-time"))
- end
- end
- ne.eventresponder["mbtn_left_up"] = function()
- state.tc_ms = not state.tc_ms
- request_init()
- end
- -- tc_right (total/remaining time)
- ne = new_element("tc_right", "button")
- ne.visible = (mp.get_property_number("duration", 0) > 0)
- ne.content = function()
- if (state.rightTC_trem) then
- if state.tc_ms then
- return ("-" .. mp.get_property_osd("playtime-remaining/full"))
- else
- return ("-" .. mp.get_property_osd("playtime-remaining"))
- end
- else
- if state.tc_ms then
- return (mp.get_property_osd("duration/full"))
- else
- return (mp.get_property_osd("duration"))
- end
- end
- end
- ne.eventresponder["mbtn_left_up"] = function()
- state.rightTC_trem = not state.rightTC_trem
- end
- -- cache
- ne = new_element("cache", "button")
- ne.content = function()
- local cache_state = mp.get_property_native("demuxer-cache-state", {})
- if not (cache_state["seekable-ranges"] and
- #cache_state["seekable-ranges"] > 0) then
- -- probably not a network stream
- return ""
- end
- local dmx_cache = mp.get_property_number("demuxer-cache-duration")
- if dmx_cache and
- (dmx_cache > state.dmx_cache * 1.1 or dmx_cache < state.dmx_cache *
- 0.9) then
- state.dmx_cache = dmx_cache
- else
- dmx_cache = state.dmx_cache
- end
- local min = math.floor(dmx_cache / 60)
- local sec = dmx_cache % 60
- return "Cache: " ..
- (min > 0 and string.format("%sm%02.0fs", min, sec) or
- string.format("%3.0fs", dmx_cache))
- end
- -- volume
- ne = new_element("volume", "button")
- ne.content = function()
- local volume = mp.get_property_number("volume", 0)
- local mute = mp.get_property_native("mute")
- local volicon = {
- "\238\132\139", "\238\132\140", "\238\132\141", "\238\132\142"
- }
- if volume == 0 or mute then
- return "\238\132\138"
- else
- return volicon[math.min(4, math.ceil(volume / (100 / 3)))]
- end
- end
- ne.eventresponder["mbtn_left_up"] = function()
- mp.commandv("cycle", "mute")
- end
- ne.eventresponder["wheel_up_press"] =
- function() mp.commandv("osd-auto", "add", "volume", 5) end
- ne.eventresponder["wheel_down_press"] =
- function() mp.commandv("osd-auto", "add", "volume", -5) end
- -- load layout
- layouts[user_opts.layout]()
- -- do something with the elements
- prepare_elements()
- end
- --
- -- Other important stuff
- --
- function show_osc()
- -- show when disabled can happen (e.g. mouse_move) due to async/delayed unbinding
- if not state.enabled then return end
- msg.trace("show_osc")
- -- remember last time of invocation (mouse move)
- state.showtime = mp.get_time()
- osc_visible(true)
- if (user_opts.fadeduration > 0) then state.anitype = nil end
- end
- function hide_osc()
- msg.trace("hide_osc")
- if not state.enabled then
- -- typically hide happens at render() from tick(), but now tick() is
- -- no-op and won't render again to remove the osc, so do that manually.
- state.osc_visible = false
- timer_stop()
- render_wipe()
- elseif (user_opts.fadeduration > 0) then
- if not (state.osc_visible == false) then
- state.anitype = "out"
- control_timer()
- end
- else
- osc_visible(false)
- end
- end
- function osc_visible(visible)
- state.osc_visible = visible
- control_timer()
- end
- function pause_state(name, enabled)
- state.paused = enabled
- control_timer()
- end
- function cache_state(name, idle)
- state.cache_idle = idle
- control_timer()
- end
- function control_timer()
- if (state.paused) and (state.osc_visible) and
- (not (state.cache_idle) or not (state.anitype == nil)) then
- timer_start()
- else
- timer_stop()
- end
- end
- function timer_start()
- if not (state.timer_active) then
- msg.trace("timer start")
- if (state.timer == nil) then
- -- create new timer
- state.timer = mp.add_periodic_timer(0.03, tick)
- else
- -- resume existing one
- state.timer:resume()
- end
- state.timer_active = true
- end
- end
- function timer_stop()
- if (state.timer_active) then
- msg.trace("timer stop")
- if not (state.timer == nil) then
- -- kill timer
- state.timer:kill()
- end
- state.timer_active = false
- end
- end
- function mouse_leave()
- if user_opts.hidetimeout >= 0 then hide_osc() end
- -- reset mouse position
- state.last_mouseX, state.last_mouseY = nil, nil
- end
- function request_init() state.initREQ = true end
- function render_wipe()
- msg.trace("render_wipe()")
- mp.set_osd_ass(0, 0, "{}")
- end
- function render()
- msg.trace("rendering")
- local current_screen_sizeX, current_screen_sizeY, aspect = mp.get_osd_size()
- local mouseX, mouseY = get_virt_mouse_pos()
- local now = mp.get_time()
- -- check if display changed, if so request reinit
- if not (state.mp_screen_sizeX == current_screen_sizeX and
- state.mp_screen_sizeY == current_screen_sizeY) then
- request_init()
- state.mp_screen_sizeX = current_screen_sizeX
- state.mp_screen_sizeY = current_screen_sizeY
- end
- -- init management
- if state.initREQ then
- osc_init()
- state.initREQ = false
- -- store initial mouse position
- if (state.last_mouseX == nil or state.last_mouseY == nil) and
- not (mouseX == nil or mouseY == nil) then
- state.last_mouseX, state.last_mouseY = mouseX, mouseY
- end
- end
- -- fade animation
- if not (state.anitype == nil) then
- if (state.anistart == nil) then state.anistart = now end
- if (now < state.anistart + (user_opts.fadeduration / 1000)) then
- if (state.anitype == "in") then -- fade in
- osc_visible(true)
- state.animation = scale_value(state.anistart, (state.anistart +
- (user_opts.fadeduration / 1000)),
- 255, 0, now)
- elseif (state.anitype == "out") then -- fade out
- state.animation = scale_value(state.anistart, (state.anistart +
- (user_opts.fadeduration / 1000)),
- 0, 255, now)
- end
- else
- if (state.anitype == "out") then osc_visible(false) end
- state.anistart = nil
- state.animation = nil
- state.anitype = nil
- end
- else
- state.anistart = nil
- state.animation = nil
- state.anitype = nil
- end
- -- mouse show/hide area
- for k, cords in pairs(osc_param.areas["showhide"]) do
- set_virt_mouse_area(cords.x1, cords.y1, cords.x2, cords.y2, "showhide")
- end
- do_enable_keybindings()
- -- mouse input area
- local mouse_over_osc = false
- for _, cords in ipairs(osc_param.areas["input"]) do
- if state.osc_visible then -- activate only when OSC is actually visible
- set_virt_mouse_area(cords.x1, cords.y1, cords.x2, cords.y2, "input")
- end
- if state.osc_visible ~= state.input_enabled then
- if state.osc_visible then
- mp.enable_key_bindings("input")
- else
- mp.disable_key_bindings("input")
- end
- state.input_enabled = state.osc_visible
- end
- if (mouse_hit_coords(cords.x1, cords.y1, cords.x2, cords.y2)) then
- mouse_over_osc = true
- end
- end
- -- autohide
- if not (state.showtime == nil) and (user_opts.hidetimeout >= 0) and
- (state.showtime + (user_opts.hidetimeout / 1000) < now) and
- (state.active_element == nil) and not (mouse_over_osc) then
- hide_osc()
- end
- -- actual rendering
- local ass = assdraw.ass_new()
- -- Messages
- render_message(ass)
- -- actual OSC
- if state.osc_visible then render_elements(ass) end
- -- submit
- mp.set_osd_ass(osc_param.playresy * osc_param.display_aspect,
- osc_param.playresy, ass.text)
- end
- --
- -- Eventhandling
- --
- local function element_has_action(element, action)
- return element and element.eventresponder and element.eventresponder[action]
- end
- function process_event(source, what)
- local action = string.format("%s%s", source, what and ("_" .. what) or "")
- if what == "down" or what == "press" then
- for n = 1, #elements do
- if mouse_hit(elements[n]) and elements[n].eventresponder and
- (elements[n].eventresponder[source .. "_up"] or
- elements[n].eventresponder[action]) then
- if what == "down" then
- state.active_element = n
- state.active_event_source = source
- end
- -- fire the down or press event if the element has one
- if element_has_action(elements[n], action) then
- elements[n].eventresponder[action](elements[n])
- end
- end
- end
- elseif what == "up" then
- if elements[state.active_element] then
- local n = state.active_element
- if n == 0 then
- -- click on background (does not work)
- elseif element_has_action(elements[n], action) and
- mouse_hit(elements[n]) then
- elements[n].eventresponder[action](elements[n])
- end
- -- reset active element
- if element_has_action(elements[n], "reset") then
- elements[n].eventresponder["reset"](elements[n])
- end
- end
- state.active_element = nil
- state.mouse_down_counter = 0
- elseif source == "mouse_move" then
- local mouseX, mouseY = get_virt_mouse_pos()
- if (user_opts.minmousemove == 0) or
- (not ((state.last_mouseX == nil) or (state.last_mouseY == nil)) and
- ((math.abs(mouseX - state.last_mouseX) >= user_opts.minmousemove) or
- (math.abs(mouseY - state.last_mouseY) >=
- user_opts.minmousemove))) then show_osc() end
- state.last_mouseX, state.last_mouseY = mouseX, mouseY
- local n = state.active_element
- if element_has_action(elements[n], action) then
- elements[n].eventresponder[action](elements[n])
- end
- tick()
- end
- end
- -- called by mpv on every frame
- function tick()
- if (not state.enabled) then return end
- if (state.idle) then
- -- render idle message
- msg.trace("idle message")
- local _, _, display_aspect = mp.get_osd_size()
- local display_h = 360
- local display_w = display_h * display_aspect
- -- logo is rendered at 2^(6-1) = 32 times resolution with size 1800x1800
- local icon_x, icon_y = (display_w - 1800 / 32) / 2, 140
- local ass = assdraw.ass_new()
- ass:new_event()
- ass:pos(icon_x, icon_y)
- ass:append(
- "{\\rDefault\\an7\\c&H430142&\\1a&H00&\\bord0\\shad0\\p6}m 1605 828 b 1605 1175 1324 1456 977 1456 631 1456 349 1175 349 828 349 482 631 200 977 200 1324 200 1605 482 1605 828{\\p0}")
- ass:new_event()
- ass:pos(icon_x, icon_y)
- ass:append(
- "{\\rDefault\\an7\\c&HDDDBDD&\\1a&H00&\\bord0\\shad0\\p6}m 1296 910 b 1296 1131 1117 1310 897 1310 676 1310 497 1131 497 910 497 689 676 511 897 511 1117 511 1296 689 1296 910{\\p0}")
- ass:new_event()
- ass:pos(icon_x, icon_y)
- ass:append(
- "{\\rDefault\\an7\\c&H691F69&\\1a&H00&\\bord0\\shad0\\p6}m 762 1113 l 762 708 b 881 776 1000 843 1119 911 1000 978 881 1046 762 1113{\\p0}")
- ass:new_event()
- ass:pos(icon_x, icon_y)
- ass:append(
- "{\\rDefault\\an7\\c&H682167&\\1a&H00&\\bord0\\shad0\\p6}m 925 42 b 463 42 87 418 87 880 87 1343 463 1718 925 1718 1388 1718 1763 1343 1763 880 1763 418 1388 42 925 42 m 925 42 m 977 200 b 1324 200 1605 482 1605 828 1605 1175 1324 1456 977 1456 631 1456 349 1175 349 828 349 482 631 200 977 200{\\p0}")
- ass:new_event()
- ass:pos(icon_x, icon_y)
- ass:append(
- "{\\rDefault\\an7\\c&H753074&\\1a&H00&\\bord0\\shad0\\p6}m 977 198 b 630 198 348 480 348 828 348 1176 630 1458 977 1458 1325 1458 1607 1176 1607 828 1607 480 1325 198 977 198 m 977 198 m 977 202 b 1323 202 1604 483 1604 828 1604 1174 1323 1454 977 1454 632 1454 351 1174 351 828 351 483 632 202 977 202{\\p0}")
- ass:new_event()
- ass:pos(icon_x, icon_y)
- ass:append(
- "{\\rDefault\\an7\\c&HE5E5E5&\\1a&H00&\\bord0\\shad0\\p6}m 895 10 b 401 10 0 410 0 905 0 1399 401 1800 895 1800 1390 1800 1790 1399 1790 905 1790 410 1390 10 895 10 m 895 10 m 925 42 b 1388 42 1763 418 1763 880 1763 1343 1388 1718 925 1718 463 1718 87 1343 87 880 87 418 463 42 925 42{\\p0}")
- ass:new_event()
- ass:pos(display_w / 2, icon_y + 65)
- ass:an(8)
- ass:append("Drop files or URLs to play here.")
- mp.set_osd_ass(display_w, display_h, ass.text)
- if state.showhide_enabled then
- mp.disable_key_bindings("showhide")
- state.showhide_enabled = false
- end
- elseif (state.fullscreen and user_opts.showfullscreen) or
- (not state.fullscreen and user_opts.showwindowed) then
- -- render the OSC
- render()
- else
- -- Flush OSD
- mp.set_osd_ass(osc_param.playresy, osc_param.playresy, "")
- end
- end
- function do_enable_keybindings()
- if state.enabled then
- if not state.showhide_enabled then
- mp.enable_key_bindings("showhide",
- "allow-vo-dragging+allow-hide-cursor")
- end
- state.showhide_enabled = true
- end
- end
- function enable_osc(enable)
- state.enabled = enable
- if enable then
- do_enable_keybindings()
- else
- hide_osc() -- acts immediately when state.enabled == false
- if state.showhide_enabled then
- mp.disable_key_bindings("showhide")
- end
- state.showhide_enabled = false
- end
- end
- validate_user_opts()
- mp.register_event("start-file", request_init)
- mp.observe_property("osc", "bool", function(name, value)
- if value == true then
- mp.set_property("osc", "no")
- end
- end)
- mp.register_event("tracks-changed", request_init)
- mp.observe_property("playlist", nil, request_init)
- mp.register_script_message("osc-message", show_message)
- mp.register_script_message("osc-chapterlist", function(dur)
- show_message(get_chapterlist(), dur)
- end)
- mp.register_script_message("osc-playlist",
- function(dur) show_message(get_playlist(), dur) end)
- mp.register_script_message("osc-tracklist", function(dur)
- local msg = {}
- for k, v in pairs(nicetypes) do table.insert(msg, get_tracklist(k)) end
- show_message(table.concat(msg, '\n\n'), dur)
- end)
- mp.observe_property("fullscreen", "bool", function(name, val)
- state.fullscreen = val
- request_init()
- end)
- mp.observe_property("idle-active", "bool", function(name, val)
- state.idle = val
- tick()
- end)
- mp.observe_property("pause", "bool", pause_state)
- mp.observe_property("cache-idle", "bool", cache_state)
- mp.observe_property("vo-configured", "bool", function(name, val)
- if val then
- mp.register_event("tick", tick)
- else
- mp.unregister_event(tick)
- end
- end)
- -- mouse show/hide bindings
- mp.set_key_bindings({
- {"mouse_move", function(e) process_event("mouse_move", nil) end},
- {"mouse_leave", mouse_leave}
- }, "showhide", "force")
- do_enable_keybindings()
- -- mouse input bindings
- mp.set_key_bindings({
- {
- "mbtn_left", function(e) process_event("mbtn_left", "up") end,
- function(e) process_event("mbtn_left", "down") end
- }, {
- "shift+mbtn_left",
- function(e) process_event("shift+mbtn_left", "up") end,
- function(e) process_event("shift+mbtn_left", "down") end
- }, {
- "mbtn_right", function(e) process_event("mbtn_right", "up") end,
- function(e) process_event("mbtn_right", "down") end
- }, {"wheel_up", function(e) process_event("wheel_up", "press") end},
- {"wheel_down", function(e) process_event("wheel_down", "press") end},
- {"mbtn_left_dbl", "ignore"}, {"shift+mbtn_left_dbl", "ignore"},
- {"mbtn_right_dbl", "ignore"}
- }, "input", "force")
- mp.enable_key_bindings("input")
- user_opts.hidetimeout_orig = user_opts.hidetimeout
- function always_on(val)
- if val then
- user_opts.hidetimeout = -1 -- disable autohide
- if state.enabled then show_osc() end
- else
- user_opts.hidetimeout = user_opts.hidetimeout_orig
- if state.enabled then hide_osc() end
- end
- end
- -- mode can be auto/always/never/cycle
- -- the modes only affect internal variables and not stored on its own.
- function visibility_mode(mode, no_osd)
- if mode == "cycle" then
- if not state.enabled then
- mode = "auto"
- elseif user_opts.hidetimeout >= 0 then
- mode = "always"
- else
- mode = "never"
- end
- end
- if mode == "auto" then
- always_on(false)
- enable_osc(true)
- elseif mode == "always" then
- enable_osc(true)
- always_on(true)
- elseif mode == "never" then
- enable_osc(false)
- else
- msg.warn("Ignoring unknown visibility mode '" .. mode .. "'")
- return
- end
- if not no_osd and tonumber(mp.get_property("osd-level")) >= 1 then
- mp.osd_message("OSC visibility: " .. mode)
- end
- end
- visibility_mode(user_opts.visibility, true)
- mp.register_script_message("osc-visibility", visibility_mode)
- mp.add_key_binding(nil, "visibility", function() visibility_mode("cycle") end)
- mp.register_script_message("thumbfast-info", function(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
- thumbfast = data
- end
- end)
- set_virt_mouse_area(0, 0, 0, 0, "input")
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement