Something went wrong. Try again.
🎥 Command line media player
Something went wrong. Try again.
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356235723582359236023612362236323642365236623672368236923702371237223732374237523762377237823792380238123822383238423852386238723882389239023912392239323942395local 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.conflocal 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 = 80, -- alpha of the background box, -- 0 (opaque) to 255 (fully transparent) hidetimeout = 500, -- 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.5, -- 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", -- slider (diamond marker), knob (circle -- marker with guide), or bar (fill) title = "${media-title}", -- string compatible with property-expansion -- to be shown as OSC title tooltipborder = 1, -- border of tooltip in bottom/topbar timetotal = false, -- display total time instead of remaining time? timems = false, -- display timecodes with milliseconds? seekranges = true, -- display seek ranges? 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-lineopt.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}", timecodesBar = "{\\blur0\\bord0\\1c&HFFFFFF\\3c&HFFFFFF\\fs27}", timePosBar = "{\\blur0\\bord".. user_opts.tooltipborder .."\\1c&HFFFFFF\\3c&H000000\\fs30}", vidtitleBar = "{\\blur0\\bord0\\1c&HFFFFFF\\3c&HFFFFFF\\fs18\\q2}",}
-- internal states, do not touchlocal 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,}
---- Helperfunctions--
-- scale factor for translating between real and virtual ASS coordinatesfunction 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 / hend
-- 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 * syend
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) + bend
-- returns hitbox spanning coordinates (top left, bottom right corner)-- according to alignmentfunction 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.y2end
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 valend
-- translate value into element coordinatesfunction 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 valuefunction 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 positionfunction 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 valend
-- 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 improvedfunction 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
---- Tracklist Management--
local nicetypes = {video = "Video", audio = "Audio", sub = "Subtitle"}
-- updates the OSC internal playlists, should be run each time the track-layout changesfunction 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 endend
-- 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 msgend
-- 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 "")) endend
-- get the currently selected track of <type>, OSC-style countedfunction 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 0end
---- 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() static_ass:round_rect_cw(0, 0, elem_geo.w, elem_geo.h, element.layout.box.radius) static_ass:draw_stop()
elseif (element.type == "slider") then --draw static slider parts
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 == "slider") or (slider_lo.stype == "knob") then element.slider.min.ele_pos = elem_geo.h / 2 element.slider.max.ele_pos = elem_geo.w - (elem_geo.h / 2)
elseif (slider_lo.stype == "bar") then 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 static_ass:rect_cw(0, 0, elem_geo.w, elem_geo.h);
-- the "hole" static_ass:rect_ccw(slider_lo.border, slider_lo.border, elem_geo.w - slider_lo.border, elem_geo.h - slider_lo.border)
-- 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 endend
---- 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)
--alpha local ar = element.layout.alpha if not (state.animation == nil) then ar = {} for ai, av in pairs(element.layout.alpha) do ar[ai] = mult_alpha(av, state.animation) end end
style_ass:append(string.format("{\\1a&H%X&\\2a&H%X&\\3a&H%X&\\4a&H%X&}", ar[1], ar[2], ar[3], ar[4]))
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 pos = element.slider.posF()
if not (pos == nil) then
local foV = slider_lo.border + slider_lo.gap local foH = 0 if (slider_lo.stype == "slider") or (slider_lo.stype == "knob") then foH = elem_geo.h / 2 elseif (slider_lo.stype == "bar") then foH = slider_lo.border + slider_lo.gap end
local xp = get_slider_ele_pos_for(element, pos)
-- the filling local innerH = elem_geo.h - (2*foV)
if (slider_lo.stype == "bar") then elem_ass:rect_cw(foH, foV, xp, elem_geo.h - foV) elseif (slider_lo.stype == "slider") then elem_ass:move_to(xp, foV) elem_ass:line_to(xp+(innerH/2), (innerH/2)+foV) elem_ass:line_to(xp, (innerH)+foV) elem_ass:line_to(xp-(innerH/2), (innerH/2)+foV) elseif (slider_lo.stype == "knob") then elem_ass:rect_cw(xp, (9*innerH/20) + foV, elem_geo.w - foH, (11*innerH/20) + foV) elem_ass:rect_cw(foH, (3*innerH/8) + foV, xp, (5*innerH/8) + foV) elem_ass:round_rect_cw(xp - innerH/2, foV, xp + innerH/2, foV + innerH, innerH/2.0) end end
-- seek ranges local seekRanges = element.slider.seekRangesF() if not (seekRanges == nil) then 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"]) elem_ass:rect_ccw(pstart, (elem_geo.h/2)-1, pend, (elem_geo.h/2) + 1) 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() 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)
--alpha local ar = slider_lo.alpha if not (state.animation == nil) then ar = {} for ai, av in pairs(slider_lo.alpha) do ar[ai] = mult_alpha(av, state.animation) end end elem_ass:append(string.format("{\\1a&H%X&\\2a&H%X&\\3a&H%X&\\4a&H%X&}", ar[1], ar[2], ar[3], ar[4]))
elem_ass:append(tooltiplabel)
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) endend
---- Message display--
-- pos is 1 basedfunction 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, reslistend
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 messageend
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 messageend
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() + durationend
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 endend
---- 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} end
return elements[name].layout else msg.error("Can't add_layout to element \""..name.."\", doesn't exist.") endend
---- Layouts--
local layouts = {}
-- Classic box layoutlayouts["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"] if lo.slider.stype == "knob" then lo.slider.border = 0 end
-- -- 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 layoutlayouts["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 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.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 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 = 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
-- 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)
-- Playback control buttons geo = { x = osc_geo.x + padX, y = line2, 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 - padX - tcW - 10, y = geo.y, an = 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
-- Seekbar geo = { x = sb_l, y = geo.y, an = geo.an, 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)
lo = add_layout("seekbar") lo.geometry = geo lo.style = osc_styles.timecodes lo.slider.border = 0 lo.slider.gap = 2 lo.slider.tooltip_style = osc_styles.timePosBar lo.slider.tooltip_an = 5 lo.slider.stype = user_opts["seekbarstyle"]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)
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.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 optionsfunction validate_user_opts() if layouts[user_opts.layout] == nil then msg.warn("Invalid setting \""..user_opts.layout.."\" for layout") user_opts.layout = "box" end
if user_opts.seekbarstyle ~= "slider" and user_opts.seekbarstyle ~= "bar" and user_opts.seekbarstyle ~= "knob" then msg.warn("Invalid setting \"" .. user_opts.seekbarstyle .. "\" for seekbarstyle") user_opts.seekbarstyle = "slider" endend
-- OSC INITfunction 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
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 not (user_opts.seekranges) 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 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", "keyframes") 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 dmx_cache = mp.get_property_number("demuxer-cache-duration") local cache_used = mp.get_property_number("cache-used", 0) * 1024 local dmx_cache_state = mp.get_property_native("demuxer-cache-state", {}) local is_network = mp.get_property_native("demuxer-via-network") local show_cache = cache_used > 0 if dmx_cache then dmx_cache = string.format("%3.0fs", dmx_cache) end if dmx_cache_state["fw-bytes"] then cache_used = cache_used + dmx_cache_state["fw-bytes"] end if (is_network and dmx_cache) or show_cache then -- Only show dmx-cache-duration by itself if it's a network file. -- Cache can be forced even for local files, so always show that. return string.format("Cache: %s%s%s", (dmx_cache and dmx_cache or ""), ((dmx_cache and show_cache) and " | " or ""), (show_cache and utils.format_bytes_humanized(cache_used) or "")) else return "" end 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 endend
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) endend
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() endend
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 endend
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 endend
function mouse_leave() if user_opts.hidetimeout >= 0 then hide_osc() end -- reset mouse position state.last_mouseX, state.last_mouseY = nil, nilend
function request_init() state.initREQ = trueend
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() endend
-- called by mpv on every framefunction tick() if (not state.enabled) then return end
if (state.idle) then
-- render idle message msg.trace("idle message") local icon_x, icon_y = 320 - 26, 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(320, icon_y+65) ass:an(8) ass:append("Drop files or URLs to play here.") mp.set_osd_ass(640, 360, 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, "") endend
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 endend
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 endend
validate_user_opts()
mp.register_event("start-file", request_init)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) endend)
-- mouse show/hide bindingsmp.set_key_bindings({ {"mouse_move", function(e) process_event("mouse_move", nil) end}, {"mouse_leave", mouse_leave},}, "showhide", "force")do_enable_keybindings()
--mouse input bindingsmp.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 endend
-- 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) endend
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)
set_virt_mouse_area(0, 0, 0, 0, "input")