@@ -48,6 +48,10 @@ local user_opts = {
48
48
windowcontrols = " auto" , -- whether to show window controls
49
49
windowcontrols_alignment = " right" , -- which side to show window controls on
50
50
greenandgrumpy = false , -- disable santa hat
51
+ livemarkers = true , -- update seekbar chapter markers on duration change
52
+ chapters_osd = true , -- whether to show chapters OSD on next/prev
53
+ playlist_osd = true , -- whether to show playlist OSD on next/prev
54
+ chapter_fmt = " Chapter: %s" , -- chapter print format for seekbar-hover. "no" to disable
51
55
}
52
56
53
57
-- read options from config and command-line
@@ -358,6 +362,12 @@ local is_december = os.date("*t").month == 12
358
362
-- Helperfunctions
359
363
--
360
364
365
+ function kill_animation ()
366
+ state .anistart = nil
367
+ state .animation = nil
368
+ state .anitype = nil
369
+ end
370
+
361
371
function set_osd (res_x , res_y , text )
362
372
if state .osd .res_x == res_x and
363
373
state .osd .res_y == res_y and
819
829
-- Element Rendering
820
830
--
821
831
832
+ -- returns nil or a chapter element from the native property chapter-list
833
+ function get_chapter (possec )
834
+ local cl = mp .get_property_native (" chapter-list" , {})
835
+ local ch = nil
836
+
837
+ -- chapters might not be sorted by time. find nearest-before/at possec
838
+ for n = 1 , # cl do
839
+ if possec >= cl [n ].time and (not ch or cl [n ].time > ch .time ) then
840
+ ch = cl [n ]
841
+ end
842
+ end
843
+ return ch
844
+ end
845
+
822
846
function render_elements (master_ass )
823
847
848
+ -- when the slider is dragged or hovered and we have a target chapter name
849
+ -- then we use it instead of the normal title. we calculate it before the
850
+ -- render iterations because the title may be rendered before the slider.
851
+ state .forced_title = nil
852
+ local se , ae = state .slider_element , elements [state .active_element ]
853
+ if user_opts .chapter_fmt ~= " no" and se and (ae == se or (not ae and mouse_hit (se ))) then
854
+ local dur = mp .get_property_number (" duration" , 0 )
855
+ if dur > 0 then
856
+ local possec = get_slider_value (se ) * dur / 100 -- of mouse pos
857
+ local ch = get_chapter (possec )
858
+ if ch and ch .title and ch .title ~= " " then
859
+ state .forced_title = string.format (user_opts .chapter_fmt , ch .title )
860
+ end
861
+ end
862
+ end
863
+
824
864
for n = 1 , # elements do
825
865
local element = elements [n ]
826
866
@@ -1919,6 +1959,7 @@ function update_options(list)
1919
1959
validate_user_opts ()
1920
1960
request_tick ()
1921
1961
visibility_mode (user_opts .visibility , true )
1962
+ update_duration_watch ()
1922
1963
request_init ()
1923
1964
end
1924
1965
@@ -1970,7 +2011,8 @@ function osc_init()
1970
2011
ne = new_element (" title" , " button" )
1971
2012
1972
2013
ne .content = function ()
1973
- local title = mp .command_native ({" expand-text" , user_opts .title })
2014
+ local title = state .forced_title or
2015
+ mp .command_native ({" expand-text" , user_opts .title })
1974
2016
-- escape ASS, and strip newlines and trailing slashes
1975
2017
title = title :gsub (" \\ n" , " " ):gsub (" \\ $" , " " ):gsub (" {" ," \\ {" )
1976
2018
return not (title == " " ) and title or " mpv"
@@ -1998,7 +2040,9 @@ function osc_init()
1998
2040
ne .eventresponder [" mbtn_left_up" ] =
1999
2041
function ()
2000
2042
mp .commandv (" playlist-prev" , " weak" )
2001
- show_message (get_playlist (), 3 )
2043
+ if user_opts .playlist_osd then
2044
+ show_message (get_playlist (), 3 )
2045
+ end
2002
2046
end
2003
2047
ne .eventresponder [" shift+mbtn_left_up" ] =
2004
2048
function () show_message (get_playlist (), 3 ) end
@@ -2013,7 +2057,9 @@ function osc_init()
2013
2057
ne .eventresponder [" mbtn_left_up" ] =
2014
2058
function ()
2015
2059
mp .commandv (" playlist-next" , " weak" )
2016
- show_message (get_playlist (), 3 )
2060
+ if user_opts .playlist_osd then
2061
+ show_message (get_playlist (), 3 )
2062
+ end
2017
2063
end
2018
2064
ne .eventresponder [" shift+mbtn_left_up" ] =
2019
2065
function () show_message (get_playlist (), 3 ) end
@@ -2068,7 +2114,9 @@ function osc_init()
2068
2114
ne .eventresponder [" mbtn_left_up" ] =
2069
2115
function ()
2070
2116
mp .commandv (" add" , " chapter" , - 1 )
2071
- show_message (get_chapterlist (), 3 )
2117
+ if user_opts .chapters_osd then
2118
+ show_message (get_chapterlist (), 3 )
2119
+ end
2072
2120
end
2073
2121
ne .eventresponder [" shift+mbtn_left_up" ] =
2074
2122
function () show_message (get_chapterlist (), 3 ) end
@@ -2083,7 +2131,9 @@ function osc_init()
2083
2131
ne .eventresponder [" mbtn_left_up" ] =
2084
2132
function ()
2085
2133
mp .commandv (" add" , " chapter" , 1 )
2086
- show_message (get_chapterlist (), 3 )
2134
+ if user_opts .chapters_osd then
2135
+ show_message (get_chapterlist (), 3 )
2136
+ end
2087
2137
end
2088
2138
ne .eventresponder [" shift+mbtn_left_up" ] =
2089
2139
function () show_message (get_chapterlist (), 3 ) end
@@ -2147,6 +2197,7 @@ function osc_init()
2147
2197
ne = new_element (" seekbar" , " slider" )
2148
2198
2149
2199
ne .enabled = not (mp .get_property (" percent-pos" ) == nil )
2200
+ state .slider_element = ne .enabled and ne or nil -- used for forced_title
2150
2201
ne .slider .markerF = function ()
2151
2202
local duration = mp .get_property_number (" duration" , nil )
2152
2203
if not (duration == nil ) then
@@ -2275,10 +2326,10 @@ function osc_init()
2275
2326
dmx_cache = state .dmx_cache
2276
2327
end
2277
2328
local min = math.floor (dmx_cache / 60 )
2278
- local sec = dmx_cache % 60
2329
+ local sec = math.floor ( dmx_cache % 60 ) -- don't round e.g. 59.9 to 60
2279
2330
return " Cache: " .. (min > 0 and
2280
2331
string.format (" %sm%02.0fs" , min , sec ) or
2281
- string.format (" %3.0fs" , dmx_cache ))
2332
+ string.format (" %3.0fs" , sec ))
2282
2333
end
2283
2334
2284
2335
-- volume
@@ -2492,7 +2543,14 @@ function render()
2492
2543
end
2493
2544
2494
2545
-- init management
2495
- if state .initREQ then
2546
+ if state .active_element then
2547
+ -- mouse is held down on some element - keep ticking and igore initReq
2548
+ -- till it's released, or else the mouse-up (click) will misbehave or
2549
+ -- get ignored. that's because osc_init() recreates the osc elements,
2550
+ -- but mouse handling depends on the elements staying unmodified
2551
+ -- between mouse-down and mouse-up (using the index active_element).
2552
+ request_tick ()
2553
+ elseif state .initREQ then
2496
2554
osc_init ()
2497
2555
state .initREQ = false
2498
2556
@@ -2529,14 +2587,10 @@ function render()
2529
2587
if (state .anitype == " out" ) then
2530
2588
osc_visible (false )
2531
2589
end
2532
- state .anistart = nil
2533
- state .animation = nil
2534
- state .anitype = nil
2590
+ kill_animation ()
2535
2591
end
2536
2592
else
2537
- state .anistart = nil
2538
- state .animation = nil
2539
- state .anitype = nil
2593
+ kill_animation ()
2540
2594
end
2541
2595
2542
2596
-- mouse show/hide area
@@ -2719,8 +2773,10 @@ function process_event(source, what)
2719
2773
if element_has_action (elements [n ], action ) then
2720
2774
elements [n ].eventresponder [action ](elements [n ])
2721
2775
end
2722
- request_tick ()
2723
2776
end
2777
+
2778
+ -- ensure rendering after any (mouse) event - icons could change etc
2779
+ request_tick ()
2724
2780
end
2725
2781
2726
2782
@@ -2806,7 +2862,17 @@ function tick()
2806
2862
state .tick_last_time = mp .get_time ()
2807
2863
2808
2864
if state .anitype ~= nil then
2809
- request_tick ()
2865
+ -- state.anistart can be nil - animation should now start, or it can
2866
+ -- be a timestamp when it started. state.idle has no animation.
2867
+ if not state .idle and
2868
+ (not state .anistart or
2869
+ mp .get_time () < 1 + state .anistart + user_opts .fadeduration / 1000 )
2870
+ then
2871
+ -- animating or starting, or still within 1s past the deadline
2872
+ request_tick ()
2873
+ else
2874
+ kill_animation ()
2875
+ end
2810
2876
end
2811
2877
end
2812
2878
@@ -2834,6 +2900,28 @@ function enable_osc(enable)
2834
2900
end
2835
2901
end
2836
2902
2903
+ -- duration is observed for the sole purpose of updating chapter markers
2904
+ -- positions. live streams with chapters are very rare, and the update is also
2905
+ -- expensive (with request_init), so it's only observed when we have chapters
2906
+ -- and the user didn't disable the livemarkers option (update_duration_watch).
2907
+ function on_duration () request_init () end
2908
+
2909
+ local duration_watched = false
2910
+ function update_duration_watch ()
2911
+ local want_watch = user_opts .livemarkers and
2912
+ (mp .get_property_number (" chapters" , 0 ) or 0 ) > 0 and
2913
+ true or false -- ensure it's a boolean
2914
+
2915
+ if (want_watch ~= duration_watched ) then
2916
+ if want_watch then
2917
+ mp .observe_property (" duration" , nil , on_duration )
2918
+ else
2919
+ mp .unobserve_property (on_duration )
2920
+ end
2921
+ duration_watched = want_watch
2922
+ end
2923
+ end
2924
+
2837
2925
-- mpv_thumbnail_script.lua --
2838
2926
2839
2927
local builtin_osc_enabled = mp .get_property_native (' osc' )
@@ -2850,11 +2938,16 @@ end
2850
2938
2851
2939
2852
2940
validate_user_opts ()
2941
+ update_duration_watch ()
2853
2942
2854
2943
mp .register_event (" shutdown" , shutdown )
2855
2944
mp .register_event (" start-file" , request_init )
2856
2945
mp .observe_property (" track-list" , nil , request_init )
2857
2946
mp .observe_property (" playlist" , nil , request_init )
2947
+ mp .observe_property (" chapter-list" , nil , function ()
2948
+ update_duration_watch ()
2949
+ request_init ()
2950
+ end )
2858
2951
2859
2952
mp .register_script_message (" osc-message" , show_message )
2860
2953
mp .register_script_message (" osc-chapterlist" , function (dur )
@@ -2990,6 +3083,7 @@ function visibility_mode(mode, no_osd)
2990
3083
end
2991
3084
2992
3085
user_opts .visibility = mode
3086
+ utils .shared_script_property_set (" osc-visibility" , mode )
2993
3087
2994
3088
if not no_osd and tonumber (mp .get_property (" osd-level" )) >= 1 then
2995
3089
mp .osd_message (" OSC visibility: " .. mode )
0 commit comments