Focus-friendly VMC shell for Tizen with live TV and playback against the master API. Co-authored-by: Cursor <cursoragent@cursor.com>
764 lines
22 KiB
JavaScript
764 lines
22 KiB
JavaScript
(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);
|