local utils = require('mp.utils') local script_dir = mp.get_script_directory() local stream_prefix = mp.get_opt('iptv_menu.stream_prefix') local function load_json_file(path) local f = io.open(script_dir .. '/' .. path, 'r') local data = f:read('*all') f:close() return utils.parse_json(data) end local osd = mp.create_osd_overlay('ass-events') local osd_lines = 23 local categories = load_json_file('categories.json') local streams = load_json_file('streams.json') local depth = 1 local menus = {categories} local menu_top = {1} local menu_pos = {1} local function osd_menu_lines() return osd_lines - depth + 1 end local function update_osd() local out = {} for i = 1, depth - 1 do out[#out+1] = '{\\c&H999999&}[' .. menus[i][menu_pos[i]]['category_name'] .. ']{\\c}' end for i = menu_top[depth], math.min( menu_top[depth] + osd_menu_lines() - 1, #menus[depth]) do local key = (depth == 1 and 'category_name' or 'name') local str = menus[depth][i][key] if i == menu_pos[depth] then str = '{\\c&HFF00&}* ' .. str .. '{\\c}' end out[#out+1] = str end osd.data = '{\\q2}' .. table.concat(out, '\\N') osd:update() end local function advance_cursor(n, opts) pos = math.max(1, math.min(menu_pos[depth] + n, #menus[depth])) top = menu_top[depth] if opts and opts.advance_page then top = top + n end -- move page to keep selected option visible if pos < top then top = pos elseif pos > top + osd_menu_lines() - 1 then top = pos - osd_menu_lines() + 1 end top = math.max(1, math.min(top, #menus[depth] - osd_menu_lines() + 1)) menu_pos[depth] = pos menu_top[depth] = top update_osd() end local function next_option() advance_cursor(1) end local function prev_option() advance_cursor(-1) end local function next_page() advance_cursor(osd_menu_lines(), {advance_page=true}) end local function prev_page() advance_cursor(-osd_menu_lines(), {advance_page=true}) end local function first_option() advance_cursor(-math.huge) end local function last_option() advance_cursor(math.huge) end local function select_category() local cat = categories[menu_pos[depth]] local menu = {} for _, v in ipairs(streams) do if v['category_id'] == cat['category_id'] then menu[#menu+1] = v end end depth = depth + 1 menus[depth] = menu menu_top[depth] = 1 menu_pos[depth] = 1 update_osd() end local function select_stream() local url = stream_prefix .. menus[depth][menu_pos[depth]]['stream_id'] mp.commandv('loadfile', url) end local function select_option() if depth == 1 then select_category() else select_stream() end end local function prev_menu() if depth > 1 then depth = depth - 1 update_osd() end end local key_bindings = {} local function bind_key(key, name, func, opts) key_bindings[#key_bindings+1] = name mp.add_forced_key_binding(key, name, func, opts) end local function unbind_keys() for _, name in ipairs(key_bindings) do mp.remove_key_binding(name) end key_bindings = {} end local function bind_keys() local repeatable = {repeatable=true} bind_key('DOWN', 'next-option', next_option, repeatable) bind_key('UP', 'prev-option', prev_option, repeatable) bind_key('PGDWN', 'next-page', next_page, repeatable) bind_key('PGUP', 'prev-page', prev_page, repeatable) bind_key('HOME', 'first-option', first_option) bind_key('END', 'last-option', last_option) bind_key('ENTER', 'select-option', select_option) bind_key('BS', 'prev-menu', prev_menu) end local function toggle_menu() osd.hidden = not osd.hidden osd:update() if osd.hidden then unbind_keys() else bind_keys() end end mp.add_forced_key_binding('TAB', 'toggle-menu', toggle_menu) bind_keys() update_osd()