(function (global) { var state = { categories: [], channels: [], categoryId: "__all__", selected: null, playList: [], playIndex: -1, mounted: false, focusInList: false, }; function $(id) { return document.getElementById(id); } function toast(msg) { if (global.McApp && McApp.toast) McApp.toast(msg); } function showLoading(on) { if (global.McApp && McApp.showLoading) McApp.showLoading(on); } function escapeHtml(s) { return String(s || "") .replace(/&/g, "&") .replace(//g, ">") .replace(/"/g, """); } function safeUrl(u) { return String(u || "").replace(/'/g, "%27"); } function categoryList() { var all = [{ id: "__all__", name: "Alles", channelCount: 0 }].concat(state.categories || []); var favs = McApi.getFavs(); if (favs.length) { all.splice(1, 0, { id: "__fav__", name: "Favorieten", channelCount: favs.length }); } return all; } /** Bouw categoriechips één keer; update alleen active + behoud focus. */ function renderCategories(opts) { opts = opts || {}; var el = $("tv-cats"); if (!el) return; var all = categoryList(); var activeId = state.categoryId || "__all__"; var prevFocusId = null; var ae = document.activeElement; if (ae && ae.getAttribute && ae.getAttribute("data-cat-id")) { prevFocusId = ae.getAttribute("data-cat-id"); } el.innerHTML = ""; var focusTarget = null; all.forEach(function (c) { var b = document.createElement("button"); b.className = "cat-chip focusable" + (activeId === c.id ? " active" : ""); b.type = "button"; b.setAttribute("data-cat-id", c.id); b.innerHTML = escapeHtml(c.name) + (c.channelCount ? " (" + c.channelCount + ")" : "") + ''; b.addEventListener("click", function () { if (state.categoryId === c.id) { b.focus(); return; } state.categoryId = c.id; state.focusInList = false; setCategoryActive(c.id); loadChannels(c.id, { keepCategoryFocus: true, focusChipId: c.id }); }); b.addEventListener("focus", function () { state.focusInList = false; }); el.appendChild(b); if (c.id === (opts.focusChipId || prevFocusId || activeId)) { focusTarget = b; } }); if (opts.autoFocus || opts.keepCategoryFocus) { setTimeout(function () { if (focusTarget) focusTarget.focus(); }, 30); } } function setCategoryActive(id) { var chips = document.querySelectorAll("#tv-cats .cat-chip"); for (var i = 0; i < chips.length; i++) { var chip = chips[i]; if (chip.getAttribute("data-cat-id") === id) chip.classList.add("active"); else chip.classList.remove("active"); } } function renderChannels(channels, opts) { opts = opts || {}; state.channels = channels || []; var list = $("tv-list"); var detail = $("tv-detail"); if (!list) return; list.innerHTML = ""; if (!state.channels.length) { list.innerHTML = '
Geen zenders in deze categorie
'; if (detail) { detail.innerHTML = '

Geen zenders

'; } return; } state.channels.forEach(function (ch, idx) { var row = document.createElement("button"); row.className = "ch-row focusable"; row.type = "button"; row.setAttribute("data-idx", String(idx)); var now = ch.nowTitle || ""; var fav = McApi.isFav(ch.streamId); var logo = ch.logoUrl ? '" : ''; row.innerHTML = '' + logo + '
' + '
' + (fav ? "* " : "") + escapeHtml(ch.name) + "
" + '
' + escapeHtml(now ? "Nu · " + now : "Geen programma-info") + "
"; row.addEventListener("click", function () { selectChannel(ch, idx); }); row.addEventListener("focus", function () { state.focusInList = true; selectChannel(ch, idx, { skipRebuildActions: true }); }); row.addEventListener("keydown", function (e) { if ((e.keyCode || e.which) === 13) playChannel(ch, idx); }); list.appendChild(row); }); var keepIdx = 0; if (state.selected) { for (var i = 0; i < state.channels.length; i++) { if (state.channels[i].streamId === state.selected.streamId) { keepIdx = i; break; } } } selectChannel(state.channels[keepIdx], keepIdx, { skipRebuildActions: false }); if (opts.focusFirstChannel) { var first = list.querySelector(".ch-row"); if (first) setTimeout(function () { first.focus(); }, 40); } } function paintDetail(ch, idx) { var detail = $("tv-detail"); if (!detail || !ch) return; var logoBlock = ch.logoUrl ? '" : ""; detail.innerHTML = logoBlock + "

" + escapeHtml(ch.name) + "

" + '
' + '
Nu
' + '
' + escapeHtml(ch.nowTitle || "Geen EPG beschikbaar") + "
" + '
' + '
Straks
' + '
' + escapeHtml(ch.nextTitle || "—") + "
" + '
' + '' + '
" + '

OK = kijken · Favoriet toevoegen met Favoriet

'; var playBtn = $("tv-play"); var favBtn = $("tv-fav"); if (playBtn) playBtn.onclick = function () { playChannel(ch, idx); }; if (favBtn) { favBtn.onclick = function () { McApi.toggleFav(ch.streamId); renderCategories({ keepCategoryFocus: true, focusChipId: state.categoryId }); loadChannels(state.categoryId || "__all__", { keepCategoryFocus: true, focusChipId: state.categoryId, }); }; } } function selectChannel(ch, idx, opts) { opts = opts || {}; state.selected = ch; state.playIndex = idx; var rows = document.querySelectorAll("#tv-list .ch-row"); for (var i = 0; i < rows.length; i++) { if (i === idx) rows[i].classList.add("active"); else rows[i].classList.remove("active"); } paintDetail(ch, idx); McApi.iptvEpg(ch.streamId).then(function (epg) { if (!state.selected || state.selected.streamId !== ch.streamId) return; ch.nowTitle = epg.now && epg.now.title; ch.nextTitle = epg.next && epg.next.title; var nowEl = $("tv-epg-now"); var nextEl = $("tv-epg-next"); if (nowEl) nowEl.textContent = ch.nowTitle || "Geen EPG beschikbaar"; if (nextEl) nextEl.textContent = ch.nextTitle || "—"; var row = document.querySelector('#tv-list .ch-row[data-idx="' + idx + '"] .ch-epg'); if (row) { row.textContent = ch.nowTitle ? "Nu · " + ch.nowTitle : "Geen programma-info"; } }).catch(function () {}); } function playChannel(ch, idx) { if (!ch) return; showLoading(true); McApi.iptvPlay(ch.streamId) .then(function (info) { showLoading(false); state.playList = state.channels.slice(); state.playIndex = idx; if (global.McApp && McApp.openLivePlayer) { McApp.openLivePlayer({ title: info.name || ch.name, url: info.streamUrl, fallbackUrl: info.fallbackStreamUrl, channels: state.playList, index: idx, }); } }) .catch(function (e) { showLoading(false); toast(e.message || "Live starten mislukt"); }); } function loadChannels(categoryId, opts) { opts = opts || {}; var id = categoryId || "__all__"; state.categoryId = id; setCategoryActive(id); showLoading(true); var req; if (id === "__fav__") { req = McApi.iptvChannels(null, true).then(function (res) { var all = (res && res.channels) || res || []; return (all || []).filter(function (c) { return McApi.isFav(c.streamId); }); }); } else if (id === "__all__") { req = McApi.iptvChannels(null, true).then(function (res) { return (res && res.channels) || res || []; }); } else { req = McApi.iptvChannels(id, true).then(function (res) { return (res && res.channels) || res || []; }); } req .then(function (channels) { showLoading(false); renderChannels(channels, { focusFirstChannel: !!opts.focusFirstChannel, }); if (opts.keepCategoryFocus) { var chip = document.querySelector('#tv-cats .cat-chip[data-cat-id="' + (opts.focusChipId || id) + '"]'); if (chip) { setTimeout(function () { chip.focus(); }, 40); } } }) .catch(function (e) { showLoading(false); toast(e.message || "Zenders laden mislukt"); if (opts.keepCategoryFocus) { var chip = document.querySelector('#tv-cats .cat-chip[data-cat-id="' + (opts.focusChipId || id) + '"]'); if (chip) chip.focus(); } }); } function mount(container) { state.mounted = true; state.focusInList = false; container.innerHTML = '
' + '
' + '
' + '
' + '

Kies een zender om te kijken

' + "
"; if (state.categories.length) { var id = state.categoryId || "__all__"; renderCategories({ autoFocus: true, focusChipId: id }); loadChannels(id, { keepCategoryFocus: true, focusChipId: id }); return; } state.categoryId = "__all__"; state.selected = null; showLoading(true); McApi.iptvCategories() .then(function (res) { state.categories = (res && res.categories) || res || []; renderCategories({ autoFocus: true, focusChipId: "__all__" }); loadChannels("__all__", { keepCategoryFocus: true, focusChipId: "__all__" }); }) .catch(function (e) { showLoading(false); container.innerHTML = '

' + escapeHtml(e.message || "Live TV niet beschikbaar") + "

"; }); } function zap(delta) { if (!state.playList.length) return null; var next = state.playIndex + delta; if (next < 0) next = state.playList.length - 1; if (next >= state.playList.length) next = 0; var ch = state.playList[next]; state.playIndex = next; return ch; } global.McLive = { mount: mount, zap: zap, playChannel: playChannel, getPlayState: function () { return { list: state.playList, index: state.playIndex }; }, }; })(window);