feat: implemented shuffle
and next_file_on_end
options
The `shuffle` option built into mpv doesn't shuffle playlist playback, but instead just randomizes the order of every new opened playlist. So when you enable this after opening a playlist, it does nothing. If you enable it before opening a playlist, it'll randomize its list order, so you can't browse alphabetically. None wants this, none expects this. And since its is expected from a ui such as uosc to provide a shuffle button that works like in any other media player on the planet, I had to simulate it. It decides on the next file 300ms before the end, so it can potentially cut out punchlines from short videos such as those from r/perfectlycutscreams. But this is necessary because a lot of files actually end like ~100ms or more before their duration, but on corrupted files or files where stream ended prematurely, this can happen way before that. If file is force ended by mpv before our timer kicks in, our simulated `file-end` event won't fire, and uosc is unable to take over to decide to play the next one. I couldn't figure out a better way to implement this. There's no 'fil-reached-end' even in mpv. There are only 'im-unloading-the-file' events (`file-end`, `on_unload`, ... etc), which is useless here because it triggers even when user opens a different file during playback. Implementing `shuffle` allowed for trivial `next_file_on_end`, so that got added as well. It load next file in directory when current file ends. Directory navigation now also adheres to `shuffle` uosc option, and `playlist-loop`, `playlist-repeat`, & `loop-file` mpv options. Additionally, the `<has_playlist>` disposition was removed from the `shuffle` and `loop-playlist` control bar buttons as they now affect directory navigation as well. closes #235
This commit is contained in:
443
scripts/uosc.lua
443
scripts/uosc.lua
@@ -169,7 +169,7 @@ local options = {
|
||||
timeline_step = 5,
|
||||
timeline_chapters_opacity = 0.8,
|
||||
|
||||
controls = 'menu,gap,subtitles,<has_many_audio>audio,<stream>stream-quality,gap,space,speed,space,<has_playlist>shuffle,<has_playlist>loop-playlist,loop-file,gap,prev,items,next,gap,fullscreen',
|
||||
controls = 'menu,gap,subtitles,<has_many_audio>audio,<stream>stream-quality,gap,space,speed,space,shuffle,loop-playlist,loop-file,gap,prev,items,next,gap,fullscreen',
|
||||
controls_size = 32,
|
||||
controls_size_fullscreen = 40,
|
||||
controls_margin = 8,
|
||||
@@ -209,6 +209,9 @@ local options = {
|
||||
window_border_size = 1,
|
||||
window_border_opacity = 0.8,
|
||||
|
||||
next_file_on_end = false,
|
||||
shuffle = false,
|
||||
|
||||
ui_scale = 1,
|
||||
font_scale = 1,
|
||||
text_border = 1.2,
|
||||
@@ -405,6 +408,7 @@ local state = {
|
||||
has_sub = false,
|
||||
has_chapter = false,
|
||||
has_playlist = false,
|
||||
shuffle = options.shuffle,
|
||||
cursor_autohide_timer = mp.add_timeout(mp.get_property_native('cursor-autohide') / 1000, function()
|
||||
if not options.autohide then return end
|
||||
handle_mouse_leave()
|
||||
@@ -423,7 +427,7 @@ local thumbnail = {width = 0, height = 0, disabled = false}
|
||||
--[[ HELPERS ]]
|
||||
|
||||
-- Sorting comparator close to (but not exactly) how file explorers sort files
|
||||
local word_order_comparator = (function()
|
||||
local file_order_comparator = (function()
|
||||
local symbol_order
|
||||
local default_order
|
||||
|
||||
@@ -742,39 +746,79 @@ function get_files_in_directory(directory, allowed_types)
|
||||
end)
|
||||
end
|
||||
|
||||
table.sort(files, word_order_comparator)
|
||||
table.sort(files, file_order_comparator)
|
||||
|
||||
return files
|
||||
end
|
||||
|
||||
-- Returns full absolute paths of files in the same directory as file_path,
|
||||
-- and index of the current file in the table.
|
||||
---@param file_path string
|
||||
---@param direction 'forward'|'backward'
|
||||
---@param allowed_types? string[]
|
||||
---@return nil|string
|
||||
function get_adjacent_file(file_path, direction, allowed_types)
|
||||
function get_adjacent_paths(file_path, allowed_types)
|
||||
local current_file = serialize_path(file_path)
|
||||
if not current_file then return end
|
||||
local files = get_files_in_directory(current_file.dirname, allowed_types)
|
||||
if not files then return end
|
||||
|
||||
local current_file_index
|
||||
local paths = {}
|
||||
for index, file in ipairs(files) do
|
||||
if current_file.basename == file then
|
||||
if direction == 'forward' then
|
||||
if files[index + 1] then return utils.join_path(current_file.dirname, files[index + 1]) end
|
||||
if options.directory_navigation_loops and files[1] then
|
||||
return utils.join_path(current_file.dirname, files[1])
|
||||
end
|
||||
else
|
||||
if files[index - 1] then return utils.join_path(current_file.dirname, files[index - 1]) end
|
||||
if options.directory_navigation_loops and files[#files] then
|
||||
return utils.join_path(current_file.dirname, files[#files])
|
||||
end
|
||||
end
|
||||
|
||||
-- This is the only file in directory
|
||||
return nil
|
||||
end
|
||||
paths[#paths + 1] = utils.join_path(current_file.dirname, file)
|
||||
if current_file.basename == file then current_file_index = index end
|
||||
end
|
||||
if not current_file_index then return end
|
||||
return paths, current_file_index
|
||||
end
|
||||
|
||||
-- Navigates in a list, using delta or, when `state.shuffle` is enabled,
|
||||
-- randomness to determine the next item. Loops around if `loop-playlist` is enabled.
|
||||
---@param list table
|
||||
---@param current_index number
|
||||
---@param delta number
|
||||
function decide_navigation_in_list(list, current_index, delta)
|
||||
if #list < 2 then return #list, list[#list] end
|
||||
|
||||
if state.shuffle then
|
||||
local new_index = current_index
|
||||
while current_index == new_index do new_index = math.random(#list) end
|
||||
return new_index, list[new_index]
|
||||
end
|
||||
local new_index = current_index + delta
|
||||
|
||||
if mp.get_property_native('loop-playlist') then
|
||||
if new_index > #list then new_index = new_index % #list
|
||||
elseif new_index < 1 then new_index = #list - new_index end
|
||||
elseif new_index < 1 or new_index > #list then
|
||||
return
|
||||
end
|
||||
|
||||
return new_index, list[new_index]
|
||||
end
|
||||
|
||||
---@param delta number
|
||||
function navigate_directory(delta)
|
||||
if not state.path or is_protocol(state.path) then return false end
|
||||
local paths, current_index = get_adjacent_paths(state.path, config.media_types)
|
||||
if paths and current_index then
|
||||
local _, path = decide_navigation_in_list(paths, current_index, delta)
|
||||
if path then mp.commandv('loadfile', path) return true end
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
---@param delta number
|
||||
function navigate_playlist(delta)
|
||||
local playlist, pos = mp.get_property_native('playlist'), mp.get_property_native('playlist-pos-1')
|
||||
if playlist and #playlist > 1 and pos then
|
||||
local index = decide_navigation_in_list(playlist, pos, delta)
|
||||
if index then mp.commandv('playlist-play-index', index - 1) return true end
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
---@param delta number
|
||||
function navigate_item(delta)
|
||||
if state.has_playlist then return navigate_playlist(delta) else return navigate_directory(delta) end
|
||||
end
|
||||
|
||||
-- Can't use `os.remove()` as it fails on paths with unicode characters.
|
||||
@@ -2453,6 +2497,7 @@ function CycleButton:new(id, props) return Class.new(self, id, props) --[[@as Cy
|
||||
---@param id string
|
||||
---@param props CycleButtonProps
|
||||
function CycleButton:init(id, props)
|
||||
local is_state_prop = itable_index_of({'shuffle'}, props.prop)
|
||||
self.prop = props.prop
|
||||
self.states = props.states
|
||||
|
||||
@@ -2463,10 +2508,17 @@ function CycleButton:init(id, props)
|
||||
self.current_state_index = 1
|
||||
self.on_click = function()
|
||||
local new_state = self.states[self.current_state_index + 1] or self.states[1]
|
||||
mp.set_property(self.prop, new_state.value)
|
||||
if is_state_prop then
|
||||
local new_value = new_state.value
|
||||
if itable_index_of({'yes', 'no'}, new_state.value) then new_value = new_value == 'yes' end
|
||||
set_state(self.prop, new_value)
|
||||
else
|
||||
mp.set_property(self.prop, new_state.value)
|
||||
end
|
||||
end
|
||||
|
||||
self.handle_change = function(name, value)
|
||||
if is_state_prop and type(value) == 'boolean' then value = value and 'yes' or 'no' end
|
||||
local index = itable_find(self.states, function(state) return state.value == value end)
|
||||
self.current_state_index = index or 1
|
||||
self.icon = self.states[self.current_state_index].icon
|
||||
@@ -2474,7 +2526,12 @@ function CycleButton:init(id, props)
|
||||
request_render()
|
||||
end
|
||||
|
||||
mp.observe_property(self.prop, 'string', self.handle_change)
|
||||
-- Built in state props
|
||||
if is_state_prop then
|
||||
self['on_prop_' .. self.prop] = function(self, value) self.handle_change(self.prop, value) end
|
||||
else
|
||||
mp.observe_property(self.prop, 'string', self.handle_change)
|
||||
end
|
||||
end
|
||||
|
||||
function CycleButton:destroy()
|
||||
@@ -3654,130 +3711,6 @@ if options.controls and options.controls ~= 'never' then Controls:new() end
|
||||
if itable_index_of({'left', 'right'}, options.volume) then Volume:new() end
|
||||
Curtain:new()
|
||||
|
||||
-- EVENT HANDLERS
|
||||
|
||||
function create_state_setter(name, callback)
|
||||
return function(_, value)
|
||||
set_state(name, value)
|
||||
if callback then callback() end
|
||||
request_render()
|
||||
end
|
||||
end
|
||||
|
||||
function set_state(name, value)
|
||||
state[name] = value
|
||||
Elements:trigger('prop_' .. name, value)
|
||||
end
|
||||
|
||||
function update_cursor_position()
|
||||
cursor.x, cursor.y = mp.get_mouse_pos()
|
||||
|
||||
-- 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
|
||||
if cursor.x > 0 and cursor.y > 0 then
|
||||
state.first_real_mouse_move_received = true
|
||||
else
|
||||
cursor.x = infinity
|
||||
cursor.y = infinity
|
||||
end
|
||||
end
|
||||
|
||||
local dpi_scale = mp.get_property_native('display-hidpi-scale', 1.0)
|
||||
dpi_scale = dpi_scale * options.ui_scale
|
||||
|
||||
-- add 0.5 to be in the middle of the pixel
|
||||
cursor.x = (cursor.x + 0.5) / dpi_scale
|
||||
cursor.y = (cursor.y + 0.5) / dpi_scale
|
||||
|
||||
Elements:update_proximities()
|
||||
request_render()
|
||||
end
|
||||
|
||||
function handle_mouse_leave()
|
||||
-- Slowly fadeout elements that are currently visible
|
||||
for _, element_name in ipairs({'timeline', 'volume', 'top_bar'}) do
|
||||
local element = Elements[element_name]
|
||||
if element and element.proximity > 0 then
|
||||
element:tween_property('forced_visibility', element:get_visibility(), 0, function()
|
||||
element.forced_visibility = nil
|
||||
end)
|
||||
end
|
||||
end
|
||||
|
||||
cursor.hidden = true
|
||||
Elements:update_proximities()
|
||||
Elements:trigger('global_mouse_leave')
|
||||
end
|
||||
|
||||
function handle_mouse_enter()
|
||||
cursor.hidden = false
|
||||
update_cursor_position()
|
||||
Elements:trigger('global_mouse_enter')
|
||||
end
|
||||
|
||||
function handle_mouse_move()
|
||||
-- Handle case when we are in cursor hidden state but not left the actual
|
||||
-- window (i.e. when autohide simulates mouse_leave).
|
||||
if cursor.hidden then
|
||||
handle_mouse_enter()
|
||||
return
|
||||
end
|
||||
|
||||
update_cursor_position()
|
||||
Elements:proximity_trigger('mouse_move')
|
||||
request_render()
|
||||
|
||||
-- Restart timer that hides UI when mouse is autohidden
|
||||
if options.autohide then
|
||||
state.cursor_autohide_timer:kill()
|
||||
state.cursor_autohide_timer:resume()
|
||||
end
|
||||
end
|
||||
|
||||
function navigate_directory(direction)
|
||||
local path = mp.get_property_native('path')
|
||||
|
||||
if not path or is_protocol(path) then return end
|
||||
|
||||
local next_file = get_adjacent_file(path, direction, config.media_types)
|
||||
|
||||
if next_file then
|
||||
mp.commandv('loadfile', utils.join_path(serialize_path(path).dirname, next_file))
|
||||
end
|
||||
end
|
||||
|
||||
function load_file_in_current_directory(index)
|
||||
local path = mp.get_property_native('path')
|
||||
|
||||
if not path or is_protocol(path) then return end
|
||||
|
||||
local serialized = serialize_path(path)
|
||||
if serialized and serialized.dirname then
|
||||
local files = get_files_in_directory(serialized.dirname, config.media_types)
|
||||
|
||||
if not files then return end
|
||||
if index < 0 then index = #files + index + 1 end
|
||||
|
||||
if files[index] then
|
||||
mp.commandv('loadfile', utils.join_path(serialized.dirname, files[index]))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function update_render_delay(name, fps)
|
||||
if fps then state.render_delay = 1 / fps end
|
||||
end
|
||||
|
||||
function observe_display_fps(name, fps)
|
||||
if fps then
|
||||
mp.unobserve_property(update_render_delay)
|
||||
mp.unobserve_property(observe_display_fps)
|
||||
mp.observe_property('display-fps', 'native', update_render_delay)
|
||||
end
|
||||
end
|
||||
|
||||
--[[ MENUS ]]
|
||||
|
||||
---@param data MenuData
|
||||
@@ -3952,7 +3885,7 @@ function open_file_navigation_menu(directory_path, handle_select, opts)
|
||||
end
|
||||
|
||||
-- Files are already sorted
|
||||
table.sort(directories, word_order_comparator)
|
||||
table.sort(directories, file_order_comparator)
|
||||
|
||||
-- Pre-populate items with parent directory selector if not at root
|
||||
-- Each item value is a serialized path table it points to.
|
||||
@@ -4064,6 +3997,126 @@ function open_drives_menu(handle_select, opts)
|
||||
return Menu:open({type = opts.type, title = opts.title or 'Drives', items = items}, handle_select)
|
||||
end
|
||||
|
||||
-- EVENT HANDLERS
|
||||
|
||||
function create_state_setter(name, callback)
|
||||
return function(_, value)
|
||||
set_state(name, value)
|
||||
if callback then callback() end
|
||||
request_render()
|
||||
end
|
||||
end
|
||||
|
||||
function set_state(name, value)
|
||||
state[name] = value
|
||||
Elements:trigger('prop_' .. name, value)
|
||||
end
|
||||
|
||||
function update_cursor_position()
|
||||
cursor.x, cursor.y = mp.get_mouse_pos()
|
||||
|
||||
-- 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
|
||||
if cursor.x > 0 and cursor.y > 0 then
|
||||
state.first_real_mouse_move_received = true
|
||||
else
|
||||
cursor.x = infinity
|
||||
cursor.y = infinity
|
||||
end
|
||||
end
|
||||
|
||||
local dpi_scale = mp.get_property_native('display-hidpi-scale', 1.0)
|
||||
dpi_scale = dpi_scale * options.ui_scale
|
||||
|
||||
-- add 0.5 to be in the middle of the pixel
|
||||
cursor.x = (cursor.x + 0.5) / dpi_scale
|
||||
cursor.y = (cursor.y + 0.5) / dpi_scale
|
||||
|
||||
Elements:update_proximities()
|
||||
request_render()
|
||||
end
|
||||
|
||||
function handle_mouse_leave()
|
||||
-- Slowly fadeout elements that are currently visible
|
||||
for _, element_name in ipairs({'timeline', 'volume', 'top_bar'}) do
|
||||
local element = Elements[element_name]
|
||||
if element and element.proximity > 0 then
|
||||
element:tween_property('forced_visibility', element:get_visibility(), 0, function()
|
||||
element.forced_visibility = nil
|
||||
end)
|
||||
end
|
||||
end
|
||||
|
||||
cursor.hidden = true
|
||||
Elements:update_proximities()
|
||||
Elements:trigger('global_mouse_leave')
|
||||
end
|
||||
|
||||
function handle_mouse_enter()
|
||||
cursor.hidden = false
|
||||
update_cursor_position()
|
||||
Elements:trigger('global_mouse_enter')
|
||||
end
|
||||
|
||||
function handle_mouse_move()
|
||||
-- Handle case when we are in cursor hidden state but not left the actual
|
||||
-- window (i.e. when autohide simulates mouse_leave).
|
||||
if cursor.hidden then
|
||||
handle_mouse_enter()
|
||||
return
|
||||
end
|
||||
|
||||
update_cursor_position()
|
||||
Elements:proximity_trigger('mouse_move')
|
||||
request_render()
|
||||
|
||||
-- Restart timer that hides UI when mouse is autohidden
|
||||
if options.autohide then
|
||||
state.cursor_autohide_timer:kill()
|
||||
state.cursor_autohide_timer:resume()
|
||||
end
|
||||
end
|
||||
|
||||
function handle_file_end()
|
||||
if not state.loop_file and
|
||||
(state.has_playlist and navigate_playlist(1) or options.next_file_on_end and navigate_directory(1)) then
|
||||
-- Resume only when navigation happened
|
||||
mp.command('set pause no')
|
||||
end
|
||||
end
|
||||
local file_end_timer = mp.add_timeout(1, handle_file_end)
|
||||
file_end_timer:kill()
|
||||
|
||||
function load_file_index_in_current_directory(index)
|
||||
if not state.path or is_protocol(state.path) then return end
|
||||
|
||||
local serialized = serialize_path(state.path)
|
||||
if serialized and serialized.dirname then
|
||||
local files = get_files_in_directory(serialized.dirname, config.media_types)
|
||||
|
||||
if not files then return end
|
||||
if index < 0 then index = #files + index + 1 end
|
||||
|
||||
if files[index] then
|
||||
mp.commandv('loadfile', utils.join_path(serialized.dirname, files[index]))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function update_render_delay(name, fps)
|
||||
if fps then state.render_delay = 1 / fps end
|
||||
end
|
||||
|
||||
function observe_display_fps(name, fps)
|
||||
if fps then
|
||||
mp.unobserve_property(update_render_delay)
|
||||
mp.unobserve_property(observe_display_fps)
|
||||
mp.observe_property('display-fps', 'native', update_render_delay)
|
||||
end
|
||||
end
|
||||
|
||||
--[[ HOOKS]]
|
||||
|
||||
-- Mouse movement key binds
|
||||
@@ -4103,15 +4156,30 @@ function update_title(title_template)
|
||||
set_state('title', mp.command_native({'expand-text', title_template}))
|
||||
end
|
||||
mp.register_event('file-loaded', function()
|
||||
set_state('path', normalize_path(mp.get_property_native('path')))
|
||||
update_title(mp.get_property_native('title'))
|
||||
end)
|
||||
mp.register_event('end-file ', function() set_state('title', nil) end)
|
||||
mp.register_event('end-file', function() set_state('title', nil) end)
|
||||
mp.observe_property('title', 'string', function(_, title)
|
||||
-- Don't change title if there is currently none
|
||||
if state.title then update_title(title) end
|
||||
end)
|
||||
mp.observe_property('playback-time', 'number', create_state_setter('time', function()
|
||||
-- Create a file-end event that triggers right before file is closed.
|
||||
file_end_timer:kill()
|
||||
if state.duration and state.time then
|
||||
local remaining = state.duration - state.time
|
||||
if remaining < 5 then
|
||||
local timeout = remaining - 0.3
|
||||
if timeout > 0 then
|
||||
file_end_timer.timeout = timeout
|
||||
file_end_timer:resume()
|
||||
else handle_file_end() end
|
||||
end
|
||||
end
|
||||
|
||||
update_human_times()
|
||||
|
||||
-- Select current chapter
|
||||
local current_chapter
|
||||
if state.time and state.chapters then
|
||||
@@ -4150,6 +4218,7 @@ mp.observe_property('chapter-list', 'native', function(_, chapters)
|
||||
Elements:trigger('dispositions')
|
||||
end)
|
||||
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'))
|
||||
@@ -4256,7 +4325,7 @@ for _, loader in ipairs(track_loaders) do
|
||||
mp.add_key_binding(nil, menu_type, function()
|
||||
if Menu:is_open(menu_type) then Menu:close() return end
|
||||
|
||||
local path = mp.get_property_native('path') --[[@as string|nil|false]]
|
||||
local path = state.path
|
||||
if path then
|
||||
if is_protocol(path) then
|
||||
path = false
|
||||
@@ -4350,23 +4419,19 @@ mp.add_key_binding(nil, 'chapters', create_self_updating_menu_opener({
|
||||
on_select = function(time) mp.commandv('seek', tostring(time), 'absolute') end,
|
||||
}))
|
||||
mp.add_key_binding(nil, 'show-in-directory', function()
|
||||
local path = mp.get_property_native('path')
|
||||
|
||||
-- Ignore URLs
|
||||
if not path or is_protocol(path) then return end
|
||||
|
||||
path = normalize_path(path)
|
||||
if not state.path or is_protocol(state.path) then return end
|
||||
|
||||
if state.os == 'windows' then
|
||||
utils.subprocess_detached({args = {'explorer', '/select,', path}, cancellable = false})
|
||||
utils.subprocess_detached({args = {'explorer', '/select,', state.path}, cancellable = false})
|
||||
elseif state.os == 'macos' then
|
||||
utils.subprocess_detached({args = {'open', '-R', path}, cancellable = false})
|
||||
utils.subprocess_detached({args = {'open', '-R', state.path}, cancellable = false})
|
||||
elseif state.os == 'linux' then
|
||||
local result = utils.subprocess({args = {'nautilus', path}, cancellable = false})
|
||||
local result = utils.subprocess({args = {'nautilus', state.path}, cancellable = false})
|
||||
|
||||
-- Fallback opens the folder with xdg-open instead
|
||||
if result.status ~= 0 then
|
||||
utils.subprocess({args = {'xdg-open', serialize_path(path).dirname}, cancellable = false})
|
||||
utils.subprocess({args = {'xdg-open', serialize_path(state.path).dirname}, cancellable = false})
|
||||
end
|
||||
end
|
||||
end)
|
||||
@@ -4411,18 +4476,17 @@ end)
|
||||
mp.add_key_binding(nil, 'open-file', function()
|
||||
if Menu:is_open('open-file') then Menu:close() return end
|
||||
|
||||
local path = mp.get_property_native('path')
|
||||
local directory
|
||||
local active_file
|
||||
|
||||
if path == nil or is_protocol(path) then
|
||||
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(path)
|
||||
local serialized = serialize_path(state.path)
|
||||
if serialized then
|
||||
directory = serialized.dirname
|
||||
active_file = serialized.path
|
||||
@@ -4430,15 +4494,14 @@ mp.add_key_binding(nil, 'open-file', function()
|
||||
end
|
||||
|
||||
if not directory then
|
||||
msg.error('Couldn\'t serialize path "' .. path .. '".')
|
||||
msg.error('Couldn\'t serialize path "' .. state.path .. '".')
|
||||
return
|
||||
end
|
||||
|
||||
-- Update active file in directory navigation menu
|
||||
local function handle_file_loaded()
|
||||
if Menu:is_open('open-file') then
|
||||
local path = normalize_path(mp.get_property_native('path'))
|
||||
Elements.menu:activate_value(path)
|
||||
Elements.menu:activate_value(normalize_path(mp.get_property_native('path')))
|
||||
end
|
||||
end
|
||||
|
||||
@@ -4454,6 +4517,7 @@ mp.add_key_binding(nil, 'open-file', function()
|
||||
}
|
||||
)
|
||||
end)
|
||||
mp.add_key_binding(nil, 'shuffle', function() set_state('shuffle', not state.shuffle) end)
|
||||
mp.add_key_binding(nil, 'items', function()
|
||||
if state.has_playlist then
|
||||
mp.command('script-binding uosc/playlist')
|
||||
@@ -4461,65 +4525,54 @@ mp.add_key_binding(nil, 'items', function()
|
||||
mp.command('script-binding uosc/open-file')
|
||||
end
|
||||
end)
|
||||
mp.add_key_binding(nil, 'next', function()
|
||||
if state.has_playlist then
|
||||
mp.command('playlist-next')
|
||||
else
|
||||
navigate_directory('forward')
|
||||
end
|
||||
end)
|
||||
mp.add_key_binding(nil, 'prev', function()
|
||||
if state.has_playlist then
|
||||
mp.command('playlist-prev')
|
||||
else
|
||||
navigate_directory('backward')
|
||||
end
|
||||
end)
|
||||
mp.add_key_binding(nil, 'next-file', function() navigate_directory('forward') end)
|
||||
mp.add_key_binding(nil, 'prev-file', function() navigate_directory('backward') end)
|
||||
mp.add_key_binding(nil, 'next', function() navigate_item(1) end)
|
||||
mp.add_key_binding(nil, 'prev', function() navigate_item(-1) end)
|
||||
mp.add_key_binding(nil, 'next-file', function() navigate_directory(1) end)
|
||||
mp.add_key_binding(nil, 'prev-file', function() navigate_directory(-1) end)
|
||||
mp.add_key_binding(nil, 'first', function()
|
||||
if state.has_playlist then
|
||||
mp.commandv('set', 'playlist-pos-1', '1')
|
||||
else
|
||||
load_file_in_current_directory(1)
|
||||
load_file_index_in_current_directory(1)
|
||||
end
|
||||
end)
|
||||
mp.add_key_binding(nil, 'last', function()
|
||||
if state.has_playlist then
|
||||
mp.commandv('set', 'playlist-pos-1', tostring(state.playlist_count))
|
||||
else
|
||||
load_file_in_current_directory(-1)
|
||||
load_file_index_in_current_directory(-1)
|
||||
end
|
||||
end)
|
||||
mp.add_key_binding(nil, 'first-file', function() load_file_in_current_directory(1) end)
|
||||
mp.add_key_binding(nil, 'last-file', function() load_file_in_current_directory(-1) end)
|
||||
mp.add_key_binding(nil, 'first-file', function() load_file_index_in_current_directory(1) end)
|
||||
mp.add_key_binding(nil, 'last-file', function() load_file_index_in_current_directory(-1) end)
|
||||
mp.add_key_binding(nil, 'delete-file-next', function()
|
||||
local next_file = nil
|
||||
local path = mp.get_property_native('path')
|
||||
local is_local_file = path and not is_protocol(path)
|
||||
local is_local_file = state.path and not is_protocol(state.path)
|
||||
|
||||
if is_local_file then
|
||||
path = normalize_path(path)
|
||||
if Menu:is_open('open-file') then Elements.menu:delete_value(path) end
|
||||
if Menu:is_open('open-file') then Elements.menu:delete_value(state.path) end
|
||||
end
|
||||
|
||||
if state.has_playlist then
|
||||
mp.commandv('playlist-remove', 'current')
|
||||
else
|
||||
if is_local_file then
|
||||
next_file = get_adjacent_file(path, 'forward', config.media_types)
|
||||
local paths, current_index = get_adjacent_paths(state.path, config.media_types)
|
||||
if paths and current_index then
|
||||
local index, path = decide_navigation_in_list(paths, current_index, 1)
|
||||
if path then next_file = path end
|
||||
end
|
||||
end
|
||||
|
||||
if next_file then mp.commandv('loadfile', next_file)
|
||||
else mp.commandv('stop') end
|
||||
end
|
||||
|
||||
if is_local_file then delete_file(path) end
|
||||
if is_local_file then delete_file(state.path) end
|
||||
end)
|
||||
mp.add_key_binding(nil, 'delete-file-quit', function()
|
||||
local path = mp.get_property_native('path')
|
||||
mp.command('stop')
|
||||
if path and not is_protocol(path) then delete_file(normalize_path(path)) end
|
||||
if state.path and not is_protocol(state.path) then delete_file(state.path) end
|
||||
mp.command('quit')
|
||||
end)
|
||||
mp.add_key_binding(nil, 'audio-device', create_self_updating_menu_opener({
|
||||
|
Reference in New Issue
Block a user