diff options
| author | David Vazgenovich Shakaryan <dvshakaryan@gmail.com> | 2026-01-27 01:12:16 -0800 |
|---|---|---|
| committer | David Vazgenovich Shakaryan <dvshakaryan@gmail.com> | 2026-01-27 01:16:58 -0800 |
| commit | 82526665364389d92e0f2c33eba04678275863bb (patch) | |
| tree | 547f3a797b861e85c79bde7baf87fe5145c0396d /rt.lua | |
| parent | 1d2c82bfb4dcfd71045f2948bb320a94013971a5 (diff) | |
| download | mpv-iptv-menu-82526665364389d92e0f2c33eba04678275863bb.tar.gz mpv-iptv-menu-82526665364389d92e0f2c33eba04678275863bb.tar.xz | |
on-demand calculation and update of option info
Using metatables to calculate info strings on render, we can avoid
precomputing it for all options when generating the menu, making certain
menus open much faster. This also allows us to update dynamic info, e.g.
the currently programme, while the menu is open.
Diffstat (limited to 'rt.lua')
| -rw-r--r-- | rt.lua | 349 |
1 files changed, 211 insertions, 138 deletions
@@ -29,7 +29,148 @@ local function cache_miss_status_msg(str) } end +local function series_children(series) + local info = ctx.xc:with_opts('get_series_info', series.series_id, + cache_miss_status_msg('Loading series info...')) + if not info or not info.seasons then + return {} + end + + local seasons = {} + for _, season in pairs(info.seasons) do + local episodes = {} + local season_num = tostring(season.season_number) + if info.episodes and info.episodes[season_num] then + for i, episode in pairs(info.episodes[season_num]) do + local epinfo = episode.info or {} + local t = { + name = util.strip(episode.title), + type = 'stream', + stream_type = 'series', + id = series.section .. ':stream:' .. + episode.id, + stream_id = episode.id, + img_url = util.strip_ne( + epinfo.movie_image), + } + t.info_data = { + name = t.name, + cover = t.img_url, + description = epinfo.plot, + releasedate = epinfo.releasedate, + duration = epinfo.duration, + video = epinfo.video, + audio = epinfo.audio, + } + episodes[#episodes+1] = t + end + end + + local count = tostring(#episodes) + local tmp = util.strip_ne(season.episode_count) + if tmp then + count = count .. '/' .. tmp + end + local t = { + type = 'group', + group_type = 'season', + id = series.id .. ':season:' .. season.id, + children = episodes, + name = util.strip(season.name), + info = count, + img_url = util.strip_ne(season.cover_big) or + util.strip_ne(season.cover), + } + t.info_data = { + name = t.name, + cover = t.img_url, + description = season.overview, + releasedate = season.air_date, + num_episodes = count, + } + seasons[#seasons+1] = t + end + + return seasons +end + +local function catalogue_add_section(sect, cats, elems) + ctx.catalogue:add({ + section = sect.id, + type = 'group', + group_type = 'category', + id = sect.id .. ':category:0', + parent_id = 'root', + name = sect.name, + }) + + -- currently, this will not correctly handle subcategories which come + -- before their parent category + for _, v in ipairs(cats) do + ctx.catalogue:add({ + section = sect.id, + type = 'group', + group_type = 'category', + id = sect.id .. ':category:' .. v.category_id, + parent_id = sect.id .. ':category:' .. v.parent_id, + name = util.strip(v.category_name), + }) + end + + for _, v in ipairs(elems) do + local vv = { + section = sect.id, + parent_id = sect.id .. ':category:' .. v.category_id, + name = util.strip(v.name), + } + + if sect.type == 'series' then + vv.type = 'group' + vv.group_type = 'series' + vv.id = sect.id .. ':series:' .. v.series_id + vv.series_id = v.series_id + vv.img_url = util.strip_ne(v.cover) + vv.count = 1 + vv.hide_count = true + vv.children_f = series_children + else + vv.type = 'stream' + vv.id = sect.id .. ':stream:' .. v.stream_id + vv.stream_type = v.stream_type + vv.stream_id = v.stream_id + vv.img_url = util.strip_ne(v.stream_icon) + vv.epg_channel_id = util.strip_ne(v.epg_channel_id) + end + + ctx.catalogue:add(vv) + end +end + function rt.load_data(force) + ctx.catalogue:add({ + type = 'group', + id = 'favourites', + parent_id = 'root', + name = 'Favourites', + count_f = function() + return #state.favourites + end, + children_f = function() + local options = {} + for i, id in ipairs(state.favourites) do + -- missing favourites are displayed so that + -- they can be removed + options[i] = ctx.catalogue:get(id) or + { + id = id, + name = id, + missing = true, + } + end + return options + end, + }) + local arr = { {id = 'live', name = 'Live TV', type = 'live'}, {id = 'movie', name = 'Movies', type = 'vod'}, @@ -56,15 +197,15 @@ function rt.load_data(force) end end, } - for _, v in ipairs(arr) do - sect_str = base_str .. ' » ' .. v.name - v.categories = ctx.xc:with_opts( - 'get_' .. v.type .. '_categories', call_opts) - v.elements = ctx.xc:with_opts( - v.type == 'series' and 'get_series' or - ('get_' .. v.type .. '_streams'), + for _, sect in ipairs(arr) do + sect_str = base_str .. ' » ' .. sect.name + local cats = ctx.xc:with_opts( + 'get_' .. sect.type .. '_categories', call_opts) + local elems = ctx.xc:with_opts( + sect.type == 'series' and 'get_series' or + ('get_' .. sect.type .. '_streams'), call_opts) - ctx.catalogue:load_xc_section(v) + catalogue_add_section(sect, cats, elems) end osd:set_status('Loading EPG...') @@ -257,140 +398,65 @@ local function add_programme(opt, time) end end -local function group_count(group) - if group.children and not group.lazy then - local count = 0 - for _, v in ipairs(group.children) do - if v.type == 'stream' or v.group_type == 'series' then - count = count + 1 - elseif v.type == 'group' then - local c = group_count(v) - if c then - count = count + c - end - end - end - return count - elseif group.id == 'favourites' then - -- not recursive - return #state.favourites - end -end +local menu_option_mt_count = 0 +local menu_option_mt = { + __index = function(t, k) + local v = t._v -local function favourites_group_menu_options(group) - local options = {} - local time = os.time() - for _, id in ipairs(state.favourites) do - local obj = ctx.catalogue:get(id) - if obj then - obj = util.copy_table(obj) - add_programme(obj, time) - local c = group_count(obj) - if c then - obj.info = tostring(c) - end - local path = ctx.catalogue:path_from_root(obj) - if path then - obj.path = path - end - options[#options+1] = obj - else - -- display missing favourites so that they can be - -- removed - options[#options+1] = { - id = id, - name = id, - missing = true, - } - end - end - return options -end + if k == 'info' then + if v.type == 'group' and not v.hide_count then + local c = ctx.catalogue:group_count(v) + local ret = c and tostring(c) or '' + rawset(t, 'info', ret) + return ret + elseif v.epg_channel_id then + if t._info_exp and + osd.redraw_time < + t._info_exp then + return t._info + end -local function series_group_menu_options(series) - local info = ctx.xc:with_opts('get_series_info', series.series_id, - cache_miss_status_msg('Loading series info...')) - if not info or not info.seasons then - return {} - end + local prog = ctx.epg:scheduled_programme( + v.epg_channel_id, + osd.redraw_time) + local ret = prog and prog.title or '' + local exp = prog and prog.stop + + if not prog then + prog = ctx.epg:next_programme( + v.epg_channel_id, + osd.redraw_time) + exp = prog and prog.start + + if not prog then + rawset(t, 'info', ret) + return ret + end + end - local seasons = {} - for _, season in pairs(info.seasons) do - local episodes = {} - local season_num = tostring(season.season_number) - if info.episodes and info.episodes[season_num] then - for i, episode in pairs(info.episodes[season_num]) do - local epinfo = episode.info or {} - local t = { - name = util.strip(episode.title), - type = 'stream', - stream_type = 'series', - id = series.section .. ':stream:' .. - episode.id, - stream_id = episode.id, - img_url = util.strip_ne( - epinfo.movie_image), - } - t.info_data = { - name = t.name, - cover = t.img_url, - description = epinfo.plot, - releasedate = epinfo.releasedate, - duration = epinfo.duration, - video = epinfo.video, - audio = epinfo.audio, - } - episodes[#episodes+1] = t + rawset(t, '_info_exp', exp) + rawset(t, '_info', ret) + return ret end end - local count = tostring(#episodes) - local tmp = util.strip_ne(season.episode_count) - if tmp then - count = count .. '/' .. tmp - end - local t = { - type = 'group', - group_type = 'season', - id = series.id .. ':season:' .. season.id, - children = episodes, - name = util.strip(season.name), - info = count, - img_url = util.strip_ne(season.cover_big) or - util.strip_ne(season.cover), - } - t.info_data = { - name = t.name, - cover = t.img_url, - description = season.overview, - releasedate = season.air_date, - num_episodes = count, - } - seasons[#seasons+1] = t - end - - return seasons -end + return v[k] + end, +} local function group_menu_options(group) - if group.id == 'favourites' then - return favourites_group_menu_options(group) - end - - if group.group_type == 'series' then - return series_group_menu_options(group) - end - local options = {} - local time = os.time() - for i, v in ipairs(group.children) do - v = util.copy_table(v) - add_programme(v, time) - local c = group_count(v) - if c then - v.info = tostring(c) + for i, v in ipairs(ctx.catalogue:group_children(group)) do + local t = setmetatable({_v = v}, menu_option_mt) + + if group.id == 'favourites' then + local path = ctx.catalogue:path_from_root(v) + if path then + t.path = path + end end - options[i] = v + + options[i] = t end return options end @@ -784,12 +850,9 @@ function rt.open_option_info(opt) end local function search_menu_options_build(options, t, path) - local menu = state:menu() - local path = path or {} - for _, v in ipairs(options) do - local v = util.copy_table(v) v.path = path + if v.type == 'group' and v.group_type ~= 'series' then t.categories[#t.categories+1] = v else @@ -797,7 +860,7 @@ local function search_menu_options_build(options, t, path) end -- contents of lazy-loaded groups should not be searchable - if v.type == 'group' and not v.lazy then + if v.type == 'group' and v.children then local path = util.copy_table(path) path[#path+1] = v search_menu_options_build( @@ -808,7 +871,17 @@ end local function search_menu_options(options) local t = {categories = {}, elements = {}} - search_menu_options_build(options, t) + + local opts = {} + for i, v in ipairs(options) do + -- menu options may contain dynamic data that is updated on + -- redraw. using a proxy table instead of copying allows a + -- single update to target both the source and search menus. + opts[i] = setmetatable({}, {__index = v}) + end + + -- empty table is needed to shadow existing path from proxied table + search_menu_options_build(opts, t, {}) -- display categories first local ret = t.categories |
