Focus-friendly VMC shell for Tizen with live TV and playback against the master API. Co-authored-by: Cursor <cursoragent@cursor.com>
1346 lines
No EOL
42 KiB
JavaScript
1346 lines
No EOL
42 KiB
JavaScript
(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, ">").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 =
|
||
'<div class="avatar">' + avatarEmoji(p.avatarId) + "</div>" +
|
||
'<div class="name">' + escapeHtml(p.name) + "</div>" +
|
||
'<div class="badge">' +
|
||
escapeHtml(p.isKids ? "Kids" : p.isOwner ? "Hoofd" : p.hasPin ? "PIN" : "") +
|
||
"</div>";
|
||
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 += '<span class="' + (i < state.pin.length ? "on" : "") + '"></span>';
|
||
$("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 = '<span class="nav-profile-name">Profiel</span>';
|
||
return;
|
||
}
|
||
el.innerHTML =
|
||
'<span class="nav-avatar" aria-hidden="true">' +
|
||
avatarEmoji(p.avatarId) +
|
||
'</span><span class="nav-profile-name">' +
|
||
escapeHtml(p.name) +
|
||
"</span>";
|
||
}
|
||
|
||
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
|
||
? '<div class="hero-badge">' + escapeHtml(String(hero.badge).toUpperCase()) + "</div>"
|
||
: "";
|
||
var titleBlock;
|
||
if (hero && hero.logoUrl) {
|
||
titleBlock =
|
||
'<div class="hero-logo" style="background-image:url(\'' +
|
||
String(hero.logoUrl).replace(/'/g, "%27") +
|
||
"')\"></div>";
|
||
} else {
|
||
titleBlock =
|
||
'<div class="hero-title">' +
|
||
escapeHtml(hero && hero.title ? hero.title : "Welkom bij VMC") +
|
||
"</div>";
|
||
}
|
||
var metaParts = [];
|
||
if (hero && hero.year) metaParts.push(String(hero.year));
|
||
if (hero && hero.id) metaParts.push(heroTypeLabel(hero));
|
||
var metaHtml = metaParts.length
|
||
? '<div class="hero-meta">' + escapeHtml(metaParts.join(" · ")) + "</div>"
|
||
: "";
|
||
|
||
var dots = "";
|
||
if (items.length > 1) {
|
||
dots = '<div class="hero-dots">';
|
||
for (var d = 0; d < items.length; d++) {
|
||
dots += '<span class="hero-dot' + (d === idx ? " on" : "") + '"></span>';
|
||
}
|
||
dots += "</div>";
|
||
}
|
||
|
||
var hadPlayFocus = document.activeElement && document.activeElement.id === "hero-play";
|
||
var hadInfoFocus = document.activeElement && document.activeElement.id === "hero-info";
|
||
|
||
content.innerHTML =
|
||
badgeHtml +
|
||
titleBlock +
|
||
metaHtml +
|
||
'<div class="hero-actions">' +
|
||
'<button class="btn primary focusable" id="hero-play">Afspelen</button>' +
|
||
'<button class="btn focusable" id="hero-info">Meer info</button>' +
|
||
"</div>" +
|
||
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 =
|
||
'<div class="hero" id="home-hero">' +
|
||
'<div class="hero-bg" id="hero-bg"></div>' +
|
||
'<div class="hero-scrim"></div>' +
|
||
'<div class="hero-content" id="hero-content"></div>' +
|
||
"</div>" +
|
||
'<div class="rows" id="home-rows"></div>';
|
||
|
||
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 =
|
||
'<div class="row-head"><div class="row-title">' +
|
||
escapeHtml(title) +
|
||
"</div></div>";
|
||
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 =
|
||
'<div class="poster">' +
|
||
'<div class="poster-ph">' + escapeHtml(letter) + "</div>" +
|
||
(url
|
||
? '<div class="poster-img" style="background-image:url(\'' +
|
||
String(url).replace(/'/g, "%27") +
|
||
"')\"></div>"
|
||
: "") +
|
||
(progress > 0
|
||
? '<div class="poster-bar"><i style="width:' + progress + '%"></i></div>'
|
||
: "") +
|
||
"</div>" +
|
||
'<div class="tile-title">' + escapeHtml(item.title || "") + "</div>";
|
||
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 =
|
||
'<div class="pane-title">' + escapeHtml(title) + "</div>" +
|
||
'<div class="subnav" id="browse-sub-' + paneKey + '"></div>' +
|
||
'<div class="grid" id="browse-grid-' + paneKey + '"></div>';
|
||
|
||
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) + '<span class="chip-line"></span>';
|
||
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 = '<p class="muted" style="font-size:16px;padding:20px">Geen items</p>';
|
||
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 =
|
||
'<div class="search-wrap">' +
|
||
'<div class="pane-title">Zoeken</div>' +
|
||
'<div class="search-query" id="search-q">' + escapeHtml(state.searchQ || " ") + "</div>" +
|
||
'<div class="keyboard" id="search-kb"></div>' +
|
||
'<div class="grid" id="search-grid" style="padding-left:0;padding-right:0"></div>' +
|
||
"</div>";
|
||
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 =
|
||
'<div class="settings-wrap">' +
|
||
'<div class="pane-title">Instellingen</div>' +
|
||
'<p class="muted" style="margin-bottom:20px">Profiel: ' +
|
||
escapeHtml(p ? p.name : "—") + "</p>" +
|
||
'<button class="settings-row focusable" id="set-switch">Profiel wisselen</button>' +
|
||
'<button class="settings-row focusable" id="set-logout">Uitloggen / apparaat ontkoppelen</button>' +
|
||
"</div>";
|
||
$("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 = '<span class="muted">Geen afspeelbare bestanden</span>';
|
||
} 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 =
|
||
'<div class="ep-title">Afl. ' + ep.episodeNumber + " · " + escapeHtml(ep.title || "") + "</div>" +
|
||
'<div class="ep-meta">' + escapeHtml((ep.overview || "").slice(0, 140)) + "</div>";
|
||
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:<br/>" + (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:<br/>" + msg + " (regel " + line + ")";
|
||
}
|
||
return false;
|
||
};
|
||
|
||
if (document.readyState === "loading") {
|
||
document.addEventListener("DOMContentLoaded", bootOnce);
|
||
}
|
||
bootOnce();
|
||
setTimeout(bootOnce, 100);
|
||
setTimeout(bootOnce, 500);
|
||
})(window); |