(function (global) {
/* Zelfde set als Android ProfileAvatars.kt — emoji + naam (niet alleen letter) */
var AVATARS = {
fox: "🦊",
panda: "🐼",
robot: "🤖",
alien: "👽",
ninja: "🥷",
pirate: "🏴☠️",
wizard: "🧙",
dino: "🦕",
cat: "🐱",
rocket: "🚀",
dog: "🐶",
owl: "🦉",
star: "⭐",
game: "🎮",
music: "🎵",
kids: "🧒",
lion: "🦁",
};
function avatarEmoji(id) {
return AVATARS[id] || "🙂";
}
var state = {
screen: "pair",
tab: "home",
iptvEnabled: false,
home: null,
heroItem: null,
spotlight: [],
spotlightIndex: 0,
heroTimer: null,
detail: null,
detailMeta: null,
pendingProfile: null,
pin: "",
pollTimer: null,
browseSkip: {},
searchQ: "",
liveMode: false,
liveCtx: null,
progressCtx: null,
progressTimer: null,
mediaFileId: null,
playerMenu: null,
};
function $(id) { return document.getElementById(id); }
function escapeHtml(s) {
return String(s || "")
.replace(/&/g, "&").replace(//g, ">").replace(/"/g, """);
}
function showLoading(on) {
var el = $("loading");
if (!el) return;
if (on) el.classList.remove("hidden");
else el.classList.add("hidden");
}
function toast(msg) {
var el = $("toast");
if (!el) return;
el.textContent = msg;
el.classList.remove("hidden");
clearTimeout(toast._t);
toast._t = setTimeout(function () { el.classList.add("hidden"); }, 3200);
}
function showScreen(name) {
state.screen = name;
["pair", "profiles", "main", "detail", "player"].forEach(function (s) {
var el = $("screen-" + s);
if (!el) return;
if (s === name) el.classList.remove("hidden");
else el.classList.add("hidden");
});
setTimeout(function () {
if (window.McFocus) McFocus.focusFirst($("screen-" + name));
}, 40);
}
function poster(item) { return item.posterUrl || item.backdropUrl || ""; }
/* ---------- Pairing ---------- */
function startPairing() {
showScreen("pair");
refreshCode();
}
function refreshCode() {
clearInterval(state.pollTimer);
$("pair-status").textContent = "Code ophalen…";
McApi.deviceCode().then(function (data) {
$("pair-code").textContent = data.code;
$("pair-link").textContent = data.linkUrl || McApi.BASE + "/link";
$("pair-status").textContent = "Wachten op koppeling…";
var interval = (data.pollIntervalSeconds || 2) * 1000;
state.pollTimer = setInterval(function () {
McApi.devicePoll(data.code).then(function (res) {
if (res.status === "linked" && res.refreshToken) {
clearInterval(state.pollTimer);
McApi.setToken(res.refreshToken);
toast("TV gekoppeld");
afterAuth();
} else if (res.status === "expired") {
clearInterval(state.pollTimer);
$("pair-status").textContent = "Code verlopen";
}
}).catch(function () {});
}, interval);
}).catch(function (e) {
$("pair-status").textContent = e.message || "Code ophalen mislukt";
});
}
/* ---------- Profiles ---------- */
function afterAuth() {
showLoading(true);
McApi.me().then(function (me) {
state.iptvEnabled = !!(me.iptv && me.iptv.enabled);
var tvNav = $("nav-tv");
if (tvNav) {
if (state.iptvEnabled) tvNav.classList.remove("hidden");
else tvNav.classList.add("hidden");
}
return McApi.listProfiles();
}).then(function (data) {
showLoading(false);
if (data.activeProfileId) {
var active = (data.profiles || []).filter(function (p) {
return p.id === data.activeProfileId;
})[0];
if (active) McApi.setProfileCache(active);
enterMain();
} else {
renderProfiles(data.profiles || []);
}
}).catch(function (e) {
showLoading(false);
if (e.status === 401) { McApi.clearSession(); startPairing(); return; }
toast(e.message || "Sessie mislukt");
startPairing();
});
}
function renderProfiles(profiles) {
showScreen("profiles");
$("pin-panel").classList.add("hidden");
var grid = $("profile-grid");
grid.innerHTML = "";
profiles.forEach(function (p, idx) {
var btn = document.createElement("button");
btn.className = "profile-tile";
btn.type = "button";
btn.innerHTML =
'
' + avatarEmoji(p.avatarId) + "
" +
'' + escapeHtml(p.name) + "
" +
'' +
escapeHtml(p.isKids ? "Kids" : p.isOwner ? "Hoofd" : p.hasPin ? "PIN" : "") +
"
";
btn.onclick = function () {
if (p.hasPin) openPin(p);
else selectProfile(p, null);
};
grid.appendChild(btn);
if (idx === 0) setTimeout(function () { btn.focus(); }, 40);
});
}
function openPin(profile) {
state.pendingProfile = profile;
state.pin = "";
$("pin-panel").classList.remove("hidden");
$("pin-title").textContent = "PIN voor " + profile.name;
renderPinDots();
var pad = $("pin-pad");
pad.innerHTML = "";
["1","2","3","4","5","6","7","8","9","⌫","0","C"].forEach(function (k) {
var b = document.createElement("button");
b.className = "num-key";
b.type = "button";
b.textContent = k;
b.onclick = function () {
if (k === "⌫") state.pin = state.pin.slice(0, -1);
else if (k === "C") state.pin = "";
else if (state.pin.length < 4) state.pin += k;
renderPinDots();
if (state.pin.length === 4) selectProfile(state.pendingProfile, state.pin);
};
pad.appendChild(b);
});
McFocus.focusFirst($("pin-pad"));
}
function renderPinDots() {
var html = "";
for (var i = 0; i < 4; i++) html += '';
$("pin-dots").innerHTML = html;
}
function selectProfile(profile, pin) {
showLoading(true);
McApi.selectProfile(profile.id, pin).then(function (res) {
showLoading(false);
McApi.setProfileCache(res.profile || profile);
$("pin-panel").classList.add("hidden");
enterMain();
}).catch(function (e) {
showLoading(false);
state.pin = "";
renderPinDots();
toast(e.message || "PIN onjuist");
});
}
function switchProfile() {
showLoading(true);
McApi.clearProfile().then(function () {
McApi.setProfileCache(null);
return McApi.listProfiles();
}).then(function (d) {
showLoading(false);
renderProfiles(d.profiles || []);
}).catch(function () {
showLoading(false);
McApi.listProfiles().then(function (d) { renderProfiles(d.profiles || []); });
});
}
/* ---------- Main hub ---------- */
function enterMain() {
showScreen("main");
updateNavProfile();
setTab(state.tab || "home");
}
function updateNavProfile() {
var p = McApi.getProfileCache();
var el = $("nav-profile");
if (!el) return;
if (!p) {
el.innerHTML = 'Profiel';
return;
}
el.innerHTML =
'' +
avatarEmoji(p.avatarId) +
'' +
escapeHtml(p.name) +
"";
}
function setTab(tab) {
state.tab = tab;
var topnav = $("topnav");
var body = $("main-body");
if (topnav) {
if (tab === "home") topnav.classList.add("overlay");
else topnav.classList.remove("overlay");
}
if (body) {
if (tab === "home") body.classList.add("home-mode");
else body.classList.remove("home-mode");
}
if (tab !== "home") stopHeroRotate();
var tabs = document.querySelectorAll(".nav-tab");
for (var i = 0; i < tabs.length; i++) {
var t = tabs[i];
var on = t.getAttribute("data-tab") === tab;
if (on) t.classList.add("active");
else t.classList.remove("active");
}
["home","movies","series","tv","search","settings"].forEach(function (name) {
var pane = $("tab-" + name);
if (!pane) return;
if (name === tab) pane.classList.remove("hidden");
else pane.classList.add("hidden");
});
if (tab === "home") loadHome();
else if (tab === "movies") loadBrowse("movies", "Films", ["movies","recent-movies","4k-movies"]);
else if (tab === "series") loadBrowse("series", "Series", ["series","recent-series"]);
else if (tab === "tv") McLive.mount($("tab-tv"));
else if (tab === "search") renderSearch();
else if (tab === "settings") renderSettings();
}
/* ---------- Home (Android HomeScreen / SpotlightHero) ---------- */
function stopHeroRotate() {
if (state.heroTimer) {
clearInterval(state.heroTimer);
state.heroTimer = null;
}
}
function loadHome() {
showLoading(true);
McApi.home().then(function (home) {
showLoading(false);
state.home = home;
renderHome(home);
}).catch(function (e) {
showLoading(false);
if (e.status === 428 || e.code === "PROFILE_REQUIRED") {
switchProfile();
return;
}
if (e.status === 401) { McApi.clearSession(); startPairing(); return; }
toast(e.message || "Home laden mislukt");
});
}
function buildSpotlight(home) {
var list = (home && home.spotlight) || [];
if (list.length) return list.slice();
var fallback = [];
if (home && home.continueWatching && home.continueWatching[0]) {
fallback.push(home.continueWatching[0]);
}
if (home && home.shelves) {
for (var i = 0; i < home.shelves.length && fallback.length < 5; i++) {
var items = home.shelves[i].items || [];
if (items[0]) fallback.push(items[0]);
}
}
if (!fallback.length) {
fallback.push({ type: "movie", id: "", title: "Welkom bij VMC" });
}
return fallback;
}
function heroTypeLabel(item) {
if (!item) return "";
if (item.type === "series" || item.type === "episode") return "Serie";
return "Film";
}
function paintHero() {
var heroBg = $("hero-bg");
var content = $("hero-content");
if (!content) return;
var items = state.spotlight;
var idx = state.spotlightIndex;
if (!items.length) return;
if (idx < 0 || idx >= items.length) idx = 0;
var hero = items[idx];
state.heroItem = hero;
if (heroBg) {
var url = hero && (hero.backdropUrl || hero.posterUrl)
? String(hero.backdropUrl || hero.posterUrl).replace(/'/g, "%27")
: "";
if (url) {
heroBg.style.backgroundImage = "url('" + url + "')";
heroBg.style.backgroundSize = "cover";
heroBg.style.backgroundPosition = "center top";
heroBg.style.backgroundRepeat = "no-repeat";
heroBg.classList.remove("empty");
} else {
heroBg.style.backgroundImage = "none";
heroBg.classList.add("empty");
}
}
var badgeHtml = hero && hero.badge
? '' + escapeHtml(String(hero.badge).toUpperCase()) + "
"
: "";
var titleBlock;
if (hero && hero.logoUrl) {
titleBlock =
'";
} else {
titleBlock =
'' +
escapeHtml(hero && hero.title ? hero.title : "Welkom bij VMC") +
"
";
}
var metaParts = [];
if (hero && hero.year) metaParts.push(String(hero.year));
if (hero && hero.id) metaParts.push(heroTypeLabel(hero));
var metaHtml = metaParts.length
? '' + escapeHtml(metaParts.join(" · ")) + "
"
: "";
var dots = "";
if (items.length > 1) {
dots = '';
for (var d = 0; d < items.length; d++) {
dots += '';
}
dots += "
";
}
var hadPlayFocus = document.activeElement && document.activeElement.id === "hero-play";
var hadInfoFocus = document.activeElement && document.activeElement.id === "hero-info";
content.innerHTML =
badgeHtml +
titleBlock +
metaHtml +
'' +
'' +
'' +
"
" +
dots;
var playBtn = $("hero-play");
var infoBtn = $("hero-info");
if (playBtn) playBtn.onclick = playHero;
if (infoBtn) {
infoBtn.onclick = function () {
openDetail(state.heroItem);
};
}
if (hadPlayFocus && playBtn) playBtn.focus();
else if (hadInfoFocus && infoBtn) infoBtn.focus();
}
function startHeroRotate() {
stopHeroRotate();
if (state.spotlight.length <= 1) return;
state.heroTimer = setInterval(function () {
if (state.tab !== "home" || state.screen !== "main") return;
state.spotlightIndex = (state.spotlightIndex + 1) % state.spotlight.length;
paintHero();
}, 7000);
}
function renderHome(home) {
var pane = $("tab-home");
state.spotlight = buildSpotlight(home);
state.spotlightIndex = 0;
state.heroItem = state.spotlight[0] || null;
/* Full-bleed bg-laag + zachte scrims (geen zware zwarte platen die de art tot een strip maken) */
pane.innerHTML =
'" +
'';
paintHero();
startHeroRotate();
var rows = $("home-rows");
function addRow(title, items) {
if (!items || !items.length) return;
var row = document.createElement("div");
row.className = "row";
row.innerHTML =
'' +
escapeHtml(title) +
"
";
var scroller = document.createElement("div");
scroller.className = "row-scroller";
items.forEach(function (item) {
scroller.appendChild(makeTile(item));
});
row.appendChild(scroller);
rows.appendChild(row);
}
if (home.continueWatching && home.continueWatching.length) {
addRow("Verder kijken", home.continueWatching);
}
(home.shelves || []).forEach(function (shelf) {
addRow(shelf.title || shelf.name || "Rij", shelf.items || []);
});
var playBtn = $("hero-play");
if (playBtn) playBtn.focus();
else McFocus.focusFirst(pane);
}
function makeTile(item) {
var tile = document.createElement("button");
tile.className = "tile focusable";
tile.type = "button";
var url = poster(item);
var progress = 0;
if (item.positionMs && item.durationMs && item.durationMs > 0) {
progress = Math.min(100, Math.round((item.positionMs / item.durationMs) * 100));
}
var letter = (item.title || "?").charAt(0).toUpperCase();
var posterHtml =
'' +
'
' + escapeHtml(letter) + "
" +
(url
? '
"
: "") +
(progress > 0
? '
'
: "") +
"
" +
'' + escapeHtml(item.title || "") + "
";
tile.innerHTML = posterHtml;
tile.onclick = function () {
if (item.mediaFileId && item.type === "episode") {
playMedia(item.mediaFileId, item.title, {
mediaType: "EPISODE",
episodeId: item.id,
seriesId: item.seriesId,
startMs: item.positionMs || 0,
});
} else {
openDetail(item);
}
};
return tile;
}
function playHero() {
var item = state.heroItem;
if (!item) return;
if (item.mediaFileId) {
playMedia(item.mediaFileId, item.title, {
mediaType: item.type === "episode" ? "EPISODE" : "MOVIE",
movieId: item.type === "movie" ? item.id : null,
episodeId: item.type === "episode" ? item.id : null,
seriesId: item.seriesId || null,
startMs: item.positionMs || 0,
});
return;
}
openDetail(item);
}
/* ---------- Browse ---------- */
function loadBrowse(paneKey, title, catalogIds) {
var pane = $("tab-" + (paneKey === "movies" ? "movies" : "series"));
var primary = catalogIds[0];
pane.innerHTML =
'' + escapeHtml(title) + "
" +
'' +
'';
var labels = {
movies: "Alles",
"recent-movies": "Recent",
"4k-movies": "4K",
series: "Alles",
"recent-series": "Recent",
};
var sub = $("browse-sub-" + paneKey);
catalogIds.forEach(function (id, i) {
var chip = document.createElement("button");
chip.className = "cat-chip focusable" + (i === 0 ? " active" : "");
chip.type = "button";
chip.setAttribute("data-cat", id);
chip.innerHTML = escapeHtml(labels[id] || id) + '';
chip.onclick = function () {
var chips = sub.querySelectorAll(".cat-chip");
for (var j = 0; j < chips.length; j++) chips[j].classList.remove("active");
chip.classList.add("active");
fillCatalog(paneKey, id, { keepChipFocus: true, focusChip: chip });
};
sub.appendChild(chip);
});
fillCatalog(paneKey, primary, { focusGrid: true });
}
function fillCatalog(paneKey, catalogId, opts) {
opts = opts || {};
var grid = $("browse-grid-" + paneKey);
if (!grid) return;
showLoading(true);
McApi.catalog(catalogId, 0, 60).then(function (page) {
showLoading(false);
grid.innerHTML = "";
var items = page.items || [];
if (!items.length) {
grid.innerHTML = 'Geen items
';
if (opts.focusChip) opts.focusChip.focus();
return;
}
items.forEach(function (item) { grid.appendChild(makeTile(item)); });
if (opts.keepChipFocus && opts.focusChip) {
opts.focusChip.focus();
} else if (opts.focusGrid !== false) {
McFocus.focusFirst(grid);
}
}).catch(function (e) {
showLoading(false);
toast(e.message || "Catalogus mislukt");
if (opts.focusChip) opts.focusChip.focus();
});
}
/* ---------- Search ---------- */
function renderSearch() {
var pane = $("tab-search");
pane.innerHTML =
'' +
'
Zoeken
' +
'
' + escapeHtml(state.searchQ || " ") + "
" +
'
' +
'
' +
"
";
var kb = $("search-kb");
var keys = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789".split("");
keys.forEach(function (k) {
var b = document.createElement("button");
b.className = "kb-key focusable";
b.type = "button";
b.textContent = k;
b.onclick = function () {
state.searchQ = (state.searchQ || "") + k;
$("search-q").textContent = state.searchQ;
runSearch();
};
kb.appendChild(b);
});
[["␣", " "], ["⌫", "back"], ["Wis", "clear"]].forEach(function (pair) {
var b = document.createElement("button");
b.className = "kb-key wide focusable";
b.type = "button";
b.textContent = pair[0];
b.onclick = function () {
if (pair[1] === "back") state.searchQ = (state.searchQ || "").slice(0, -1);
else if (pair[1] === "clear") state.searchQ = "";
else state.searchQ = (state.searchQ || "") + " ";
$("search-q").textContent = state.searchQ || " ";
runSearch();
};
kb.appendChild(b);
});
if (state.searchQ) runSearch();
McFocus.focusFirst(kb);
}
function runSearch() {
clearTimeout(runSearch._t);
runSearch._t = setTimeout(function () {
var q = (state.searchQ || "").trim();
var grid = $("search-grid");
if (!grid) return;
if (q.length < 2) { grid.innerHTML = ""; return; }
McApi.search(q).then(function (res) {
grid.innerHTML = "";
var items = res.results || res || [];
items.forEach(function (item) { grid.appendChild(makeTile(item)); });
}).catch(function (e) { toast(e.message || "Zoeken mislukt"); });
}, 250);
}
/* ---------- Settings ---------- */
function renderSettings() {
var pane = $("tab-settings");
var p = McApi.getProfileCache();
pane.innerHTML =
'' +
'
Instellingen
' +
'
Profiel: ' +
escapeHtml(p ? p.name : "—") + "
" +
'
' +
'
' +
"
";
$("set-switch").onclick = switchProfile;
$("set-logout").onclick = function () {
McApi.clearSession();
toast("Ontkoppeld");
startPairing();
};
McFocus.focusFirst(pane);
}
/* ---------- Detail ---------- */
function openDetail(item) {
if (!item) return;
var type = item.type;
var id = item.id;
if (type === "episode" && item.seriesId) {
type = "series";
id = item.seriesId;
}
showLoading(true);
var req = type === "series" ? McApi.series(id) : McApi.movie(id);
req.then(function (detail) {
showLoading(false);
state.detail = detail;
renderDetail(detail);
}).catch(function (e) {
showLoading(false);
toast(e.message || "Detail laden mislukt");
});
}
function renderDetail(d) {
showScreen("detail");
$("detail-bg").style.backgroundImage = d.backdropUrl || d.posterUrl
? 'url("' + (d.backdropUrl || d.posterUrl) + '")' : "";
var logoEl = $("detail-logo");
var titleEl = $("detail-title");
if (logoEl && d.logoUrl) {
logoEl.classList.remove("hidden");
logoEl.style.backgroundImage = "url('" + String(d.logoUrl).replace(/'/g, "%27") + "')";
if (titleEl) titleEl.classList.add("hidden");
} else {
if (logoEl) {
logoEl.classList.add("hidden");
logoEl.style.backgroundImage = "none";
}
if (titleEl) {
titleEl.classList.remove("hidden");
titleEl.textContent = d.title || "";
}
}
$("detail-meta").textContent = [
d.year,
d.type === "series" ? "Serie" : "Film",
(d.genres || []).slice(0, 3).join(", "),
].filter(Boolean).join(" · ");
$("detail-overview").textContent = d.overview || "";
var actions = $("detail-actions");
var episodes = $("detail-episodes");
actions.innerHTML = "";
episodes.innerHTML = "";
if (d.type === "movie") {
var files = (d.mediaFiles || []).slice().sort(function (a, b) {
var ao = a.nodeOnline === false ? 1 : 0;
var bo = b.nodeOnline === false ? 1 : 0;
return ao - bo;
});
if (!files.length) {
actions.innerHTML = 'Geen afspeelbare bestanden';
} else {
files.forEach(function (f, i) {
var btn = document.createElement("button");
var online = f.nodeOnline !== false;
btn.className = "btn focusable" + (i === 0 && online ? " primary" : "");
btn.type = "button";
btn.textContent = f.label || f.resolution || "Afspelen";
if (!online) {
btn.textContent += " (offline)";
btn.disabled = true;
}
btn.onclick = function () {
playMedia(f.id, d.title, { mediaType: "MOVIE", movieId: d.id });
};
actions.appendChild(btn);
});
}
} else {
(d.seasons || []).forEach(function (season) {
var h = document.createElement("h3");
h.style.margin = "18px 0 10px";
h.textContent = "Seizoen " + season.seasonNumber;
episodes.appendChild(h);
(season.episodes || []).forEach(function (ep) {
var btn = document.createElement("button");
btn.className = "ep focusable" + (ep.available && ep.mediaFileId ? "" : " disabled");
btn.type = "button";
btn.disabled = !(ep.available && ep.mediaFileId);
btn.innerHTML =
'Afl. ' + ep.episodeNumber + " · " + escapeHtml(ep.title || "") + "
" +
'' + escapeHtml((ep.overview || "").slice(0, 140)) + "
";
btn.onclick = function () {
playMedia(ep.mediaFileId, d.title + " S" + ep.seasonNumber + "E" + ep.episodeNumber, {
mediaType: "EPISODE",
episodeId: ep.id,
seriesId: d.id,
});
};
episodes.appendChild(btn);
});
});
}
McFocus.focusFirst($("screen-detail"));
}
/* ---------- Playback ---------- */
var playBusy = false;
function closePlayerMenu() {
state.playerMenu = null;
var menu = $("player-menu");
if (menu) menu.classList.add("hidden");
McPlayer.showUi(true);
}
function menuRow(label, sub, onClick, active) {
var b = document.createElement("button");
b.type = "button";
b.className = "player-menu-row focusable" + (active ? " is-active" : "");
var mark = document.createElement("span");
mark.className = "player-menu-mark";
b.appendChild(mark);
var main = document.createElement("span");
main.className = "player-menu-main";
main.textContent = label || "Ondertitel";
b.appendChild(main);
if (sub) {
var s = document.createElement("span");
s.className = "muted";
s.textContent = sub;
b.appendChild(s);
}
b.onclick = onClick;
return b;
}
function openPlayerMenu(kind) {
state.playerMenu = kind;
McPlayer.showUi(true);
var menu = $("player-menu");
var title = $("player-menu-title");
var list = $("player-menu-list");
if (!menu || !list) return;
menu.classList.remove("hidden");
list.innerHTML = "";
if (kind === "audio") {
title.textContent = "Audio";
var tracks = McPlayer.listAudioTracks();
if (!tracks.length) {
list.appendChild(menuRow("Geen audio-sporen", null, function () {}));
} else {
tracks.forEach(function (t) {
list.appendChild(
menuRow(t.label, null, function () {
McPlayer.selectAudio(t.index);
closePlayerMenu();
toast("Audio: " + t.label);
})
);
});
}
} else if (kind === "text") {
title.textContent = "Ondertiteling";
list.appendChild(
menuRow("Uit", null, function () {
McPlayer.disableText();
closePlayerMenu();
})
);
if (state.mediaFileId && !state.liveMode) {
list.appendChild(
menuRow("Zoeken op OpenSubtitles…", null, function () {
openOsMenu();
})
);
}
var texts = McPlayer.listTextTracks();
if (!texts.length) {
list.appendChild(menuRow("Geen ingebouwde ondertitels", null, function () {}));
} else {
texts.forEach(function (t) {
if (t.type === "EXTERNAL") {
list.appendChild(menuRow(t.label + " (actief)", null, function () {
closePlayerMenu();
}));
return;
}
list.appendChild(
menuRow(t.label, null, function () {
McPlayer.selectText(t.index);
closePlayerMenu();
toast("Ondertitel: " + t.label);
})
);
});
}
} else if (kind === "os") {
title.textContent = "OpenSubtitles";
list.appendChild(menuRow("Zoeken…", null, function () {}));
}
setTimeout(function () {
McFocus.focusFirst(list);
}, 40);
}
function openOsMenu() {
if (!state.mediaFileId) {
toast("Geen mediabestand voor ondertitels");
return;
}
state.playerMenu = "os";
var menu = $("player-menu");
var title = $("player-menu-title");
var list = $("player-menu-list");
if (!menu || !list) return;
menu.classList.remove("hidden");
title.textContent = "OpenSubtitles";
list.innerHTML = "";
list.appendChild(menuRow("Zoeken…", null, function () {}));
McApi.searchSubtitles(state.mediaFileId)
.then(function (res) {
list.innerHTML = "";
if (!res.configured) {
list.appendChild(menuRow("OpenSubtitles staat uit in de admin", null, function () {}));
} else if (!(res.results || []).length) {
list.appendChild(menuRow("Geen ondertitels gevonden", null, function () {}));
} else {
(res.results || []).forEach(function (hit) {
var lang =
McPlayer.langLabel(hit.language || hit.lang || "") ||
String(hit.language || hit.lang || "").toUpperCase() ||
"Ondertitel";
var bits = [];
var release = hit.release || hit.fileName || "";
if (release) bits.push(String(release).slice(0, 48));
if (hit.hearingImpaired) bits.push("SDH");
if (hit.source) bits.push(String(hit.source).toUpperCase());
if (hit.downloadCount) bits.push(hit.downloadCount + "×");
list.appendChild(
menuRow(lang, bits.join(" · ") || null, function () {
pickOsHit(hit);
})
);
});
}
list.appendChild(
menuRow("Terug", null, function () {
openPlayerMenu("text");
})
);
McFocus.focusFirst(list);
})
.catch(function (e) {
list.innerHTML = "";
list.appendChild(menuRow(e.message || "Zoeken mislukt", null, function () {}));
list.appendChild(
menuRow("Terug", null, function () {
openPlayerMenu("text");
})
);
McFocus.focusFirst(list);
});
}
function pickOsHit(hit) {
showLoading(true);
McApi.fetchSubtitle(hit.fileId, hit.source)
.then(function (fetched) {
showLoading(false);
var label = [
McPlayer.langLabel(hit.language || ""),
hit.release ? String(hit.release).slice(0, 40) : "",
]
.filter(Boolean)
.join(" · ");
fetched.label = label;
var n = McPlayer.applyExternalSubtitle(fetched);
closePlayerMenu();
toast("Ondertitel geladen (" + n + ")");
})
.catch(function (e) {
showLoading(false);
toast(e.message || "Download mislukt");
});
}
function playMedia(mediaFileId, title, meta, attempt) {
attempt = attempt || 1;
if (!mediaFileId) { toast("Geen mediabestand"); return; }
if (playBusy && attempt === 1) return;
playBusy = true;
showLoading(true);
McApi.play(mediaFileId).then(function (res) {
playBusy = false;
showLoading(false);
if (!res.streamUrl) throw new Error("Geen stream-URL");
state.liveMode = false;
state.liveCtx = null;
state.mediaFileId = mediaFileId;
state.progressCtx = meta || null;
state.detailMeta = meta;
closePlayerMenu();
showScreen("player");
var acts = $("player-actions");
if (acts) acts.classList.remove("hidden");
McPlayer.playUrl(res.streamUrl, title || "");
startProgressLoop();
}).catch(function (e) {
var msg = (e && e.message) || "";
var code = e && e.code;
var retriable =
code === "NODE_OFFLINE" ||
/register session|node|offline|503/i.test(msg);
if (retriable && attempt < 3) {
toast("Mediaserver bezet — opnieuw proberen (" + attempt + "/2)…");
setTimeout(function () {
playBusy = false;
playMedia(mediaFileId, title, meta, attempt + 1);
}, 1200 * attempt);
return;
}
playBusy = false;
showLoading(false);
if (retriable) {
toast("Mediaserver reageert niet. Kies andere kwaliteit of probeer later.");
} else {
toast(msg || "Afspelen mislukt");
}
});
}
function openLivePlayer(ctx) {
state.liveMode = true;
state.liveCtx = ctx;
state.progressCtx = null;
state.mediaFileId = null;
stopProgressLoop();
closePlayerMenu();
showScreen("player");
var acts = $("player-actions");
if (acts) acts.classList.add("hidden");
McPlayer.playUrl(ctx.url, ctx.title || "Live TV");
}
function startProgressLoop() {
stopProgressLoop();
state.progressTimer = setInterval(function () {
postProgress(false);
}, 20000);
}
function stopProgressLoop() {
if (state.progressTimer) clearInterval(state.progressTimer);
state.progressTimer = null;
}
function postProgress(force) {
var ctx = state.progressCtx;
if (!ctx || state.liveMode) return;
var pos = McPlayer.getPositionMs ? McPlayer.getPositionMs() : 0;
var dur = McPlayer.getDurationMs ? McPlayer.getDurationMs() : 0;
if (!force && pos < 5000) return;
var body = {
mediaType: ctx.mediaType || "MOVIE",
positionMs: pos,
durationMs: dur || 0,
};
if (ctx.movieId) body.movieId = ctx.movieId;
if (ctx.episodeId) body.episodeId = ctx.episodeId;
if (ctx.seriesId) body.seriesId = ctx.seriesId;
McApi.progress(body).catch(function () {});
}
function leavePlayer() {
closePlayerMenu();
postProgress(true);
stopProgressLoop();
McPlayer.stop();
state.liveMode = false;
state.mediaFileId = null;
if (state.detail) showScreen("detail");
else {
showScreen("main");
setTab(state.tab || "home");
}
}
function liveZap(delta) {
if (!state.liveMode) return;
var ch = McLive.zap(delta);
if (!ch) return;
showLoading(true);
McApi.iptvPlay(ch.streamId).then(function (info) {
showLoading(false);
state.liveCtx = {
title: info.name || ch.name,
url: info.streamUrl,
fallbackUrl: info.fallbackStreamUrl,
};
McPlayer.playUrl(info.streamUrl, state.liveCtx.title);
}).catch(function (e) {
showLoading(false);
toast(e.message || "Zap mislukt");
});
}
/* ---------- Keys ---------- */
function onKey(e) {
var code = e.keyCode || e.which;
var K = McFocus.keys;
if (state.screen === "player") {
var uiOn = McPlayer.isUiVisible && McPlayer.isUiVisible();
if (state.playerMenu) {
if (code === K.BACK || code === K.ESC) {
e.preventDefault();
if (state.playerMenu === "os") openPlayerMenu("text");
else closePlayerMenu();
return;
}
if (code === K.LEFT) { e.preventDefault(); McFocus.move("left"); return; }
if (code === K.RIGHT) { e.preventDefault(); McFocus.move("right"); return; }
if (code === K.UP) { e.preventDefault(); McFocus.move("up"); return; }
if (code === K.DOWN) { e.preventDefault(); McFocus.move("down"); return; }
if (code === K.ENTER) { return; }
return;
}
/* Controls zichtbaar: navigeren, niet spoelen */
if (uiOn && !state.liveMode) {
if (code === K.BACK || code === K.ESC) {
e.preventDefault();
McPlayer.showUi(false);
return;
}
if (code === K.LEFT) {
e.preventDefault();
McPlayer.showUi(true);
McFocus.move("left");
return;
}
if (code === K.RIGHT) {
e.preventDefault();
McPlayer.showUi(true);
McFocus.move("right");
return;
}
if (code === K.UP || code === K.DOWN) {
e.preventDefault();
McPlayer.showUi(true);
McFocus.move(code === K.UP ? "up" : "down");
return;
}
if (code === K.ENTER) {
var ae = document.activeElement;
if (ae && ae.className && String(ae.className).indexOf("pctrl") >= 0) {
return; /* laat click/default op knop */
}
e.preventDefault();
McPlayer.togglePause();
return;
}
if (code === K.PLAYPAUSE || code === K.PAUSE || code === K.PLAY) {
e.preventDefault();
McPlayer.togglePause();
return;
}
if (code === K.STOP) {
e.preventDefault();
leavePlayer();
return;
}
return;
}
/* Controls verborgen (of live): spoelen / zappen / UI tonen */
if (code === K.BACK || code === K.ESC || code === K.STOP) {
e.preventDefault(); leavePlayer(); return;
}
if (code === K.ENTER || code === K.PLAYPAUSE || code === K.PAUSE || code === K.PLAY) {
e.preventDefault();
if (!uiOn) {
McPlayer.showUi(true, { focusPlay: true });
return;
}
McPlayer.togglePause();
return;
}
if (code === K.RIGHT || code === K.FF) {
e.preventDefault();
if (state.liveMode) liveZap(1);
else McPlayer.seek(10000);
return;
}
if (code === K.LEFT || code === K.REW) {
e.preventDefault();
if (state.liveMode) liveZap(-1);
else McPlayer.seek(-10000);
return;
}
if (state.liveMode && (code === K.UP || code === K.DOWN)) {
e.preventDefault();
liveZap(code === K.UP ? -1 : 1);
return;
}
if (!state.liveMode && (code === K.UP || code === K.DOWN)) {
e.preventDefault();
McPlayer.showUi(true, { focusPlay: true });
return;
}
McPlayer.showUi(true, { focusPlay: true });
return;
}
if (code === K.BACK || code === K.ESC) {
e.preventDefault();
if (state.screen === "detail") {
showScreen("main");
return;
}
if (state.screen === "profiles" && !$("pin-panel").classList.contains("hidden")) {
$("pin-panel").classList.add("hidden");
return;
}
if (state.screen === "main" && state.tab !== "home") {
setTab("home");
return;
}
return;
}
if (code === K.LEFT) { e.preventDefault(); McFocus.move("left"); }
else if (code === K.RIGHT) { e.preventDefault(); McFocus.move("right"); }
else if (code === K.UP) { e.preventDefault(); McFocus.move("up"); }
else if (code === K.DOWN) { e.preventDefault(); McFocus.move("down"); }
}
function bind() {
$("pair-refresh").onclick = refreshCode;
$("detail-back").onclick = function () { showScreen("main"); };
$("pin-cancel").onclick = function () {
$("pin-panel").classList.add("hidden");
state.pendingProfile = null;
state.pin = "";
};
$("nav-profile").onclick = switchProfile;
var tabs = document.querySelectorAll(".nav-tab");
for (var i = 0; i < tabs.length; i++) {
(function (tab) {
tab.onclick = function () {
setTab(tab.getAttribute("data-tab"));
};
})(tabs[i]);
}
var btnAudio = $("btn-audio");
var btnSubs = $("btn-subs");
var btnClose = $("player-menu-close");
var btnPlay = $("btn-play");
var btnRew = $("btn-rew");
var btnFf = $("btn-ff");
if (btnAudio) {
btnAudio.onclick = function () {
openPlayerMenu("audio");
};
}
if (btnSubs) {
btnSubs.onclick = function () {
openPlayerMenu("text");
};
}
if (btnClose) {
btnClose.onclick = function () {
closePlayerMenu();
};
}
if (btnPlay) {
btnPlay.onclick = function () {
McPlayer.togglePause();
};
}
if (btnRew) {
btnRew.onclick = function () {
McPlayer.seek(-10000);
McPlayer.showUi(true);
};
}
if (btnFf) {
btnFf.onclick = function () {
McPlayer.seek(10000);
McPlayer.showUi(true);
};
}
document.addEventListener("keydown", onKey);
McPlayer.on("onEnded", function () {
if (state.liveMode) return;
leavePlayer();
});
}
global.McApp = {
toast: toast,
showLoading: showLoading,
openLivePlayer: openLivePlayer,
};
function boot() {
try {
var banner = document.getElementById("boot-banner");
if (banner) {
banner.style.display = "none";
banner.style.visibility = "hidden";
}
if (!window.McApi) throw new Error("api.js niet geladen");
if (!window.McFocus) throw new Error("focus.js niet geladen");
if (!window.McPlayer) throw new Error("player.js niet geladen");
McFocus.registerKeys();
bind();
if (McApi.getToken()) afterAuth();
else startPairing();
} catch (e) {
var banner2 = document.getElementById("boot-banner");
if (banner2) {
banner2.style.display = "block";
banner2.style.visibility = "visible";
banner2.style.background = "#12080a";
banner2.style.color = "#fff";
banner2.style.paddingTop = "30%";
banner2.innerHTML = "Startfout:
" + (e.message || e);
}
var fatal = document.getElementById("fatal");
if (fatal) {
fatal.classList.remove("hidden");
fatal.textContent = "Startfout: " + (e.message || e);
}
}
}
var booted = false;
function bootOnce() {
if (booted) return;
booted = true;
boot();
}
window.onerror = function (msg, url, line) {
var fatal = document.getElementById("fatal");
if (fatal) {
fatal.classList.remove("hidden");
fatal.textContent = "JS-fout: " + msg + " @" + line;
}
var banner = document.getElementById("boot-banner");
if (banner && banner.style.display !== "none") {
banner.style.background = "#12080a";
banner.style.color = "#fff";
banner.innerHTML = "JS-fout:
" + msg + " (regel " + line + ")";
}
return false;
};
if (document.readyState === "loading") {
document.addEventListener("DOMContentLoaded", bootOnce);
}
bootOnce();
setTimeout(bootOnce, 100);
setTimeout(bootOnce, 500);
})(window);