2023-05-23 13:31:17 +00:00
|
|
|
--[[ uosc 4.7.0 - 2023-Apr-15 | https://github.com/tomasklaen/uosc ]]
|
2023-06-15 08:12:30 +00:00
|
|
|
local uosc_version = "4.7.0"
|
2023-05-23 13:31:17 +00:00
|
|
|
|
2023-06-15 08:12:30 +00:00
|
|
|
assdraw = require("mp.assdraw")
|
|
|
|
opt = require("mp.options")
|
|
|
|
utils = require("mp.utils")
|
|
|
|
msg = require("mp.msg")
|
|
|
|
osd = mp.create_osd_overlay("ass-events")
|
2023-05-23 13:31:17 +00:00
|
|
|
INFINITY = 1e309
|
|
|
|
QUARTER_PI_SIN = math.sin(math.pi / 4)
|
|
|
|
|
|
|
|
-- Enables relative requires from `scripts` directory
|
2023-06-15 08:12:30 +00:00
|
|
|
package.path = package.path .. ";" .. mp.find_config_file("scripts") .. "/?.lua"
|
2023-05-23 13:31:17 +00:00
|
|
|
|
2023-06-15 08:12:30 +00:00
|
|
|
require("uosc_shared/lib/std")
|
2023-05-23 13:31:17 +00:00
|
|
|
|
|
|
|
--[[ OPTIONS ]]
|
|
|
|
|
|
|
|
defaults = {
|
2023-06-15 08:12:30 +00:00
|
|
|
timeline_style = "line",
|
2023-05-23 13:31:17 +00:00
|
|
|
timeline_line_width = 2,
|
|
|
|
timeline_line_width_fullscreen = 3,
|
|
|
|
timeline_line_width_minimized_scale = 10,
|
|
|
|
timeline_size_min = 2,
|
|
|
|
timeline_size_max = 40,
|
|
|
|
timeline_size_min_fullscreen = 0,
|
|
|
|
timeline_size_max_fullscreen = 60,
|
|
|
|
timeline_start_hidden = false,
|
2023-06-15 08:12:30 +00:00
|
|
|
timeline_persistency = "paused",
|
2023-05-23 13:31:17 +00:00
|
|
|
timeline_opacity = 0.9,
|
|
|
|
timeline_border = 1,
|
|
|
|
timeline_step = 5,
|
|
|
|
timeline_chapters_opacity = 0.8,
|
|
|
|
timeline_cache = true,
|
|
|
|
|
2023-06-15 08:12:30 +00:00
|
|
|
controls = "menu,gap,subtitles,<has_many_audio>audio,<has_many_video>video,<has_many_edition>editions,<stream>stream-quality,gap,space,speed,space,shuffle,loop-playlist,loop-file,gap,prev,items,next,gap,fullscreen",
|
2023-05-23 13:31:17 +00:00
|
|
|
controls_size = 32,
|
|
|
|
controls_size_fullscreen = 40,
|
|
|
|
controls_margin = 8,
|
|
|
|
controls_spacing = 2,
|
2023-06-15 08:12:30 +00:00
|
|
|
controls_persistency = "",
|
2023-05-23 13:31:17 +00:00
|
|
|
|
2023-06-15 08:12:30 +00:00
|
|
|
volume = "right",
|
2023-05-23 13:31:17 +00:00
|
|
|
volume_size = 40,
|
|
|
|
volume_size_fullscreen = 52,
|
2023-06-15 08:12:30 +00:00
|
|
|
volume_persistency = "",
|
2023-05-23 13:31:17 +00:00
|
|
|
volume_opacity = 0.9,
|
|
|
|
volume_border = 1,
|
|
|
|
volume_step = 1,
|
|
|
|
|
2023-06-15 08:12:30 +00:00
|
|
|
speed_persistency = "",
|
2023-05-23 13:31:17 +00:00
|
|
|
speed_opacity = 0.6,
|
|
|
|
speed_step = 0.1,
|
|
|
|
speed_step_is_factor = false,
|
|
|
|
|
|
|
|
menu_item_height = 36,
|
|
|
|
menu_item_height_fullscreen = 50,
|
|
|
|
menu_min_width = 260,
|
|
|
|
menu_min_width_fullscreen = 360,
|
|
|
|
menu_opacity = 1,
|
|
|
|
menu_parent_opacity = 0.4,
|
|
|
|
|
2023-06-15 08:12:30 +00:00
|
|
|
top_bar = "no-border",
|
2023-05-23 13:31:17 +00:00
|
|
|
top_bar_size = 40,
|
|
|
|
top_bar_size_fullscreen = 46,
|
2023-06-15 08:12:30 +00:00
|
|
|
top_bar_persistency = "",
|
2023-05-23 13:31:17 +00:00
|
|
|
top_bar_controls = true,
|
2023-06-15 08:12:30 +00:00
|
|
|
top_bar_title = "yes",
|
|
|
|
top_bar_alt_title = "",
|
|
|
|
top_bar_alt_title_place = "below",
|
2023-05-23 13:31:17 +00:00
|
|
|
top_bar_title_opacity = 0.8,
|
|
|
|
|
|
|
|
window_border_size = 1,
|
|
|
|
window_border_opacity = 0.8,
|
|
|
|
|
|
|
|
autoload = false,
|
2023-06-15 08:12:30 +00:00
|
|
|
autoload_types = "video,audio,image",
|
2023-05-23 13:31:17 +00:00
|
|
|
shuffle = false,
|
|
|
|
|
|
|
|
ui_scale = 1,
|
|
|
|
font_scale = 1,
|
|
|
|
text_border = 1.2,
|
|
|
|
text_width_estimation = true,
|
|
|
|
pause_on_click_shorter_than = 0, -- deprecated by below
|
|
|
|
click_threshold = 0,
|
2023-06-15 08:12:30 +00:00
|
|
|
click_command = "cycle pause; script-binding uosc/flash-pause-indicator",
|
2023-05-23 13:31:17 +00:00
|
|
|
flash_duration = 1000,
|
|
|
|
proximity_in = 40,
|
|
|
|
proximity_out = 120,
|
2023-06-15 08:12:30 +00:00
|
|
|
foreground = "ffffff",
|
|
|
|
foreground_text = "000000",
|
|
|
|
background = "000000",
|
|
|
|
background_text = "ffffff",
|
2023-05-23 13:31:17 +00:00
|
|
|
total_time = false, -- deprecated by below
|
2023-06-15 08:12:30 +00:00
|
|
|
destination_time = "playtime-remaining",
|
2023-05-23 13:31:17 +00:00
|
|
|
time_precision = 0,
|
|
|
|
font_bold = false,
|
|
|
|
autohide = false,
|
|
|
|
buffered_time_threshold = 60,
|
2023-06-15 08:12:30 +00:00
|
|
|
pause_indicator = "flash",
|
2023-05-23 13:31:17 +00:00
|
|
|
curtain_opacity = 0.5,
|
2023-06-15 08:12:30 +00:00
|
|
|
stream_quality_options = "4320,2160,1440,1080,720,480,360,240,144",
|
|
|
|
video_types = "3g2,3gp,asf,avi,f4v,flv,h264,h265,m2ts,m4v,mkv,mov,mp4,mp4v,mpeg,mpg,ogm,ogv,rm,rmvb,ts,vob,webm,wmv,y4m",
|
|
|
|
audio_types = "aac,ac3,aiff,ape,au,dsf,dts,flac,m4a,mid,midi,mka,mp3,mp4a,oga,ogg,opus,spx,tak,tta,wav,weba,wma,wv",
|
|
|
|
image_types = "apng,avif,bmp,gif,j2k,jp2,jfif,jpeg,jpg,jxl,mj2,png,svg,tga,tif,tiff,webp",
|
|
|
|
subtitle_types = "aqt,ass,gsub,idx,jss,lrc,mks,pgs,pjs,psb,rt,slt,smi,sub,sup,srt,ssa,ssf,ttxt,txt,usf,vt,vtt",
|
|
|
|
default_directory = "~/",
|
2023-05-23 13:31:17 +00:00
|
|
|
use_trash = false,
|
|
|
|
adjust_osd_margins = true,
|
2023-06-15 08:12:30 +00:00
|
|
|
chapter_ranges = "openings:30abf964,endings:30abf964,ads:c54e4e80",
|
|
|
|
chapter_range_patterns = "openings:オープニング;endings:エンディング",
|
2020-05-13 13:08:03 +00:00
|
|
|
}
|
2023-05-23 13:31:17 +00:00
|
|
|
options = table_shallow_copy(defaults)
|
2023-06-15 08:12:30 +00:00
|
|
|
opt.read_options(options, "uosc")
|
2023-05-23 13:31:17 +00:00
|
|
|
-- Normalize values
|
|
|
|
options.proximity_out = math.max(options.proximity_out, options.proximity_in + 1)
|
2023-06-15 08:12:30 +00:00
|
|
|
if options.chapter_ranges:sub(1, 4) == "^op|" then
|
|
|
|
options.chapter_ranges = defaults.chapter_ranges
|
|
|
|
end
|
2023-05-23 13:31:17 +00:00
|
|
|
if options.pause_on_click_shorter_than > 0 and options.click_threshold == 0 then
|
2023-06-15 08:12:30 +00:00
|
|
|
msg.warn("`pause_on_click_shorter_than` is deprecated. Use `click_threshold` and `click_command` instead.")
|
2023-05-23 13:31:17 +00:00
|
|
|
options.click_threshold = options.pause_on_click_shorter_than
|
|
|
|
end
|
2023-06-15 08:12:30 +00:00
|
|
|
if options.total_time and options.destination_time == "playtime-remaining" then
|
|
|
|
msg.warn("`total_time` is deprecated. Use `destination_time` instead.")
|
|
|
|
options.destination_time = "total"
|
|
|
|
elseif not itable_index_of({ "total", "playtime-remaining", "time-remaining" }, options.destination_time) then
|
|
|
|
options.destination_time = "playtime-remaining"
|
2023-05-23 13:31:17 +00:00
|
|
|
end
|
|
|
|
-- Ensure required environment configuration
|
2023-06-15 08:12:30 +00:00
|
|
|
if options.autoload then
|
|
|
|
mp.commandv("set", "keep-open-pause", "no")
|
|
|
|
end
|
2023-05-23 13:31:17 +00:00
|
|
|
-- Color shorthands
|
|
|
|
fg, bg = serialize_rgba(options.foreground).color, serialize_rgba(options.background).color
|
|
|
|
fgt, bgt = serialize_rgba(options.foreground_text).color, serialize_rgba(options.background_text).color
|
|
|
|
|
|
|
|
--[[ CONFIG ]]
|
|
|
|
|
|
|
|
function create_default_menu()
|
|
|
|
return {
|
2023-06-15 08:12:30 +00:00
|
|
|
{ title = "Subtitles", value = "script-binding uosc/subtitles" },
|
|
|
|
{ title = "Audio tracks", value = "script-binding uosc/audio" },
|
|
|
|
{ title = "Stream quality", value = "script-binding uosc/stream-quality" },
|
|
|
|
{ title = "Playlist", value = "script-binding uosc/items" },
|
|
|
|
{ title = "Chapters", value = "script-binding uosc/chapters" },
|
|
|
|
{
|
|
|
|
title = "Navigation",
|
|
|
|
items = {
|
|
|
|
{ title = "Next", hint = "playlist or file", value = "script-binding uosc/next" },
|
|
|
|
{ title = "Prev", hint = "playlist or file", value = "script-binding uosc/prev" },
|
|
|
|
{ title = "Delete file & Next", value = "script-binding uosc/delete-file-next" },
|
|
|
|
{ title = "Delete file & Prev", value = "script-binding uosc/delete-file-prev" },
|
|
|
|
{ title = "Delete file & Quit", value = "script-binding uosc/delete-file-quit" },
|
|
|
|
{ title = "Open file", value = "script-binding uosc/open-file" },
|
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
title = "Utils",
|
|
|
|
items = {
|
|
|
|
{
|
|
|
|
title = "Aspect ratio",
|
|
|
|
items = {
|
|
|
|
{ title = "Default", value = 'set video-aspect-override "-1"' },
|
|
|
|
{ title = "16:9", value = 'set video-aspect-override "16:9"' },
|
|
|
|
{ title = "4:3", value = 'set video-aspect-override "4:3"' },
|
|
|
|
{ title = "2.35:1", value = 'set video-aspect-override "2.35:1"' },
|
|
|
|
},
|
|
|
|
},
|
|
|
|
{ title = "Audio devices", value = "script-binding uosc/audio-device" },
|
|
|
|
{ title = "Editions", value = "script-binding uosc/editions" },
|
|
|
|
{ title = "Screenshot", value = "async screenshot" },
|
|
|
|
{ title = "Show in directory", value = "script-binding uosc/show-in-directory" },
|
|
|
|
{ title = "Open config folder", value = "script-binding uosc/open-config-directory" },
|
|
|
|
},
|
|
|
|
},
|
|
|
|
{ title = "Quit", value = "quit" },
|
2023-05-23 13:31:17 +00:00
|
|
|
}
|
2020-05-13 13:08:03 +00:00
|
|
|
end
|
|
|
|
|
2023-05-23 13:31:17 +00:00
|
|
|
config = {
|
|
|
|
version = uosc_version,
|
|
|
|
-- sets max rendering frequency in case the
|
|
|
|
-- native rendering frequency could not be detected
|
|
|
|
render_delay = 1 / 60,
|
2023-06-15 08:12:30 +00:00
|
|
|
font = mp.get_property("options/osd-font"),
|
|
|
|
osd_margin_x = mp.get_property("osd-margin-x"),
|
|
|
|
osd_margin_y = mp.get_property("osd-margin-y"),
|
|
|
|
osd_alignment_x = mp.get_property("osd-align-x"),
|
|
|
|
osd_alignment_y = mp.get_property("osd-align-y"),
|
2023-05-23 13:31:17 +00:00
|
|
|
types = {
|
2023-06-15 08:12:30 +00:00
|
|
|
video = split(options.video_types, " *, *"),
|
|
|
|
audio = split(options.audio_types, " *, *"),
|
|
|
|
image = split(options.image_types, " *, *"),
|
|
|
|
subtitle = split(options.subtitle_types, " *, *"),
|
|
|
|
media = split(options.video_types .. "," .. options.audio_types .. "," .. options.image_types, " *, *"),
|
2023-05-23 13:31:17 +00:00
|
|
|
autoload = (function()
|
|
|
|
---@type string[]
|
|
|
|
local option_values = {}
|
2023-06-15 08:12:30 +00:00
|
|
|
for _, name in ipairs(split(options.autoload_types, " *, *")) do
|
|
|
|
local value = options[name .. "_types"]
|
|
|
|
if type(value) == "string" then
|
|
|
|
option_values[#option_values + 1] = value
|
|
|
|
end
|
2023-05-23 13:31:17 +00:00
|
|
|
end
|
2023-06-15 08:12:30 +00:00
|
|
|
return split(table.concat(option_values, ","), " *, *")
|
2023-05-23 13:31:17 +00:00
|
|
|
end)(),
|
|
|
|
},
|
2023-06-15 08:12:30 +00:00
|
|
|
stream_quality_options = split(options.stream_quality_options, " *, *"),
|
2023-05-23 13:31:17 +00:00
|
|
|
menu_items = (function()
|
2023-06-15 08:12:30 +00:00
|
|
|
local input_conf_property = mp.get_property_native("input-conf")
|
2023-05-23 13:31:17 +00:00
|
|
|
local input_conf_path = mp.command_native({
|
2023-06-15 08:12:30 +00:00
|
|
|
"expand-path",
|
|
|
|
input_conf_property == "" and "~~/input.conf" or input_conf_property,
|
2023-05-23 13:31:17 +00:00
|
|
|
})
|
|
|
|
local input_conf_meta, meta_error = utils.file_info(input_conf_path)
|
|
|
|
|
|
|
|
-- File doesn't exist
|
2023-06-15 08:12:30 +00:00
|
|
|
if not input_conf_meta or not input_conf_meta.is_file then
|
|
|
|
return create_default_menu()
|
|
|
|
end
|
2023-05-23 13:31:17 +00:00
|
|
|
|
2023-06-15 08:12:30 +00:00
|
|
|
local main_menu = { items = {}, items_by_command = {} }
|
2023-05-23 13:31:17 +00:00
|
|
|
local by_id = {}
|
|
|
|
|
|
|
|
for line in io.lines(input_conf_path) do
|
2023-06-15 08:12:30 +00:00
|
|
|
local key, command, comment = string.match(line, "%s*([%S]+)%s+(.-)%s+#%s*(.-)%s*$")
|
|
|
|
local title = ""
|
2023-05-23 13:31:17 +00:00
|
|
|
if comment then
|
2023-06-15 08:12:30 +00:00
|
|
|
local comments = split(comment, "#")
|
|
|
|
local titles = itable_filter(comments, function(v, i)
|
|
|
|
return v:match("^!") or v:match("^menu:")
|
|
|
|
end)
|
2023-05-23 13:31:17 +00:00
|
|
|
if titles and #titles > 0 then
|
2023-06-15 08:12:30 +00:00
|
|
|
title = titles[1]:match("^!%s*(.*)%s*") or titles[1]:match("^menu:%s*(.*)%s*")
|
2023-05-23 13:31:17 +00:00
|
|
|
end
|
|
|
|
end
|
2023-06-15 08:12:30 +00:00
|
|
|
if title ~= "" then
|
|
|
|
local is_dummy = key:sub(1, 1) == "#"
|
|
|
|
local submenu_id = ""
|
2023-05-23 13:31:17 +00:00
|
|
|
local target_menu = main_menu
|
2023-06-15 08:12:30 +00:00
|
|
|
local title_parts = split(title or "", " *> *")
|
2023-05-23 13:31:17 +00:00
|
|
|
|
2023-06-15 08:12:30 +00:00
|
|
|
for index, title_part in ipairs(#title_parts > 0 and title_parts or { "" }) do
|
2023-05-23 13:31:17 +00:00
|
|
|
if index < #title_parts then
|
|
|
|
submenu_id = submenu_id .. title_part
|
|
|
|
|
|
|
|
if not by_id[submenu_id] then
|
|
|
|
local items = {}
|
2023-06-15 08:12:30 +00:00
|
|
|
by_id[submenu_id] = { items = items, items_by_command = {} }
|
|
|
|
target_menu.items[#target_menu.items + 1] = { title = title_part, items = items }
|
2023-05-23 13:31:17 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
target_menu = by_id[submenu_id]
|
|
|
|
else
|
2023-06-15 08:12:30 +00:00
|
|
|
if command == "ignore" then
|
|
|
|
break
|
|
|
|
end
|
2023-05-23 13:31:17 +00:00
|
|
|
-- If command is already in menu, just append the key to it
|
|
|
|
if target_menu.items_by_command[command] then
|
|
|
|
local hint = target_menu.items_by_command[command].hint
|
2023-06-15 08:12:30 +00:00
|
|
|
target_menu.items_by_command[command].hint = hint and hint .. ", " .. key or key
|
2023-05-23 13:31:17 +00:00
|
|
|
else
|
|
|
|
local item = {
|
|
|
|
title = title_part,
|
|
|
|
hint = not is_dummy and key or nil,
|
|
|
|
value = command,
|
|
|
|
}
|
|
|
|
target_menu.items_by_command[command] = item
|
|
|
|
target_menu.items[#target_menu.items + 1] = item
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
if #main_menu.items > 0 then
|
|
|
|
return main_menu.items
|
|
|
|
else
|
|
|
|
-- Default context menu
|
|
|
|
return create_default_menu()
|
|
|
|
end
|
|
|
|
end)(),
|
|
|
|
chapter_ranges = (function()
|
|
|
|
---@type table<string, string[]> Alternative patterns.
|
|
|
|
local alt_patterns = {}
|
2023-06-15 08:12:30 +00:00
|
|
|
if options.chapter_range_patterns and options.chapter_range_patterns ~= "" then
|
|
|
|
for _, definition in ipairs(split(options.chapter_range_patterns, ";+ *")) do
|
|
|
|
local name_patterns = split(definition, " *:")
|
2023-05-23 13:31:17 +00:00
|
|
|
local name, patterns = name_patterns[1], name_patterns[2]
|
2023-06-15 08:12:30 +00:00
|
|
|
if name and patterns then
|
|
|
|
alt_patterns[name] = split(patterns, ",")
|
|
|
|
end
|
2023-05-23 13:31:17 +00:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
---@type table<string, {color: string; opacity: number; patterns?: string[]}>
|
|
|
|
local ranges = {}
|
2023-06-15 08:12:30 +00:00
|
|
|
if options.chapter_ranges and options.chapter_ranges ~= "" then
|
|
|
|
for _, definition in ipairs(split(options.chapter_ranges, " *,+ *")) do
|
|
|
|
local name_color = split(definition, " *:+ *")
|
2023-05-23 13:31:17 +00:00
|
|
|
local name, color = name_color[1], name_color[2]
|
2023-06-15 08:12:30 +00:00
|
|
|
if
|
|
|
|
name
|
|
|
|
and color
|
|
|
|
and name:match("^[a-zA-Z0-9_]+$")
|
|
|
|
and color:match("^[a-fA-F0-9]+$")
|
|
|
|
and (#color == 6 or #color == 8)
|
|
|
|
then
|
2023-05-23 13:31:17 +00:00
|
|
|
local range = serialize_rgba(name_color[2])
|
|
|
|
range.patterns = alt_patterns[name]
|
|
|
|
ranges[name_color[1]] = range
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
return ranges
|
|
|
|
end)(),
|
2020-05-13 13:08:03 +00:00
|
|
|
}
|
2023-05-23 13:31:17 +00:00
|
|
|
-- Adds `{element}_persistency` property with table of flags when the element should be visible (`{paused = true}`)
|
2023-06-15 08:12:30 +00:00
|
|
|
for _, name in ipairs({ "timeline", "controls", "volume", "top_bar", "speed" }) do
|
|
|
|
local option_name = name .. "_persistency"
|
2023-05-23 13:31:17 +00:00
|
|
|
local value, flags = options[option_name], {}
|
2023-06-15 08:12:30 +00:00
|
|
|
if type(value) == "string" then
|
|
|
|
for _, state in ipairs(split(value, " *, *")) do
|
|
|
|
flags[state] = true
|
|
|
|
end
|
2023-05-23 13:31:17 +00:00
|
|
|
end
|
|
|
|
config[option_name] = flags
|
|
|
|
end
|
|
|
|
|
|
|
|
--[[ STATE ]]
|
|
|
|
|
2023-06-15 08:12:30 +00:00
|
|
|
display = { width = 1280, height = 720, scale_x = 1, scale_y = 1, initialized = false }
|
2023-05-23 13:31:17 +00:00
|
|
|
cursor = {
|
|
|
|
x = 0,
|
|
|
|
y = 0,
|
|
|
|
hidden = true,
|
|
|
|
hover_raw = false,
|
|
|
|
-- Event handlers that are only fired on cursor, bound during render loop. Guidelines:
|
|
|
|
-- - element activations (clicks) go to `on_primary_down` handler
|
|
|
|
-- - `on_primary_up` is only for clearing dragging/swiping, and prevents autohide when bound
|
|
|
|
on_primary_down = nil,
|
|
|
|
on_primary_up = nil,
|
|
|
|
on_wheel_down = nil,
|
|
|
|
on_wheel_up = nil,
|
|
|
|
-- Called at the beginning of each render
|
|
|
|
reset_handlers = function()
|
|
|
|
cursor.on_primary_down, cursor.on_primary_up = nil, nil
|
|
|
|
cursor.on_wheel_down, cursor.on_wheel_up = nil, nil
|
|
|
|
end,
|
|
|
|
-- Enables pointer key group captures needed by handlers (called at the end of each render)
|
|
|
|
mbtn_left_enabled = nil,
|
|
|
|
wheel_enabled = nil,
|
|
|
|
decide_keybinds = function()
|
|
|
|
local enable_mbtn_left = (cursor.on_primary_down or cursor.on_primary_up) ~= nil
|
|
|
|
local enable_wheel = (cursor.on_wheel_down or cursor.on_wheel_up) ~= nil
|
|
|
|
if enable_mbtn_left ~= cursor.mbtn_left_enabled then
|
2023-06-15 08:12:30 +00:00
|
|
|
mp[(enable_mbtn_left and "enable" or "disable") .. "_key_bindings"]("mbtn_left")
|
2023-05-23 13:31:17 +00:00
|
|
|
cursor.mbtn_left_enabled = enable_mbtn_left
|
|
|
|
end
|
|
|
|
if enable_wheel ~= cursor.wheel_enabled then
|
2023-06-15 08:12:30 +00:00
|
|
|
mp[(enable_wheel and "enable" or "disable") .. "_key_bindings"]("wheel")
|
2023-05-23 13:31:17 +00:00
|
|
|
cursor.wheel_enabled = enable_wheel
|
|
|
|
end
|
|
|
|
end,
|
|
|
|
-- Cursor auto-hiding after period of inactivity
|
|
|
|
autohide = function()
|
2023-06-15 08:12:30 +00:00
|
|
|
if not cursor.on_primary_up and not Menu:is_open() then
|
|
|
|
handle_mouse_leave()
|
|
|
|
end
|
2023-05-23 13:31:17 +00:00
|
|
|
end,
|
|
|
|
autohide_timer = (function()
|
2023-06-15 08:12:30 +00:00
|
|
|
local timer = mp.add_timeout(mp.get_property_native("cursor-autohide") / 1000, function()
|
|
|
|
cursor.autohide()
|
|
|
|
end)
|
2023-05-23 13:31:17 +00:00
|
|
|
timer:kill()
|
|
|
|
return timer
|
|
|
|
end)(),
|
|
|
|
queue_autohide = function()
|
|
|
|
if options.autohide and not cursor.on_primary_up then
|
|
|
|
cursor.autohide_timer:kill()
|
|
|
|
cursor.autohide_timer:resume()
|
|
|
|
end
|
2023-06-15 08:12:30 +00:00
|
|
|
end,
|
2020-05-13 13:08:03 +00:00
|
|
|
}
|
2023-05-23 13:31:17 +00:00
|
|
|
state = {
|
|
|
|
platform = (function()
|
2023-06-15 08:12:30 +00:00
|
|
|
local platform = mp.get_property_native("platform")
|
2023-05-23 13:31:17 +00:00
|
|
|
if platform then
|
2023-06-15 08:12:30 +00:00
|
|
|
if itable_index_of({ "windows", "darwin" }, platform) then
|
|
|
|
return platform
|
|
|
|
end
|
2023-05-23 13:31:17 +00:00
|
|
|
else
|
2023-06-15 08:12:30 +00:00
|
|
|
if os.getenv("windir") ~= nil then
|
|
|
|
return "windows"
|
|
|
|
end
|
|
|
|
local homedir = os.getenv("HOME")
|
|
|
|
if homedir ~= nil and string.sub(homedir, 1, 6) == "/Users" then
|
|
|
|
return "darwin"
|
|
|
|
end
|
2023-05-23 13:31:17 +00:00
|
|
|
end
|
2023-06-15 08:12:30 +00:00
|
|
|
return "linux"
|
2023-05-23 13:31:17 +00:00
|
|
|
end)(),
|
2023-06-15 08:12:30 +00:00
|
|
|
cwd = mp.get_property("working-directory"),
|
2023-05-23 13:31:17 +00:00
|
|
|
path = nil, -- current file path or URL
|
|
|
|
title = nil,
|
|
|
|
alt_title = nil,
|
|
|
|
time = nil, -- current media playback time
|
|
|
|
speed = 1,
|
|
|
|
duration = nil, -- current media duration
|
|
|
|
time_human = nil, -- current playback time in human format
|
|
|
|
destination_time_human = nil, -- depends on options.destination_time
|
2023-06-15 08:12:30 +00:00
|
|
|
pause = mp.get_property_native("pause"),
|
2023-05-23 13:31:17 +00:00
|
|
|
chapters = {},
|
|
|
|
current_chapter = nil,
|
|
|
|
chapter_ranges = {},
|
2023-06-15 08:12:30 +00:00
|
|
|
border = mp.get_property_native("border"),
|
|
|
|
fullscreen = mp.get_property_native("fullscreen"),
|
|
|
|
maximized = mp.get_property_native("window-maximized"),
|
|
|
|
fullormaxed = mp.get_property_native("fullscreen") or mp.get_property_native("window-maximized"),
|
2023-05-23 13:31:17 +00:00
|
|
|
render_timer = nil,
|
|
|
|
render_last_time = 0,
|
|
|
|
volume = nil,
|
|
|
|
volume_max = nil,
|
|
|
|
mute = nil,
|
|
|
|
is_idle = false,
|
|
|
|
is_video = false,
|
|
|
|
is_audio = false, -- true if file is audio only (mp3, etc)
|
|
|
|
is_image = false,
|
|
|
|
is_stream = false,
|
|
|
|
has_audio = false,
|
|
|
|
has_sub = false,
|
|
|
|
has_chapter = false,
|
|
|
|
has_playlist = false,
|
|
|
|
shuffle = options.shuffle,
|
|
|
|
mouse_bindings_enabled = false,
|
|
|
|
uncached_ranges = nil,
|
|
|
|
cache = nil,
|
|
|
|
cache_buffering = 100,
|
|
|
|
cache_underrun = false,
|
|
|
|
core_idle = false,
|
|
|
|
eof_reached = false,
|
|
|
|
render_delay = config.render_delay,
|
|
|
|
first_real_mouse_move_received = false,
|
|
|
|
playlist_count = 0,
|
|
|
|
playlist_pos = 0,
|
|
|
|
margin_top = 0,
|
|
|
|
margin_bottom = 0,
|
|
|
|
margin_left = 0,
|
|
|
|
margin_right = 0,
|
|
|
|
hidpi_scale = 1,
|
2020-05-13 13:08:03 +00:00
|
|
|
}
|
2023-06-15 08:12:30 +00:00
|
|
|
thumbnail = { width = 0, height = 0, disabled = false }
|
2023-05-23 13:31:17 +00:00
|
|
|
external = {} -- Properties set by external scripts
|
|
|
|
key_binding_overwrites = {} -- Table of key_binding:mpv_command
|
2023-06-15 08:12:30 +00:00
|
|
|
Elements = require("uosc_shared/elements/Elements")
|
|
|
|
Menu = require("uosc_shared/elements/Menu")
|
2020-05-13 13:08:03 +00:00
|
|
|
|
2023-05-23 13:31:17 +00:00
|
|
|
-- State dependent utilities
|
2023-06-15 08:12:30 +00:00
|
|
|
require("uosc_shared/lib/utils")
|
|
|
|
require("uosc_shared/lib/text")
|
|
|
|
require("uosc_shared/lib/ass")
|
|
|
|
require("uosc_shared/lib/menus")
|
2020-05-13 13:08:03 +00:00
|
|
|
|
2023-05-23 13:31:17 +00:00
|
|
|
--[[ STATE UPDATERS ]]
|
2020-05-13 13:08:03 +00:00
|
|
|
|
|
|
|
function update_display_dimensions()
|
2023-05-23 13:31:17 +00:00
|
|
|
local scale = (state.hidpi_scale or 1) * options.ui_scale
|
|
|
|
local real_width, real_height = mp.get_osd_size()
|
2023-06-15 08:12:30 +00:00
|
|
|
if real_width <= 0 then
|
|
|
|
return
|
|
|
|
end
|
2023-05-23 13:31:17 +00:00
|
|
|
local scaled_width, scaled_height = round(real_width / scale), round(real_height / scale)
|
|
|
|
display.width, display.height = scaled_width, scaled_height
|
|
|
|
display.scale_x, display.scale_y = real_width / scaled_width, real_height / scaled_height
|
|
|
|
display.initialized = true
|
|
|
|
|
|
|
|
-- Tell elements about this
|
2023-06-15 08:12:30 +00:00
|
|
|
Elements:trigger("display")
|
2023-05-23 13:31:17 +00:00
|
|
|
|
|
|
|
-- Some elements probably changed their rectangles as a reaction to `display`
|
|
|
|
Elements:update_proximities()
|
|
|
|
request_render()
|
|
|
|
end
|
|
|
|
|
|
|
|
function update_fullormaxed()
|
|
|
|
state.fullormaxed = state.fullscreen or state.maximized
|
|
|
|
update_display_dimensions()
|
2023-06-15 08:12:30 +00:00
|
|
|
Elements:trigger("prop_fullormaxed", state.fullormaxed)
|
2023-05-23 13:31:17 +00:00
|
|
|
update_cursor_position(INFINITY, INFINITY)
|
|
|
|
end
|
|
|
|
|
|
|
|
function update_human_times()
|
|
|
|
if state.time then
|
|
|
|
state.time_human = format_time(state.time, state.duration)
|
|
|
|
if state.duration then
|
|
|
|
local speed = state.speed or 1
|
2023-06-15 08:12:30 +00:00
|
|
|
if options.destination_time == "playtime-remaining" then
|
2023-05-23 13:31:17 +00:00
|
|
|
state.destination_time_human = format_time((state.time - state.duration) / speed, state.duration)
|
2023-06-15 08:12:30 +00:00
|
|
|
elseif options.destination_time == "total" then
|
2023-05-23 13:31:17 +00:00
|
|
|
state.destination_time_human = format_time(state.duration, state.duration)
|
|
|
|
else
|
|
|
|
state.destination_time_human = format_time(state.time - state.duration, state.duration)
|
|
|
|
end
|
|
|
|
else
|
|
|
|
state.destination_time_human = nil
|
|
|
|
end
|
|
|
|
else
|
|
|
|
state.time_human = nil
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
-- Notifies other scripts such as console about where the unoccupied parts of the screen are.
|
|
|
|
function update_margins()
|
2023-06-15 08:12:30 +00:00
|
|
|
if display.height == 0 then
|
|
|
|
return
|
|
|
|
end
|
2023-05-23 13:31:17 +00:00
|
|
|
|
2023-06-15 08:12:30 +00:00
|
|
|
local function is_persistent(element)
|
|
|
|
return element and element.enabled and element:is_persistent()
|
|
|
|
end
|
2023-05-23 13:31:17 +00:00
|
|
|
local timeline, top_bar, controls, volume = Elements.timeline, Elements.top_bar, Elements.controls, Elements.volume
|
|
|
|
-- margins are normalized to window size
|
|
|
|
local left, right, top, bottom = 0, 0, 0, 0
|
|
|
|
|
2023-06-15 08:12:30 +00:00
|
|
|
if is_persistent(controls) then
|
|
|
|
bottom = (display.height - controls.ay) / display.height
|
|
|
|
elseif is_persistent(timeline) then
|
|
|
|
bottom = (display.height - timeline.ay) / display.height
|
|
|
|
end
|
2023-05-23 13:31:17 +00:00
|
|
|
|
2023-06-15 08:12:30 +00:00
|
|
|
if is_persistent(top_bar) then
|
|
|
|
top = top_bar.title_by / display.height
|
|
|
|
end
|
2023-05-23 13:31:17 +00:00
|
|
|
|
|
|
|
if is_persistent(volume) then
|
2023-06-15 08:12:30 +00:00
|
|
|
if options.volume == "left" then
|
|
|
|
left = volume.bx / display.width
|
|
|
|
elseif options.volume == "right" then
|
|
|
|
right = volume.ax / display.width
|
|
|
|
end
|
2023-05-23 13:31:17 +00:00
|
|
|
end
|
|
|
|
|
2023-06-15 08:12:30 +00:00
|
|
|
if
|
|
|
|
top == state.margin_top
|
|
|
|
and bottom == state.margin_bottom
|
|
|
|
and left == state.margin_left
|
|
|
|
and right == state.margin_right
|
|
|
|
then
|
|
|
|
return
|
|
|
|
end
|
2023-05-23 13:31:17 +00:00
|
|
|
|
|
|
|
state.margin_top = top
|
|
|
|
state.margin_bottom = bottom
|
|
|
|
state.margin_left = left
|
|
|
|
state.margin_right = right
|
|
|
|
|
2023-06-15 08:12:30 +00:00
|
|
|
utils.shared_script_property_set("osc-margins", string.format("%f,%f,%f,%f", 0, 0, top, bottom))
|
|
|
|
mp.set_property_native("user-data/osc/margins", { l = left, r = right, t = top, b = bottom })
|
2023-05-23 13:31:17 +00:00
|
|
|
|
2023-06-15 08:12:30 +00:00
|
|
|
if not options.adjust_osd_margins then
|
|
|
|
return
|
|
|
|
end
|
2023-05-23 13:31:17 +00:00
|
|
|
local osd_margin_y, osd_margin_x, osd_factor_x = 0, 0, display.width / display.height * 720
|
2023-06-15 08:12:30 +00:00
|
|
|
if config.osd_alignment_y == "bottom" then
|
|
|
|
osd_margin_y = round(bottom * 720)
|
|
|
|
elseif config.osd_alignment_y == "top" then
|
|
|
|
osd_margin_y = round(top * 720)
|
|
|
|
end
|
|
|
|
if config.osd_alignment_x == "left" then
|
|
|
|
osd_margin_x = round(left * osd_factor_x)
|
|
|
|
elseif config.osd_alignment_x == "right" then
|
|
|
|
osd_margin_x = round(right * osd_factor_x)
|
|
|
|
end
|
|
|
|
mp.set_property_native("osd-margin-y", osd_margin_y + config.osd_margin_y)
|
|
|
|
mp.set_property_native("osd-margin-x", osd_margin_x + config.osd_margin_x)
|
2023-05-23 13:31:17 +00:00
|
|
|
end
|
|
|
|
function create_state_setter(name, callback)
|
|
|
|
return function(_, value)
|
|
|
|
set_state(name, value)
|
2023-06-15 08:12:30 +00:00
|
|
|
if callback then
|
|
|
|
callback()
|
|
|
|
end
|
2023-05-23 13:31:17 +00:00
|
|
|
request_render()
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
function set_state(name, value)
|
|
|
|
state[name] = value
|
2023-06-15 08:12:30 +00:00
|
|
|
Elements:trigger("prop_" .. name, value)
|
2023-05-23 13:31:17 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
function update_cursor_position(x, y)
|
|
|
|
local old_x, old_y = cursor.x, cursor.y
|
|
|
|
|
|
|
|
-- mpv reports initial mouse position on linux as (0, 0), which always
|
|
|
|
-- displays the top bar, so we hardcode cursor position as infinity until
|
|
|
|
-- we receive a first real mouse move event with coordinates other than 0,0.
|
|
|
|
if not state.first_real_mouse_move_received then
|
2023-06-15 08:12:30 +00:00
|
|
|
if x > 0 and y > 0 then
|
|
|
|
state.first_real_mouse_move_received = true
|
|
|
|
else
|
|
|
|
x, y = INFINITY, INFINITY
|
|
|
|
end
|
2023-05-23 13:31:17 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
-- add 0.5 to be in the middle of the pixel
|
|
|
|
cursor.x, cursor.y = (x + 0.5) / display.scale_x, (y + 0.5) / display.scale_y
|
|
|
|
|
|
|
|
if old_x ~= cursor.x or old_y ~= cursor.y then
|
|
|
|
Elements:update_proximities()
|
|
|
|
|
|
|
|
if cursor.x == INFINITY or cursor.y == INFINITY then
|
|
|
|
cursor.hidden = true
|
2023-06-15 08:12:30 +00:00
|
|
|
Elements:trigger("global_mouse_leave")
|
2023-05-23 13:31:17 +00:00
|
|
|
elseif cursor.hidden then
|
|
|
|
cursor.hidden = false
|
2023-06-15 08:12:30 +00:00
|
|
|
Elements:trigger("global_mouse_enter")
|
2023-05-23 13:31:17 +00:00
|
|
|
end
|
|
|
|
|
2023-06-15 08:12:30 +00:00
|
|
|
Elements:proximity_trigger("mouse_move")
|
2023-05-23 13:31:17 +00:00
|
|
|
cursor.queue_autohide()
|
|
|
|
end
|
|
|
|
|
|
|
|
request_render()
|
2020-05-13 13:08:03 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
function handle_mouse_leave()
|
2023-05-23 13:31:17 +00:00
|
|
|
-- Slowly fadeout elements that are currently visible
|
2023-06-15 08:12:30 +00:00
|
|
|
for _, element_name in ipairs({ "timeline", "volume", "top_bar" }) do
|
2023-05-23 13:31:17 +00:00
|
|
|
local element = Elements[element_name]
|
|
|
|
if element and element.proximity > 0 then
|
2023-06-15 08:12:30 +00:00
|
|
|
element:tween_property("forced_visibility", element:get_visibility(), 0, function()
|
2023-05-23 13:31:17 +00:00
|
|
|
element.forced_visibility = nil
|
|
|
|
end)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
update_cursor_position(INFINITY, INFINITY)
|
|
|
|
end
|
|
|
|
|
|
|
|
function handle_file_end()
|
|
|
|
local resume = false
|
|
|
|
if not state.loop_file then
|
2023-06-15 08:12:30 +00:00
|
|
|
if state.has_playlist then
|
|
|
|
resume = state.shuffle and navigate_playlist(1)
|
|
|
|
else
|
|
|
|
resume = options.autoload and navigate_directory(1)
|
|
|
|
end
|
2023-05-23 13:31:17 +00:00
|
|
|
end
|
|
|
|
-- Resume only when navigation happened
|
2023-06-15 08:12:30 +00:00
|
|
|
if resume then
|
|
|
|
mp.command("set pause no")
|
|
|
|
end
|
2023-05-23 13:31:17 +00:00
|
|
|
end
|
|
|
|
local file_end_timer = mp.add_timeout(1, handle_file_end)
|
|
|
|
file_end_timer:kill()
|
|
|
|
|
|
|
|
function load_file_index_in_current_directory(index)
|
2023-06-15 08:12:30 +00:00
|
|
|
if not state.path or is_protocol(state.path) then
|
|
|
|
return
|
|
|
|
end
|
2023-05-23 13:31:17 +00:00
|
|
|
|
|
|
|
local serialized = serialize_path(state.path)
|
|
|
|
if serialized and serialized.dirname then
|
|
|
|
local files = read_directory(serialized.dirname, config.types.autoload)
|
|
|
|
|
2023-06-15 08:12:30 +00:00
|
|
|
if not files then
|
|
|
|
return
|
|
|
|
end
|
2023-05-23 13:31:17 +00:00
|
|
|
sort_filenames(files)
|
2023-06-15 08:12:30 +00:00
|
|
|
if index < 0 then
|
|
|
|
index = #files + index + 1
|
|
|
|
end
|
2023-05-23 13:31:17 +00:00
|
|
|
|
|
|
|
if files[index] then
|
2023-06-15 08:12:30 +00:00
|
|
|
mp.commandv("loadfile", join_path(serialized.dirname, files[index]))
|
2023-05-23 13:31:17 +00:00
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
function update_render_delay(name, fps)
|
2023-06-15 08:12:30 +00:00
|
|
|
if fps then
|
|
|
|
state.render_delay = 1 / fps
|
|
|
|
end
|
2023-05-23 13:31:17 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
function observe_display_fps(name, fps)
|
|
|
|
if fps then
|
|
|
|
mp.unobserve_property(update_render_delay)
|
|
|
|
mp.unobserve_property(observe_display_fps)
|
2023-06-15 08:12:30 +00:00
|
|
|
mp.observe_property("display-fps", "native", update_render_delay)
|
2023-05-23 13:31:17 +00:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
function select_current_chapter()
|
|
|
|
local current_chapter
|
|
|
|
if state.time and state.chapters then
|
2023-06-15 08:12:30 +00:00
|
|
|
_, current_chapter = itable_find(state.chapters, function(c)
|
|
|
|
return state.time >= c.time
|
|
|
|
end, true)
|
2023-05-23 13:31:17 +00:00
|
|
|
end
|
2023-06-15 08:12:30 +00:00
|
|
|
set_state("current_chapter", current_chapter)
|
2023-05-23 13:31:17 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
--[[ STATE HOOKS ]]
|
|
|
|
|
|
|
|
-- Click detection
|
|
|
|
if options.click_threshold > 0 then
|
|
|
|
-- Executes custom command for clicks shorter than `options.click_threshold`
|
|
|
|
-- while filtering out double clicks.
|
|
|
|
local click_time = options.click_threshold / 1000
|
2023-06-15 08:12:30 +00:00
|
|
|
local doubleclick_time = mp.get_property_native("input-doubleclick-time") / 1000
|
2023-05-23 13:31:17 +00:00
|
|
|
local last_down, last_up = 0, 0
|
|
|
|
local click_timer = mp.add_timeout(math.max(click_time, doubleclick_time), function()
|
|
|
|
local delta = last_up - last_down
|
2023-06-15 08:12:30 +00:00
|
|
|
if delta > 0 and delta < click_time and delta > 0.02 then
|
|
|
|
mp.command(options.click_command)
|
|
|
|
end
|
2023-05-23 13:31:17 +00:00
|
|
|
end)
|
|
|
|
click_timer:kill()
|
2023-06-15 08:12:30 +00:00
|
|
|
mp.set_key_bindings(
|
|
|
|
{
|
|
|
|
{
|
|
|
|
"mbtn_left",
|
|
|
|
function()
|
|
|
|
last_up = mp.get_time()
|
|
|
|
end,
|
|
|
|
function()
|
|
|
|
last_down = mp.get_time()
|
|
|
|
if click_timer:is_enabled() then
|
|
|
|
click_timer:kill()
|
|
|
|
else
|
|
|
|
click_timer:resume()
|
|
|
|
end
|
|
|
|
end,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
"mouse_movement",
|
|
|
|
"force"
|
|
|
|
)
|
|
|
|
mp.enable_key_bindings("mouse_movement", "allow-vo-dragging+allow-hide-cursor")
|
2023-05-23 13:31:17 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
function handle_mouse_pos(_, mouse)
|
2023-06-15 08:12:30 +00:00
|
|
|
if not mouse then
|
|
|
|
return
|
|
|
|
end
|
2023-05-23 13:31:17 +00:00
|
|
|
if cursor.hover_raw and not mouse.hover then
|
|
|
|
handle_mouse_leave()
|
|
|
|
else
|
|
|
|
update_cursor_position(mouse.x, mouse.y)
|
|
|
|
end
|
|
|
|
cursor.hover_raw = mouse.hover
|
|
|
|
end
|
2023-06-15 08:12:30 +00:00
|
|
|
mp.observe_property("mouse-pos", "native", handle_mouse_pos)
|
|
|
|
mp.observe_property("osc", "bool", function(name, value)
|
|
|
|
if value == true then
|
|
|
|
mp.set_property("osc", "no")
|
|
|
|
end
|
|
|
|
end)
|
|
|
|
mp.register_event("file-loaded", function()
|
|
|
|
set_state("path", normalize_path(mp.get_property_native("path")))
|
|
|
|
Elements:flash({ "top_bar" })
|
2023-05-23 13:31:17 +00:00
|
|
|
end)
|
2023-06-15 08:12:30 +00:00
|
|
|
mp.register_event("end-file", function(event)
|
|
|
|
set_state("path", nil)
|
|
|
|
if event.reason == "eof" then
|
2023-05-23 13:31:17 +00:00
|
|
|
file_end_timer:kill()
|
|
|
|
handle_file_end()
|
|
|
|
end
|
|
|
|
end)
|
|
|
|
-- Top bar titles
|
|
|
|
do
|
|
|
|
local function update_state_with_template(prop, template)
|
|
|
|
-- escape ASS, and strip newlines and trailing slashes and trim whitespace
|
2023-06-15 08:12:30 +00:00
|
|
|
local tmp =
|
|
|
|
mp.command_native({ "expand-text", template }):gsub("\\n", " "):gsub("[\\%s]+$", ""):gsub("^%s+", "")
|
2023-05-23 13:31:17 +00:00
|
|
|
set_state(prop, ass_escape(tmp))
|
|
|
|
end
|
|
|
|
|
|
|
|
local function add_template_listener(template, callback)
|
|
|
|
local props = get_expansion_props(template)
|
|
|
|
for prop, _ in pairs(props) do
|
2023-06-15 08:12:30 +00:00
|
|
|
mp.observe_property(prop, "native", callback)
|
|
|
|
end
|
|
|
|
if not next(props) then
|
|
|
|
callback()
|
2023-05-23 13:31:17 +00:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2023-06-15 08:12:30 +00:00
|
|
|
local function remove_template_listener(callback)
|
|
|
|
mp.unobserve_property(callback)
|
|
|
|
end
|
2023-05-23 13:31:17 +00:00
|
|
|
|
|
|
|
-- Main title
|
2023-06-15 08:12:30 +00:00
|
|
|
if #options.top_bar_title > 0 and options.top_bar_title ~= "no" then
|
|
|
|
if options.top_bar_title == "yes" then
|
2023-05-23 13:31:17 +00:00
|
|
|
local template = nil
|
2023-06-15 08:12:30 +00:00
|
|
|
local function update_title()
|
|
|
|
update_state_with_template("title", template)
|
|
|
|
end
|
|
|
|
mp.observe_property("title", "string", function(_, title)
|
2023-05-23 13:31:17 +00:00
|
|
|
remove_template_listener(update_title)
|
|
|
|
template = title
|
|
|
|
if template then
|
2023-06-15 08:12:30 +00:00
|
|
|
if template:sub(-6) == " - mpv" then
|
|
|
|
template = template:sub(1, -7)
|
|
|
|
end
|
2023-05-23 13:31:17 +00:00
|
|
|
add_template_listener(template, update_title)
|
|
|
|
end
|
|
|
|
end)
|
2023-06-15 08:12:30 +00:00
|
|
|
elseif type(options.top_bar_title) == "string" then
|
2023-05-23 13:31:17 +00:00
|
|
|
add_template_listener(options.top_bar_title, function()
|
2023-06-15 08:12:30 +00:00
|
|
|
update_state_with_template("title", options.top_bar_title)
|
2023-05-23 13:31:17 +00:00
|
|
|
end)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
-- Alt title
|
2023-06-15 08:12:30 +00:00
|
|
|
if #options.top_bar_alt_title > 0 and options.top_bar_alt_title ~= "no" then
|
2023-05-23 13:31:17 +00:00
|
|
|
add_template_listener(options.top_bar_alt_title, function()
|
2023-06-15 08:12:30 +00:00
|
|
|
update_state_with_template("alt_title", options.top_bar_alt_title)
|
2023-05-23 13:31:17 +00:00
|
|
|
end)
|
|
|
|
end
|
|
|
|
end
|
2023-06-15 08:12:30 +00:00
|
|
|
mp.observe_property(
|
|
|
|
"playback-time",
|
|
|
|
"number",
|
|
|
|
create_state_setter("time", function()
|
|
|
|
-- Create a file-end event that triggers right before file ends
|
|
|
|
file_end_timer:kill()
|
|
|
|
if state.duration and state.time and not state.pause then
|
|
|
|
local remaining = (state.duration - state.time) / state.speed
|
|
|
|
if remaining < 5 then
|
|
|
|
local timeout = remaining - 0.02
|
|
|
|
if timeout > 0 then
|
|
|
|
file_end_timer.timeout = timeout
|
|
|
|
file_end_timer:resume()
|
|
|
|
else
|
|
|
|
handle_file_end()
|
|
|
|
end
|
|
|
|
end
|
2023-05-23 13:31:17 +00:00
|
|
|
end
|
|
|
|
|
2023-06-15 08:12:30 +00:00
|
|
|
update_human_times()
|
|
|
|
select_current_chapter()
|
|
|
|
end)
|
|
|
|
)
|
|
|
|
mp.observe_property("duration", "number", create_state_setter("duration", update_human_times))
|
|
|
|
mp.observe_property("speed", "number", create_state_setter("speed", update_human_times))
|
|
|
|
mp.observe_property("track-list", "native", function(name, value)
|
2023-05-23 13:31:17 +00:00
|
|
|
-- checks the file dispositions
|
2023-06-15 08:12:30 +00:00
|
|
|
local types = { sub = 0, image = 0, audio = 0, video = 0 }
|
2023-05-23 13:31:17 +00:00
|
|
|
for _, track in ipairs(value) do
|
2023-06-15 08:12:30 +00:00
|
|
|
if track.type == "video" then
|
|
|
|
if track.image or track.albumart then
|
|
|
|
types.image = types.image + 1
|
|
|
|
else
|
|
|
|
types.video = types.video + 1
|
|
|
|
end
|
|
|
|
elseif types[track.type] then
|
|
|
|
types[track.type] = types[track.type] + 1
|
|
|
|
end
|
|
|
|
end
|
|
|
|
set_state("is_audio", types.video == 0 and types.audio > 0)
|
|
|
|
set_state("is_image", types.image > 0 and types.video == 0 and types.audio == 0)
|
|
|
|
set_state("has_audio", types.audio > 0)
|
|
|
|
set_state("has_many_audio", types.audio > 1)
|
|
|
|
set_state("has_sub", types.sub > 0)
|
|
|
|
set_state("has_many_sub", types.sub > 1)
|
|
|
|
set_state("is_video", types.video > 0)
|
|
|
|
set_state("has_many_video", types.video > 1)
|
|
|
|
Elements:trigger("dispositions")
|
2023-05-23 13:31:17 +00:00
|
|
|
end)
|
2023-06-15 08:12:30 +00:00
|
|
|
mp.observe_property("editions", "number", function(_, editions)
|
|
|
|
if editions then
|
|
|
|
set_state("has_many_edition", editions > 1)
|
|
|
|
end
|
|
|
|
Elements:trigger("dispositions")
|
2023-05-23 13:31:17 +00:00
|
|
|
end)
|
2023-06-15 08:12:30 +00:00
|
|
|
mp.observe_property("chapter-list", "native", function(_, chapters)
|
2023-05-23 13:31:17 +00:00
|
|
|
local chapters, chapter_ranges = serialize_chapters(chapters), {}
|
2023-06-15 08:12:30 +00:00
|
|
|
if chapters then
|
|
|
|
chapters, chapter_ranges = serialize_chapter_ranges(chapters)
|
|
|
|
end
|
|
|
|
set_state("chapters", chapters)
|
|
|
|
set_state("chapter_ranges", chapter_ranges)
|
|
|
|
set_state("has_chapter", #chapters > 0)
|
2023-05-23 13:31:17 +00:00
|
|
|
select_current_chapter()
|
2023-06-15 08:12:30 +00:00
|
|
|
Elements:trigger("dispositions")
|
2023-05-23 13:31:17 +00:00
|
|
|
end)
|
2023-06-15 08:12:30 +00:00
|
|
|
mp.observe_property("border", "bool", create_state_setter("border"))
|
|
|
|
mp.observe_property("loop-file", "native", create_state_setter("loop_file"))
|
|
|
|
mp.observe_property("ab-loop-a", "number", create_state_setter("ab_loop_a"))
|
|
|
|
mp.observe_property("ab-loop-b", "number", create_state_setter("ab_loop_b"))
|
|
|
|
mp.observe_property("playlist-pos-1", "number", create_state_setter("playlist_pos"))
|
|
|
|
mp.observe_property("playlist-count", "number", function(_, value)
|
|
|
|
set_state("playlist_count", value)
|
|
|
|
set_state("has_playlist", value > 1)
|
|
|
|
Elements:trigger("dispositions")
|
2023-05-23 13:31:17 +00:00
|
|
|
end)
|
2023-06-15 08:12:30 +00:00
|
|
|
mp.observe_property("fullscreen", "bool", create_state_setter("fullscreen", update_fullormaxed))
|
|
|
|
mp.observe_property("window-maximized", "bool", create_state_setter("maximized", update_fullormaxed))
|
|
|
|
mp.observe_property("idle-active", "bool", function(_, idle)
|
|
|
|
set_state("is_idle", idle)
|
|
|
|
Elements:trigger("dispositions")
|
2023-05-23 13:31:17 +00:00
|
|
|
end)
|
2023-06-15 08:12:30 +00:00
|
|
|
mp.observe_property(
|
|
|
|
"pause",
|
|
|
|
"bool",
|
|
|
|
create_state_setter("pause", function()
|
|
|
|
file_end_timer:kill()
|
|
|
|
end)
|
|
|
|
)
|
|
|
|
mp.observe_property("volume", "number", create_state_setter("volume"))
|
|
|
|
mp.observe_property("volume-max", "number", create_state_setter("volume_max"))
|
|
|
|
mp.observe_property("mute", "bool", create_state_setter("mute"))
|
|
|
|
mp.observe_property("osd-dimensions", "native", function(name, val)
|
2023-05-23 13:31:17 +00:00
|
|
|
update_display_dimensions()
|
|
|
|
request_render()
|
2020-05-13 13:08:03 +00:00
|
|
|
end)
|
2023-06-15 08:12:30 +00:00
|
|
|
mp.observe_property("display-hidpi-scale", "native", create_state_setter("hidpi_scale", update_display_dimensions))
|
|
|
|
mp.observe_property("cache", "string", create_state_setter("cache"))
|
|
|
|
mp.observe_property("cache-buffering-state", "number", create_state_setter("cache_buffering"))
|
|
|
|
mp.observe_property(
|
|
|
|
"demuxer-via-network",
|
|
|
|
"native",
|
|
|
|
create_state_setter("is_stream", function()
|
|
|
|
Elements:trigger("dispositions")
|
|
|
|
end)
|
|
|
|
)
|
|
|
|
mp.observe_property("demuxer-cache-state", "native", function(prop, cache_state)
|
2023-05-23 13:31:17 +00:00
|
|
|
local cached_ranges, bof, eof, uncached_ranges = nil, nil, nil, nil
|
|
|
|
if cache_state then
|
2023-06-15 08:12:30 +00:00
|
|
|
cached_ranges, bof, eof = cache_state["seekable-ranges"], cache_state["bof-cached"], cache_state["eof-cached"]
|
|
|
|
set_state("cache_underrun", cache_state["underrun"])
|
|
|
|
else
|
|
|
|
cached_ranges = {}
|
|
|
|
end
|
2023-05-23 13:31:17 +00:00
|
|
|
|
2023-06-15 08:12:30 +00:00
|
|
|
if
|
|
|
|
not (
|
|
|
|
state.duration
|
|
|
|
and (#cached_ranges > 0 or state.cache == "yes" or (state.cache == "auto" and state.is_stream))
|
|
|
|
)
|
|
|
|
then
|
|
|
|
if state.uncached_ranges then
|
|
|
|
set_state("uncached_ranges", nil)
|
|
|
|
end
|
2023-05-23 13:31:17 +00:00
|
|
|
return
|
|
|
|
end
|
|
|
|
|
|
|
|
-- Normalize
|
|
|
|
local ranges = {}
|
|
|
|
for _, range in ipairs(cached_ranges) do
|
|
|
|
ranges[#ranges + 1] = {
|
2023-06-15 08:12:30 +00:00
|
|
|
math.max(range["start"] or 0, 0),
|
|
|
|
math.min(range["end"] or state.duration, state.duration),
|
2023-05-23 13:31:17 +00:00
|
|
|
}
|
|
|
|
end
|
2023-06-15 08:12:30 +00:00
|
|
|
table.sort(ranges, function(a, b)
|
|
|
|
return a[1] < b[1]
|
|
|
|
end)
|
|
|
|
if bof then
|
|
|
|
ranges[1][1] = 0
|
|
|
|
end
|
|
|
|
if eof then
|
|
|
|
ranges[#ranges][2] = state.duration
|
|
|
|
end
|
2023-05-23 13:31:17 +00:00
|
|
|
-- Invert cached ranges into uncached ranges, as that's what we're rendering
|
2023-06-15 08:12:30 +00:00
|
|
|
local inverted_ranges = { { 0, state.duration } }
|
2023-05-23 13:31:17 +00:00
|
|
|
for _, cached in pairs(ranges) do
|
|
|
|
inverted_ranges[#inverted_ranges][2] = cached[1]
|
2023-06-15 08:12:30 +00:00
|
|
|
inverted_ranges[#inverted_ranges + 1] = { cached[2], state.duration }
|
2023-05-23 13:31:17 +00:00
|
|
|
end
|
|
|
|
uncached_ranges = {}
|
|
|
|
local last_range = nil
|
|
|
|
for _, range in ipairs(inverted_ranges) do
|
|
|
|
if last_range and last_range[2] + 0.5 > range[1] then -- fuse ranges
|
|
|
|
last_range[2] = range[2]
|
|
|
|
else
|
|
|
|
if range[2] - range[1] > 0.5 then -- skip short ranges
|
|
|
|
uncached_ranges[#uncached_ranges + 1] = range
|
|
|
|
last_range = range
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2023-06-15 08:12:30 +00:00
|
|
|
set_state("uncached_ranges", uncached_ranges)
|
2020-05-13 13:08:03 +00:00
|
|
|
end)
|
2023-06-15 08:12:30 +00:00
|
|
|
mp.observe_property("display-fps", "native", observe_display_fps)
|
|
|
|
mp.observe_property("estimated-display-fps", "native", update_render_delay)
|
|
|
|
mp.observe_property("eof-reached", "native", create_state_setter("eof_reached"))
|
|
|
|
mp.observe_property("core-idle", "native", create_state_setter("core_idle"))
|
2023-05-23 13:31:17 +00:00
|
|
|
|
|
|
|
--[[ KEY BINDS ]]
|
|
|
|
|
|
|
|
-- Pointer related binding groups
|
|
|
|
function make_cursor_handler(event, cb)
|
|
|
|
return function(...)
|
|
|
|
call_maybe(cursor[event], ...)
|
|
|
|
call_maybe(cb, ...)
|
|
|
|
cursor.queue_autohide() -- refresh cursor autohide timer
|
|
|
|
end
|
|
|
|
end
|
|
|
|
mp.set_key_bindings({
|
|
|
|
{
|
2023-06-15 08:12:30 +00:00
|
|
|
"mbtn_left",
|
|
|
|
make_cursor_handler("on_primary_up"),
|
|
|
|
make_cursor_handler("on_primary_down", function(...)
|
|
|
|
handle_mouse_pos(nil, mp.get_property_native("mouse-pos"))
|
2023-05-23 13:31:17 +00:00
|
|
|
end),
|
|
|
|
},
|
2023-06-15 08:12:30 +00:00
|
|
|
{ "mbtn_left_dbl", "ignore" },
|
|
|
|
}, "mbtn_left", "force")
|
2023-05-23 13:31:17 +00:00
|
|
|
mp.set_key_bindings({
|
2023-06-15 08:12:30 +00:00
|
|
|
{ "wheel_up", make_cursor_handler("on_wheel_up") },
|
|
|
|
{ "wheel_down", make_cursor_handler("on_wheel_down") },
|
|
|
|
}, "wheel", "force")
|
2023-05-23 13:31:17 +00:00
|
|
|
|
|
|
|
-- Adds a key binding that respects rerouting set by `key_binding_overwrites` table.
|
|
|
|
---@param name string
|
|
|
|
---@param callback fun(event: table)
|
|
|
|
---@param flags nil|string
|
|
|
|
function bind_command(name, callback, flags)
|
|
|
|
mp.add_key_binding(nil, name, function(...)
|
2023-06-15 08:12:30 +00:00
|
|
|
if key_binding_overwrites[name] then
|
|
|
|
mp.command(key_binding_overwrites[name])
|
|
|
|
else
|
|
|
|
callback(...)
|
|
|
|
end
|
2023-05-23 13:31:17 +00:00
|
|
|
end, flags)
|
|
|
|
end
|
|
|
|
|
2023-06-15 08:12:30 +00:00
|
|
|
bind_command("toggle-ui", function()
|
|
|
|
Elements:toggle({ "timeline", "controls", "volume", "top_bar" })
|
|
|
|
end)
|
|
|
|
bind_command("flash-ui", function()
|
|
|
|
Elements:flash({ "timeline", "controls", "volume", "top_bar" })
|
|
|
|
end)
|
|
|
|
bind_command("flash-timeline", function()
|
|
|
|
Elements:flash({ "timeline" })
|
|
|
|
end)
|
|
|
|
bind_command("flash-top-bar", function()
|
|
|
|
Elements:flash({ "top_bar" })
|
|
|
|
end)
|
|
|
|
bind_command("flash-volume", function()
|
|
|
|
Elements:flash({ "volume" })
|
|
|
|
end)
|
|
|
|
bind_command("flash-speed", function()
|
|
|
|
Elements:flash({ "speed" })
|
|
|
|
end)
|
|
|
|
bind_command("flash-pause-indicator", function()
|
|
|
|
Elements:flash({ "pause_indicator" })
|
|
|
|
end)
|
|
|
|
bind_command("toggle-progress", function()
|
2023-05-23 13:31:17 +00:00
|
|
|
local timeline = Elements.timeline
|
|
|
|
if timeline.size_min_override then
|
2023-06-15 08:12:30 +00:00
|
|
|
timeline:tween_property("size_min_override", timeline.size_min_override, timeline.size_min, function()
|
2023-05-23 13:31:17 +00:00
|
|
|
timeline.size_min_override = nil
|
|
|
|
end)
|
|
|
|
else
|
2023-06-15 08:12:30 +00:00
|
|
|
timeline:tween_property("size_min_override", timeline.size_min, 0)
|
2023-05-23 13:31:17 +00:00
|
|
|
end
|
|
|
|
end)
|
2023-06-15 08:12:30 +00:00
|
|
|
bind_command("toggle-title", function()
|
|
|
|
Elements.top_bar:toggle_title()
|
|
|
|
end)
|
|
|
|
bind_command("decide-pause-indicator", function()
|
|
|
|
Elements.pause_indicator:decide()
|
|
|
|
end)
|
|
|
|
bind_command("menu", function()
|
|
|
|
toggle_menu_with_items()
|
|
|
|
end)
|
|
|
|
bind_command("menu-blurred", function()
|
|
|
|
toggle_menu_with_items({ mouse_nav = true })
|
|
|
|
end)
|
2023-05-23 13:31:17 +00:00
|
|
|
local track_loaders = {
|
2023-06-15 08:12:30 +00:00
|
|
|
{ name = "subtitles", prop = "sub", allowed_types = itable_join(config.types.video, config.types.subtitle) },
|
|
|
|
{ name = "audio", prop = "audio", allowed_types = itable_join(config.types.video, config.types.audio) },
|
|
|
|
{ name = "video", prop = "video", allowed_types = config.types.video },
|
2020-05-13 13:08:03 +00:00
|
|
|
}
|
2023-05-23 13:31:17 +00:00
|
|
|
for _, loader in ipairs(track_loaders) do
|
2023-06-15 08:12:30 +00:00
|
|
|
local menu_type = "load-" .. loader.name
|
2023-05-23 13:31:17 +00:00
|
|
|
bind_command(menu_type, function()
|
2023-06-15 08:12:30 +00:00
|
|
|
if Menu:is_open(menu_type) then
|
|
|
|
Menu:close()
|
|
|
|
return
|
|
|
|
end
|
2023-05-23 13:31:17 +00:00
|
|
|
|
|
|
|
local path = state.path
|
|
|
|
if path then
|
|
|
|
if is_protocol(path) then
|
|
|
|
path = false
|
|
|
|
else
|
|
|
|
local serialized_path = serialize_path(path)
|
|
|
|
path = serialized_path ~= nil and serialized_path.dirname or false
|
|
|
|
end
|
|
|
|
end
|
|
|
|
if not path then
|
|
|
|
path = get_default_directory()
|
|
|
|
end
|
2023-06-15 08:12:30 +00:00
|
|
|
open_file_navigation_menu(path, function(path)
|
|
|
|
mp.commandv(loader.prop .. "-add", path)
|
|
|
|
end, { type = menu_type, title = "Load " .. loader.name, allowed_types = loader.allowed_types })
|
2023-05-23 13:31:17 +00:00
|
|
|
end)
|
|
|
|
end
|
2023-06-15 08:12:30 +00:00
|
|
|
bind_command(
|
|
|
|
"subtitles",
|
|
|
|
create_select_tracklist_type_menu_opener("Subtitles", "sub", "sid", "script-binding uosc/load-subtitles")
|
|
|
|
)
|
|
|
|
bind_command(
|
|
|
|
"audio",
|
|
|
|
create_select_tracklist_type_menu_opener("Audio", "audio", "aid", "script-binding uosc/load-audio")
|
|
|
|
)
|
|
|
|
bind_command(
|
|
|
|
"video",
|
|
|
|
create_select_tracklist_type_menu_opener("Video", "video", "vid", "script-binding uosc/load-video")
|
|
|
|
)
|
|
|
|
bind_command(
|
|
|
|
"playlist",
|
|
|
|
create_self_updating_menu_opener({
|
|
|
|
title = "Playlist",
|
|
|
|
type = "playlist",
|
|
|
|
list_prop = "playlist",
|
|
|
|
serializer = function(playlist)
|
|
|
|
local items = {}
|
|
|
|
for index, item in ipairs(playlist) do
|
|
|
|
local is_url = item.filename:find("://")
|
|
|
|
local item_title = type(item.title) == "string" and #item.title > 0 and item.title or false
|
|
|
|
items[index] = {
|
|
|
|
title = item_title or (is_url and item.filename or serialize_path(item.filename).basename),
|
|
|
|
hint = tostring(index),
|
|
|
|
active = item.current,
|
|
|
|
value = index,
|
|
|
|
}
|
|
|
|
end
|
|
|
|
return items
|
|
|
|
end,
|
|
|
|
on_select = function(index)
|
|
|
|
mp.commandv("set", "playlist-pos-1", tostring(index))
|
|
|
|
end,
|
|
|
|
on_move_item = function(from, to)
|
|
|
|
mp.commandv("playlist-move", tostring(math.max(from, to) - 1), tostring(math.min(from, to) - 1))
|
|
|
|
end,
|
|
|
|
on_delete_item = function(index)
|
|
|
|
mp.commandv("playlist-remove", tostring(index - 1))
|
|
|
|
end,
|
|
|
|
})
|
|
|
|
)
|
|
|
|
bind_command(
|
|
|
|
"chapters",
|
|
|
|
create_self_updating_menu_opener({
|
|
|
|
title = "Chapters",
|
|
|
|
type = "chapters",
|
|
|
|
list_prop = "chapter-list",
|
|
|
|
active_prop = "chapter",
|
|
|
|
serializer = function(chapters, current_chapter)
|
|
|
|
local items = {}
|
|
|
|
chapters = normalize_chapters(chapters)
|
|
|
|
for index, chapter in ipairs(chapters) do
|
|
|
|
items[index] = {
|
|
|
|
title = chapter.title or "",
|
|
|
|
hint = format_time(chapter.time, state.duration),
|
|
|
|
value = index,
|
|
|
|
active = index - 1 == current_chapter,
|
|
|
|
}
|
|
|
|
end
|
|
|
|
return items
|
|
|
|
end,
|
|
|
|
on_select = function(index)
|
|
|
|
mp.commandv("set", "chapter", tostring(index - 1))
|
|
|
|
end,
|
|
|
|
})
|
|
|
|
)
|
|
|
|
bind_command(
|
|
|
|
"editions",
|
|
|
|
create_self_updating_menu_opener({
|
|
|
|
title = "Editions",
|
|
|
|
type = "editions",
|
|
|
|
list_prop = "edition-list",
|
|
|
|
active_prop = "current-edition",
|
|
|
|
serializer = function(editions, current_id)
|
|
|
|
local items = {}
|
|
|
|
for _, edition in ipairs(editions or {}) do
|
|
|
|
items[#items + 1] = {
|
|
|
|
title = edition.title or "Edition",
|
|
|
|
hint = tostring(edition.id + 1),
|
|
|
|
value = edition.id,
|
|
|
|
active = edition.id == current_id,
|
|
|
|
}
|
|
|
|
end
|
|
|
|
return items
|
|
|
|
end,
|
|
|
|
on_select = function(id)
|
|
|
|
mp.commandv("set", "edition", id)
|
|
|
|
end,
|
|
|
|
})
|
|
|
|
)
|
|
|
|
bind_command("show-in-directory", function()
|
2023-05-23 13:31:17 +00:00
|
|
|
-- Ignore URLs
|
2023-06-15 08:12:30 +00:00
|
|
|
if not state.path or is_protocol(state.path) then
|
|
|
|
return
|
|
|
|
end
|
2023-05-23 13:31:17 +00:00
|
|
|
|
2023-06-15 08:12:30 +00:00
|
|
|
if state.platform == "windows" then
|
|
|
|
utils.subprocess_detached({ args = { "explorer", "/select,", state.path }, cancellable = false })
|
|
|
|
elseif state.platform == "macos" then
|
|
|
|
utils.subprocess_detached({ args = { "open", "-R", state.path }, cancellable = false })
|
|
|
|
elseif state.platform == "linux" then
|
|
|
|
local result = utils.subprocess({ args = { "nautilus", state.path }, cancellable = false })
|
2023-05-23 13:31:17 +00:00
|
|
|
|
|
|
|
-- Fallback opens the folder with xdg-open instead
|
|
|
|
if result.status ~= 0 then
|
2023-06-15 08:12:30 +00:00
|
|
|
utils.subprocess({ args = { "xdg-open", serialize_path(state.path).dirname }, cancellable = false })
|
2023-05-23 13:31:17 +00:00
|
|
|
end
|
|
|
|
end
|
|
|
|
end)
|
2023-06-15 08:12:30 +00:00
|
|
|
bind_command("stream-quality", function()
|
|
|
|
if Menu:is_open("stream-quality") then
|
|
|
|
Menu:close()
|
|
|
|
return
|
|
|
|
end
|
2023-05-23 13:31:17 +00:00
|
|
|
|
2023-06-15 08:12:30 +00:00
|
|
|
local ytdl_format = mp.get_property_native("ytdl-format")
|
2023-05-23 13:31:17 +00:00
|
|
|
local items = {}
|
|
|
|
|
|
|
|
for _, height in ipairs(config.stream_quality_options) do
|
2023-06-15 08:12:30 +00:00
|
|
|
local format = "bestvideo[height<=?" .. height .. "]+bestaudio/best[height<=?" .. height .. "]"
|
|
|
|
items[#items + 1] = { title = height .. "p", value = format, active = format == ytdl_format }
|
2023-05-23 13:31:17 +00:00
|
|
|
end
|
|
|
|
|
2023-06-15 08:12:30 +00:00
|
|
|
Menu:open({ type = "stream-quality", title = "Stream quality", items = items }, function(format)
|
|
|
|
mp.set_property("ytdl-format", format)
|
2023-05-23 13:31:17 +00:00
|
|
|
|
|
|
|
-- Reload the video to apply new format
|
|
|
|
-- This is taken from https://github.com/jgreco/mpv-youtube-quality
|
|
|
|
-- which is in turn taken from https://github.com/4e6/mpv-reload/
|
|
|
|
-- Dunno if playlist_pos shenanigans below are necessary.
|
2023-06-15 08:12:30 +00:00
|
|
|
local playlist_pos = mp.get_property_number("playlist-pos")
|
|
|
|
local duration = mp.get_property_native("duration")
|
|
|
|
local time_pos = mp.get_property("time-pos")
|
2023-05-23 13:31:17 +00:00
|
|
|
|
2023-06-15 08:12:30 +00:00
|
|
|
mp.set_property_number("playlist-pos", playlist_pos)
|
2023-05-23 13:31:17 +00:00
|
|
|
|
|
|
|
-- Tries to determine live stream vs. pre-recorded VOD. VOD has non-zero
|
|
|
|
-- duration property. When reloading VOD, to keep the current time position
|
|
|
|
-- we should provide offset from the start. Stream doesn't have fixed start.
|
|
|
|
-- Decent choice would be to reload stream from it's current 'live' position.
|
|
|
|
-- That's the reason we don't pass the offset when reloading streams.
|
|
|
|
if duration and duration > 0 then
|
|
|
|
local function seeker()
|
2023-06-15 08:12:30 +00:00
|
|
|
mp.commandv("seek", time_pos, "absolute")
|
2023-05-23 13:31:17 +00:00
|
|
|
mp.unregister_event(seeker)
|
|
|
|
end
|
2023-06-15 08:12:30 +00:00
|
|
|
mp.register_event("file-loaded", seeker)
|
2023-05-23 13:31:17 +00:00
|
|
|
end
|
|
|
|
end)
|
2020-05-13 13:08:03 +00:00
|
|
|
end)
|
2023-06-15 08:12:30 +00:00
|
|
|
bind_command("open-file", function()
|
|
|
|
if Menu:is_open("open-file") then
|
|
|
|
Menu:close()
|
|
|
|
return
|
|
|
|
end
|
2023-05-23 13:31:17 +00:00
|
|
|
|
|
|
|
local directory
|
|
|
|
local active_file
|
|
|
|
|
|
|
|
if state.path == nil or is_protocol(state.path) then
|
|
|
|
local serialized = serialize_path(get_default_directory())
|
|
|
|
if serialized then
|
|
|
|
directory = serialized.path
|
|
|
|
active_file = nil
|
|
|
|
end
|
|
|
|
else
|
|
|
|
local serialized = serialize_path(state.path)
|
|
|
|
if serialized then
|
|
|
|
directory = serialized.dirname
|
|
|
|
active_file = serialized.path
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
if not directory then
|
2023-06-15 08:12:30 +00:00
|
|
|
msg.error("Couldn't serialize path \"" .. state.path .. '".')
|
2023-05-23 13:31:17 +00:00
|
|
|
return
|
|
|
|
end
|
|
|
|
|
|
|
|
-- Update active file in directory navigation menu
|
|
|
|
local function handle_file_loaded()
|
2023-06-15 08:12:30 +00:00
|
|
|
if Menu:is_open("open-file") then
|
|
|
|
Elements.menu:activate_one_value(normalize_path(mp.get_property_native("path")))
|
2023-05-23 13:31:17 +00:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2023-06-15 08:12:30 +00:00
|
|
|
open_file_navigation_menu(directory, function(path)
|
|
|
|
mp.commandv("loadfile", path)
|
|
|
|
end, {
|
|
|
|
type = "open-file",
|
|
|
|
allowed_types = config.types.media,
|
|
|
|
active_path = active_file,
|
|
|
|
on_open = function()
|
|
|
|
mp.register_event("file-loaded", handle_file_loaded)
|
|
|
|
end,
|
|
|
|
on_close = function()
|
|
|
|
mp.unregister_event(handle_file_loaded)
|
|
|
|
end,
|
|
|
|
})
|
|
|
|
end)
|
|
|
|
bind_command("shuffle", function()
|
|
|
|
set_state("shuffle", not state.shuffle)
|
2020-05-13 13:08:03 +00:00
|
|
|
end)
|
2023-06-15 08:12:30 +00:00
|
|
|
bind_command("items", function()
|
2023-05-23 13:31:17 +00:00
|
|
|
if state.has_playlist then
|
2023-06-15 08:12:30 +00:00
|
|
|
mp.command("script-binding uosc/playlist")
|
2023-05-23 13:31:17 +00:00
|
|
|
else
|
2023-06-15 08:12:30 +00:00
|
|
|
mp.command("script-binding uosc/open-file")
|
2023-05-23 13:31:17 +00:00
|
|
|
end
|
2020-05-13 13:08:03 +00:00
|
|
|
end)
|
2023-06-15 08:12:30 +00:00
|
|
|
bind_command("next", function()
|
|
|
|
navigate_item(1)
|
|
|
|
end)
|
|
|
|
bind_command("prev", function()
|
|
|
|
navigate_item(-1)
|
|
|
|
end)
|
|
|
|
bind_command("next-file", function()
|
|
|
|
navigate_directory(1)
|
|
|
|
end)
|
|
|
|
bind_command("prev-file", function()
|
|
|
|
navigate_directory(-1)
|
|
|
|
end)
|
|
|
|
bind_command("first", function()
|
2023-05-23 13:31:17 +00:00
|
|
|
if state.has_playlist then
|
2023-06-15 08:12:30 +00:00
|
|
|
mp.commandv("set", "playlist-pos-1", "1")
|
2023-05-23 13:31:17 +00:00
|
|
|
else
|
|
|
|
load_file_index_in_current_directory(1)
|
|
|
|
end
|
2020-05-13 13:08:03 +00:00
|
|
|
end)
|
2023-06-15 08:12:30 +00:00
|
|
|
bind_command("last", function()
|
2023-05-23 13:31:17 +00:00
|
|
|
if state.has_playlist then
|
2023-06-15 08:12:30 +00:00
|
|
|
mp.commandv("set", "playlist-pos-1", tostring(state.playlist_count))
|
2023-05-23 13:31:17 +00:00
|
|
|
else
|
|
|
|
load_file_index_in_current_directory(-1)
|
|
|
|
end
|
2020-05-13 13:08:03 +00:00
|
|
|
end)
|
2023-06-15 08:12:30 +00:00
|
|
|
bind_command("first-file", function()
|
|
|
|
load_file_index_in_current_directory(1)
|
|
|
|
end)
|
|
|
|
bind_command("last-file", function()
|
|
|
|
load_file_index_in_current_directory(-1)
|
|
|
|
end)
|
|
|
|
bind_command("delete-file-next", function()
|
2023-05-23 13:31:17 +00:00
|
|
|
local next_file = nil
|
|
|
|
local is_local_file = state.path and not is_protocol(state.path)
|
|
|
|
|
|
|
|
if is_local_file then
|
2023-06-15 08:12:30 +00:00
|
|
|
if Menu:is_open("open-file") then
|
|
|
|
Elements.menu:delete_value(state.path)
|
|
|
|
end
|
2023-05-23 13:31:17 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
if state.has_playlist then
|
2023-06-15 08:12:30 +00:00
|
|
|
mp.commandv("playlist-remove", "current")
|
2023-05-23 13:31:17 +00:00
|
|
|
else
|
|
|
|
if is_local_file then
|
|
|
|
local paths, current_index = get_adjacent_files(state.path, config.types.autoload)
|
|
|
|
if paths and current_index then
|
|
|
|
local index, path = decide_navigation_in_list(paths, current_index, 1)
|
2023-06-15 08:12:30 +00:00
|
|
|
if path then
|
|
|
|
next_file = path
|
|
|
|
end
|
2023-05-23 13:31:17 +00:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2023-06-15 08:12:30 +00:00
|
|
|
if next_file then
|
|
|
|
mp.commandv("loadfile", next_file)
|
|
|
|
else
|
|
|
|
mp.commandv("stop")
|
|
|
|
end
|
2023-05-23 13:31:17 +00:00
|
|
|
end
|
|
|
|
|
2023-06-15 08:12:30 +00:00
|
|
|
if is_local_file then
|
|
|
|
delete_file(state.path)
|
|
|
|
end
|
2020-05-13 13:08:03 +00:00
|
|
|
end)
|
2023-06-15 08:12:30 +00:00
|
|
|
bind_command("delete-file-quit", function()
|
|
|
|
mp.command("stop")
|
|
|
|
if state.path and not is_protocol(state.path) then
|
|
|
|
delete_file(state.path)
|
|
|
|
end
|
|
|
|
mp.command("quit")
|
2020-05-13 13:08:03 +00:00
|
|
|
end)
|
2023-06-15 08:12:30 +00:00
|
|
|
bind_command(
|
|
|
|
"audio-device",
|
|
|
|
create_self_updating_menu_opener({
|
|
|
|
title = "Audio devices",
|
|
|
|
type = "audio-device-list",
|
|
|
|
list_prop = "audio-device-list",
|
|
|
|
active_prop = "audio-device",
|
|
|
|
serializer = function(audio_device_list, current_device)
|
|
|
|
current_device = current_device or "auto"
|
|
|
|
local ao = mp.get_property("current-ao") or ""
|
|
|
|
local items = {}
|
|
|
|
for _, device in ipairs(audio_device_list) do
|
|
|
|
if device.name == "auto" or string.match(device.name, "^" .. ao) then
|
|
|
|
local hint = string.match(device.name, ao .. "/(.+)")
|
|
|
|
if not hint then
|
|
|
|
hint = device.name
|
|
|
|
end
|
|
|
|
items[#items + 1] = {
|
|
|
|
title = device.description,
|
|
|
|
hint = hint,
|
|
|
|
active = device.name == current_device,
|
|
|
|
value = device.name,
|
|
|
|
}
|
|
|
|
end
|
2023-05-23 13:31:17 +00:00
|
|
|
end
|
2023-06-15 08:12:30 +00:00
|
|
|
return items
|
|
|
|
end,
|
|
|
|
on_select = function(name)
|
|
|
|
mp.commandv("set", "audio-device", name)
|
|
|
|
end,
|
|
|
|
})
|
|
|
|
)
|
|
|
|
bind_command("open-config-directory", function()
|
|
|
|
local config_path = mp.command_native({ "expand-path", "~~/mpv.conf" })
|
2023-05-23 13:31:17 +00:00
|
|
|
local config = serialize_path(normalize_path(config_path))
|
|
|
|
|
|
|
|
if config then
|
|
|
|
local args
|
|
|
|
|
2023-06-15 08:12:30 +00:00
|
|
|
if state.platform == "windows" then
|
|
|
|
args = { "explorer", "/select,", config.path }
|
|
|
|
elseif state.platform == "macos" then
|
|
|
|
args = { "open", "-R", config.path }
|
|
|
|
elseif state.platform == "linux" then
|
|
|
|
args = { "xdg-open", config.dirname }
|
2023-05-23 13:31:17 +00:00
|
|
|
end
|
|
|
|
|
2023-06-15 08:12:30 +00:00
|
|
|
utils.subprocess_detached({ args = args, cancellable = false })
|
2023-05-23 13:31:17 +00:00
|
|
|
else
|
2023-06-15 08:12:30 +00:00
|
|
|
msg.error("Couldn't serialize config path \"" .. config_path .. '".')
|
2023-05-23 13:31:17 +00:00
|
|
|
end
|
2020-05-13 13:08:03 +00:00
|
|
|
end)
|
2023-05-23 13:31:17 +00:00
|
|
|
|
|
|
|
--[[ MESSAGE HANDLERS ]]
|
|
|
|
|
2023-06-15 08:12:30 +00:00
|
|
|
mp.register_script_message("show-submenu", function(id)
|
|
|
|
toggle_menu_with_items({ submenu = id })
|
|
|
|
end)
|
|
|
|
mp.register_script_message("show-submenu-blurred", function(id)
|
|
|
|
toggle_menu_with_items({ submenu = id, mouse_nav = true })
|
2020-05-13 13:08:03 +00:00
|
|
|
end)
|
2023-06-15 08:12:30 +00:00
|
|
|
mp.register_script_message("get-version", function(script)
|
|
|
|
mp.commandv("script-message-to", script, "uosc-version", config.version)
|
2020-05-13 13:08:03 +00:00
|
|
|
end)
|
2023-06-15 08:12:30 +00:00
|
|
|
mp.register_script_message("open-menu", function(json, submenu_id)
|
2023-05-23 13:31:17 +00:00
|
|
|
local data = utils.parse_json(json)
|
2023-06-15 08:12:30 +00:00
|
|
|
if type(data) ~= "table" or type(data.items) ~= "table" then
|
|
|
|
msg.error("open-menu: received json didn't produce a table with menu configuration")
|
2023-05-23 13:31:17 +00:00
|
|
|
else
|
2023-06-15 08:12:30 +00:00
|
|
|
if data.type and Menu:is_open(data.type) then
|
|
|
|
Menu:close()
|
|
|
|
else
|
|
|
|
open_command_menu(data, { submenu = submenu_id, on_close = data.on_close })
|
|
|
|
end
|
2023-05-23 13:31:17 +00:00
|
|
|
end
|
2020-05-13 13:08:03 +00:00
|
|
|
end)
|
2023-06-15 08:12:30 +00:00
|
|
|
mp.register_script_message("update-menu", function(json)
|
2023-05-23 13:31:17 +00:00
|
|
|
local data = utils.parse_json(json)
|
2023-06-15 08:12:30 +00:00
|
|
|
if type(data) ~= "table" or type(data.items) ~= "table" then
|
|
|
|
msg.error("update-menu: received json didn't produce a table with menu configuration")
|
2023-05-23 13:31:17 +00:00
|
|
|
else
|
|
|
|
local menu = data.type and Menu:is_open(data.type)
|
2023-06-15 08:12:30 +00:00
|
|
|
if menu then
|
|
|
|
menu:update(data)
|
|
|
|
else
|
|
|
|
open_command_menu(data)
|
|
|
|
end
|
2023-05-23 13:31:17 +00:00
|
|
|
end
|
2020-05-13 13:08:03 +00:00
|
|
|
end)
|
2023-06-15 08:12:30 +00:00
|
|
|
mp.register_script_message("thumbfast-info", function(json)
|
2023-05-23 13:31:17 +00:00
|
|
|
local data = utils.parse_json(json)
|
2023-06-15 08:12:30 +00:00
|
|
|
if type(data) ~= "table" or not data.width or not data.height then
|
2023-05-23 13:31:17 +00:00
|
|
|
thumbnail.disabled = true
|
2023-06-15 08:12:30 +00:00
|
|
|
msg.error("thumbfast-info: received json didn't produce a table with thumbnail information")
|
2023-05-23 13:31:17 +00:00
|
|
|
else
|
|
|
|
thumbnail = data
|
|
|
|
request_render()
|
|
|
|
end
|
2020-05-13 13:08:03 +00:00
|
|
|
end)
|
2023-06-15 08:12:30 +00:00
|
|
|
mp.register_script_message("set", function(name, value)
|
2023-05-23 13:31:17 +00:00
|
|
|
external[name] = value
|
2023-06-15 08:12:30 +00:00
|
|
|
Elements:trigger("external_prop_" .. name, value)
|
|
|
|
end)
|
|
|
|
mp.register_script_message("toggle-elements", function(elements)
|
|
|
|
Elements:toggle(split(elements, " *, *"))
|
2020-05-13 13:08:03 +00:00
|
|
|
end)
|
2023-06-15 08:12:30 +00:00
|
|
|
mp.register_script_message("set-min-visibility", function(visibility, elements)
|
2023-05-23 13:31:17 +00:00
|
|
|
local fraction = tonumber(visibility)
|
2023-06-15 08:12:30 +00:00
|
|
|
local ids = split(elements and elements ~= "" and elements or "timeline,controls,volume,top_bar", " *, *")
|
|
|
|
if fraction then
|
|
|
|
Elements:set_min_visibility(clamp(0, fraction, 1), ids)
|
|
|
|
end
|
|
|
|
end)
|
|
|
|
mp.register_script_message("flash-elements", function(elements)
|
|
|
|
Elements:flash(split(elements, " *, *"))
|
|
|
|
end)
|
|
|
|
mp.register_script_message("overwrite-binding", function(name, command)
|
|
|
|
key_binding_overwrites[name] = command
|
2020-05-13 13:08:03 +00:00
|
|
|
end)
|
2023-05-23 13:31:17 +00:00
|
|
|
|
|
|
|
--[[ ELEMENTS ]]
|
|
|
|
|
2023-06-15 08:12:30 +00:00
|
|
|
require("uosc_shared/elements/WindowBorder"):new()
|
|
|
|
require("uosc_shared/elements/BufferingIndicator"):new()
|
|
|
|
require("uosc_shared/elements/PauseIndicator"):new()
|
|
|
|
require("uosc_shared/elements/TopBar"):new()
|
|
|
|
require("uosc_shared/elements/Timeline"):new()
|
|
|
|
if options.controls and options.controls ~= "never" then
|
|
|
|
require("uosc_shared/elements/Controls"):new()
|
|
|
|
end
|
|
|
|
if itable_index_of({ "left", "right" }, options.volume) then
|
|
|
|
require("uosc_shared/elements/Volume"):new()
|
|
|
|
end
|
|
|
|
require("uosc_shared/elements/Curtain"):new()
|