(function (global) { var usingAvplay = false; var htmlVideo = null; var listeners = {}; var hideTimer = null; var tickTimer = null; var webapisLoaded = false; var currentUrl = ""; var currentTitle = ""; var externalCues = []; var externalLabel = ""; var silentText = false; var lastAvSubText = ""; var playSession = 0; var fallbackOnce = false; function $(id) { return document.getElementById(id); } function hostOf(url) { try { var m = String(url).match(/^https?:\/\/([^\/]+)/i); return m ? m[1] : "?"; } catch (e) { return "?"; } } function loadWebapis(cb) { if (webapisLoaded || (global.webapis && webapis.avplay)) { webapisLoaded = true; cb(true); return; } var s = document.createElement("script"); s.src = "$WEBAPIS/webapis/webapis.js"; s.onload = function () { webapisLoaded = true; cb(!!(global.webapis && webapis.avplay)); }; s.onerror = function () { cb(false); }; document.head.appendChild(s); } function hasAvplay() { return !!(global.webapis && webapis.avplay); } function ensureAvObject() { var wrap = $("av-object-wrap"); if (!wrap) return null; wrap.style.display = "block"; wrap.style.position = "absolute"; wrap.style.left = "0"; wrap.style.top = "0"; wrap.style.width = "100%"; wrap.style.height = "100%"; var obj = document.getElementById("av-player"); if (!obj) { obj = document.createElement("object"); obj.id = "av-player"; obj.type = "application/avplayer"; obj.setAttribute( "style", "position:absolute;left:0;top:0;width:1920px;height:1080px;" ); wrap.appendChild(obj); } return obj; } function showUi(show, opts) { opts = opts || {}; var ui = $("player-ui"); if (!ui) return; if (show) ui.classList.remove("fade"); else ui.classList.add("fade"); clearTimeout(hideTimer); if (show) { if (opts.focusPlay) { var playBtn = $("btn-play"); if (playBtn) setTimeout(function () { playBtn.focus(); }, 30); } hideTimer = setTimeout(function () { if ($("player-menu") && !$("player-menu").classList.contains("hidden")) return; var ae = document.activeElement; if (ae && ae.className && String(ae.className).indexOf("pctrl") >= 0) { /* focus in controls: niet auto-verbergen */ return; } ui.classList.add("fade"); }, 6000); } } function isUiVisible() { var ui = $("player-ui"); return !!(ui && !ui.classList.contains("fade")); } function setPlayingIcon(playing) { var btn = $("btn-play"); if (!btn) return; /* Geen fancy unicode — ontbreekt vaak op Tizen-fonts */ btn.textContent = playing ? "Pauze" : "Play"; } function safeAvClose() { if (!hasAvplay()) return; try { var st = ""; try { st = webapis.avplay.getState(); } catch (e0) { st = ""; } if (st === "PLAYING" || st === "PAUSED" || st === "READY") { try { webapis.avplay.stop(); } catch (e1) {} } if (st && st !== "NONE") { try { webapis.avplay.close(); } catch (e2) {} } } catch (e) { try { webapis.avplay.close(); } catch (e3) {} } } function destroyAvObject() { safeAvClose(); var wrap = $("av-object-wrap"); if (wrap) { wrap.innerHTML = ""; wrap.style.display = "none"; } } function formatTime(ms) { if (!ms || ms < 0) return "0:00"; var s = Math.floor(ms / 1000); var m = Math.floor(s / 60); var h = Math.floor(m / 60); s = s % 60; m = m % 60; function two(n) { return n < 10 ? "0" + n : String(n); } if (h > 0) return h + ":" + two(m) + ":" + two(s); return m + ":" + two(s); } function getPositionMs() { try { if (usingAvplay) return webapis.avplay.getCurrentTime() || 0; if (htmlVideo) return (htmlVideo.currentTime || 0) * 1000; } catch (e) {} return 0; } function getDurationMs() { try { if (usingAvplay) return webapis.avplay.getDuration() || 0; if (htmlVideo) return (htmlVideo.duration || 0) * 1000; } catch (e) {} return 0; } function setSubOverlay(text) { var el = $("player-subs"); if (!el) return; if (!text || silentText) { el.classList.add("hidden"); el.textContent = ""; return; } el.textContent = text; el.classList.remove("hidden"); } function updateExternalCue() { if (!externalCues.length || silentText) { if (!lastAvSubText) setSubOverlay(""); return; } var pos = getPositionMs(); var text = ""; for (var i = 0; i < externalCues.length; i++) { var c = externalCues[i]; if (pos >= c.start && pos < c.end) { text = c.text; break; } } setSubOverlay(text); } function updateProgress() { var pos = getPositionMs(); var dur = getDurationMs(); var fill = $("player-fill"); var time = $("player-time"); if (fill && dur > 0) fill.style.width = Math.min(100, (pos / dur) * 100) + "%"; if (time) time.textContent = formatTime(pos) + " / " + formatTime(dur); updateExternalCue(); } function startTick() { stopTick(); tickTimer = setInterval(updateProgress, 400); } function stopTick() { if (tickTimer) clearInterval(tickTimer); tickTimer = null; } function setError(msg) { var el = $("player-error"); if (!el) return; if (msg) { el.textContent = msg; el.classList.remove("hidden"); } else { el.classList.add("hidden"); } } function clearExternal() { externalCues = []; externalLabel = ""; lastAvSubText = ""; setSubOverlay(""); } function stop() { playSession += 1; fallbackOnce = false; stopTick(); setError(null); clearExternal(); silentText = false; usingAvplay = false; destroyAvObject(); if (htmlVideo) { try { htmlVideo.onplaying = null; htmlVideo.onended = null; htmlVideo.onerror = null; htmlVideo.removeAttribute("src"); htmlVideo.load(); } catch (e) {} htmlVideo.classList.add("hidden"); } } function playWithHtml(url, note) { var session = playSession; usingAvplay = false; setError(null); htmlVideo = $("html-player"); if (!htmlVideo) { setError("Geen HTML5-speler beschikbaar"); return; } htmlVideo.classList.remove("hidden"); htmlVideo.setAttribute( "style", "position:absolute;left:0;top:0;width:100%;height:100%;background:#000;" ); try { htmlVideo.onplaying = null; htmlVideo.onended = null; htmlVideo.onerror = null; } catch (e0) {} htmlVideo.onplaying = function () { if (session !== playSession) return; setError(null); setPlayingIcon(true); }; htmlVideo.onended = function () { if (session !== playSession) return; if (listeners.onEnded) listeners.onEnded(); }; htmlVideo.onerror = function () { if (session !== playSession) return; setError("Kan stream niet openen.\nHost: " + hostOf(url)); }; function tryPlay(attempt) { if (session !== playSession) return; try { if (!htmlVideo.src || htmlVideo.src.indexOf(url.split("?")[0]) < 0) { htmlVideo.src = url; } var p = htmlVideo.play(); if (p && p.catch) { p.catch(function (e) { if (session !== playSession) return; var msg = (e && e.message) || String(e); if (/interrupted|AbortError/i.test(msg) && attempt < 3) { setTimeout(function () { tryPlay(attempt + 1); }, 300 * attempt); return; } setError( "Afspelen mislukt op deze TV.\nHost: " + hostOf(url) + (note ? "\n(" + note + ")" : "") ); }); } startTick(); } catch (e) { if (attempt < 3) { setTimeout(function () { tryPlay(attempt + 1); }, 300); } else { setError("HTML5 crash: " + (e.message || e)); } } } setTimeout(function () { tryPlay(1); }, 50); } function fallbackHtml(url, reason) { if (fallbackOnce) return; fallbackOnce = true; /* AVPlay-callbacks doden + decoder vrijgeven vóór HTML5 */ playSession += 1; var session = playSession; usingAvplay = false; destroyAvObject(); setError(null); setTimeout(function () { if (session !== playSession) return; playWithHtml(url, reason || null); }, 450); } function playWithAvplay(url) { var session = playSession; fallbackOnce = false; usingAvplay = true; var hv = $("html-player"); if (hv) { try { hv.onplaying = null; hv.onended = null; hv.onerror = null; hv.removeAttribute("src"); hv.load(); } catch (eH) {} hv.classList.add("hidden"); } ensureAvObject(); setTimeout(function () { if (session !== playSession) return; try { safeAvClose(); } catch (eClose) {} setTimeout(function () { if (session !== playSession) return; try { webapis.avplay.open(url); try { webapis.avplay.setDisplayMethod("PLAYER_DISPLAY_MODE_AUTO_ASPECT_RATIO"); } catch (e1) {} webapis.avplay.setDisplayRect(0, 0, 1920, 1080); try { webapis.avplay.setStreamingProperty( "USER_AGENT", "Mozilla/5.0 (SMART-TV; Linux; Tizen 6.0) AppleWebKit/537.36" ); } catch (e2) {} webapis.avplay.setListener({ onbufferingstart: function () {}, onbufferingprogress: function () {}, onbufferingcomplete: function () {}, oncurrentplaytime: function () { if (session !== playSession) return; updateProgress(); }, onsubtitlechange: function (duration, text) { if (session !== playSession) return; if (externalCues.length) return; var s = text; if (Object.prototype.toString.call(text) === "[object Array]") { s = text.join("\n"); } else if (text && typeof text === "object") { s = text.text || text.Data || text.data || ""; } lastAvSubText = String(s || "").replace(/<[^>]+>/g, ""); if (!silentText) setSubOverlay(lastAvSubText); }, onstreamcompleted: function () { if (session !== playSession) return; stop(); if (listeners.onEnded) listeners.onEnded(); }, onerror: function (type) { if (session !== playSession) return; fallbackHtml(url, String(type || "error")); if (listeners.onError) listeners.onError(type); }, }); webapis.avplay.prepareAsync( function () { if (session !== playSession) return; try { var st = ""; try { st = webapis.avplay.getState(); } catch (eS) {} if (st !== "READY" && st !== "PAUSED" && st !== "PLAYING") { fallbackHtml(url, "AVPlay state " + st); return; } webapis.avplay.play(); startTick(); setError(null); setPlayingIcon(true); } catch (ePlay) { fallbackHtml(url, "play"); } }, function (e) { if (session !== playSession) return; fallbackHtml(url, e && (e.message || e.name) ? String(e.message || e.name) : "prepare"); } ); } catch (e) { if (session !== playSession) return; fallbackHtml(url, e.message || String(e)); } }, 150); }, 80); } function playUrl(url, title) { stop(); playSession += 1; fallbackOnce = false; currentUrl = url; currentTitle = title || ""; $("player-title").textContent = currentTitle; setError(null); setPlayingIcon(true); showUi(true, { focusPlay: true }); if (/^http:\/\//i.test(url)) { playWithHtml(url, null); return; } loadWebapis(function (ok) { if (ok && hasAvplay()) playWithAvplay(url); else playWithHtml(url, null); }); } function parseExtra(extra) { if (!extra) return {}; if (typeof extra === "object") return extra; try { return JSON.parse(extra); } catch (e) { return { raw: String(extra) }; } } function langLabel(code) { var c = String(code || "").trim().toLowerCase(); if (!c || c === "und" || c === "unknown" || c === "null") return ""; if (c === "nl" || c === "dut" || c === "nld") return "Nederlands"; if (c === "en" || c === "eng") return "Engels"; if (c === "de" || c === "ger" || c === "deu") return "Duits"; if (c === "fr" || c === "fre" || c === "fra") return "Frans"; if (c === "es" || c === "spa") return "Spaans"; if (c === "it" || c === "ita") return "Italiaans"; if (c === "pt" || c === "por") return "Portugees"; if (c === "pl" || c === "pol") return "Pools"; if (c === "ru" || c === "rus") return "Russisch"; if (c === "ja" || c === "jpn") return "Japans"; if (c === "zh" || c === "chi" || c === "zho") return "Chinees"; return c.toUpperCase(); } function isJunkLang(v) { var s = String(v || "").trim().toLowerCase(); if (!s) return true; if (s.indexOf("/") >= 0) return true; /* text/plain, application/x-subrip */ if (s.indexOf("text") === 0) return true; if (s === "plain" || s === "utf-8" || s === "utf8") return true; if (s === "und" || s === "unknown" || s === "null" || s === "undefined") return true; if (/^(srt|ass|ssa|vtt|sub|idx|pgs|hdmv)$/.test(s)) return true; return false; } function pickLangFromExtra(extra) { var keys = [ "track_lang", "language", "lang", "language_code", "Language", "lang_code", ]; for (var i = 0; i < keys.length; i++) { var v = extra[keys[i]]; if (v && typeof v === "object") v = v.code || v.name || ""; if (!isJunkLang(v)) return String(v).trim(); } return ""; } function trackLabel(type, index, extra) { var lang = pickLangFromExtra(extra); var nice = langLabel(lang); var channels = extra.channels || extra.audio_channels || ""; if (typeof channels === "object") channels = ""; var base = nice || (type === "AUDIO" ? "Audio " + (index + 1) : "Ondertitel " + (index + 1)); if (channels && String(channels).match(/^\d+$/)) base += " · " + channels + "ch"; return base; } function listAvTracks(type) { var out = []; if (!usingAvplay || !hasAvplay()) return out; try { var info = webapis.avplay.getTotalTrackInfo() || []; for (var i = 0; i < info.length; i++) { var t = info[i]; if (!t) continue; var tType = String(t.type || "").toUpperCase(); if (tType !== type) continue; var idx = typeof t.index === "number" ? t.index : i; var extra = parseExtra(t.extra_info); out.push({ type: type, index: idx, label: trackLabel(type, idx, extra), lang: extra.track_lang || extra.language || "", }); } } catch (e) {} return out; } function listHtmlAudio() { var out = []; if (!htmlVideo) return out; var tracks = htmlVideo.audioTracks; if (!tracks || !tracks.length) return out; for (var i = 0; i < tracks.length; i++) { var t = tracks[i]; var nice = langLabel(t.language || "") || t.label || ("Audio " + (i + 1)); out.push({ type: "AUDIO", index: i, label: nice, lang: t.language || "" }); } return out; } function listHtmlText() { var out = []; if (!htmlVideo) return out; var tracks = htmlVideo.textTracks; if (!tracks || !tracks.length) return out; for (var i = 0; i < tracks.length; i++) { var t = tracks[i]; var kind = String(t.kind || "").toLowerCase(); if (kind && kind !== "subtitles" && kind !== "captions") continue; var nice = langLabel(t.language || "") || t.label || ("Ondertitel " + (i + 1)); out.push({ type: "TEXT", index: i, label: nice, lang: t.language || "" }); } return out; } function listAudioTracks() { return usingAvplay ? listAvTracks("AUDIO") : listHtmlAudio(); } function listTextTracks() { var embedded = usingAvplay ? listAvTracks("TEXT") : listHtmlText(); if (externalCues.length) { embedded = embedded.concat([{ type: "EXTERNAL", index: -1, label: externalLabel || "OpenSubtitles", lang: "", }]); } return embedded; } function selectAudio(index) { try { if (usingAvplay) { webapis.avplay.setSelectTrack("AUDIO", index); } else if (htmlVideo && htmlVideo.audioTracks) { for (var i = 0; i < htmlVideo.audioTracks.length; i++) { htmlVideo.audioTracks[i].enabled = i === index; } } } catch (e) {} } function disableText() { silentText = true; lastAvSubText = ""; setSubOverlay(""); try { if (usingAvplay) { webapis.avplay.setSilentSubtitle(true); } else if (htmlVideo && htmlVideo.textTracks) { for (var i = 0; i < htmlVideo.textTracks.length; i++) { htmlVideo.textTracks[i].mode = "disabled"; } } } catch (e) {} } function selectText(index) { silentText = false; try { if (usingAvplay) { webapis.avplay.setSilentSubtitle(false); webapis.avplay.setSelectTrack("TEXT", index); } else if (htmlVideo && htmlVideo.textTracks) { for (var i = 0; i < htmlVideo.textTracks.length; i++) { htmlVideo.textTracks[i].mode = i === index ? "showing" : "disabled"; } } } catch (e) {} } function parseTimeToMs(raw) { var s = String(raw).trim().replace(",", "."); var parts = s.split(":"); if (parts.length < 3) return 0; var h = parseInt(parts[0], 10) || 0; var m = parseInt(parts[1], 10) || 0; var sec = parseFloat(parts[2]) || 0; return Math.round(((h * 60 + m) * 60 + sec) * 1000); } function parseSrt(text) { var blocks = String(text).replace(/\r/g, "").split(/\n\n+/); var cues = []; for (var i = 0; i < blocks.length; i++) { var lines = blocks[i].split("\n").filter(function (l) { return l.length; }); if (lines.length < 2) continue; var timeLine = lines[0].indexOf("-->") >= 0 ? lines[0] : lines[1]; if (!timeLine || timeLine.indexOf("-->") < 0) continue; var times = timeLine.split("-->"); var start = parseTimeToMs(times[0]); var end = parseTimeToMs(times[1]); var bodyStart = lines[0].indexOf("-->") >= 0 ? 1 : 2; var body = lines.slice(bodyStart).join("\n").replace(/<[^>]+>/g, ""); if (end > start && body) cues.push({ start: start, end: end, text: body }); } return cues; } function parseVtt(text) { var cleaned = String(text).replace(/\r/g, ""); if (cleaned.indexOf("WEBVTT") === 0) { cleaned = cleaned.replace(/^WEBVTT[^\n]*\n+/, ""); } return parseSrt(cleaned); } function b64ToText(b64) { try { var bin = atob(b64); try { return decodeURIComponent(escape(bin)); } catch (e1) { return bin; } } catch (e) { return ""; } } function applyExternalSubtitle(payload) { var text = b64ToText(payload.contentBase64 || ""); if (!text) throw new Error("Lege ondertitel"); var format = String(payload.format || "srt").toLowerCase(); var cues = format === "vtt" ? parseVtt(text) : parseSrt(text); if (!cues.length) throw new Error("Kon ondertitel niet lezen"); externalCues = cues; externalLabel = payload.label || payload.fileName || "OpenSubtitles"; silentText = false; lastAvSubText = ""; try { if (usingAvplay) webapis.avplay.setSilentSubtitle(true); else if (htmlVideo && htmlVideo.textTracks) { for (var i = 0; i < htmlVideo.textTracks.length; i++) { htmlVideo.textTracks[i].mode = "disabled"; } } } catch (e) {} updateExternalCue(); return cues.length; } function togglePause() { showUi(true); try { if (usingAvplay) { var state = webapis.avplay.getState(); if (state === "PLAYING") { webapis.avplay.pause(); setPlayingIcon(false); } else if (state === "PAUSED") { webapis.avplay.play(); setPlayingIcon(true); } } else if (htmlVideo) { if (htmlVideo.paused) { htmlVideo.play(); setPlayingIcon(true); } else { htmlVideo.pause(); setPlayingIcon(false); } } } catch (e) {} } function seek(deltaMs) { showUi(true); try { if (usingAvplay) { var pos = webapis.avplay.getCurrentTime() + deltaMs; if (pos < 0) pos = 0; webapis.avplay.seekTo(pos); } else if (htmlVideo) { htmlVideo.currentTime = Math.max(0, htmlVideo.currentTime + deltaMs / 1000); } } catch (e) {} updateProgress(); } global.McPlayer = { playUrl: playUrl, stop: stop, togglePause: togglePause, seek: seek, showUi: showUi, isUiVisible: isUiVisible, setPlayingIcon: setPlayingIcon, getPositionMs: getPositionMs, getDurationMs: getDurationMs, listAudioTracks: listAudioTracks, listTextTracks: listTextTracks, selectAudio: selectAudio, selectText: selectText, disableText: disableText, applyExternalSubtitle: applyExternalSubtitle, langLabel: langLabel, on: function (name, fn) { listeners[name] = fn; }, }; })(window);