diff options
| author | David Vazgenovich Shakaryan <dvshakaryan@gmail.com> | 2026-01-07 13:33:47 -0800 |
|---|---|---|
| committer | David Vazgenovich Shakaryan <dvshakaryan@gmail.com> | 2026-01-07 13:33:47 -0800 |
| commit | c442abc24ebbddaf8d9fab36375128e06547d65e (patch) | |
| tree | 4f37b8ed27fd9d70091c6b925605bc70585d9114 | |
| parent | d9efccd403dbb189e99f6c43fdad16940e695e30 (diff) | |
| download | mpv-iptv-menu-c442abc24ebbddaf8d9fab36375128e06547d65e.tar.gz mpv-iptv-menu-c442abc24ebbddaf8d9fab36375128e06547d65e.tar.xz | |
move state to new module
| -rw-r--r-- | main.lua | 177 | ||||
| -rw-r--r-- | osd.lua | 33 | ||||
| -rw-r--r-- | state.lua | 70 |
3 files changed, 156 insertions, 124 deletions
@@ -7,6 +7,7 @@ local _catalogue = require('catalogue') local _downloader = require('downloader') local _epg = require('epg') local _osd = require('osd') +local _state = require('state') local _xc = require('xc') local mp_utils = require('mp.utils') @@ -14,6 +15,8 @@ local mp_utils = require('mp.utils') local script_name = mp.get_script_name() local script_dir = mp.get_script_directory() +local state = _state.new() + local downloader = _downloader.new() local xc = _xc.new({ server = mp.get_opt('iptv_menu.xc_server'), @@ -40,19 +43,15 @@ xc = cacher.wrap(xc, { local catalogue = _catalogue.new() local epg = _epg.new() local osd = _osd.new() -local favourites -local playing_id -local depth = 0 -local menus = {} local key_bindings = {} local function update_osd() - osd:redraw(menus, depth, favourites, playing_id) + osd:redraw(state) end local function osd_menu_lines() - return osd:menu_lines(depth) + return osd:menu_lines(state) end -- FIXME leaving this here as a global for now since the image is downloaded @@ -93,24 +92,16 @@ local function load_data() epg:load_xc_data(xc:get_epg()) local t = util.read_json_file(config.favourites_file) - favourites = t.favourites or {} + state.favourites = t.favourites or {} end local function save_favourites() - util.write_json_file(config.favourites_file, {favourites = favourites}) -end - --- returns index or nil -function favourited(id) - for i, v in ipairs(favourites) do - if v == id then - return i - end - end + util.write_json_file( + config.favourites_file, {favourites = state.favourites}) end local function set_cursor(pos, opts) - local menu = menus[depth] + local menu = state:menu() local lines = osd_menu_lines() local pos = math.max(1, math.min(pos, #menu.options)) @@ -139,11 +130,11 @@ local function set_cursor(pos, opts) end local function cursor_up() - set_cursor(menus[depth].cursor - 1) + set_cursor(state:menu().cursor - 1) end local function cursor_down() - set_cursor(menus[depth].cursor + 1) + set_cursor(state:menu().cursor + 1) end local function cursor_start() @@ -151,21 +142,21 @@ local function cursor_start() end local function cursor_end() - set_cursor(#menus[depth].options) + set_cursor(#state:menu().options) end local function cursor_page_up() set_cursor( - menus[depth].cursor - osd_menu_lines(), {keep_offset = true}) + state:menu().cursor - osd_menu_lines(), {keep_offset = true}) end local function cursor_page_down() set_cursor( - menus[depth].cursor + osd_menu_lines(), {keep_offset = true}) + state:menu().cursor + osd_menu_lines(), {keep_offset = true}) end local function cursor_to_object(id) - for i, v in ipairs(menus[depth].options) do + for i, v in ipairs(state:menu().options) do if v.id == id then set_cursor(i, {centre = true}) return @@ -173,25 +164,8 @@ local function cursor_to_object(id) end end --- inserts the given id into the favourites array before the next favourited --- menu option, starting from the next cursor position, or the end if no such --- option is found. this is meant for in-place favouriting from the favourites --- menu. -local function insert_favourite_before_next_in_menu(id) - local menu = menus[depth] - for i = menu.cursor+1, #menu.options do - local ind = favourited(menu.options[i].id) - if ind then - table.insert(favourites, ind, id) - return - end - end - - favourites[#favourites+1] = id -end - local function move_option(pos, opts) - local menu = menus[depth] + local menu = state:menu() if menu.group_id ~= 'favourites' or menu.sorted then return end @@ -207,10 +181,10 @@ local function move_option(pos, opts) local opt = table.remove(menu.options, prev_cursor) table.insert(menu.options, menu.cursor, opt) - local ind = favourited(opt.id) + local ind = state:favourited(opt.id) if ind then - table.remove(favourites, ind) - insert_favourite_before_next_in_menu(opt.id) + state:remove_favourite_at(ind) + state:insert_favourite_before_next_in_menu(opt.id) save_favourites() end @@ -218,11 +192,11 @@ local function move_option(pos, opts) end local function move_option_up() - move_option(menus[depth].cursor - 1) + move_option(state:menu().cursor - 1) end local function move_option_down() - move_option(menus[depth].cursor + 1) + move_option(state:menu().cursor + 1) end local function move_option_start() @@ -230,32 +204,17 @@ local function move_option_start() end local function move_option_end() - move_option(#menus[depth].options) + move_option(#state:menu().options) end local function move_option_page_up() move_option( - menus[depth].cursor - osd_menu_lines(), {keep_offset = true}) + state:menu().cursor - osd_menu_lines(), {keep_offset = true}) end local function move_option_page_down() move_option( - menus[depth].cursor + osd_menu_lines(), {keep_offset = true}) -end - -local function push_menu(t) - local menu = { - options = {}, - cursor = 1, - view_top = 1, - } - - for k, v in pairs(t) do - menu[k] = v - end - - depth = depth + 1 - menus[depth] = menu + state:menu().cursor + osd_menu_lines(), {keep_offset = true}) end local function sort_options(options) @@ -265,7 +224,7 @@ local function sort_options(options) if v.missing then score = score - 4 end - if favourited(v.id) then + if state:favourited(v.id) then score = score + 2 end if v.type == 'group' and v.group_type ~= 'series' then @@ -310,14 +269,14 @@ local function group_count(group) return count elseif group.id == 'favourites' then -- not recursive - return #favourites + return #state.favourites end end local function favourites_group_menu_options(group) local options = {} local time = os.time() - for _, id in ipairs(favourites) do + for _, id in ipairs(state.favourites) do local obj = catalogue:get(id) if obj then local path = {} @@ -440,7 +399,7 @@ local function group_menu_options(group) end local function push_group_menu(group) - push_menu({ + state:push_menu({ options = group_menu_options(group), title = group.name, type = 'group', @@ -477,7 +436,7 @@ end -- is always respected, while still preserving the relative order of existing -- options when possible. local function refresh_favourites_menu() - local menu = menus[depth] + local menu = state:menu() local opt = menu.options[menu.cursor] local sorted = menu.sorted if sorted then @@ -523,13 +482,13 @@ local function refresh_favourites_menu() end local function prev_menu() - depth = depth - 1 + state.depth = state.depth - 1 - if depth == 0 then + if state.depth == 0 then -- reset main menu - push_group_menu(catalogue:get(menus[1].group_id)) + push_group_menu(catalogue:get(state.menus[1].group_id)) else - if menus[depth].group_id == 'favourites' then + if state:menu().group_id == 'favourites' then refresh_favourites_menu() end update_osd() @@ -550,7 +509,7 @@ local function play_stream(stream) end local function select_option() - local menu = menus[depth] + local menu = state:menu() local opt = menu.options[menu.cursor] if not opt or not opt.id then return @@ -564,19 +523,19 @@ local function select_option() end local function favourite_option() - local menu = menus[depth] + local menu = state:menu() local opt = menu.options[menu.cursor] if not opt or not opt.id then return end - local ind = favourited(opt.id) + local ind = state:favourited(opt.id) if ind then - table.remove(favourites, ind) + state:remove_favourite_at(ind) elseif menu.group_id == 'favourites' then - insert_favourite_before_next_in_menu(opt.id) + state:insert_favourite_before_next_in_menu(opt.id) else - favourites[#favourites+1] = opt.id + state:add_favourite(opt.id) end save_favourites() @@ -584,17 +543,17 @@ local function favourite_option() end local function goto_option() - local menu = menus[depth] + local menu = state:menu() local opt = menu.options[menu.cursor] if not opt then return end if menu.group_id == 'favourites' then - depth = 1 + state.depth = 1 elseif menu.type == 'search' then - depth = depth - 1 - if menus[depth].group_id == 'favourites' then + state.depth = state.depth - 1 + if state:menu().group_id == 'favourites' then refresh_favourites_menu() end end @@ -610,11 +569,11 @@ local function goto_option() end local function goto_playing() - if not playing_id then + if not state.playing_id then return end - local obj = catalogue:get(playing_id) + local obj = catalogue:get(state.playing_id) if not obj then return end @@ -630,7 +589,7 @@ local function goto_playing() return end - depth = 1 + state.depth = 1 for i = #path, 1, -1 do cursor_to_object(path[i].id) push_group_menu(path[i]) @@ -654,7 +613,7 @@ local function open_epg_programme(prog) end end - push_menu({ + state:push_menu({ options = options, title = 'Programme: ' .. prog.title, type = 'epg', @@ -688,7 +647,7 @@ local function open_option_epg(opt) options[i] = prog end - push_menu({ + state:push_menu({ options = options, title = 'EPG: ' .. opt.name .. ' (' .. ch .. ')', type = 'epg', @@ -806,7 +765,7 @@ local function open_option_title_info(title, info) m.img_url = img end - push_menu(m) + state:push_menu(m) update_osd() end @@ -835,7 +794,7 @@ local function open_option_episode_info(opt) end local function open_option_info() - local menu = menus[depth] + local menu = state:menu() local opt = menu.options[menu.cursor] if not opt then return @@ -857,7 +816,7 @@ local function open_option_info() end local function search_menu_options_build(options, t, path) - local menu = menus[depth] + local menu = state:menu() local path = path or {} for _, v in ipairs(options) do @@ -892,7 +851,7 @@ local function search_menu_options(options) end function update_search_matches() - local menu = menus[depth] + local menu = state:menu() if #menu.search_text == 0 then menu.options = menu.search_options @@ -936,7 +895,7 @@ local function search_input_char(event) return end - local menu = menus[depth] + local menu = state:menu() menu.search_text = menu.search_text:sub(1, menu.search_cursor - 1) .. event.key_text .. menu.search_text:sub(menu.search_cursor) menu.search_cursor = menu.search_cursor + #event.key_text @@ -945,7 +904,7 @@ local function search_input_char(event) end local function search_input_bs() - local menu = menus[depth] + local menu = state:menu() if menu.search_cursor <= 1 then return end @@ -959,7 +918,7 @@ local function search_input_bs() end local function search_input_del() - local menu = menus[depth] + local menu = state:menu() if menu.search_cursor > #menu.search_text then return end @@ -972,7 +931,7 @@ local function search_input_del() end local function set_search_cursor(pos) - local menu = menus[depth] + local menu = state:menu() local pos = math.max(1, math.min(#menu.search_text + 1, pos)) if pos == menu.search_cursor then return @@ -983,13 +942,13 @@ local function set_search_cursor(pos) end local function search_cursor_left() - local menu = menus[depth] + local menu = state:menu() set_search_cursor(util.utf8_seek( menu.search_text, menu.search_cursor, -1)) end local function search_cursor_right() - local menu = menus[depth] + local menu = state:menu() set_search_cursor(util.utf8_seek( menu.search_text, menu.search_cursor, 1)) end @@ -999,14 +958,14 @@ local function search_cursor_start() end local function search_cursor_end() - set_search_cursor(#menus[depth].search_text + 1) + set_search_cursor(#state:menu().search_text + 1) end local bind_search_keys local bind_menu_keys local function start_search() - local menu = menus[depth] + local menu = state:menu() local title = 'Searching: <text_with_cursor>' .. ' <colours.info>(<num_matches>/<num_total>)' @@ -1022,7 +981,7 @@ local function start_search() menu.cursor = 1 menu.view_top = 1 else - push_menu({ + state:push_menu({ title = title, type = 'search', search_active = true, @@ -1038,7 +997,7 @@ local function start_search() end local function end_search() - local menu = menus[depth] + local menu = state:menu() menu.search_active = false menu.title = 'Search results: <text>' .. ' <colours.info>(<num_matches>/<num_total>)' @@ -1047,7 +1006,7 @@ local function end_search() end local function cancel_search() - local menu = menus[depth] + local menu = state:menu() -- cancelling resumed search restores previous state if menu.prev_search_text then @@ -1060,13 +1019,13 @@ local function cancel_search() end menu.search_active = false - depth = depth - 1 + state.depth = state.depth - 1 update_osd() bind_menu_keys() end local function toggle_menu_sort() - local menu = menus[depth] + local menu = state:menu() if menu.type ~= 'group' and menu.type ~= 'search' then return end @@ -1151,7 +1110,7 @@ local function set_key_bindings() if osd:is_hidden() then unbind_keys() mp.command_native({'enable-section', 'default'}) - elseif menus[depth].search_active then + elseif state:menu().search_active then bind_search_keys() mp.command_native({'disable-section', 'default'}) else @@ -1171,12 +1130,12 @@ mp.observe_property('osd-dimensions', 'native', function(_, val) end) mp.register_event('start-file', function() - playing_id = mp.get_opt('iptv_menu.playing_id') + state.playing_id = mp.get_opt('iptv_menu.playing_id') update_osd() end) mp.register_event('end-file', function() - playing_id = nil + state.playing_id = nil update_osd() end) @@ -57,10 +57,10 @@ function mt:resize(w, h) self.scale = h / 720 end -function mt:menu_lines(depth) - if depth > 1 then +function mt:menu_lines(state) + if state.depth > 1 then -- leaves an extra line for padding between titles and options - return self.lines - depth + return self.lines - state.depth else return self.lines end @@ -252,9 +252,10 @@ function mt:remove_image() self.img = nil end -function mt:draw_scrollbar(menu, depth) +function mt:draw_scrollbar(state) + local menu = state:menu() local opts = #menu.options - local lines = self:menu_lines(depth) + local lines = self:menu_lines(state) if opts <= lines then return end @@ -274,17 +275,17 @@ function mt:draw_scrollbar(menu, depth) '{\\pos(2,' .. top + pos .. ')}' .. draw_rect(0, 0, w, hh)) end -function mt:redraw(menus, depth, favourites, playing_id) +function mt:redraw(state) local out = {} - if depth > 1 then - for i = 2, depth do - out[#out+1] = self:menu_title(menus[i]) + if state.depth > 1 then + for i = 2, state.depth do + out[#out+1] = self:menu_title(state.menus[i]) end out[#out+1] = ' ' -- space character for correct line height end - local menu = menus[depth] + local menu = state:menu() local img if menu.img_url then @@ -292,13 +293,13 @@ function mt:redraw(menus, depth, favourites, playing_id) end for i = menu.view_top, math.min( - menu.view_top + self:menu_lines(depth) - 1, + menu.view_top + self:menu_lines(state) - 1, #menu.options) do local opt = menu.options[i] -- use real-time count for favourites if opt.id == 'favourites' then - opt.info = tostring(#favourites) + opt.info = tostring(#state.favourites) end local selected = i == menu.cursor and not menu.search_active @@ -306,8 +307,10 @@ function mt:redraw(menus, depth, favourites, playing_id) selected = selected, empty = (opt.type == 'group' and not opt.lazy and #opt.children == 0), - playing = not not (opt.id and opt.id == playing_id), - favourited = not not (opt.id and favourited(opt.id)), + playing = not not ( + opt.id and opt.id == state.playing_id), + favourited = not not ( + opt.id and state:favourited(opt.id)), } out[#out+1] = self:option_icons(opt, info) .. self:option_text(opt, info) .. @@ -325,7 +328,7 @@ function mt:redraw(menus, depth, favourites, playing_id) self.bg.data = '{\\pos(0,0)\\alpha&H' .. config.bg_alpha .. '&\\c&H&}' .. draw_rect(0, 0, 7680, 720) - local sb = self:draw_scrollbar(menu, depth) + local sb = self:draw_scrollbar(state) if sb then self.bg.data = self.bg.data .. '\n' .. sb end diff --git a/state.lua b/state.lua new file mode 100644 index 0000000..472e4d2 --- /dev/null +++ b/state.lua @@ -0,0 +1,70 @@ +-- Copyright 2025 David Vazgenovich Shakaryan + +local state = {} +local mt = {} +mt.__index = mt + +function state.new() + return setmetatable({ + menus = {}, + depth = 0, + + favourites = {}, + playing_id = nil, + }, mt) +end + +function mt:menu() + return self.menus[self.depth] +end + +function mt:push_menu(t) + local menu = { + options = {}, + cursor = 1, + view_top = 1, + } + + for k, v in pairs(t) do + menu[k] = v + end + + self.depth = self.depth + 1 + self.menus[self.depth] = menu +end + +-- returns index if found +function mt:favourited(id) + for i, v in ipairs(self.favourites) do + if v == id then + return i + end + end +end + +function mt:add_favourite(id) + self.favourites[#self.favourites+1] = id +end + +function mt:remove_favourite_at(i) + table.remove(self.favourites, i) +end + +-- inserts the given id into the favourites array before the next favourited +-- menu option, starting from the next cursor position, or the end if no such +-- option is found. this is meant for in-place favouriting from the favourites +-- menu. +function mt:insert_favourite_before_next_in_menu(id) + local menu = self:menu() + for i = menu.cursor+1, #menu.options do + local ind = self:favourited(menu.options[i].id) + if ind then + table.insert(self.favourites, ind, id) + return + end + end + + self:add_favourite(id) +end + +return state |
