Advertisement
Guest User

osc.lua

a guest
Jul 27th, 2019
224
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 80.39 KB | None | 0 0
  1. local assdraw = require 'mp.assdraw'
  2. local msg = require 'mp.msg'
  3. local opt = require 'mp.options'
  4. local utils = require 'mp.utils'
  5.  
  6. --
  7. -- Parameters
  8. --
  9.  
  10. -- default user option values
  11. -- do not touch, change them in osc.conf
  12. local user_opts = {
  13. showwindowed = true, -- show OSC when windowed?
  14. showfullscreen = true, -- show OSC when fullscreen?
  15. scalewindowed = 1, -- scaling of the controller when windowed
  16. scalefullscreen = 1, -- scaling of the controller when fullscreen
  17. scaleforcedwindow = 2, -- scaling when rendered on a forced window
  18. vidscale = true, -- scale the controller with the video?
  19. valign = 0.8, -- vertical alignment, -1 (top) to 1 (bottom)
  20. halign = 0, -- horizontal alignment, -1 (left) to 1 (right)
  21. barmargin = 0, -- vertical margin of top/bottombar
  22. boxalpha = 80, -- alpha of the background box,
  23. -- 0 (opaque) to 255 (fully transparent)
  24. hidetimeout = 500, -- duration in ms until the OSC hides if no
  25. -- mouse movement. enforced non-negative for the
  26. -- user, but internally negative is "always-on".
  27. fadeduration = 200, -- duration of fade out in ms, 0 = no fade
  28. deadzonesize = 0.5, -- size of deadzone
  29. minmousemove = 0, -- minimum amount of pixels the mouse has to
  30. -- move between ticks to make the OSC show up
  31. iamaprogrammer = false, -- use native mpv values and disable OSC
  32. -- internal track list management (and some
  33. -- functions that depend on it)
  34. layout = "bottombar",
  35. seekbarstyle = "bar", -- slider (diamond marker), knob (circle
  36. -- marker with guide), or bar (fill)
  37. seekbarkeyframes = true, -- use keyframes when dragging the seekbar
  38. title = "${media-title}", -- string compatible with property-expansion
  39. -- to be shown as OSC title
  40. tooltipborder = 3, -- border of tooltip in bottom/topbar
  41. timetotal = false, -- display total time instead of remaining time?
  42. timems = false, -- display timecodes with milliseconds?
  43. seekranges = true, -- display seek ranges?
  44. visibility = "auto", -- only used at init to set visibility_mode(...)
  45. boxmaxchars = 145, -- title crop threshold for box layout
  46. }
  47.  
  48. -- read_options may modify hidetimeout, so save the original default value in
  49. -- case the user set hidetimeout < 0 and we need the default instead.
  50. local hidetimeout_def = user_opts.hidetimeout
  51. -- read options from config and command-line
  52. opt.read_options(user_opts, "osc")
  53. if user_opts.hidetimeout < 0 then
  54. user_opts.hidetimeout = hidetimeout_def
  55. msg.warn("hidetimeout cannot be negative. Using " .. user_opts.hidetimeout)
  56. end
  57.  
  58. local osc_param = { -- calculated by osc_init()
  59. playresy = 0, -- canvas size Y
  60. playresx = 0, -- canvas size X
  61. display_aspect = 1,
  62. unscaled_y = 0,
  63. areas = {},
  64. }
  65.  
  66. local osc_styles = {
  67. bigButtons = "{\\blur0\\bord0\\1c&HCBCBCB\\3c&HFFFFFF\\fs40\\fnmpv-osd-symbols}",
  68. smallButtonsL = "{\\blur0\\bord0\\1c&HCBCBCB\\3c&HFFFFFF\\fs19\\fnmpv-osd-symbols}",
  69. smallButtonsLlabel = "{\\fscx105\\fscy105\\fn" .. mp.get_property("options/osd-font") .. "}",
  70. smallButtonsR = "{\\blur0\\bord0\\1c&HCBCBCB\\3c&HFFFFFF\\fs30\\fnmpv-osd-symbols}",
  71. topButtons = "{\\blur0\\bord0\\1c&HCBCBCB\\3c&HFFFFFF\\fs12\\fnmpv-osd-symbols}",
  72.  
  73. elementDown = "{\\1c&H999999}",
  74. timecodes = "{\\blur0\\bord0\\1c&HDD9A00\\3c&HFFFFFF\\fs18}",
  75. vidtitle = "{\\blur0\\bord0\\1c&HFFFFFF\\3c&HFFFFFF\\fs18\\q2}",
  76. box = "{\\rDefault\\blur0\\bord0\\1c&H3D3833\\3c&HFFFFFF}",
  77.  
  78. topButtonsBar = "{\\blur0\\bord0\\1c&HCBCBCB\\3c&HFFFFFF\\fs19\\fnmpv-osd-symbols}",
  79. smallButtonsBar = "{\\blur0\\bord0\\1c&HCBCBCB\\3c&HFFFFFF\\fs25\\fnmpv-osd-symbols}",
  80. timecodesBar = "{\\blur0\\bord0\\1c&HCBCBCB\\3c&HFFFFFF\\fs27}",
  81. timePosBar = "{\\blur0\\bord".. user_opts.tooltipborder .."\\1c&HFFFFFF\\3c&H000000\\fs30}",
  82. vidtitleBar = "{\\blur0\\bord0\\1c&HFFFFFF\\3c&HFFFFFF\\fs18\\q2}",
  83. }
  84.  
  85. -- internal states, do not touch
  86. local state = {
  87. showtime, -- time of last invocation (last mouse move)
  88. osc_visible = false,
  89. anistart, -- time when the animation started
  90. anitype, -- current type of animation
  91. animation, -- current animation alpha
  92. mouse_down_counter = 0, -- used for softrepeat
  93. active_element = nil, -- nil = none, 0 = background, 1+ = see elements[]
  94. active_event_source = nil, -- the "button" that issued the current event
  95. rightTC_trem = not user_opts.timetotal, -- if the right timecode should display total or remaining time
  96. tc_ms = user_opts.timems, -- Should the timecodes display their time with milliseconds
  97. mp_screen_sizeX, mp_screen_sizeY, -- last screen-resolution, to detect resolution changes to issue reINITs
  98. initREQ = false, -- is a re-init request pending?
  99. last_mouseX, last_mouseY, -- last mouse position, to detect significant mouse movement
  100. message_text,
  101. message_timeout,
  102. fullscreen = false,
  103. timer = nil,
  104. cache_idle = false,
  105. idle = false,
  106. enabled = true,
  107. input_enabled = true,
  108. showhide_enabled = false,
  109. dmx_cache = 0,
  110. }
  111.  
  112.  
  113.  
  114.  
  115. --
  116. -- Helperfunctions
  117. --
  118.  
  119. -- scale factor for translating between real and virtual ASS coordinates
  120. function get_virt_scale_factor()
  121. local w, h = mp.get_osd_size()
  122. if w <= 0 or h <= 0 then
  123. return 0, 0
  124. end
  125. return osc_param.playresx / w, osc_param.playresy / h
  126. end
  127.  
  128. -- return mouse position in virtual ASS coordinates (playresx/y)
  129. function get_virt_mouse_pos()
  130. local sx, sy = get_virt_scale_factor()
  131. local x, y = mp.get_mouse_pos()
  132. return x * sx, y * sy
  133. end
  134.  
  135. function set_virt_mouse_area(x0, y0, x1, y1, name)
  136. local sx, sy = get_virt_scale_factor()
  137. mp.set_mouse_area(x0 / sx, y0 / sy, x1 / sx, y1 / sy, name)
  138. end
  139.  
  140. function scale_value(x0, x1, y0, y1, val)
  141. local m = (y1 - y0) / (x1 - x0)
  142. local b = y0 - (m * x0)
  143. return (m * val) + b
  144. end
  145.  
  146. -- returns hitbox spanning coordinates (top left, bottom right corner)
  147. -- according to alignment
  148. function get_hitbox_coords(x, y, an, w, h)
  149.  
  150. local alignments = {
  151. [1] = function () return x, y-h, x+w, y end,
  152. [2] = function () return x-(w/2), y-h, x+(w/2), y end,
  153. [3] = function () return x-w, y-h, x, y end,
  154.  
  155. [4] = function () return x, y-(h/2), x+w, y+(h/2) end,
  156. [5] = function () return x-(w/2), y-(h/2), x+(w/2), y+(h/2) end,
  157. [6] = function () return x-w, y-(h/2), x, y+(h/2) end,
  158.  
  159. [7] = function () return x, y, x+w, y+h end,
  160. [8] = function () return x-(w/2), y, x+(w/2), y+h end,
  161. [9] = function () return x-w, y, x, y+h end,
  162. }
  163.  
  164. return alignments[an]()
  165. end
  166.  
  167. function get_hitbox_coords_geo(geometry)
  168. return get_hitbox_coords(geometry.x, geometry.y, geometry.an,
  169. geometry.w, geometry.h)
  170. end
  171.  
  172. function get_element_hitbox(element)
  173. return element.hitbox.x1, element.hitbox.y1,
  174. element.hitbox.x2, element.hitbox.y2
  175. end
  176.  
  177. function mouse_hit(element)
  178. return mouse_hit_coords(get_element_hitbox(element))
  179. end
  180.  
  181. function mouse_hit_coords(bX1, bY1, bX2, bY2)
  182. local mX, mY = get_virt_mouse_pos()
  183. return (mX >= bX1 and mX <= bX2 and mY >= bY1 and mY <= bY2)
  184. end
  185.  
  186. function limit_range(min, max, val)
  187. if val > max then
  188. val = max
  189. elseif val < min then
  190. val = min
  191. end
  192. return val
  193. end
  194.  
  195. -- translate value into element coordinates
  196. function get_slider_ele_pos_for(element, val)
  197.  
  198. local ele_pos = scale_value(
  199. element.slider.min.value, element.slider.max.value,
  200. element.slider.min.ele_pos, element.slider.max.ele_pos,
  201. val)
  202.  
  203. return limit_range(
  204. element.slider.min.ele_pos, element.slider.max.ele_pos,
  205. ele_pos)
  206. end
  207.  
  208. -- translates global (mouse) coordinates to value
  209. function get_slider_value_at(element, glob_pos)
  210.  
  211. local val = scale_value(
  212. element.slider.min.glob_pos, element.slider.max.glob_pos,
  213. element.slider.min.value, element.slider.max.value,
  214. glob_pos)
  215.  
  216. return limit_range(
  217. element.slider.min.value, element.slider.max.value,
  218. val)
  219. end
  220.  
  221. -- get value at current mouse position
  222. function get_slider_value(element)
  223. return get_slider_value_at(element, get_virt_mouse_pos())
  224. end
  225.  
  226. function countone(val)
  227. if not (user_opts.iamaprogrammer) then
  228. val = val + 1
  229. end
  230. return val
  231. end
  232.  
  233. -- align: -1 .. +1
  234. -- frame: size of the containing area
  235. -- obj: size of the object that should be positioned inside the area
  236. -- margin: min. distance from object to frame (as long as -1 <= align <= +1)
  237. function get_align(align, frame, obj, margin)
  238. return (frame / 2) + (((frame / 2) - margin - (obj / 2)) * align)
  239. end
  240.  
  241. -- multiplies two alpha values, formular can probably be improved
  242. function mult_alpha(alphaA, alphaB)
  243. return 255 - (((1-(alphaA/255)) * (1-(alphaB/255))) * 255)
  244. end
  245.  
  246. function add_area(name, x1, y1, x2, y2)
  247. -- create area if needed
  248. if (osc_param.areas[name] == nil) then
  249. osc_param.areas[name] = {}
  250. end
  251. table.insert(osc_param.areas[name], {x1=x1, y1=y1, x2=x2, y2=y2})
  252. end
  253.  
  254.  
  255. --
  256. -- Tracklist Management
  257. --
  258.  
  259. local nicetypes = {video = "Video", audio = "Audio", sub = "Subtitle"}
  260.  
  261. -- updates the OSC internal playlists, should be run each time the track-layout changes
  262. function update_tracklist()
  263. local tracktable = mp.get_property_native("track-list", {})
  264.  
  265. -- by osc_id
  266. tracks_osc = {}
  267. tracks_osc.video, tracks_osc.audio, tracks_osc.sub = {}, {}, {}
  268. -- by mpv_id
  269. tracks_mpv = {}
  270. tracks_mpv.video, tracks_mpv.audio, tracks_mpv.sub = {}, {}, {}
  271. for n = 1, #tracktable do
  272. if not (tracktable[n].type == "unknown") then
  273. local type = tracktable[n].type
  274. local mpv_id = tonumber(tracktable[n].id)
  275.  
  276. -- by osc_id
  277. table.insert(tracks_osc[type], tracktable[n])
  278.  
  279. -- by mpv_id
  280. tracks_mpv[type][mpv_id] = tracktable[n]
  281. tracks_mpv[type][mpv_id].osc_id = #tracks_osc[type]
  282. end
  283. end
  284. end
  285.  
  286. -- return a nice list of tracks of the given type (video, audio, sub)
  287. function get_tracklist(type)
  288. local msg = "Available " .. nicetypes[type] .. " Tracks: "
  289. if #tracks_osc[type] == 0 then
  290. msg = msg .. "none"
  291. else
  292. for n = 1, #tracks_osc[type] do
  293. local track = tracks_osc[type][n]
  294. local lang, title, selected = "unknown", "", "○"
  295. if not(track.lang == nil) then lang = track.lang end
  296. if not(track.title == nil) then title = track.title end
  297. if (track.id == tonumber(mp.get_property(type))) then
  298. selected = "●"
  299. end
  300. msg = msg.."\n"..selected.." "..n..": ["..lang.."] "..title
  301. end
  302. end
  303. return msg
  304. end
  305.  
  306. -- relatively change the track of given <type> by <next> tracks
  307. --(+1 -> next, -1 -> previous)
  308. function set_track(type, next)
  309. local current_track_mpv, current_track_osc
  310. if (mp.get_property(type) == "no") then
  311. current_track_osc = 0
  312. else
  313. current_track_mpv = tonumber(mp.get_property(type))
  314. current_track_osc = tracks_mpv[type][current_track_mpv].osc_id
  315. end
  316. local new_track_osc = (current_track_osc + next) % (#tracks_osc[type] + 1)
  317. local new_track_mpv
  318. if new_track_osc == 0 then
  319. new_track_mpv = "no"
  320. else
  321. new_track_mpv = tracks_osc[type][new_track_osc].id
  322. end
  323.  
  324. mp.commandv("set", type, new_track_mpv)
  325.  
  326. if (new_track_osc == 0) then
  327. show_message(nicetypes[type] .. " Track: none")
  328. else
  329. show_message(nicetypes[type] .. " Track: "
  330. .. new_track_osc .. "/" .. #tracks_osc[type]
  331. .. " [".. (tracks_osc[type][new_track_osc].lang or "unknown") .."] "
  332. .. (tracks_osc[type][new_track_osc].title or ""))
  333. end
  334. end
  335.  
  336. -- get the currently selected track of <type>, OSC-style counted
  337. function get_track(type)
  338. local track = mp.get_property(type)
  339. if track ~= "no" and track ~= nil then
  340. local tr = tracks_mpv[type][tonumber(track)]
  341. if tr then
  342. return tr.osc_id
  343. end
  344. end
  345. return 0
  346. end
  347.  
  348.  
  349. --
  350. -- Element Management
  351. --
  352.  
  353. local elements = {}
  354.  
  355. function prepare_elements()
  356.  
  357. -- remove elements without layout or invisble
  358. local elements2 = {}
  359. for n, element in pairs(elements) do
  360. if not (element.layout == nil) and (element.visible) then
  361. table.insert(elements2, element)
  362. end
  363. end
  364. elements = elements2
  365.  
  366. function elem_compare (a, b)
  367. return a.layout.layer < b.layout.layer
  368. end
  369.  
  370. table.sort(elements, elem_compare)
  371.  
  372.  
  373. for _,element in pairs(elements) do
  374.  
  375. local elem_geo = element.layout.geometry
  376.  
  377. -- Calculate the hitbox
  378. local bX1, bY1, bX2, bY2 = get_hitbox_coords_geo(elem_geo)
  379. element.hitbox = {x1 = bX1, y1 = bY1, x2 = bX2, y2 = bY2}
  380.  
  381. local style_ass = assdraw.ass_new()
  382.  
  383. -- prepare static elements
  384. style_ass:append("{}") -- hack to troll new_event into inserting a \n
  385. style_ass:new_event()
  386. style_ass:pos(elem_geo.x, elem_geo.y)
  387. style_ass:an(elem_geo.an)
  388. style_ass:append(element.layout.style)
  389.  
  390. element.style_ass = style_ass
  391.  
  392. local static_ass = assdraw.ass_new()
  393.  
  394.  
  395. if (element.type == "box") then
  396. --draw box
  397. static_ass:draw_start()
  398. static_ass:round_rect_cw(0, 0, elem_geo.w, elem_geo.h,
  399. element.layout.box.radius)
  400. static_ass:draw_stop()
  401.  
  402.  
  403. elseif (element.type == "slider") then
  404. --draw static slider parts
  405.  
  406. local slider_lo = element.layout.slider
  407. -- offset between element outline and drag-area
  408. local foV = slider_lo.border + slider_lo.gap
  409.  
  410. -- calculate positions of min and max points
  411. if (slider_lo.stype == "slider") or
  412. (slider_lo.stype == "knob") then
  413. element.slider.min.ele_pos = elem_geo.h / 2
  414. element.slider.max.ele_pos = elem_geo.w - (elem_geo.h / 2)
  415.  
  416. elseif (slider_lo.stype == "bar") then
  417. element.slider.min.ele_pos =
  418. slider_lo.border + slider_lo.gap
  419. element.slider.max.ele_pos =
  420. elem_geo.w - (slider_lo.border + slider_lo.gap)
  421. end
  422.  
  423. element.slider.min.glob_pos =
  424. element.hitbox.x1 + element.slider.min.ele_pos
  425. element.slider.max.glob_pos =
  426. element.hitbox.x1 + element.slider.max.ele_pos
  427.  
  428. -- -- --
  429.  
  430. static_ass:draw_start()
  431.  
  432. -- the box
  433. static_ass:rect_cw(0, 0, elem_geo.w, elem_geo.h);
  434.  
  435. -- the "hole"
  436. static_ass:rect_ccw(slider_lo.border, slider_lo.border,
  437. elem_geo.w - slider_lo.border, elem_geo.h - slider_lo.border)
  438.  
  439. -- marker nibbles
  440. if not (element.slider.markerF == nil) and (slider_lo.gap > 0) then
  441. local markers = element.slider.markerF()
  442. for _,marker in pairs(markers) do
  443. if (marker > element.slider.min.value) and
  444. (marker < element.slider.max.value) then
  445.  
  446. local s = get_slider_ele_pos_for(element, marker)
  447.  
  448. if (slider_lo.gap > 1) then -- draw triangles
  449.  
  450. local a = slider_lo.gap / 0.5 --0.866
  451.  
  452. --top
  453. if (slider_lo.nibbles_top) then
  454. static_ass:move_to(s - (a/2), slider_lo.border)
  455. static_ass:line_to(s + (a/2), slider_lo.border)
  456. static_ass:line_to(s, foV)
  457. end
  458.  
  459. --bottom
  460. if (slider_lo.nibbles_bottom) then
  461. static_ass:move_to(s - (a/2),
  462. elem_geo.h - slider_lo.border)
  463. static_ass:line_to(s,
  464. elem_geo.h - foV)
  465. static_ass:line_to(s + (a/2),
  466. elem_geo.h - slider_lo.border)
  467. end
  468.  
  469. else -- draw 2x1px nibbles
  470.  
  471. --top
  472. if (slider_lo.nibbles_top) then
  473. static_ass:rect_cw(s - 1, slider_lo.border,
  474. s + 1, slider_lo.border + slider_lo.gap);
  475. end
  476.  
  477. --bottom
  478. if (slider_lo.nibbles_bottom) then
  479. static_ass:rect_cw(s - 1,
  480. elem_geo.h -slider_lo.border -slider_lo.gap,
  481. s + 1, elem_geo.h - slider_lo.border);
  482. end
  483. end
  484. end
  485. end
  486. end
  487. end
  488.  
  489. element.static_ass = static_ass
  490.  
  491.  
  492. -- if the element is supposed to be disabled,
  493. -- style it accordingly and kill the eventresponders
  494. if not (element.enabled) then
  495. element.layout.alpha[1] = 136
  496. element.eventresponder = nil
  497. end
  498. end
  499. end
  500.  
  501.  
  502. --
  503. -- Element Rendering
  504. --
  505.  
  506. function render_elements(master_ass)
  507.  
  508. for n=1, #elements do
  509. local element = elements[n]
  510.  
  511. local style_ass = assdraw.ass_new()
  512. style_ass:merge(element.style_ass)
  513.  
  514. --alpha
  515. local ar = element.layout.alpha
  516. if not (state.animation == nil) then
  517. ar = {}
  518. for ai, av in pairs(element.layout.alpha) do
  519. ar[ai] = mult_alpha(av, state.animation)
  520. end
  521. end
  522.  
  523. style_ass:append(string.format("{\\1a&H%X&\\2a&H%X&\\3a&H%X&\\4a&H%X&}",
  524. ar[1], ar[2], ar[3], ar[4]))
  525.  
  526. if element.eventresponder and (state.active_element == n) then
  527.  
  528. -- run render event functions
  529. if not (element.eventresponder.render == nil) then
  530. element.eventresponder.render(element)
  531. end
  532.  
  533. if mouse_hit(element) then
  534. -- mouse down styling
  535. if (element.styledown) then
  536. style_ass:append(osc_styles.elementDown)
  537. end
  538.  
  539. if (element.softrepeat) and (state.mouse_down_counter >= 15
  540. and state.mouse_down_counter % 5 == 0) then
  541.  
  542. element.eventresponder[state.active_event_source.."_down"](element)
  543. end
  544. state.mouse_down_counter = state.mouse_down_counter + 1
  545. end
  546.  
  547. end
  548.  
  549. local elem_ass = assdraw.ass_new()
  550.  
  551. elem_ass:merge(style_ass)
  552.  
  553. if not (element.type == "button") then
  554. elem_ass:merge(element.static_ass)
  555. end
  556.  
  557. if (element.type == "slider") then
  558.  
  559. local slider_lo = element.layout.slider
  560. local elem_geo = element.layout.geometry
  561. local s_min = element.slider.min.value
  562. local s_max = element.slider.max.value
  563.  
  564. -- draw pos marker
  565. local pos = element.slider.posF()
  566.  
  567. if not (pos == nil) then
  568.  
  569. local foV = slider_lo.border + slider_lo.gap
  570. local foH = 0
  571. if (slider_lo.stype == "slider") or
  572. (slider_lo.stype == "knob") then
  573. foH = elem_geo.h / 2
  574. elseif (slider_lo.stype == "bar") then
  575. foH = slider_lo.border + slider_lo.gap
  576. end
  577.  
  578. local xp = get_slider_ele_pos_for(element, pos)
  579.  
  580. -- the filling
  581. local innerH = elem_geo.h - (2*foV)
  582.  
  583. if (slider_lo.stype == "bar") then
  584. elem_ass:rect_cw(foH, foV, xp, elem_geo.h - foV)
  585. elseif (slider_lo.stype == "slider") then
  586. elem_ass:move_to(xp, foV)
  587. elem_ass:line_to(xp+(innerH/2), (innerH/2)+foV)
  588. elem_ass:line_to(xp, (innerH)+foV)
  589. elem_ass:line_to(xp-(innerH/2), (innerH/2)+foV)
  590. elseif (slider_lo.stype == "knob") then
  591. elem_ass:rect_cw(xp, (9*innerH/20) + foV,
  592. elem_geo.w - foH, (11*innerH/20) + foV)
  593. elem_ass:rect_cw(foH, (3*innerH/8) + foV,
  594. xp, (5*innerH/8) + foV)
  595. elem_ass:round_rect_cw(xp - innerH/2, foV,
  596. xp + innerH/2, foV + innerH, innerH/2.0)
  597. end
  598. end
  599.  
  600. -- seek ranges
  601. local seekRanges = element.slider.seekRangesF()
  602. if not (seekRanges == nil) then
  603. for _,range in pairs(seekRanges) do
  604. local pstart = get_slider_ele_pos_for(element, range["start"])
  605. local pend = get_slider_ele_pos_for(element, range["end"])
  606. elem_ass:rect_ccw(pstart, (elem_geo.h/2)-1, pend, (elem_geo.h/2) + 1)
  607. end
  608. end
  609.  
  610. elem_ass:draw_stop()
  611.  
  612. -- add tooltip
  613. if not (element.slider.tooltipF == nil) then
  614.  
  615. if mouse_hit(element) then
  616. local sliderpos = get_slider_value(element)
  617. local tooltiplabel = element.slider.tooltipF(sliderpos)
  618.  
  619. local an = slider_lo.tooltip_an
  620.  
  621. local ty
  622.  
  623. if (an == 2) then
  624. ty = element.hitbox.y1 - slider_lo.border
  625. else
  626. ty = element.hitbox.y1 + elem_geo.h/2
  627. end
  628.  
  629. local tx = get_virt_mouse_pos()
  630. if (slider_lo.adjust_tooltip) then
  631. if (an == 2) then
  632. if (sliderpos < (s_min + 3)) then
  633. an = an - 1
  634. elseif (sliderpos > (s_max - 3)) then
  635. an = an + 1
  636. end
  637. elseif (sliderpos > (s_max-s_min)/2) then
  638. an = an + 1
  639. tx = tx - 5
  640. else
  641. an = an - 1
  642. tx = tx + 10
  643. end
  644. end
  645.  
  646. -- tooltip label
  647. elem_ass:new_event()
  648. elem_ass:pos(tx, ty - 20)
  649. elem_ass:an(an)
  650. elem_ass:append(slider_lo.tooltip_style)
  651.  
  652. --alpha
  653. local ar = slider_lo.alpha
  654. if not (state.animation == nil) then
  655. ar = {}
  656. for ai, av in pairs(slider_lo.alpha) do
  657. ar[ai] = mult_alpha(av, state.animation)
  658. end
  659. end
  660. elem_ass:append(string.format("{\\1a&H%X&\\2a&H%X&\\3a&H%X&\\4a&H%X&}",
  661. ar[1], ar[2], ar[3], ar[4]))
  662.  
  663. local chapters = mp.get_property_native("chapter-list", {})
  664.  
  665. if (#chapters > 0) then
  666. local current_chapter = chapters[#chapters].title
  667. for n = 1, #chapters do
  668. if (mp.format_time(chapters[n].time) > tooltiplabel) then
  669. current_chapter = chapters[n-1].title
  670. break
  671. end
  672. end
  673. elem_ass:append(tooltiplabel .. " - " .. current_chapter)
  674. else
  675. elem_ass:append(tooltiplabel)
  676. end
  677.  
  678. end
  679. end
  680.  
  681. elseif (element.type == "button") then
  682.  
  683. local buttontext
  684. if type(element.content) == "function" then
  685. buttontext = element.content() -- function objects
  686. elseif not (element.content == nil) then
  687. buttontext = element.content -- text objects
  688. end
  689.  
  690. local maxchars = element.layout.button.maxchars
  691. if not (maxchars == nil) and (#buttontext > maxchars) then
  692. local max_ratio = 1.25 -- up to 25% more chars while shrinking
  693. local limit = math.max(0, math.floor(maxchars * max_ratio) - 3)
  694. if (#buttontext > limit) then
  695. while (#buttontext > limit) do
  696. buttontext = buttontext:gsub(".[\128-\191]*$", "")
  697. end
  698. buttontext = buttontext .. "..."
  699. end
  700. local _, nchars2 = buttontext:gsub(".[\128-\191]*", "")
  701. local stretch = (maxchars/#buttontext)*100
  702. buttontext = string.format("{\\fscx%f}",
  703. (maxchars/#buttontext)*100) .. buttontext
  704. end
  705.  
  706. elem_ass:append(buttontext)
  707. end
  708.  
  709. master_ass:merge(elem_ass)
  710. end
  711. end
  712.  
  713. --
  714. -- Message display
  715. --
  716.  
  717. -- pos is 1 based
  718. function limited_list(prop, pos)
  719. local proplist = mp.get_property_native(prop, {})
  720. local count = #proplist
  721. if count == 0 then
  722. return count, proplist
  723. end
  724.  
  725. local fs = tonumber(mp.get_property('options/osd-font-size'))
  726. local max = math.ceil(osc_param.unscaled_y*0.75 / fs)
  727. if max % 2 == 0 then
  728. max = max - 1
  729. end
  730. local delta = math.ceil(max / 2) - 1
  731. local begi = math.max(math.min(pos - delta, count - max + 1), 1)
  732. local endi = math.min(begi + max - 1, count)
  733.  
  734. local reslist = {}
  735. for i=begi, endi do
  736. local item = proplist[i]
  737. item.current = (i == pos) and true or nil
  738. table.insert(reslist, item)
  739. end
  740. return count, reslist
  741. end
  742.  
  743. function get_playlist()
  744. local pos = mp.get_property_number('playlist-pos', 0) + 1
  745. local count, limlist = limited_list('playlist', pos)
  746. if count == 0 then
  747. return 'Empty playlist.'
  748. end
  749.  
  750. local message = string.format('Playlist [%d/%d]:\n', pos, count)
  751. for i, v in ipairs(limlist) do
  752. local title = v.title
  753. local _, filename = utils.split_path(v.filename)
  754. if title == nil then
  755. title = filename
  756. end
  757. message = string.format('%s %s %s\n', message,
  758. (v.current and '●' or '○'), title)
  759. end
  760. return message
  761. end
  762.  
  763. function get_chapterlist()
  764. local pos = mp.get_property_number('chapter', 0) + 1
  765. local count, limlist = limited_list('chapter-list', pos)
  766. if count == 0 then
  767. return 'No chapters.'
  768. end
  769.  
  770. local message = string.format('Chapters [%d/%d]:\n', pos, count)
  771. for i, v in ipairs(limlist) do
  772. local time = mp.format_time(v.time)
  773. local title = v.title
  774. if title == nil then
  775. title = string.format('Chapter %02d', i)
  776. end
  777. message = string.format('%s[%s] %s %s\n', message, time,
  778. (v.current and '●' or '○'), title)
  779. end
  780. return message
  781. end
  782.  
  783. function show_message(text, duration)
  784.  
  785. --print("text: "..text.." duration: " .. duration)
  786. if duration == nil then
  787. duration = tonumber(mp.get_property("options/osd-duration")) / 1000
  788. elseif not type(duration) == "number" then
  789. print("duration: " .. duration)
  790. end
  791.  
  792. -- cut the text short, otherwise the following functions
  793. -- may slow down massively on huge input
  794. text = string.sub(text, 0, 4000)
  795.  
  796. -- replace actual linebreaks with ASS linebreaks
  797. text = string.gsub(text, "\n", "\\N")
  798.  
  799. state.message_text = text
  800. state.message_timeout = mp.get_time() + duration
  801. end
  802.  
  803. function render_message(ass)
  804. if not(state.message_timeout == nil) and not(state.message_text == nil)
  805. and state.message_timeout > mp.get_time() then
  806. local _, lines = string.gsub(state.message_text, "\\N", "")
  807.  
  808. local fontsize = tonumber(mp.get_property("options/osd-font-size"))
  809. local outline = tonumber(mp.get_property("options/osd-border-size"))
  810. local maxlines = math.ceil(osc_param.unscaled_y*0.75 / fontsize)
  811. local counterscale = osc_param.playresy / osc_param.unscaled_y
  812.  
  813. fontsize = fontsize * counterscale / math.max(0.65 + math.min(lines/maxlines, 1), 1)
  814. outline = outline * counterscale / math.max(0.75 + math.min(lines/maxlines, 1)/2, 1)
  815.  
  816. local style = "{\\bord" .. outline .. "\\fs" .. fontsize .. "}"
  817.  
  818.  
  819. ass:new_event()
  820. ass:append(style .. state.message_text)
  821. else
  822. state.message_text = nil
  823. state.message_timeout = nil
  824. end
  825. end
  826.  
  827. --
  828. -- Initialisation and Layout
  829. --
  830.  
  831. function new_element(name, type)
  832. elements[name] = {}
  833. elements[name].type = type
  834.  
  835. -- add default stuff
  836. elements[name].eventresponder = {}
  837. elements[name].visible = true
  838. elements[name].enabled = true
  839. elements[name].softrepeat = false
  840. elements[name].styledown = (type == "button")
  841. elements[name].state = {}
  842.  
  843. if (type == "slider") then
  844. elements[name].slider = {min = {value = 0}, max = {value = 100}}
  845. end
  846.  
  847.  
  848. return elements[name]
  849. end
  850.  
  851. function add_layout(name)
  852. if not (elements[name] == nil) then
  853. -- new layout
  854. elements[name].layout = {}
  855.  
  856. -- set layout defaults
  857. elements[name].layout.layer = 50
  858. elements[name].layout.alpha = {[1] = 0, [2] = 255, [3] = 255, [4] = 255}
  859.  
  860. if (elements[name].type == "button") then
  861. elements[name].layout.button = {
  862. maxchars = nil,
  863. }
  864. elseif (elements[name].type == "slider") then
  865. -- slider defaults
  866. elements[name].layout.slider = {
  867. border = 1,
  868. gap = 1,
  869. nibbles_top = true,
  870. nibbles_bottom = true,
  871. stype = "slider",
  872. adjust_tooltip = true,
  873. tooltip_style = "",
  874. tooltip_an = 2,
  875. alpha = {[1] = 0, [2] = 255, [3] = 88, [4] = 255},
  876. }
  877. elseif (elements[name].type == "box") then
  878. elements[name].layout.box = {radius = 0}
  879. end
  880.  
  881. return elements[name].layout
  882. else
  883. msg.error("Can't add_layout to element \""..name.."\", doesn't exist.")
  884. end
  885. end
  886.  
  887. --
  888. -- Layouts
  889. --
  890.  
  891. local layouts = {}
  892.  
  893. -- Classic box layout
  894. layouts["box"] = function ()
  895.  
  896. local osc_geo = {
  897. w = 1100, -- width
  898. h = 70, -- height
  899. r = 0, -- corner-radius
  900. p = 0, -- padding
  901. }
  902.  
  903. -- make sure the OSC actually fits into the video
  904. if (osc_param.playresx < (osc_geo.w + (2 * osc_geo.p))) then
  905. osc_param.playresy = (osc_geo.w+(2*osc_geo.p))/osc_param.display_aspect
  906. osc_param.playresx = osc_param.playresy * osc_param.display_aspect
  907. end
  908.  
  909. -- position of the controller according to video aspect and valignment
  910. local posX = math.floor(get_align(user_opts.halign, osc_param.playresx,
  911. osc_geo.w, 0))
  912. local posY = math.floor(get_align(user_opts.valign, osc_param.playresy,
  913. osc_geo.h, 0))
  914.  
  915. -- position offset for contents aligned at the borders of the box
  916. local pos_offsetX = (osc_geo.w - (2*osc_geo.p)) / 2
  917. local pos_offsetY = (osc_geo.h - (2*osc_geo.p)) / 2
  918.  
  919. osc_param.areas = {} -- delete areas
  920.  
  921. -- area for active mouse input
  922. add_area("input", get_hitbox_coords(posX, posY, 5, osc_geo.w, osc_geo.h))
  923.  
  924. -- area for show/hide
  925. local sh_area_y0, sh_area_y1
  926. if user_opts.valign > 0 then
  927. -- deadzone above OSC
  928. sh_area_y0 = get_align(-1 + (2*user_opts.deadzonesize),
  929. posY - (osc_geo.h / 2), 0, 0)
  930. sh_area_y1 = osc_param.playresy
  931. else
  932. -- deadzone below OSC
  933. sh_area_y0 = 0
  934. sh_area_y1 = (posY + (osc_geo.h / 2)) +
  935. get_align(1 - (2*user_opts.deadzonesize),
  936. osc_param.playresy - (posY + (osc_geo.h / 2)), 0, 0)
  937. end
  938. add_area("showhide", 0, sh_area_y0, osc_param.playresx, sh_area_y1)
  939.  
  940. -- fetch values
  941. local osc_w, osc_h, osc_r, osc_p =
  942. osc_geo.w, osc_geo.h, osc_geo.r, osc_geo.p
  943.  
  944. local lo
  945.  
  946. --
  947. -- Background box
  948. --
  949.  
  950. new_element("bgbox", "box")
  951. lo = add_layout("bgbox")
  952.  
  953. lo.geometry = {x = posX, y = posY, an = 5, w = osc_w, h = osc_h}
  954. lo.layer = 10
  955. lo.style = osc_styles.box
  956. lo.alpha[1] = user_opts.boxalpha
  957. lo.alpha[3] = user_opts.boxalpha
  958. lo.box.radius = osc_r
  959.  
  960. --
  961. -- Title row
  962. --
  963.  
  964. local titlerowY = posY - pos_offsetY - 10
  965. local bigleft = 140
  966. local bigbtndist = 60 -22
  967. local bigbtnrowY = posY - pos_offsetY + 35 +8
  968.  
  969. lo = add_layout("title")
  970. lo.geometry = {x = posX- pos_offsetX+bigleft + (bigbtndist * 4), y = bigbtnrowY-3, an = 1, w = pos_offsetX, h = 20}
  971. lo.style = osc_styles.vidtitle
  972. lo.button.maxchars = user_opts.boxmaxchars
  973.  
  974. -- lo = add_layout("pl_prev")
  975. -- lo.geometry =
  976. -- {x = (posX - pos_offsetX), y = titlerowY, an = 7, w = 12, h = 12}
  977. -- lo.style = osc_styles.topButtons
  978.  
  979. -- lo = add_layout("pl_next")
  980. -- lo.geometry =
  981. -- {x = (posX + pos_offsetX), y = titlerowY, an = 9, w = 12, h = 12}
  982. -- lo.style = osc_styles.topButtons
  983.  
  984. --
  985. -- Big buttons
  986. --
  987.  
  988.  
  989.  
  990.  
  991. lo = add_layout("playpause")
  992. lo.geometry =
  993. {x = posX- pos_offsetX+bigleft, y = bigbtnrowY, an = 5, w = 40, h = 40}
  994. lo.style = osc_styles.bigButtons
  995.  
  996. lo = add_layout("skipback")
  997. lo.geometry =
  998. {x = posX- pos_offsetX+bigleft- bigbtndist, y = bigbtnrowY, an = 5, w = 40, h = 40}
  999. lo.style = osc_styles.smallButtonsR
  1000.  
  1001. lo = add_layout("skipfrwd")
  1002. lo.geometry =
  1003. {x = posX- pos_offsetX+bigleft+ bigbtndist, y = bigbtnrowY, an = 5, w = 40, h = 40}
  1004. lo.style = osc_styles.smallButtonsR
  1005.  
  1006. lo = add_layout("ch_prev")
  1007. lo.geometry =
  1008. {x = posX- pos_offsetX+bigleft- (bigbtndist * 2), y = bigbtnrowY, an = 5, w = 40, h = 40}
  1009. lo.style = osc_styles.smallButtonsR
  1010.  
  1011. lo = add_layout("ch_next")
  1012. lo.geometry =
  1013. {x = posX- pos_offsetX+bigleft+ (bigbtndist * 2), y = bigbtnrowY, an = 5, w = 40, h = 40}
  1014. lo.style = osc_styles.smallButtonsR
  1015.  
  1016.  
  1017. lo = add_layout("pl_prev")
  1018. lo.geometry =
  1019. {x = posX- pos_offsetX+bigleft - (bigbtndist * 3), y = bigbtnrowY, an = 5, w = 40, h = 40}
  1020. lo.style = osc_styles.smallButtonsL
  1021.  
  1022. lo = add_layout("pl_next")
  1023. lo.geometry =
  1024. {x = posX- pos_offsetX+bigleft + (bigbtndist * 3), y = bigbtnrowY, an = 5, w = 40, h = 40}
  1025. lo.style = osc_styles.smallButtonsL
  1026.  
  1027.  
  1028.  
  1029.  
  1030. lo = add_layout("cy_audio")
  1031. lo.geometry =
  1032. {x = posX+pos_offsetX - (40 * 3)-25-70 - osc_geo.p, y = bigbtnrowY, an = 7, w = 50, h = 30}
  1033. lo.style = osc_styles.topButtonsBar
  1034.  
  1035. lo = add_layout("cy_sub")
  1036. lo.geometry =
  1037. {x = posX+pos_offsetX - (40 * 3)-25- osc_geo.p, y = bigbtnrowY, an = 7, w = 50, h = 30}
  1038. lo.style = osc_styles.topButtonsBar
  1039.  
  1040. lo = add_layout("volume")
  1041. lo.geometry =
  1042. {x = posX+pos_offsetX - (40 * 2) - osc_geo.p,
  1043. y = bigbtnrowY-3, an = 7, w = 25, h = 30}
  1044. lo.style = osc_styles.smallButtonsBar
  1045.  
  1046. lo = add_layout("tog_fs")
  1047. lo.geometry =
  1048. {x = posX+pos_offsetX - 40, y = bigbtnrowY-2, an = 7, w = 25, h = 30}
  1049. lo.style = osc_styles.smallButtonsBar
  1050.  
  1051. --
  1052. -- Seekbar
  1053. --
  1054. geo ={x = posX, y = posY+pos_offsetY-22-36, an = 2, w = pos_offsetX*2, h = 12}
  1055. lo = add_layout("seekbar")
  1056.  
  1057. if user_opts["seekbarstyle"] ~= "knob" then
  1058.  
  1059. new_element("bgbar1", "box")
  1060. lo = add_layout("bgbar1")
  1061.  
  1062. lo.geometry = geo
  1063. lo.layer = 15
  1064. lo.style = osc_styles.vidtitleBar
  1065. lo.alpha[1] =
  1066. math.min(255, user_opts.boxalpha + (255 - user_opts.boxalpha)*0.05)
  1067.  
  1068.  
  1069. end
  1070.  
  1071. lo = add_layout("seekbar")
  1072. lo.geometry = geo
  1073. lo.style = osc_styles.timecodes
  1074. lo.slider.border = 0
  1075. lo.slider.gap = 2
  1076. lo.slider.tooltip_style = osc_styles.timePosBar
  1077. lo.slider.tooltip_an = 5
  1078. lo.slider.stype = user_opts["seekbarstyle"]
  1079.  
  1080. if user_opts["seekbarstyle"] == "knob" then
  1081. lo.geometry.y = posY+pos_offsetY-22-32
  1082. end
  1083.  
  1084. --
  1085. -- Timecodes + Cache
  1086. --
  1087.  
  1088. local bottomrowY = bigbtnrowY
  1089.  
  1090. lo = add_layout("tc_left")
  1091. lo.geometry =
  1092. {x = posX- pos_offsetX+bigleft + (bigbtndist * 4), y = bottomrowY+1, an = 7, w = 110, h = 18}
  1093. lo.style = osc_styles.smallButtonsL
  1094.  
  1095. lo = add_layout("tc_right")
  1096. lo.geometry =
  1097. {x = posX- pos_offsetX+bigleft + (bigbtndist * 4)+140*3, y = bottomrowY+1, an = 7, w = 110, h = 18}
  1098. lo.style = osc_styles.smallButtonsL
  1099.  
  1100. lo = add_layout("cache")
  1101. lo.geometry =
  1102. {x = posX- pos_offsetX+bigleft + (bigbtndist * 4)+140+50, y = bottomrowY+1, an = 7, w = 110, h = 18}
  1103. lo.style = osc_styles.smallButtonsL
  1104.  
  1105. end
  1106.  
  1107. -- slim box layout
  1108. layouts["slimbox"] = function ()
  1109.  
  1110. local osc_geo = {
  1111. w = 660, -- width
  1112. h = 70, -- height
  1113. r = 10, -- corner-radius
  1114. }
  1115.  
  1116. -- make sure the OSC actually fits into the video
  1117. if (osc_param.playresx < (osc_geo.w)) then
  1118. osc_param.playresy = (osc_geo.w)/osc_param.display_aspect
  1119. osc_param.playresx = osc_param.playresy * osc_param.display_aspect
  1120. end
  1121.  
  1122. -- position of the controller according to video aspect and valignment
  1123. local posX = math.floor(get_align(user_opts.halign, osc_param.playresx,
  1124. osc_geo.w, 0))
  1125. local posY = math.floor(get_align(user_opts.valign, osc_param.playresy,
  1126. osc_geo.h, 0))
  1127.  
  1128. osc_param.areas = {} -- delete areas
  1129.  
  1130. -- area for active mouse input
  1131. add_area("input", get_hitbox_coords(posX, posY, 5, osc_geo.w, osc_geo.h))
  1132.  
  1133. -- area for show/hide
  1134. local sh_area_y0, sh_area_y1
  1135. if user_opts.valign > 0 then
  1136. -- deadzone above OSC
  1137. sh_area_y0 = get_align(-1 + (2*user_opts.deadzonesize),
  1138. posY - (osc_geo.h / 2), 0, 0)
  1139. sh_area_y1 = osc_param.playresy
  1140. else
  1141. -- deadzone below OSC
  1142. sh_area_y0 = 0
  1143. sh_area_y1 = (posY + (osc_geo.h / 2)) +
  1144. get_align(1 - (2*user_opts.deadzonesize),
  1145. osc_param.playresy - (posY + (osc_geo.h / 2)), 0, 0)
  1146. end
  1147. add_area("showhide", 0, sh_area_y0, osc_param.playresx, sh_area_y1)
  1148.  
  1149. local lo
  1150.  
  1151. local tc_w, ele_h, inner_w = 100, 20, osc_geo.w - 100
  1152.  
  1153. -- styles
  1154. local styles = {
  1155. box = "{\\rDefault\\blur0\\bord1\\1c&H000000\\3c&HFFFFFF}",
  1156. timecodes = "{\\1c&HFFFFFF\\3c&H000000\\fs20\\bord2\\blur1}",
  1157. tooltip = "{\\1c&HFFFFFF\\3c&H000000\\fs12\\bord1\\blur0.5}",
  1158. }
  1159.  
  1160.  
  1161. new_element("bgbox", "box")
  1162. lo = add_layout("bgbox")
  1163.  
  1164. lo.geometry = {x = posX, y = posY - 1, an = 2, w = inner_w, h = ele_h}
  1165. lo.layer = 10
  1166. lo.style = osc_styles.box
  1167. lo.alpha[1] = user_opts.boxalpha
  1168. lo.alpha[3] = 0
  1169. if not (user_opts["seekbarstyle"] == "bar") then
  1170. lo.box.radius = osc_geo.r
  1171. end
  1172.  
  1173.  
  1174. lo = add_layout("seekbar")
  1175. lo.geometry =
  1176. {x = posX, y = posY - 1, an = 2, w = inner_w, h = ele_h}
  1177. lo.style = osc_styles.timecodes
  1178. lo.slider.border = 0
  1179. lo.slider.gap = 1.5
  1180. lo.slider.tooltip_style = styles.tooltip
  1181. lo.slider.stype = user_opts["seekbarstyle"]
  1182. lo.slider.adjust_tooltip = false
  1183.  
  1184. --
  1185. -- Timecodes
  1186. --
  1187.  
  1188. lo = add_layout("tc_left")
  1189. lo.geometry =
  1190. {x = posX - (inner_w/2) + osc_geo.r, y = posY + 1,
  1191. an = 7, w = tc_w, h = ele_h}
  1192. lo.style = styles.timecodes
  1193. lo.alpha[3] = user_opts.boxalpha
  1194.  
  1195. lo = add_layout("tc_right")
  1196. lo.geometry =
  1197. {x = posX + (inner_w/2) - osc_geo.r, y = posY + 1,
  1198. an = 9, w = tc_w, h = ele_h}
  1199. lo.style = styles.timecodes
  1200. lo.alpha[3] = user_opts.boxalpha
  1201.  
  1202. -- Cache
  1203.  
  1204. lo = add_layout("cache")
  1205. lo.geometry =
  1206. {x = posX, y = posY + 1,
  1207. an = 8, w = tc_w, h = ele_h}
  1208. lo.style = styles.timecodes
  1209. lo.alpha[3] = user_opts.boxalpha
  1210.  
  1211.  
  1212. end
  1213.  
  1214. layouts["bottombar"] = function()
  1215. local osc_geo = {
  1216. x = -2,
  1217. y = osc_param.playresy - 54 - user_opts.barmargin,
  1218. an = 7,
  1219. w = osc_param.playresx + 4,
  1220. h = 56,
  1221. }
  1222.  
  1223. if osc_geo.w >= 1150 then
  1224. osc_geo.w = 1150
  1225. osc_geo.x = (osc_param.playresx - osc_geo.w) / 2
  1226. end
  1227.  
  1228. local padX = 9
  1229. local padY = 3
  1230. local buttonW = 27
  1231. local tcW = (state.tc_ms) and 89 or 60
  1232. local tsW = 45
  1233. local minW = (buttonW + padX)*5 + (tcW + padX)*4 + (tsW + padX)*2
  1234.  
  1235. if ((osc_param.display_aspect > 0) and (osc_param.playresx < minW)) then
  1236. osc_param.playresy = minW / osc_param.display_aspect
  1237. osc_param.playresx = osc_param.playresy * osc_param.display_aspect
  1238. osc_geo.y = osc_param.playresy - 54 - user_opts.barmargin
  1239. osc_geo.w = osc_param.playresx + 4
  1240. end
  1241.  
  1242. local line1 = osc_geo.y + 9 + padY
  1243. local line2 = osc_geo.y + 36 + padY
  1244.  
  1245. osc_param.areas = {}
  1246.  
  1247. add_area("input", get_hitbox_coords(osc_geo.x, osc_geo.y, osc_geo.an,
  1248. osc_geo.w, osc_geo.h))
  1249.  
  1250. local sh_area_y0, sh_area_y1
  1251. sh_area_y0 = get_align(-1 + (2*user_opts.deadzonesize),
  1252. osc_geo.y - (osc_geo.h / 2), 0, 0)
  1253. sh_area_y1 = osc_param.playresy - user_opts.barmargin
  1254. add_area("showhide", 0, sh_area_y0, osc_param.playresx, sh_area_y1)
  1255.  
  1256. local lo, geo
  1257.  
  1258. -- Background bar
  1259. new_element("bgbox", "box")
  1260. lo = add_layout("bgbox")
  1261.  
  1262. lo.geometry = osc_geo
  1263. lo.layer = 10
  1264. lo.style = osc_styles.box
  1265. lo.alpha[1] = user_opts.boxalpha
  1266.  
  1267.  
  1268. -- -- Playlist prev/next
  1269. geo = { x = osc_geo.x + padX -padX, y = line1,
  1270. an = 4, w = 18-18, h = 18 - padY }
  1271. -- lo = add_layout("pl_prev")
  1272. -- lo.geometry = geo
  1273. -- lo.style = osc_styles.topButtonsBar
  1274.  
  1275. -- geo = { x = geo.x + geo.w + padX, y = geo.y, an = geo.an, w = geo.w, h = geo.h }
  1276. -- lo = add_layout("pl_next")
  1277. -- lo.geometry = geo
  1278. -- lo.style = osc_styles.topButtonsBar
  1279.  
  1280. local t_l = geo.x + geo.w + padX
  1281.  
  1282.  
  1283.  
  1284. -- Cache
  1285. if osc_geo.w < 1150 then
  1286.  
  1287. geo = { x = osc_geo.w - osc_geo.x - padX, y = geo.y,
  1288. an = 6, w = 100, h = geo.h }
  1289. else
  1290.  
  1291. geo = { x = osc_geo.x + osc_geo.w - padX, y = geo.y,
  1292. an = 6, w = 100, h = geo.h }
  1293. end
  1294.  
  1295. lo = add_layout("cache")
  1296. lo.geometry = geo
  1297. lo.style = osc_styles.vidtitleBar
  1298.  
  1299. local t_r = geo.x - geo.w - padX*2
  1300.  
  1301. -- Title
  1302.  
  1303. if osc_geo.w < 1150 then
  1304.  
  1305. geo = { x = t_l, y = geo.y, an = 4,
  1306. w = t_r - t_l, h = geo.h }
  1307. else
  1308.  
  1309. geo = { x = t_l, y = geo.y, an = 4,
  1310. w = t_r, h = geo.h }
  1311. end
  1312.  
  1313.  
  1314. lo = add_layout("title")
  1315. lo.geometry = geo
  1316. lo.style = string.format("%s{\\clip(%f,%f,%f,%f)}",
  1317. osc_styles.vidtitleBar,
  1318. geo.x, geo.y-geo.h, geo.w, geo.y+geo.h)
  1319.  
  1320.  
  1321.  
  1322.  
  1323. -- -- Playlist prev/next
  1324. geo = { x = osc_geo.x + padX, y = line2, an = 4,
  1325. w = buttonW, h = 36 - padY*2}
  1326.  
  1327. lo = add_layout("pl_prev")
  1328. lo.geometry = geo
  1329. lo.style = osc_styles.smallButtonsL
  1330. -- --
  1331. geo = { x = geo.x + geo.w + padX, y = geo.y, an = geo.an, w = geo.w, h = geo.h }
  1332. lo = add_layout("ch_prev")
  1333. lo.geometry = geo
  1334. lo.style = osc_styles.smallButtonsR
  1335. -- --
  1336. geo = { x = geo.x + geo.w + padX, y = geo.y, an = geo.an, w = geo.w, h = geo.h }
  1337. lo = add_layout("skipback")
  1338. lo.geometry = geo
  1339. lo.style = osc_styles.smallButtonsR
  1340.  
  1341. -- Playback control buttons
  1342. geo = { x = geo.x + geo.w + padX, y = geo.y, an = geo.an, w = geo.w, h = geo.h }
  1343. lo = add_layout("playpause")
  1344. lo.geometry = geo
  1345. lo.style = osc_styles.smallButtonsR
  1346.  
  1347. geo = { x = geo.x + geo.w + padX, y = geo.y, an = geo.an, w = geo.w, h = geo.h }
  1348. lo = add_layout("skipfrwd")
  1349. lo.geometry = geo
  1350. lo.style = osc_styles.smallButtonsR
  1351.  
  1352. geo = { x = geo.x + geo.w + padX, y = geo.y, an = geo.an, w = geo.w, h = geo.h }
  1353. lo = add_layout("ch_next")
  1354. lo.geometry = geo
  1355. lo.style = osc_styles.smallButtonsR
  1356.  
  1357. geo = { x = geo.x + geo.w + padX, y = geo.y, an = geo.an, w = geo.w, h = geo.h }
  1358. lo = add_layout("pl_next")
  1359. lo.geometry = geo
  1360. lo.style = osc_styles.smallButtonsL
  1361.  
  1362.  
  1363. -- Left timecode
  1364. geo = { x = geo.x + geo.w + padX + tcW, y = geo.y, an = 6,
  1365. w = tcW, h = geo.h }
  1366. lo = add_layout("tc_left")
  1367. lo.geometry = geo
  1368. lo.style = osc_styles.smallButtonsL
  1369.  
  1370. local sb_l = geo.x + padX
  1371.  
  1372. -- Fullscreen button
  1373. geo = { x = osc_geo.x + osc_geo.w - buttonW - padX, y = geo.y, an = 4,
  1374. w = buttonW, h = geo.h }
  1375. lo = add_layout("tog_fs")
  1376. lo.geometry = geo
  1377. lo.style = osc_styles.smallButtonsBar
  1378.  
  1379. -- Volume
  1380. geo = { x = geo.x - geo.w - padX, y = geo.y, an = geo.an, w = geo.w, h = geo.h }
  1381. lo = add_layout("volume")
  1382. lo.geometry = geo
  1383. lo.style = osc_styles.smallButtonsBar
  1384.  
  1385. -- Track selection buttons
  1386. geo = { x = geo.x - tsW - padX, y = geo.y, an = geo.an, w = tsW, h = geo.h }
  1387. lo = add_layout("cy_sub")
  1388. lo.geometry = geo
  1389. lo.style = osc_styles.topButtonsBar
  1390.  
  1391. geo = { x = geo.x - geo.w - padX, y = geo.y, an = geo.an, w = geo.w, h = geo.h }
  1392. lo = add_layout("cy_audio")
  1393. lo.geometry = geo
  1394. lo.style = osc_styles.topButtonsBar
  1395.  
  1396.  
  1397. -- Right timecode
  1398. geo = { x = geo.x - padX - tcW - 10, y = geo.y, an = geo.an,
  1399. w = tcW, h = geo.h }
  1400. lo = add_layout("tc_right")
  1401. lo.geometry = geo
  1402. lo.style = osc_styles.smallButtonsL
  1403.  
  1404. local sb_r = geo.x - padX
  1405.  
  1406.  
  1407. -- Seekbar
  1408. geo = { x = sb_l, y = geo.y+1, an = geo.an,
  1409. w = math.max(0, sb_r - sb_l), h = geo.h-20 }
  1410. lo = add_layout("seekbar")
  1411.  
  1412. if user_opts["seekbarstyle"] ~= "knob" then
  1413.  
  1414. new_element("bgbar1", "box")
  1415. lo = add_layout("bgbar1")
  1416.  
  1417. lo.geometry = geo
  1418. lo.layer = 15
  1419. lo.style = osc_styles.vidtitleBar
  1420. lo.alpha[1] =
  1421. math.min(255, user_opts.boxalpha + (255 - user_opts.boxalpha)*0.1)
  1422.  
  1423. end
  1424.  
  1425.  
  1426. lo = add_layout("seekbar")
  1427. lo.geometry = geo
  1428. lo.style = osc_styles.timecodes
  1429. lo.slider.border = 0
  1430. lo.slider.gap = 1.5
  1431. lo.slider.tooltip_style = osc_styles.timePosBar
  1432. lo.slider.tooltip_an = 5
  1433. lo.slider.stype = user_opts["seekbarstyle"]
  1434. end
  1435.  
  1436. layouts["topbar"] = function()
  1437. local osc_geo = {
  1438. x = -2,
  1439. y = 54 + user_opts.barmargin,
  1440. an = 1,
  1441. w = osc_param.playresx + 4,
  1442. h = 56,
  1443. }
  1444.  
  1445. local padX = 9
  1446. local padY = 3
  1447. local buttonW = 27
  1448. local tcW = (state.tc_ms) and 170 or 110
  1449. local tsW = 90
  1450. local minW = (buttonW + padX)*5 + (tcW + padX)*4 + (tsW + padX)*2
  1451.  
  1452. if ((osc_param.display_aspect > 0) and (osc_param.playresx < minW)) then
  1453. osc_param.playresy = minW / osc_param.display_aspect
  1454. osc_param.playresx = osc_param.playresy * osc_param.display_aspect
  1455. osc_geo.y = 54 + user_opts.barmargin
  1456. osc_geo.w = osc_param.playresx + 4
  1457. end
  1458.  
  1459. local line1 = osc_geo.y - 36 - padY
  1460. local line2 = osc_geo.y - 9 - padY
  1461.  
  1462. osc_param.areas = {}
  1463.  
  1464. add_area("input", get_hitbox_coords(osc_geo.x, osc_geo.y, osc_geo.an,
  1465. osc_geo.w, osc_geo.h))
  1466.  
  1467. local sh_area_y0, sh_area_y1
  1468. sh_area_y0 = user_opts.barmargin
  1469. sh_area_y1 = (osc_geo.y + (osc_geo.h / 2)) +
  1470. get_align(1 - (2*user_opts.deadzonesize),
  1471. osc_param.playresy - (osc_geo.y + (osc_geo.h / 2)), 0, 0)
  1472. add_area("showhide", 0, sh_area_y0, osc_param.playresx, sh_area_y1)
  1473.  
  1474. local lo, geo
  1475.  
  1476. -- Background bar
  1477. new_element("bgbox", "box")
  1478. lo = add_layout("bgbox")
  1479.  
  1480. lo.geometry = osc_geo
  1481. lo.layer = 10
  1482. lo.style = osc_styles.box
  1483. lo.alpha[1] = user_opts.boxalpha
  1484.  
  1485.  
  1486. -- Playback control buttons
  1487. geo = { x = osc_geo.x + padX, y = line1, an = 4,
  1488. w = buttonW, h = 36 - padY*2 }
  1489. lo = add_layout("playpause")
  1490. lo.geometry = geo
  1491. lo.style = osc_styles.smallButtonsBar
  1492.  
  1493. geo = { x = geo.x + geo.w + padX, y = geo.y, an = geo.an, w = geo.w, h = geo.h }
  1494. lo = add_layout("ch_prev")
  1495. lo.geometry = geo
  1496. lo.style = osc_styles.smallButtonsBar
  1497.  
  1498. geo = { x = geo.x + geo.w + padX, y = geo.y, an = geo.an, w = geo.w, h = geo.h }
  1499. lo = add_layout("ch_next")
  1500. lo.geometry = geo
  1501. lo.style = osc_styles.smallButtonsBar
  1502.  
  1503.  
  1504. -- Left timecode
  1505. geo = { x = geo.x + geo.w + padX + tcW, y = geo.y, an = 6,
  1506. w = tcW, h = geo.h }
  1507. lo = add_layout("tc_left")
  1508. lo.geometry = geo
  1509. lo.style = osc_styles.timecodesBar
  1510.  
  1511. local sb_l = geo.x + padX
  1512.  
  1513. -- Fullscreen button
  1514. geo = { x = osc_geo.x + osc_geo.w - buttonW - padX, y = geo.y, an = 4,
  1515. w = buttonW, h = geo.h }
  1516. lo = add_layout("tog_fs")
  1517. lo.geometry = geo
  1518. lo.style = osc_styles.smallButtonsBar
  1519.  
  1520. -- Volume
  1521. geo = { x = geo.x - geo.w - padX, y = geo.y, an = geo.an, w = geo.w, h = geo.h }
  1522. lo = add_layout("volume")
  1523. lo.geometry = geo
  1524. lo.style = osc_styles.smallButtonsBar
  1525.  
  1526. -- Track selection buttons
  1527. geo = { x = geo.x - tsW - padX, y = geo.y, an = geo.an, w = tsW, h = geo.h }
  1528. lo = add_layout("cy_sub")
  1529. lo.geometry = geo
  1530. lo.style = osc_styles.smallButtonsBar
  1531.  
  1532. geo = { x = geo.x - geo.w - padX, y = geo.y, an = geo.an, w = geo.w, h = geo.h }
  1533. lo = add_layout("cy_audio")
  1534. lo.geometry = geo
  1535. lo.style = osc_styles.smallButtonsBar
  1536.  
  1537.  
  1538. -- Right timecode
  1539. geo = { x = geo.x - geo.w - padX - tcW - 10, y = geo.y, an = 4,
  1540. w = tcW, h = geo.h }
  1541. lo = add_layout("tc_right")
  1542. lo.geometry = geo
  1543. lo.style = osc_styles.timecodesBar
  1544.  
  1545. local sb_r = geo.x - padX
  1546.  
  1547.  
  1548. -- Seekbar
  1549. geo = { x = sb_l, y = user_opts.barmargin, an = 7,
  1550. w = math.max(0, sb_r - sb_l), h = geo.h }
  1551. new_element("bgbar1", "box")
  1552. lo = add_layout("bgbar1")
  1553.  
  1554. lo.geometry = geo
  1555. lo.layer = 15
  1556. lo.style = osc_styles.timecodesBar
  1557. lo.alpha[1] =
  1558. math.min(255, user_opts.boxalpha + (255 - user_opts.boxalpha)*0.8)
  1559.  
  1560. lo = add_layout("seekbar")
  1561. lo.geometry = geo
  1562. lo.style = osc_styles.timecodesBar
  1563. lo.slider.border = 0
  1564. lo.slider.gap = 0
  1565. lo.slider.tooltip_style = osc_styles.timePosBar
  1566. lo.slider.stype = user_opts["seekbarstyle"]
  1567. lo.slider.tooltip_an = 5
  1568.  
  1569.  
  1570. -- Playlist prev/next
  1571. geo = { x = osc_geo.x + padX, y = line2, an = 4, w = 18, h = 18 - padY }
  1572. lo = add_layout("pl_prev")
  1573. lo.geometry = geo
  1574. lo.style = osc_styles.topButtonsBar
  1575.  
  1576. geo = { x = geo.x + geo.w + padX, y = geo.y, an = geo.an, w = geo.w, h = geo.h }
  1577. lo = add_layout("pl_next")
  1578. lo.geometry = geo
  1579. lo.style = osc_styles.topButtonsBar
  1580.  
  1581. local t_l = geo.x + geo.w + padX
  1582.  
  1583. -- Cache
  1584. geo = { x = osc_geo.x + osc_geo.w - padX, y = geo.y,
  1585. an = 6, w = 150, h = geo.h }
  1586. lo = add_layout("cache")
  1587. lo.geometry = geo
  1588. lo.style = osc_styles.vidtitleBar
  1589.  
  1590. local t_r = geo.x - geo.w - padX*2
  1591.  
  1592. -- Title
  1593. geo = { x = t_l, y = geo.y, an = 4,
  1594. w = t_r - t_l, h = geo.h }
  1595. lo = add_layout("title")
  1596. lo.geometry = geo
  1597. lo.style = string.format("%s{\\clip(%f,%f,%f,%f)}",
  1598. osc_styles.vidtitleBar,
  1599. geo.x, geo.y-geo.h, geo.w, geo.y+geo.h)
  1600. end
  1601.  
  1602. -- Validate string type user options
  1603. function validate_user_opts()
  1604. if layouts[user_opts.layout] == nil then
  1605. msg.warn("Invalid setting \""..user_opts.layout.."\" for layout")
  1606. user_opts.layout = "box"
  1607. end
  1608.  
  1609. if user_opts.seekbarstyle ~= "slider" and
  1610. user_opts.seekbarstyle ~= "bar" and
  1611. user_opts.seekbarstyle ~= "knob" then
  1612. msg.warn("Invalid setting \"" .. user_opts.seekbarstyle
  1613. .. "\" for seekbarstyle")
  1614. user_opts.seekbarstyle = "slider"
  1615. end
  1616. end
  1617.  
  1618.  
  1619. -- OSC INIT
  1620. function osc_init()
  1621. msg.debug("osc_init")
  1622.  
  1623. -- set canvas resolution according to display aspect and scaling setting
  1624. local baseResY = 720
  1625. local display_w, display_h, display_aspect = mp.get_osd_size()
  1626. local scale = 1
  1627.  
  1628. if (mp.get_property("video") == "no") then -- dummy/forced window
  1629. scale = user_opts.scaleforcedwindow
  1630. elseif state.fullscreen then
  1631. scale = user_opts.scalefullscreen
  1632. else
  1633. scale = user_opts.scalewindowed
  1634. end
  1635.  
  1636. if user_opts.vidscale then
  1637. osc_param.unscaled_y = baseResY
  1638. else
  1639. osc_param.unscaled_y = display_h
  1640. end
  1641. osc_param.playresy = osc_param.unscaled_y / scale
  1642. if (display_aspect > 0) then
  1643. osc_param.display_aspect = display_aspect
  1644. end
  1645. osc_param.playresx = osc_param.playresy * osc_param.display_aspect
  1646.  
  1647. -- stop seeking with the slider to prevent skipping files
  1648. state.active_element = nil
  1649.  
  1650.  
  1651.  
  1652. elements = {}
  1653.  
  1654. -- some often needed stuff
  1655. local pl_count = mp.get_property_number("playlist-count", 0)
  1656. local have_pl = (pl_count > 1)
  1657. local pl_pos = mp.get_property_number("playlist-pos", 0) + 1
  1658. local have_ch = (mp.get_property_number("chapters", 0) > 0)
  1659. local loop = mp.get_property("loop-playlist", "no")
  1660.  
  1661. local ne
  1662.  
  1663. -- title
  1664. ne = new_element("title", "button")
  1665.  
  1666. ne.content = function ()
  1667. local title = mp.command_native({"expand-text", user_opts.title})
  1668. -- escape ASS, and strip newlines and trailing slashes
  1669. title = title:gsub("\\n", " "):gsub("\\$", ""):gsub("{","\\{")
  1670. return not (title == "") and title or "mpv"
  1671. end
  1672.  
  1673. ne.eventresponder["mbtn_left_up"] = function ()
  1674. local title = mp.get_property_osd("media-title")
  1675. if (have_pl) then
  1676. title = string.format("[%d/%d] %s", countone(pl_pos - 1),
  1677. pl_count, title)
  1678. end
  1679. show_message(title)
  1680. end
  1681.  
  1682. ne.eventresponder["mbtn_right_up"] =
  1683. function () show_message(mp.get_property_osd("filename")) end
  1684.  
  1685. -- playlist buttons
  1686.  
  1687. -- prev
  1688. ne = new_element("pl_prev", "button")
  1689.  
  1690. ne.content = "\238\132\144"
  1691. ne.enabled = (pl_pos > 1) or (loop ~= "no")
  1692. ne.eventresponder["mbtn_left_up"] =
  1693. function ()
  1694. mp.commandv("playlist-prev", "weak")
  1695. show_message(get_playlist(), 3)
  1696. end
  1697. ne.eventresponder["shift+mbtn_left_up"] =
  1698. function () show_message(get_playlist(), 3) end
  1699. ne.eventresponder["mbtn_right_up"] =
  1700. function () show_message(get_playlist(), 3) end
  1701.  
  1702. --next
  1703. ne = new_element("pl_next", "button")
  1704.  
  1705. ne.content = "\238\132\129"
  1706. ne.enabled = (have_pl and (pl_pos < pl_count)) or (loop ~= "no")
  1707. ne.eventresponder["mbtn_left_up"] =
  1708. function ()
  1709. mp.commandv("playlist-next", "weak")
  1710. show_message(get_playlist(), 3)
  1711. end
  1712. ne.eventresponder["shift+mbtn_left_up"] =
  1713. function () show_message(get_playlist(), 3) end
  1714. ne.eventresponder["mbtn_right_up"] =
  1715. function () show_message(get_playlist(), 3) end
  1716.  
  1717.  
  1718. -- big buttons
  1719.  
  1720. --playpause
  1721. ne = new_element("playpause", "button")
  1722.  
  1723. ne.content = function ()
  1724. if mp.get_property("pause") == "yes" then
  1725. return ("\238\132\129")
  1726. else
  1727. return ("\238\128\130")
  1728. end
  1729. end
  1730. ne.eventresponder["mbtn_left_up"] =
  1731. function () mp.commandv("cycle", "pause") end
  1732.  
  1733. --skipback
  1734. ne = new_element("skipback", "button")
  1735.  
  1736. ne.softrepeat = true
  1737. ne.content = "\238\128\132"
  1738. ne.eventresponder["mbtn_left_down"] =
  1739. function () mp.commandv("seek", -5, "relative", "keyframes") end
  1740. ne.eventresponder["shift+mbtn_left_down"] =
  1741. function () mp.commandv("frame-back-step") end
  1742. ne.eventresponder["mbtn_right_down"] =
  1743. function () mp.commandv("seek", -30, "relative", "keyframes") end
  1744.  
  1745. --skipfrwd
  1746. ne = new_element("skipfrwd", "button")
  1747.  
  1748. ne.softrepeat = true
  1749. ne.content = "\238\128\133"
  1750. ne.eventresponder["mbtn_left_down"] =
  1751. function () mp.commandv("seek", 10, "relative", "keyframes") end
  1752. ne.eventresponder["shift+mbtn_left_down"] =
  1753. function () mp.commandv("frame-step") end
  1754. ne.eventresponder["mbtn_right_down"] =
  1755. function () mp.commandv("seek", 60, "relative", "keyframes") end
  1756.  
  1757. --ch_prev
  1758. ne = new_element("ch_prev", "button")
  1759.  
  1760. ne.enabled = have_ch
  1761. ne.content = "\238\132\132"
  1762. ne.eventresponder["mbtn_left_up"] =
  1763. function ()
  1764. mp.commandv("add", "chapter", -1)
  1765. show_message(get_chapterlist(), 3)
  1766. end
  1767. ne.eventresponder["shift+mbtn_left_up"] =
  1768. function () show_message(get_chapterlist(), 3) end
  1769. ne.eventresponder["mbtn_right_up"] =
  1770. function () show_message(get_chapterlist(), 3) end
  1771.  
  1772. --ch_next
  1773. ne = new_element("ch_next", "button")
  1774.  
  1775. ne.enabled = have_ch
  1776. ne.content = "\238\132\133"
  1777. ne.eventresponder["mbtn_left_up"] =
  1778. function ()
  1779. mp.commandv("add", "chapter", 1)
  1780. show_message(get_chapterlist(), 3)
  1781. end
  1782. ne.eventresponder["shift+mbtn_left_up"] =
  1783. function () show_message(get_chapterlist(), 3) end
  1784. ne.eventresponder["mbtn_right_up"] =
  1785. function () show_message(get_chapterlist(), 3) end
  1786.  
  1787. --
  1788. update_tracklist()
  1789.  
  1790. --cy_audio
  1791. ne = new_element("cy_audio", "button")
  1792.  
  1793. ne.enabled = (#tracks_osc.audio > 0)
  1794. ne.content = function ()
  1795. local aid = "–"
  1796. if not (get_track("audio") == 0) then
  1797. aid = get_track("audio")
  1798. end
  1799. return ("\238\132\134" .. osc_styles.smallButtonsLlabel
  1800. .. " " .. aid .. "/" .. #tracks_osc.audio)
  1801. end
  1802. ne.eventresponder["mbtn_left_up"] =
  1803. function () set_track("audio", 1) end
  1804. ne.eventresponder["mbtn_right_up"] =
  1805. function () set_track("audio", -1) end
  1806. ne.eventresponder["shift+mbtn_left_down"] =
  1807. function () show_message(get_tracklist("audio"), 2) end
  1808.  
  1809. --cy_sub
  1810. ne = new_element("cy_sub", "button")
  1811.  
  1812. ne.enabled = (#tracks_osc.sub > 0)
  1813. ne.content = function ()
  1814. local sid = "–"
  1815. if not (get_track("sub") == 0) then
  1816. sid = get_track("sub")
  1817. end
  1818. return ("\238\132\135" .. osc_styles.smallButtonsLlabel
  1819. .. " " .. sid .. "/" .. #tracks_osc.sub)
  1820. end
  1821. ne.eventresponder["mbtn_left_up"] =
  1822. function () set_track("sub", 1) end
  1823. ne.eventresponder["mbtn_right_up"] =
  1824. function () set_track("sub", -1) end
  1825. ne.eventresponder["shift+mbtn_left_down"] =
  1826. function () show_message(get_tracklist("sub"), 2) end
  1827.  
  1828. --tog_fs
  1829. ne = new_element("tog_fs", "button")
  1830. ne.content = function ()
  1831. if (state.fullscreen) then
  1832. return ("\238\132\137")
  1833. else
  1834. return ("\238\132\136")
  1835. end
  1836. end
  1837. ne.eventresponder["mbtn_left_up"] =
  1838. function () mp.commandv("cycle", "fullscreen") end
  1839.  
  1840.  
  1841. --seekbar
  1842. ne = new_element("seekbar", "slider")
  1843.  
  1844. ne.enabled = not (mp.get_property("percent-pos") == nil)
  1845. ne.slider.markerF = function ()
  1846. local duration = mp.get_property_number("duration", nil)
  1847. if not (duration == nil) then
  1848. local chapters = mp.get_property_native("chapter-list", {})
  1849. local markers = {}
  1850. for n = 1, #chapters do
  1851. markers[n] = (chapters[n].time / duration * 100)
  1852. end
  1853. return markers
  1854. else
  1855. return {}
  1856. end
  1857. end
  1858. ne.slider.posF =
  1859. function () return mp.get_property_number("percent-pos", nil) end
  1860. ne.slider.tooltipF = function (pos)
  1861. local duration = mp.get_property_number("duration", nil)
  1862. if not ((duration == nil) or (pos == nil)) then
  1863. possec = duration * (pos / 100)
  1864. return mp.format_time(possec)
  1865. else
  1866. return ""
  1867. end
  1868. end
  1869. ne.slider.seekRangesF = function()
  1870. if not (user_opts.seekranges) then
  1871. return nil
  1872. end
  1873. local cache_state = mp.get_property_native("demuxer-cache-state", nil)
  1874. if not cache_state then
  1875. return nil
  1876. end
  1877. local duration = mp.get_property_number("duration", nil)
  1878. if (duration == nil) or duration <= 0 then
  1879. return nil
  1880. end
  1881. local ranges = cache_state["seekable-ranges"]
  1882. for _, range in pairs(ranges) do
  1883. range["start"] = 100 * range["start"] / duration
  1884. range["end"] = 100 * range["end"] / duration
  1885. end
  1886. return ranges
  1887. end
  1888. ne.eventresponder["mouse_move"] = --keyframe seeking when mouse is dragged
  1889. function (element)
  1890. -- mouse move events may pile up during seeking and may still get
  1891. -- sent when the user is done seeking, so we need to throw away
  1892. -- identical seeks
  1893. local seekto = get_slider_value(element)
  1894. if (element.state.lastseek == nil) or
  1895. (not (element.state.lastseek == seekto)) then
  1896. mp.commandv("seek", seekto, "absolute-percent",
  1897. user_opts.seekbarkeyframes and "keyframes" or "exact")
  1898. element.state.lastseek = seekto
  1899. end
  1900.  
  1901. end
  1902. ne.eventresponder["mbtn_left_down"] = --exact seeks on single clicks
  1903. function (element) mp.commandv("seek", get_slider_value(element),
  1904. "absolute-percent", "exact") end
  1905. ne.eventresponder["reset"] =
  1906. function (element) element.state.lastseek = nil end
  1907.  
  1908.  
  1909. -- tc_left (current pos)
  1910. ne = new_element("tc_left", "button")
  1911.  
  1912. ne.content = function ()
  1913. if (state.tc_ms) then
  1914. return (mp.get_property_osd("playback-time/full"))
  1915. else
  1916. return (mp.get_property_osd("playback-time"))
  1917. end
  1918. end
  1919. ne.eventresponder["mbtn_left_up"] = function ()
  1920. state.tc_ms = not state.tc_ms
  1921. request_init()
  1922. end
  1923.  
  1924. -- tc_right (total/remaining time)
  1925. ne = new_element("tc_right", "button")
  1926.  
  1927. ne.visible = (mp.get_property_number("duration", 0) > 0)
  1928. ne.content = function ()
  1929. if (state.rightTC_trem) then
  1930. if state.tc_ms then
  1931. return ("-"..mp.get_property_osd("playtime-remaining/full"))
  1932. else
  1933. return ("-"..mp.get_property_osd("playtime-remaining"))
  1934. end
  1935. else
  1936. if state.tc_ms then
  1937. return (mp.get_property_osd("duration/full"))
  1938. else
  1939. return (mp.get_property_osd("duration"))
  1940. end
  1941. end
  1942. end
  1943. ne.eventresponder["mbtn_left_up"] =
  1944. function () state.rightTC_trem = not state.rightTC_trem end
  1945.  
  1946. -- cache
  1947. ne = new_element("cache", "button")
  1948.  
  1949. ne.content = function ()
  1950. local cache_state = mp.get_property_native("demuxer-cache-state", {})
  1951. if not (cache_state["seekable-ranges"] and
  1952. #cache_state["seekable-ranges"] > 0) then
  1953. -- probably not a network stream
  1954. return ""
  1955. end
  1956. local dmx_cache = mp.get_property_number("demuxer-cache-duration")
  1957. if dmx_cache and (dmx_cache > state.dmx_cache * 1.1 or
  1958. dmx_cache < state.dmx_cache * 0.9) then
  1959. state.dmx_cache = dmx_cache
  1960. else
  1961. dmx_cache = state.dmx_cache
  1962. end
  1963. local min = math.floor(dmx_cache / 60)
  1964. local sec = dmx_cache % 60
  1965. return "Cache: " .. (min > 0 and
  1966. string.format("%sm%02.0fs", min, sec) or
  1967. string.format("%3.0fs", dmx_cache))
  1968. end
  1969.  
  1970. -- volume
  1971. ne = new_element("volume", "button")
  1972.  
  1973. ne.content = function()
  1974. local volume = mp.get_property_number("volume", 0)
  1975. local mute = mp.get_property_native("mute")
  1976. local volicon = {"\238\132\139", "\238\132\140",
  1977. "\238\132\141", "\238\132\142"}
  1978. if volume == 0 or mute then
  1979. return "\238\132\138"
  1980. else
  1981. return volicon[math.min(4,math.ceil(volume / (100/3)))]
  1982. end
  1983. end
  1984. ne.eventresponder["mbtn_left_up"] =
  1985. function () mp.commandv("cycle", "mute") end
  1986.  
  1987. ne.eventresponder["wheel_up_press"] =
  1988. function () mp.commandv("osd-auto", "add", "volume", 5) end
  1989. ne.eventresponder["wheel_down_press"] =
  1990. function () mp.commandv("osd-auto", "add", "volume", -5) end
  1991.  
  1992.  
  1993. -- load layout
  1994. layouts[user_opts.layout]()
  1995.  
  1996. --do something with the elements
  1997. prepare_elements()
  1998.  
  1999. end
  2000.  
  2001.  
  2002.  
  2003. --
  2004. -- Other important stuff
  2005. --
  2006.  
  2007.  
  2008. function show_osc()
  2009. -- show when disabled can happen (e.g. mouse_move) due to async/delayed unbinding
  2010. if not state.enabled then return end
  2011.  
  2012. msg.trace("show_osc")
  2013. --remember last time of invocation (mouse move)
  2014. state.showtime = mp.get_time()
  2015.  
  2016. osc_visible(true)
  2017.  
  2018. if (user_opts.fadeduration > 0) then
  2019. state.anitype = nil
  2020. end
  2021. end
  2022.  
  2023. function hide_osc()
  2024. msg.trace("hide_osc")
  2025. if not state.enabled then
  2026. -- typically hide happens at render() from tick(), but now tick() is
  2027. -- no-op and won't render again to remove the osc, so do that manually.
  2028. state.osc_visible = false
  2029. timer_stop()
  2030. render_wipe()
  2031. elseif (user_opts.fadeduration > 0) then
  2032. if not(state.osc_visible == false) then
  2033. state.anitype = "out"
  2034. control_timer()
  2035. end
  2036. else
  2037. osc_visible(false)
  2038. end
  2039. end
  2040.  
  2041. function osc_visible(visible)
  2042. state.osc_visible = visible
  2043. control_timer()
  2044. end
  2045.  
  2046. function pause_state(name, enabled)
  2047. state.paused = enabled
  2048. control_timer()
  2049. end
  2050.  
  2051. function cache_state(name, idle)
  2052. state.cache_idle = idle
  2053. control_timer()
  2054. end
  2055.  
  2056. function control_timer()
  2057. if (state.paused) and (state.osc_visible) and
  2058. ( not(state.cache_idle) or not (state.anitype == nil) ) then
  2059.  
  2060. timer_start()
  2061. else
  2062. timer_stop()
  2063. end
  2064. end
  2065.  
  2066. function timer_start()
  2067. if not (state.timer_active) then
  2068. msg.trace("timer start")
  2069.  
  2070. if (state.timer == nil) then
  2071. -- create new timer
  2072. state.timer = mp.add_periodic_timer(0.03, tick)
  2073. else
  2074. -- resume existing one
  2075. state.timer:resume()
  2076. end
  2077.  
  2078. state.timer_active = true
  2079. end
  2080. end
  2081.  
  2082. function timer_stop()
  2083. if (state.timer_active) then
  2084. msg.trace("timer stop")
  2085.  
  2086. if not (state.timer == nil) then
  2087. -- kill timer
  2088. state.timer:kill()
  2089. end
  2090.  
  2091. state.timer_active = false
  2092. end
  2093. end
  2094.  
  2095.  
  2096.  
  2097. function mouse_leave()
  2098. if user_opts.hidetimeout >= 0 then
  2099. hide_osc()
  2100. end
  2101. -- reset mouse position
  2102. state.last_mouseX, state.last_mouseY = nil, nil
  2103. end
  2104.  
  2105. function request_init()
  2106. state.initREQ = true
  2107. end
  2108.  
  2109. function render_wipe()
  2110. msg.trace("render_wipe()")
  2111. mp.set_osd_ass(0, 0, "{}")
  2112. end
  2113.  
  2114. function render()
  2115. msg.trace("rendering")
  2116. local current_screen_sizeX, current_screen_sizeY, aspect = mp.get_osd_size()
  2117. local mouseX, mouseY = get_virt_mouse_pos()
  2118. local now = mp.get_time()
  2119.  
  2120. -- check if display changed, if so request reinit
  2121. if not (state.mp_screen_sizeX == current_screen_sizeX
  2122. and state.mp_screen_sizeY == current_screen_sizeY) then
  2123.  
  2124. request_init()
  2125.  
  2126. state.mp_screen_sizeX = current_screen_sizeX
  2127. state.mp_screen_sizeY = current_screen_sizeY
  2128. end
  2129.  
  2130. -- init management
  2131. if state.initREQ then
  2132. osc_init()
  2133. state.initREQ = false
  2134.  
  2135. -- store initial mouse position
  2136. if (state.last_mouseX == nil or state.last_mouseY == nil)
  2137. and not (mouseX == nil or mouseY == nil) then
  2138.  
  2139. state.last_mouseX, state.last_mouseY = mouseX, mouseY
  2140. end
  2141. end
  2142.  
  2143.  
  2144. -- fade animation
  2145. if not(state.anitype == nil) then
  2146.  
  2147. if (state.anistart == nil) then
  2148. state.anistart = now
  2149. end
  2150.  
  2151. if (now < state.anistart + (user_opts.fadeduration/1000)) then
  2152.  
  2153. if (state.anitype == "in") then --fade in
  2154. osc_visible(true)
  2155. state.animation = scale_value(state.anistart,
  2156. (state.anistart + (user_opts.fadeduration/1000)),
  2157. 255, 0, now)
  2158. elseif (state.anitype == "out") then --fade out
  2159. state.animation = scale_value(state.anistart,
  2160. (state.anistart + (user_opts.fadeduration/1000)),
  2161. 0, 255, now)
  2162. end
  2163.  
  2164. else
  2165. if (state.anitype == "out") then
  2166. osc_visible(false)
  2167. end
  2168. state.anistart = nil
  2169. state.animation = nil
  2170. state.anitype = nil
  2171. end
  2172. else
  2173. state.anistart = nil
  2174. state.animation = nil
  2175. state.anitype = nil
  2176. end
  2177.  
  2178. --mouse show/hide area
  2179. for k,cords in pairs(osc_param.areas["showhide"]) do
  2180. set_virt_mouse_area(cords.x1, cords.y1, cords.x2, cords.y2, "showhide")
  2181. end
  2182. do_enable_keybindings()
  2183.  
  2184. --mouse input area
  2185. local mouse_over_osc = false
  2186.  
  2187. for _,cords in ipairs(osc_param.areas["input"]) do
  2188. if state.osc_visible then -- activate only when OSC is actually visible
  2189. set_virt_mouse_area(cords.x1, cords.y1, cords.x2, cords.y2, "input")
  2190. end
  2191. if state.osc_visible ~= state.input_enabled then
  2192. if state.osc_visible then
  2193. mp.enable_key_bindings("input")
  2194. else
  2195. mp.disable_key_bindings("input")
  2196. end
  2197. state.input_enabled = state.osc_visible
  2198. end
  2199.  
  2200. if (mouse_hit_coords(cords.x1, cords.y1, cords.x2, cords.y2)) then
  2201. mouse_over_osc = true
  2202. end
  2203. end
  2204.  
  2205. -- autohide
  2206. if not (state.showtime == nil) and (user_opts.hidetimeout >= 0)
  2207. and (state.showtime + (user_opts.hidetimeout/1000) < now)
  2208. and (state.active_element == nil) and not (mouse_over_osc) then
  2209.  
  2210. hide_osc()
  2211. end
  2212.  
  2213.  
  2214. -- actual rendering
  2215. local ass = assdraw.ass_new()
  2216.  
  2217. -- Messages
  2218. render_message(ass)
  2219.  
  2220. -- actual OSC
  2221. if state.osc_visible then
  2222. render_elements(ass)
  2223. end
  2224.  
  2225. -- submit
  2226. mp.set_osd_ass(osc_param.playresy * osc_param.display_aspect,
  2227. osc_param.playresy, ass.text)
  2228.  
  2229.  
  2230.  
  2231.  
  2232. end
  2233.  
  2234. --
  2235. -- Eventhandling
  2236. --
  2237.  
  2238. local function element_has_action(element, action)
  2239. return element and element.eventresponder and
  2240. element.eventresponder[action]
  2241. end
  2242.  
  2243. function process_event(source, what)
  2244. local action = string.format("%s%s", source,
  2245. what and ("_" .. what) or "")
  2246.  
  2247. if what == "down" or what == "press" then
  2248.  
  2249. for n = 1, #elements do
  2250.  
  2251. if mouse_hit(elements[n]) and
  2252. elements[n].eventresponder and
  2253. (elements[n].eventresponder[source .. "_up"] or
  2254. elements[n].eventresponder[action]) then
  2255.  
  2256. if what == "down" then
  2257. state.active_element = n
  2258. state.active_event_source = source
  2259. end
  2260. -- fire the down or press event if the element has one
  2261. if element_has_action(elements[n], action) then
  2262. elements[n].eventresponder[action](elements[n])
  2263. end
  2264.  
  2265. end
  2266. end
  2267.  
  2268. elseif what == "up" then
  2269.  
  2270. if elements[state.active_element] then
  2271. local n = state.active_element
  2272.  
  2273. if n == 0 then
  2274. --click on background (does not work)
  2275. elseif element_has_action(elements[n], action) and
  2276. mouse_hit(elements[n]) then
  2277.  
  2278. elements[n].eventresponder[action](elements[n])
  2279. end
  2280.  
  2281. --reset active element
  2282. if element_has_action(elements[n], "reset") then
  2283. elements[n].eventresponder["reset"](elements[n])
  2284. end
  2285.  
  2286. end
  2287. state.active_element = nil
  2288. state.mouse_down_counter = 0
  2289.  
  2290. elseif source == "mouse_move" then
  2291.  
  2292. local mouseX, mouseY = get_virt_mouse_pos()
  2293. if (user_opts.minmousemove == 0) or
  2294. (not ((state.last_mouseX == nil) or (state.last_mouseY == nil)) and
  2295. ((math.abs(mouseX - state.last_mouseX) >= user_opts.minmousemove)
  2296. or (math.abs(mouseY - state.last_mouseY) >= user_opts.minmousemove)
  2297. )
  2298. ) then
  2299. show_osc()
  2300. end
  2301. state.last_mouseX, state.last_mouseY = mouseX, mouseY
  2302.  
  2303. local n = state.active_element
  2304. if element_has_action(elements[n], action) then
  2305. elements[n].eventresponder[action](elements[n])
  2306. end
  2307. tick()
  2308. end
  2309. end
  2310.  
  2311. -- called by mpv on every frame
  2312. function tick()
  2313. if (not state.enabled) then return end
  2314.  
  2315. if (state.idle) then
  2316.  
  2317. -- render idle message
  2318. msg.trace("idle message")
  2319. local icon_x, icon_y = 320 - 26, 140
  2320.  
  2321. local ass = assdraw.ass_new()
  2322. ass:new_event()
  2323. ass:pos(icon_x, icon_y)
  2324. 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}")
  2325. ass:new_event()
  2326. ass:pos(icon_x, icon_y)
  2327. 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}")
  2328. ass:new_event()
  2329. ass:pos(icon_x, icon_y)
  2330. 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}")
  2331. ass:new_event()
  2332. ass:pos(icon_x, icon_y)
  2333. 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}")
  2334. ass:new_event()
  2335. ass:pos(icon_x, icon_y)
  2336. 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}")
  2337. ass:new_event()
  2338. ass:pos(icon_x, icon_y)
  2339. 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}")
  2340. ass:new_event()
  2341. ass:pos(320, icon_y+65)
  2342. ass:an(8)
  2343. ass:append("Drop files or URLs to play here.")
  2344. mp.set_osd_ass(640, 360, ass.text)
  2345.  
  2346. if state.showhide_enabled then
  2347. mp.disable_key_bindings("showhide")
  2348. state.showhide_enabled = false
  2349. end
  2350.  
  2351.  
  2352. elseif (state.fullscreen and user_opts.showfullscreen)
  2353. or (not state.fullscreen and user_opts.showwindowed) then
  2354.  
  2355. -- render the OSC
  2356. render()
  2357. else
  2358. -- Flush OSD
  2359. mp.set_osd_ass(osc_param.playresy, osc_param.playresy, "")
  2360. end
  2361. end
  2362.  
  2363. function do_enable_keybindings()
  2364. if state.enabled then
  2365. if not state.showhide_enabled then
  2366. mp.enable_key_bindings("showhide", "allow-vo-dragging+allow-hide-cursor")
  2367. end
  2368. state.showhide_enabled = true
  2369. end
  2370. end
  2371.  
  2372. function enable_osc(enable)
  2373. state.enabled = enable
  2374. if enable then
  2375. do_enable_keybindings()
  2376. else
  2377. hide_osc() -- acts immediately when state.enabled == false
  2378. if state.showhide_enabled then
  2379. mp.disable_key_bindings("showhide")
  2380. end
  2381. state.showhide_enabled = false
  2382. end
  2383. end
  2384.  
  2385. validate_user_opts()
  2386.  
  2387. mp.register_event("start-file", request_init)
  2388. mp.register_event("tracks-changed", request_init)
  2389. mp.observe_property("playlist", nil, request_init)
  2390.  
  2391. mp.register_script_message("osc-message", show_message)
  2392. mp.register_script_message("osc-chapterlist", function(dur)
  2393. show_message(get_chapterlist(), dur)
  2394. end)
  2395. mp.register_script_message("osc-playlist", function(dur)
  2396. show_message(get_playlist(), dur)
  2397. end)
  2398. mp.register_script_message("osc-tracklist", function(dur)
  2399. local msg = {}
  2400. for k,v in pairs(nicetypes) do
  2401. table.insert(msg, get_tracklist(k))
  2402. end
  2403. show_message(table.concat(msg, '\n\n'), dur)
  2404. end)
  2405.  
  2406. mp.observe_property("fullscreen", "bool",
  2407. function(name, val)
  2408. state.fullscreen = val
  2409. request_init()
  2410. end
  2411. )
  2412. mp.observe_property("idle-active", "bool",
  2413. function(name, val)
  2414. state.idle = val
  2415. tick()
  2416. end
  2417. )
  2418. mp.observe_property("pause", "bool", pause_state)
  2419. mp.observe_property("cache-idle", "bool", cache_state)
  2420. mp.observe_property("vo-configured", "bool", function(name, val)
  2421. if val then
  2422. mp.register_event("tick", tick)
  2423. else
  2424. mp.unregister_event(tick)
  2425. end
  2426. end)
  2427.  
  2428. -- mouse show/hide bindings
  2429. mp.set_key_bindings({
  2430. {"mouse_move", function(e) process_event("mouse_move", nil) end},
  2431. {"mouse_leave", mouse_leave},
  2432. }, "showhide", "force")
  2433. do_enable_keybindings()
  2434.  
  2435. --mouse input bindings
  2436. mp.set_key_bindings({
  2437. {"mbtn_left", function(e) process_event("mbtn_left", "up") end,
  2438. function(e) process_event("mbtn_left", "down") end},
  2439. {"shift+mbtn_left", function(e) process_event("shift+mbtn_left", "up") end,
  2440. function(e) process_event("shift+mbtn_left", "down") end},
  2441. {"mbtn_right", function(e) process_event("mbtn_right", "up") end,
  2442. function(e) process_event("mbtn_right", "down") end},
  2443. {"wheel_up", function(e) process_event("wheel_up", "press") end},
  2444. {"wheel_down", function(e) process_event("wheel_down", "press") end},
  2445. {"mbtn_left_dbl", "ignore"},
  2446. {"shift+mbtn_left_dbl", "ignore"},
  2447. {"mbtn_right_dbl", "ignore"},
  2448. }, "input", "force")
  2449. mp.enable_key_bindings("input")
  2450.  
  2451.  
  2452. user_opts.hidetimeout_orig = user_opts.hidetimeout
  2453.  
  2454. function always_on(val)
  2455. if val then
  2456. user_opts.hidetimeout = -1 -- disable autohide
  2457. if state.enabled then show_osc() end
  2458. else
  2459. user_opts.hidetimeout = user_opts.hidetimeout_orig
  2460. if state.enabled then hide_osc() end
  2461. end
  2462. end
  2463.  
  2464. -- mode can be auto/always/never/cycle
  2465. -- the modes only affect internal variables and not stored on its own.
  2466. function visibility_mode(mode, no_osd)
  2467. if mode == "cycle" then
  2468. if not state.enabled then
  2469. mode = "auto"
  2470. elseif user_opts.hidetimeout >= 0 then
  2471. mode = "always"
  2472. else
  2473. mode = "never"
  2474. end
  2475. end
  2476.  
  2477. if mode == "auto" then
  2478. always_on(false)
  2479. enable_osc(true)
  2480. elseif mode == "always" then
  2481. enable_osc(true)
  2482. always_on(true)
  2483. elseif mode == "never" then
  2484. enable_osc(false)
  2485. else
  2486. msg.warn("Ignoring unknown visibility mode '" .. mode .. "'")
  2487. return
  2488. end
  2489.  
  2490. if not no_osd and tonumber(mp.get_property("osd-level")) >= 1 then
  2491. mp.osd_message("OSC visibility: " .. mode)
  2492. end
  2493. end
  2494.  
  2495. visibility_mode(user_opts.visibility, true)
  2496. mp.register_script_message("osc-visibility", visibility_mode)
  2497. mp.add_key_binding(nil, "visibility", function() visibility_mode("cycle") end)
  2498.  
  2499. set_virt_mouse_area(0, 0, 0, 0, "input")
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement