Spaces:
Running
Running
Upload index.html
Browse files- index.html +76 -4
index.html
CHANGED
|
@@ -114,6 +114,14 @@
|
|
| 114 |
.poster-title{font-size:12.5px; font-weight:600; margin-top:6px; line-height:1.25;
|
| 115 |
display:-webkit-box; -webkit-line-clamp:1; -webkit-box-orient:vertical; overflow:hidden;}
|
| 116 |
.poster-sub{font-size:11px; color:var(--muted); margin-top:2px;}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 117 |
.rank-num{position:absolute; left:6px; bottom:0; font-size:58px; font-weight:900; line-height:1; letter-spacing:-3px; color:transparent;
|
| 118 |
-webkit-text-stroke:2px rgba(255,255,255,.92); text-shadow:0 4px 14px rgba(0,0,0,.65); pointer-events:none; z-index:2;}
|
| 119 |
.poster-art:has(.rank-num)::after{content:''; position:absolute; inset:auto 0 0 0; height:46%; background:linear-gradient(transparent,rgba(0,0,0,.7)); pointer-events:none;}
|
|
@@ -2876,10 +2884,10 @@ const I18N_EXTRA9 = {
|
|
| 2876 |
};
|
| 2877 |
Object.keys(I18N_EXTRA9).forEach(l => { I18N[l] = Object.assign(I18N[l] || {}, I18N_EXTRA9[l]); });
|
| 2878 |
const I18N_EXTRA10 = {
|
| 2879 |
-
sw: { tr_trending: 'Zinazovuma Sasa', tr_new: 'Mpya Karibuni', tr_more: 'Zaidi za Kutazama' },
|
| 2880 |
-
en: { tr_trending: 'Trending Now', tr_new: 'New Releases', tr_more: 'More to Watch' },
|
| 2881 |
-
ar: { tr_trending: 'الأكثر رواجاً الآن', tr_new: 'أحدث الإصدارات', tr_more: 'المزيد للمشاهدة' },
|
| 2882 |
-
fr: { tr_trending: 'Tendances actuelles', tr_new: 'Nouveautés', tr_more: 'À découvrir' }
|
| 2883 |
};
|
| 2884 |
Object.keys(I18N_EXTRA10).forEach(l => { I18N[l] = Object.assign(I18N[l] || {}, I18N_EXTRA10[l]); });
|
| 2885 |
function T(k, v) { return t(k, v); }
|
|
@@ -3582,11 +3590,73 @@ async function fetchChannelPosters() {
|
|
| 3582 |
|
| 3583 |
// Inarudisha orodha bapa (flat) ya channels zote za Live TV — inatumika
|
| 3584 |
// na loadLive() (kuonyesha kwa rails) na search (kuchuja kwa query).
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3585 |
async function fetchAllLiveItems() {
|
| 3586 |
const items = [];
|
| 3587 |
let anyOk = false, lastErr = null;
|
| 3588 |
const posters = await fetchChannelPosters();
|
| 3589 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3590 |
try {
|
| 3591 |
const res = await fetch(JAYNES_MAX_TV_BASE + '/api/content/channels', {
|
| 3592 |
headers: { 'Authorization': 'Bearer ' + JAYNES_MAX_TV_KEY, 'apikey': JAYNES_MAX_TV_KEY },
|
|
@@ -6031,6 +6101,7 @@ function stopMediaOnly(){
|
|
| 6031 |
currentBlobUrls = [];
|
| 6032 |
}
|
| 6033 |
function stopPlayer(){
|
|
|
|
| 6034 |
playerEl.classList.remove('mini');
|
| 6035 |
clearUpNext();
|
| 6036 |
if (document.pictureInPictureElement) { document.exitPictureInPicture().catch(()=>{}); }
|
|
@@ -6739,6 +6810,7 @@ function failPlayback(gen, showFn){
|
|
| 6739 |
}
|
| 6740 |
|
| 6741 |
async function openLiveChannel(item){
|
|
|
|
| 6742 |
openPlayerShell(item.title);
|
| 6743 |
liveFb = { title: item.title, used: false, pending: false };
|
| 6744 |
try {
|
|
|
|
| 114 |
.poster-title{font-size:12.5px; font-weight:600; margin-top:6px; line-height:1.25;
|
| 115 |
display:-webkit-box; -webkit-line-clamp:1; -webkit-box-orient:vertical; overflow:hidden;}
|
| 116 |
.poster-sub{font-size:11px; color:var(--muted); margin-top:2px;}
|
| 117 |
+
.jtv-bar{position:absolute; left:10px; right:10px; top:10px; padding:8px 10px; border-radius:12px; background:rgba(0,0,0,.6); color:#fff; font-size:13px; z-index:5; transition:opacity .3s;}
|
| 118 |
+
.jtv-bar.hide{opacity:0; pointer-events:none;}
|
| 119 |
+
.jtv-row{display:flex; gap:8px; align-items:center; flex-wrap:wrap;}
|
| 120 |
+
.jtv-live{background:#e5484d; border-radius:6px; padding:2px 7px; font-weight:800; font-size:11px; letter-spacing:.5px;}
|
| 121 |
+
.jtv-m{opacity:.8; font-size:12px; margin-top:2px;}
|
| 122 |
+
.jtv-b{margin-left:auto; background:rgba(255,255,255,.18); color:#fff; border:0; border-radius:8px; padding:5px 10px; font:inherit; font-size:12px;}
|
| 123 |
+
.jtv-epg{margin-top:6px; max-height:38vh; overflow:auto; border-top:1px solid rgba(255,255,255,.2); padding-top:6px;}
|
| 124 |
+
.jtv-epg div{padding:3px 0;} .jtv-epg span{display:inline-block; min-width:48px; opacity:.7;}
|
| 125 |
.rank-num{position:absolute; left:6px; bottom:0; font-size:58px; font-weight:900; line-height:1; letter-spacing:-3px; color:transparent;
|
| 126 |
-webkit-text-stroke:2px rgba(255,255,255,.92); text-shadow:0 4px 14px rgba(0,0,0,.65); pointer-events:none; z-index:2;}
|
| 127 |
.poster-art:has(.rank-num)::after{content:''; position:absolute; inset:auto 0 0 0; height:46%; background:linear-gradient(transparent,rgba(0,0,0,.7)); pointer-events:none;}
|
|
|
|
| 2884 |
};
|
| 2885 |
Object.keys(I18N_EXTRA9).forEach(l => { I18N[l] = Object.assign(I18N[l] || {}, I18N_EXTRA9[l]); });
|
| 2886 |
const I18N_EXTRA10 = {
|
| 2887 |
+
sw: { jtv_next: 'Inayofuata', jtv_schedule: 'Ratiba', jtv_down: 'JaynesTV haipatikani kwa sasa', tr_trending: 'Zinazovuma Sasa', tr_new: 'Mpya Karibuni', tr_more: 'Zaidi za Kutazama' },
|
| 2888 |
+
en: { jtv_next: 'Next', jtv_schedule: 'Schedule', jtv_down: 'JaynesTV is unavailable right now', tr_trending: 'Trending Now', tr_new: 'New Releases', tr_more: 'More to Watch' },
|
| 2889 |
+
ar: { jtv_next: 'التالي', jtv_schedule: 'الجدول', jtv_down: 'JaynesTV غير متاح حاليًا', tr_trending: 'الأكثر رواجاً الآن', tr_new: 'أحدث الإصدارات', tr_more: 'المزيد للمشاهدة' },
|
| 2890 |
+
fr: { jtv_next: 'Ensuite', jtv_schedule: 'Programme', jtv_down: 'JaynesTV est indisponible', tr_trending: 'Tendances actuelles', tr_new: 'Nouveautés', tr_more: 'À découvrir' }
|
| 2891 |
};
|
| 2892 |
Object.keys(I18N_EXTRA10).forEach(l => { I18N[l] = Object.assign(I18N[l] || {}, I18N_EXTRA10[l]); });
|
| 2893 |
function T(k, v) { return t(k, v); }
|
|
|
|
| 3590 |
|
| 3591 |
// Inarudisha orodha bapa (flat) ya channels zote za Live TV — inatumika
|
| 3592 |
// na loadLive() (kuonyesha kwa rails) na search (kuchuja kwa query).
|
| 3593 |
+
// ── JAYNESTV: live TV ya kubuni kutoka video za dataset yako (ratiba: build_jaynestv.py) ──
|
| 3594 |
+
const JTV_URL = 'https://huggingface.co/datasets/jaynesmaxtv/movie-file/resolve/main/jaynestv/schedule.json';
|
| 3595 |
+
let jtvP = null, jtvAt = 0;
|
| 3596 |
+
function jtvSchedule(force) { // ratiba inahifadhiwa dakika 5; ikikosekana JaynesTV haionekani
|
| 3597 |
+
if (!jtvP || force || Date.now() - jtvAt > 300000) {
|
| 3598 |
+
jtvAt = Date.now();
|
| 3599 |
+
jtvP = fetch(JTV_URL, { cache: 'no-cache' }).then(r => r.ok ? r.json() : null)
|
| 3600 |
+
.then(s => (s && Array.isArray(s.items) && s.items.length && s.total > 0) ? s : null).catch(() => null);
|
| 3601 |
+
}
|
| 3602 |
+
return jtvP;
|
| 3603 |
+
}
|
| 3604 |
+
function jtvNow(s, nowMs) { // kipindi cha sasa + vinavyofuata, kwa saa: kila mtazamaji anaona kile kile wakati ule ule
|
| 3605 |
+
let pos = ((((nowMs - (Date.parse(s.epoch) || 0)) / 1000) % s.total) + s.total) % s.total, i = 0;
|
| 3606 |
+
while (i < s.items.length - 1 && pos >= s.items[i].dur) { pos -= s.items[i].dur; i++; }
|
| 3607 |
+
const upcoming = []; let at = nowMs + (s.items[i].dur - pos) * 1000;
|
| 3608 |
+
for (let k = 1; k <= 8; k++) { const it = s.items[(i + k) % s.items.length]; upcoming.push({ item: it, at }); at += it.dur * 1000; }
|
| 3609 |
+
return { idx: i, item: s.items[i], offset: pos, left: s.items[i].dur - pos, upcoming };
|
| 3610 |
+
}
|
| 3611 |
+
async function openJaynesTV() {
|
| 3612 |
+
openPlayerShell('JaynesTV');
|
| 3613 |
+
const s = await jtvSchedule(true);
|
| 3614 |
+
if (!s) return statusMsg('<span style="color:#ffb4ab">' + esc(T('jtv_down')) + '</span>');
|
| 3615 |
+
stage.style.position = 'relative';
|
| 3616 |
+
stage.innerHTML = '<video id="vid" controls autoplay playsinline></video><div class="jtv-bar" id="jtvBar"></div>';
|
| 3617 |
+
const v = document.getElementById('vid'), bar = document.getElementById('jtvBar');
|
| 3618 |
+
attachPlayerEnhancements(v); ensureSound(v);
|
| 3619 |
+
const bad = new Set(); let cur = null, epg = false, hideT = null, last = null;
|
| 3620 |
+
const hhmm = ms => new Date(ms).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });
|
| 3621 |
+
const show = (ms) => { bar.classList.remove('hide'); clearTimeout(hideT); if (ms) hideT = setTimeout(() => { if (!epg) bar.classList.add('hide'); }, ms); };
|
| 3622 |
+
const draw = (n, it) => {
|
| 3623 |
+
const nx = n.upcoming[0];
|
| 3624 |
+
bar.innerHTML = '<div class="jtv-row"><span class="jtv-live">● LIVE</span><b>' + esc(it.title) + '</b>' + (it.dj ? '<span class="jtv-m">' + esc(it.dj) + '</span>' : '') +
|
| 3625 |
+
'<button class="jtv-b" data-j="epg">' + esc(T('jtv_schedule')) + '</button></div>' +
|
| 3626 |
+
'<div class="jtv-m">' + esc(T('jtv_next')) + ': ' + esc(nx.item.title) + ' · ' + hhmm(nx.at) + '</div>' +
|
| 3627 |
+
(epg ? '<div class="jtv-epg">' + n.upcoming.map(u => '<div><span>' + hhmm(u.at) + '</span> ' + esc(u.item.title) + '</div>').join('') + '</div>' : '');
|
| 3628 |
+
};
|
| 3629 |
+
const sync = () => {
|
| 3630 |
+
const n = jtvNow(s, Date.now()), isBad = bad.has(n.idx);
|
| 3631 |
+
const it = isBad ? n.upcoming[0].item : n.item, off = isBad ? 0 : n.offset; // video mbovu: cheza inayofuata kutoka mwanzo
|
| 3632 |
+
if (cur !== it.url) {
|
| 3633 |
+
cur = it.url; v.src = it.url + '#t=' + Math.floor(off);
|
| 3634 |
+
v.addEventListener('loadedmetadata', () => { if (Math.abs(v.currentTime - off) > 3) v.currentTime = off; const p = v.play(); if (p && p.catch) p.catch(() => {}); }, { once: true });
|
| 3635 |
+
show(8000);
|
| 3636 |
+
} else if (!isBad && v.readyState > 1 && Math.abs(v.currentTime - off) > 4) v.currentTime = off; // rudi kwenye 'live'
|
| 3637 |
+
last = n; draw(n, it);
|
| 3638 |
+
};
|
| 3639 |
+
bar.onclick = (e) => { if (e.target.closest('[data-j="epg"]')) { epg = !epg; draw(last, last.item); show(epg ? 0 : 6000); } };
|
| 3640 |
+
v.addEventListener('ended', sync);
|
| 3641 |
+
v.addEventListener('play', sync);
|
| 3642 |
+
v.addEventListener('seeked', () => { if (last && Math.abs(v.currentTime - last.offset) > 6) sync(); });
|
| 3643 |
+
v.addEventListener('error', () => { if (last) { bad.add(last.idx); sync(); } });
|
| 3644 |
+
v.addEventListener('click', () => show(6000));
|
| 3645 |
+
const timer = setInterval(sync, 15000);
|
| 3646 |
+
window.__jtvStop = () => { clearInterval(timer); clearTimeout(hideT); };
|
| 3647 |
+
sync();
|
| 3648 |
+
}
|
| 3649 |
+
|
| 3650 |
async function fetchAllLiveItems() {
|
| 3651 |
const items = [];
|
| 3652 |
let anyOk = false, lastErr = null;
|
| 3653 |
const posters = await fetchChannelPosters();
|
| 3654 |
|
| 3655 |
+
try { // JaynesTV (ikiwa ratiba ipo): kadi inaonyesha kipindi kinachochezwa sasa
|
| 3656 |
+
const js = await jtvSchedule();
|
| 3657 |
+
if (js) { const n = jtvNow(js, Date.now()); items.push({ id: 'jaynestv', kind: 'live', title: n.item.title, poster: n.item.poster || js.logo || null, subtitle: 'JaynesTV', imgFallbackOn: true }); }
|
| 3658 |
+
} catch (_) {}
|
| 3659 |
+
|
| 3660 |
try {
|
| 3661 |
const res = await fetch(JAYNES_MAX_TV_BASE + '/api/content/channels', {
|
| 3662 |
headers: { 'Authorization': 'Bearer ' + JAYNES_MAX_TV_KEY, 'apikey': JAYNES_MAX_TV_KEY },
|
|
|
|
| 6101 |
currentBlobUrls = [];
|
| 6102 |
}
|
| 6103 |
function stopPlayer(){
|
| 6104 |
+
if (window.__jtvStop) { try { window.__jtvStop(); } catch (_) {} window.__jtvStop = null; }
|
| 6105 |
playerEl.classList.remove('mini');
|
| 6106 |
clearUpNext();
|
| 6107 |
if (document.pictureInPictureElement) { document.exitPictureInPicture().catch(()=>{}); }
|
|
|
|
| 6810 |
}
|
| 6811 |
|
| 6812 |
async function openLiveChannel(item){
|
| 6813 |
+
if (String(item.id) === 'jaynestv') return openJaynesTV();
|
| 6814 |
openPlayerShell(item.title);
|
| 6815 |
liveFb = { title: item.title, used: false, pending: false };
|
| 6816 |
try {
|