Spaces:
Running
Running
Upload index.html
Browse files- index.html +116 -96
index.html
CHANGED
|
@@ -122,12 +122,6 @@
|
|
| 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 |
-
.jtv-logo{position:absolute; right:12px; bottom:56px; width:64px; max-height:40px; object-fit:contain; opacity:.75; z-index:4; pointer-events:none; filter:drop-shadow(0 1px 3px rgba(0,0,0,.6));}
|
| 126 |
-
.jtv-ad{position:absolute; left:12px; bottom:56px; z-index:6; background:#ffc53d; color:#1b1230; font-weight:900; font-size:11px; letter-spacing:1px; padding:3px 9px; border-radius:6px; display:none;}
|
| 127 |
-
.jtv-ad.on{display:block;}
|
| 128 |
-
.jtv-ad i{font-style:normal; font-weight:700; opacity:.8; margin-left:6px;}
|
| 129 |
-
.jtv-epg .now{color:#ffc53d; font-weight:700;}
|
| 130 |
-
.jtv-epg .k{font-size:10px; opacity:.6; margin-left:6px;}
|
| 131 |
.rank-num{position:absolute; left:6px; bottom:0; font-size:58px; font-weight:900; line-height:1; letter-spacing:-3px; color:transparent;
|
| 132 |
-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;}
|
| 133 |
.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;}
|
|
@@ -1221,6 +1215,13 @@
|
|
| 1221 |
/* Nafasi ya kusogeza (scroll) ili kipengele chenye focus kisifichwe na topbar / nav */
|
| 1222 |
.poster,.cw-card,.tab-chip,.settings-row,.dq-item,.tr-ep,.cast-item{scroll-margin:110px 3vw 110px 3vw;}
|
| 1223 |
</style>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1224 |
</head>
|
| 1225 |
<body>
|
| 1226 |
<div id="gate">
|
|
@@ -1491,7 +1492,17 @@ const PIXTVMAX_KEY = SUPABASE_ANON_KEY;
|
|
| 1491 |
const TRAILER_BASE = JM_CONFIG.CONTENT_URL + '/functions/v1/movie-trailer';
|
| 1492 |
const TRAILER_KEY = SUPABASE_ANON_KEY;
|
| 1493 |
|
| 1494 |
-
//
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1495 |
const TRANSLATED_ALL = JM_CONFIG.CONTENT_URL + '/functions/v1/movie-translated-all';
|
| 1496 |
const TR_MODEL_URL = 'https://huggingface.co/jaynesmaxtv/translated-recommender/resolve/main/model.json';
|
| 1497 |
let trModelP = null;
|
|
@@ -1500,14 +1511,6 @@ function loadTrModel() {
|
|
| 1500 |
return trModelP;
|
| 1501 |
}
|
| 1502 |
// Rails za mlalo: Trending Now (Top 10, kwa model au mpya zaidi) -> New Releases -> kila DJ (DJ Mack kwanza) -> Series -> More to Watch
|
| 1503 |
-
const TR_DJ_NOISE = /\b(movies?|filamu|zote|zilizotafsiriwa|zilizotafsiri|imetafsiriwa|iliyotafsiriwa|tafsiriwa|kiswahili|swahili|kwa|mpya|kali|translated|new|all|series|full)\b/gi;
|
| 1504 |
-
|
| 1505 |
-
function normDj(raw) { // jina safi la DJ (bila maneno ya ziada na bila tofauti za herufi kubwa/ndogo)
|
| 1506 |
-
const s = String(raw || '').replace(/\(.*?\)|\[.*?\]/g, ' ').replace(TR_DJ_NOISE, ' ').replace(/[|_\-\u2013\u2014:,.]+/g, ' ').replace(/\s+/g, ' ').trim();
|
| 1507 |
-
if (!s || /^dj$/i.test(s)) return '';
|
| 1508 |
-
return s.split(' ').map(w => /^dj$/i.test(w) ? 'DJ' : w.charAt(0).toUpperCase() + w.slice(1).toLowerCase()).join(' ');
|
| 1509 |
-
}
|
| 1510 |
-
|
| 1511 |
function buildTranslatedSections(items, model) {
|
| 1512 |
const pop = (model && model.popularity) || {}, sections = [], covered = new Set();
|
| 1513 |
const hot = items.filter(it => pop[it.id] > 0).sort((a, b) => pop[b.id] - pop[a.id]);
|
|
@@ -1559,41 +1562,13 @@ async function rpc(op, args = {}) {
|
|
| 1559 |
}
|
| 1560 |
|
| 1561 |
// ββ Share: kiungo cha moja kwa moja kwenye video (#/watch/ID) ββ
|
| 1562 |
-
// ββ Kushiriki na picha: bango (poster) au nembo ya chaneli inaambatanishwa ββ
|
| 1563 |
-
async function shareImageFile(src){
|
| 1564 |
-
if (!src) return null;
|
| 1565 |
-
try {
|
| 1566 |
-
const r = await fetch(src, { mode: 'cors' });
|
| 1567 |
-
if (!r.ok) return null;
|
| 1568 |
-
const b = await r.blob();
|
| 1569 |
-
if (!b.type || !b.type.startsWith('image/')) return null;
|
| 1570 |
-
const ext = (b.type.split('/')[1] || 'jpg').replace('jpeg', 'jpg').replace('svg+xml', 'svg');
|
| 1571 |
-
return new File([b], 'jaynesmax-' + Date.now() + '.' + ext, { type: b.type });
|
| 1572 |
-
} catch (_) { return null; }
|
| 1573 |
-
}
|
| 1574 |
-
|
| 1575 |
-
async function shareWithPoster(d, url, text){
|
| 1576 |
-
const poster = d.poster || d.logo || null;
|
| 1577 |
-
if (navigator.share) {
|
| 1578 |
-
const file = await shareImageFile(poster);
|
| 1579 |
-
const withFile = { title: text, text, url };
|
| 1580 |
-
if (file && navigator.canShare && navigator.canShare({ files: [file] })) withFile.files = [file];
|
| 1581 |
-
try { await navigator.share(withFile); return; }
|
| 1582 |
-
catch (e) { if (e && e.name === 'AbortError') return; }
|
| 1583 |
-
// Kama kushiriki na picha kumeshindikana, jaribu kushiriki kiungo na maelezo tu
|
| 1584 |
-
try { await navigator.share({ title: text, text, url }); } catch (_) {}
|
| 1585 |
-
return;
|
| 1586 |
-
}
|
| 1587 |
-
const clip = text + '\n' + url + (poster ? '\nPicha: ' + poster : '');
|
| 1588 |
-
try { await navigator.clipboard.writeText(clip); showToast(t('link_copied')); }
|
| 1589 |
-
catch (_) { prompt('Nakili kiungo:', clip); }
|
| 1590 |
-
}
|
| 1591 |
-
|
| 1592 |
async function shareTitle(d, se, ep){
|
| 1593 |
const base = location.origin + location.pathname.replace(/index\.html$/i, '');
|
| 1594 |
const url = base + '#/watch/' + d.id + (se != null && ep != null ? '?se=' + se + '&ep=' + ep : '');
|
| 1595 |
const text = d.title + ' β JAYNES MAX';
|
| 1596 |
-
|
|
|
|
|
|
|
| 1597 |
}
|
| 1598 |
|
| 1599 |
// ββ Watchlist (localStorage) ββ
|
|
@@ -2910,6 +2885,13 @@ const I18N_EXTRA10 = {
|
|
| 2910 |
fr: { jtv_next: 'Ensuite', jtv_schedule: 'Programme', jtv_down: 'JaynesTV est indisponible', tr_trending: 'Tendances actuelles', tr_new: 'NouveautΓ©s', tr_more: 'Γ dΓ©couvrir' }
|
| 2911 |
};
|
| 2912 |
Object.keys(I18N_EXTRA10).forEach(l => { I18N[l] = Object.assign(I18N[l] || {}, I18N_EXTRA10[l]); });
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2913 |
function T(k, v) { return t(k, v); }
|
| 2914 |
// Maandishi yaliyoandikwa kwenye HTML kupitia data-i18n / data-i18n-ph / data-i18n-title
|
| 2915 |
function applyI18nAttrs() {
|
|
@@ -3630,71 +3612,40 @@ function jtvNow(s, nowMs) { // kipindi cha sasa + vinavyofuata, kwa saa: kila mt
|
|
| 3630 |
}
|
| 3631 |
async function openJaynesTV() {
|
| 3632 |
openPlayerShell('JaynesTV');
|
| 3633 |
-
|
| 3634 |
if (!s) return statusMsg('<span style="color:#ffb4ab">' + esc(T('jtv_down')) + '</span>');
|
| 3635 |
stage.style.position = 'relative';
|
| 3636 |
-
stage.innerHTML = '<video id="vid" controls autoplay playsinline></video><div class="jtv-bar" id="jtvBar"></div>'
|
| 3637 |
-
|
| 3638 |
-
const v = document.getElementById('vid'), bar = document.getElementById('jtvBar'), adEl = document.getElementById('jtvAd');
|
| 3639 |
-
const logoEl = document.getElementById('jtvLogo'); if (logoEl) logoEl.onerror = () => logoEl.remove();
|
| 3640 |
attachPlayerEnhancements(v); ensureSound(v);
|
| 3641 |
-
const bad = new Set(); let cur = null, epg = false, hideT = null, last = null
|
| 3642 |
const hhmm = ms => new Date(ms).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });
|
| 3643 |
-
const kindOf = it => (it && it.kind) || 'program';
|
| 3644 |
const show = (ms) => { bar.classList.remove('hide'); clearTimeout(hideT); if (ms) hideT = setTimeout(() => { if (!epg) bar.classList.add('hide'); }, ms); };
|
| 3645 |
-
const nextProgram = n => (n.upcoming.find(u => kindOf(u.item) === 'program') || n.upcoming[0]);
|
| 3646 |
-
const stopHls = () => { if (hls) { try { hls.destroy(); } catch (_) {} hls = null; } };
|
| 3647 |
-
const playSrc = (url, off) => { // MP4 au HLS (.m3u8) β live ya studio ni HLS
|
| 3648 |
-
stopHls();
|
| 3649 |
-
const isHls = /\.m3u8(\?|$)/i.test(url);
|
| 3650 |
-
if (isHls && !v.canPlayType('application/vnd.apple.mpegurl') && window.Hls && Hls.isSupported()) {
|
| 3651 |
-
hls = new Hls(); hls.loadSource(url); hls.attachMedia(v);
|
| 3652 |
-
} else v.src = isHls ? url : url + '#t=' + Math.floor(off || 0);
|
| 3653 |
-
v.addEventListener('loadedmetadata', () => { if (!isHls && Math.abs(v.currentTime - off) > 3) v.currentTime = off; const p = v.play(); if (p && p.catch) p.catch(() => {}); }, { once: true });
|
| 3654 |
-
};
|
| 3655 |
const draw = (n, it) => {
|
| 3656 |
-
|
| 3657 |
-
|
| 3658 |
-
'<span class="jtv-m">Studio</span></div>';
|
| 3659 |
-
adEl.classList.remove('on'); return;
|
| 3660 |
-
}
|
| 3661 |
-
const k = kindOf(it), nx = nextProgram(n);
|
| 3662 |
-
adEl.classList.toggle('on', k === 'ad');
|
| 3663 |
-
if (k === 'ad') adEl.querySelector('i').textContent = Math.max(0, Math.ceil(n.left)) + 's';
|
| 3664 |
-
const nowTitle = k === 'program' ? it.title : (nx.item.title); // wakati wa tangazo/kitambulisho: "inaanza punde"
|
| 3665 |
-
bar.innerHTML = '<div class="jtv-row"><span class="jtv-live">β LIVE</span><b>' + (k === 'program' ? '' : 'Punde: ') + esc(nowTitle) + '</b>' +
|
| 3666 |
-
(k === 'program' && it.dj ? '<span class="jtv-m">' + esc(it.dj) + '</span>' : '') +
|
| 3667 |
'<button class="jtv-b" data-j="epg">' + esc(T('jtv_schedule')) + '</button></div>' +
|
| 3668 |
-
|
| 3669 |
-
(epg ? '<div class="jtv-epg">' +
|
| 3670 |
-
(k === 'program' ? '<div class="now"><span>' + hhmm(Date.now() - (it.dur - n.left) * 1000) + '</span> ' + esc(it.title) + '<span class="k">SASA</span></div>' : '') +
|
| 3671 |
-
n.upcoming.filter(u => kindOf(u.item) === 'program').map(u => '<div><span>' + hhmm(u.at) + '</span> ' + esc(u.item.title) + '</div>').join('') + '</div>' : '');
|
| 3672 |
};
|
| 3673 |
const sync = () => {
|
| 3674 |
-
const liveUrl = s.live && s.live.active && s.live.url;
|
| 3675 |
-
if (liveUrl) { // studio iko hewani: hamia live yenyewe
|
| 3676 |
-
if (!liveOn || cur !== liveUrl) { liveOn = true; cur = liveUrl; playSrc(liveUrl, 0); show(8000); }
|
| 3677 |
-
draw(null, null); return;
|
| 3678 |
-
}
|
| 3679 |
-
if (liveOn) { liveOn = false; cur = null; } // live imeisha: rudi kwenye ratiba
|
| 3680 |
const n = jtvNow(s, Date.now()), isBad = bad.has(n.idx);
|
| 3681 |
const it = isBad ? n.upcoming[0].item : n.item, off = isBad ? 0 : n.offset; // video mbovu: cheza inayofuata kutoka mwanzo
|
| 3682 |
if (cur !== it.url) {
|
| 3683 |
-
cur = it.url;
|
| 3684 |
-
if (
|
|
|
|
| 3685 |
} else if (!isBad && v.readyState > 1 && Math.abs(v.currentTime - off) > 4) v.currentTime = off; // rudi kwenye 'live'
|
| 3686 |
last = n; draw(n, it);
|
| 3687 |
};
|
| 3688 |
-
|
| 3689 |
-
bar.onclick = (e) => { if (e.target.closest('[data-j="epg"]') && last) { epg = !epg; draw(last, bad.has(last.idx) ? last.upcoming[0].item : last.item); show(epg ? 0 : 6000); } };
|
| 3690 |
v.addEventListener('ended', sync);
|
| 3691 |
v.addEventListener('play', sync);
|
| 3692 |
-
v.addEventListener('seeked', () => { if (
|
| 3693 |
-
v.addEventListener('error', () => { if (
|
| 3694 |
v.addEventListener('click', () => show(6000));
|
| 3695 |
-
const timer = setInterval(sync, 15000)
|
| 3696 |
-
|
| 3697 |
-
window.__jtvStop = () => { clearInterval(timer); clearInterval(tick); clearInterval(liveTimer); clearTimeout(hideT); stopHls(); };
|
| 3698 |
sync();
|
| 3699 |
}
|
| 3700 |
|
|
@@ -3762,11 +3713,12 @@ async function loadLive() {
|
|
| 3762 |
}
|
| 3763 |
|
| 3764 |
// ββ ZILIZOTAFSIRIWA (movies/series za DJ β mradi tofauti) ββ
|
| 3765 |
-
async function fetchTranslatedList() {
|
| 3766 |
const card = (m, idPrefix) => ({
|
| 3767 |
id: idPrefix + m.id, kind: 'translated', title: m.title,
|
| 3768 |
poster: m.thumb_url || m.poster_url || null, imgFallbackOn: true,
|
| 3769 |
subtitle: normDj(m.dj) || (m.category === 'series' ? 'Series' : 'Movie'), isSeries: m.category === 'series',
|
|
|
|
| 3770 |
});
|
| 3771 |
const r = await fetch(TRANSLATED_ALL, { headers: { apikey: SUPABASE_ANON_KEY } });
|
| 3772 |
if (!r.ok) throw new Error(T('err_generic') + ' ' + r.status);
|
|
@@ -3775,12 +3727,78 @@ async function fetchTranslatedList() { // movie-translated-all: orodha moja iliy
|
|
| 3775 |
return (d.movies || []).map(m => card(m, ''));
|
| 3776 |
}
|
| 3777 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3778 |
async function loadTranslated() {
|
| 3779 |
heroWrap.innerHTML = ''; tabsEl.style.display = 'none';
|
| 3780 |
renderSkeletonRails(2);
|
| 3781 |
try {
|
| 3782 |
const [items, model] = await Promise.all([fetchTranslatedList(), loadTrModel()]);
|
| 3783 |
-
|
|
|
|
| 3784 |
} catch (e) {
|
| 3785 |
railsEl.innerHTML = '';
|
| 3786 |
railsEl.appendChild(stateBox(t('err_failed', { e: esc(e.message || t('err_generic')) }), true, loadTranslated));
|
|
@@ -4104,7 +4122,9 @@ async function shareTranslatedTitle(d) {
|
|
| 4104 |
const base = location.origin + location.pathname.replace(/index\.html$/i, '');
|
| 4105 |
const shareUrl = base + '#/translated/' + d.id;
|
| 4106 |
const text = d.title + ' β JAYNES MAX (' + T('nav_translated') + ')';
|
| 4107 |
-
|
|
|
|
|
|
|
| 4108 |
}
|
| 4109 |
|
| 4110 |
// ββ MICHEZO (fawa-live) ββ
|
|
|
|
| 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;}
|
|
|
|
| 1215 |
/* Nafasi ya kusogeza (scroll) ili kipengele chenye focus kisifichwe na topbar / nav */
|
| 1216 |
.poster,.cw-card,.tab-chip,.settings-row,.dq-item,.tr-ep,.cast-item{scroll-margin:110px 3vw 110px 3vw;}
|
| 1217 |
</style>
|
| 1218 |
+
<style>
|
| 1219 |
+
.tr-filter{margin:0 0 10px;}
|
| 1220 |
+
.tr-filter .tabs{padding:10px 16px 4px;}
|
| 1221 |
+
.tr-filter .tr-dj-row{padding-top:4px;}
|
| 1222 |
+
.tr-filter .tr-dj-row .tab-chip{font-size:12px; padding:6px 12px;}
|
| 1223 |
+
.tr-filter-note{padding:4px 16px 6px; font-size:12px; color:var(--muted);}
|
| 1224 |
+
</style>
|
| 1225 |
</head>
|
| 1226 |
<body>
|
| 1227 |
<div id="gate">
|
|
|
|
| 1492 |
const TRAILER_BASE = JM_CONFIG.CONTENT_URL + '/functions/v1/movie-trailer';
|
| 1493 |
const TRAILER_KEY = SUPABASE_ANON_KEY;
|
| 1494 |
|
| 1495 |
+
// Zilizotafsiriwa: chanzo kimoja (movie-translated-all) β n2-, ab-, abm- ids
|
| 1496 |
+
const TR_DJ_NOISE = /\b(movies?|filamu|zote|zilizotafsiriwa|zilizotafsiri|imetafsiriwa|iliyotafsiriwa|tafsiriwa|kiswahili|swahili|kwa|mpya|kali|translated|new|all|series|full|single|singo|action|cartoon|katuni|season|seasons|(?:19|20)\d{2})\b/gi;
|
| 1497 |
+
function normDj(raw) { // jina safi la DJ (bila maneno ya ziada na bila tofauti za herufi kubwa/ndogo)
|
| 1498 |
+
const s = String(raw || '').replace(/\(.*?\)|\[.*?\]/g, ' ').replace(TR_DJ_NOISE, ' ').replace(/[|_\-\u2013\u2014:,.]+/g, ' ').replace(/\s+/g, ' ').trim();
|
| 1499 |
+
if (!s || /^dj$/i.test(s)) return '';
|
| 1500 |
+
const name = s.split(' ').map(w => /^dj$/i.test(w) ? 'DJ' : w.charAt(0).toUpperCase() + w.slice(1).toLowerCase()).join(' ');
|
| 1501 |
+
return TR_DJ_ALIAS[name.toLowerCase()] || name;
|
| 1502 |
+
}
|
| 1503 |
+
// Majina tofauti ya DJ mmoja (andika kwa herufi ndogo upande wa kushoto)
|
| 1504 |
+
const TR_DJ_ALIAS = { 'dj blaki': 'DJ Black', 'blaki': 'DJ Black', 'black': 'DJ Black' };
|
| 1505 |
+
// Model iliyofunzwa (train.py -> HF Model). Ikikosekana, app inafanya kazi kama kawaida (Trending = mpya zaidi).
|
| 1506 |
const TRANSLATED_ALL = JM_CONFIG.CONTENT_URL + '/functions/v1/movie-translated-all';
|
| 1507 |
const TR_MODEL_URL = 'https://huggingface.co/jaynesmaxtv/translated-recommender/resolve/main/model.json';
|
| 1508 |
let trModelP = null;
|
|
|
|
| 1511 |
return trModelP;
|
| 1512 |
}
|
| 1513 |
// Rails za mlalo: Trending Now (Top 10, kwa model au mpya zaidi) -> New Releases -> kila DJ (DJ Mack kwanza) -> Series -> More to Watch
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1514 |
function buildTranslatedSections(items, model) {
|
| 1515 |
const pop = (model && model.popularity) || {}, sections = [], covered = new Set();
|
| 1516 |
const hot = items.filter(it => pop[it.id] > 0).sort((a, b) => pop[b.id] - pop[a.id]);
|
|
|
|
| 1562 |
}
|
| 1563 |
|
| 1564 |
// ββ Share: kiungo cha moja kwa moja kwenye video (#/watch/ID) ββ
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1565 |
async function shareTitle(d, se, ep){
|
| 1566 |
const base = location.origin + location.pathname.replace(/index\.html$/i, '');
|
| 1567 |
const url = base + '#/watch/' + d.id + (se != null && ep != null ? '?se=' + se + '&ep=' + ep : '');
|
| 1568 |
const text = d.title + ' β JAYNES MAX';
|
| 1569 |
+
if (navigator.share) { try { await navigator.share({ title: text, text, url }); } catch (_) {} return; }
|
| 1570 |
+
try { await navigator.clipboard.writeText(url); showToast(t('link_copied')); }
|
| 1571 |
+
catch (_) { prompt('Nakili kiungo:', url); }
|
| 1572 |
}
|
| 1573 |
|
| 1574 |
// ββ Watchlist (localStorage) ββ
|
|
|
|
| 2885 |
fr: { jtv_next: 'Ensuite', jtv_schedule: 'Programme', jtv_down: 'JaynesTV est indisponible', tr_trending: 'Tendances actuelles', tr_new: 'NouveautΓ©s', tr_more: 'Γ dΓ©couvrir' }
|
| 2886 |
};
|
| 2887 |
Object.keys(I18N_EXTRA10).forEach(l => { I18N[l] = Object.assign(I18N[l] || {}, I18N_EXTRA10[l]); });
|
| 2888 |
+
const I18N_EXTRA_TRF = {
|
| 2889 |
+
sw: { trf_all: 'Zote', trf_new: 'Mpya', trf_trend_movie: 'Movie Zinazovuma', trf_trend_series: 'Series Zinazovuma', trf_movie: 'Movie', trf_series: 'Series', trf_dj: 'Kwa DJ', trf_no_trend: 'Bado hakuna takwimu za kutosha za zinazovuma, tunaonyesha mpya zaidi kwanza.' },
|
| 2890 |
+
en: { trf_all: 'All', trf_new: 'New Releases', trf_trend_movie: 'Trending Movies', trf_trend_series: 'Trending Series', trf_movie: 'Movies', trf_series: 'Series', trf_dj: 'By DJ', trf_no_trend: 'Not enough viewing data yet, showing newest first.' },
|
| 2891 |
+
ar: { trf_all: 'Ψ§ΩΩΩ', trf_new: 'Ψ§ΩΨ¬Ψ―ΩΨ―', trf_trend_movie: 'Ψ£ΩΩΨ§Ω
Ψ±Ψ§Ψ¦Ψ¬Ψ©', trf_trend_series: 'Ω
Ψ³ΩΨ³ΩΨ§Ψͺ Ψ±Ψ§Ψ¦Ψ¬Ψ©', trf_movie: 'Ψ£ΩΩΨ§Ω
', trf_series: 'Ω
Ψ³ΩΨ³ΩΨ§Ψͺ', trf_dj: 'ΨΨ³Ψ¨ DJ', trf_no_trend: 'ΩΨ§ ΨͺΩΨ¬Ψ― Ψ¨ΩΨ§ΩΨ§Ψͺ Ω
Ψ΄Ψ§ΩΨ―Ψ© ΩΨ§ΩΩΨ© Ψ¨ΨΉΨ―Ψ ΩΨΉΨ±ΨΆ Ψ§ΩΨ£ΨΨ―Ψ« Ψ£ΩΩΨ§Ω.' },
|
| 2892 |
+
fr: { trf_all: 'Tout', trf_new: 'NouveautΓ©s', trf_trend_movie: 'Films tendance', trf_trend_series: 'SΓ©ries tendance', trf_movie: 'Films', trf_series: 'SΓ©ries', trf_dj: 'Par DJ', trf_no_trend: "Pas encore assez de donnΓ©es, les plus rΓ©cents d'abord." }
|
| 2893 |
+
};
|
| 2894 |
+
Object.keys(I18N_EXTRA_TRF).forEach(l => { I18N[l] = Object.assign(I18N[l] || {}, I18N_EXTRA_TRF[l]); });
|
| 2895 |
function T(k, v) { return t(k, v); }
|
| 2896 |
// Maandishi yaliyoandikwa kwenye HTML kupitia data-i18n / data-i18n-ph / data-i18n-title
|
| 2897 |
function applyI18nAttrs() {
|
|
|
|
| 3612 |
}
|
| 3613 |
async function openJaynesTV() {
|
| 3614 |
openPlayerShell('JaynesTV');
|
| 3615 |
+
const s = await jtvSchedule(true);
|
| 3616 |
if (!s) return statusMsg('<span style="color:#ffb4ab">' + esc(T('jtv_down')) + '</span>');
|
| 3617 |
stage.style.position = 'relative';
|
| 3618 |
+
stage.innerHTML = '<video id="vid" controls autoplay playsinline></video><div class="jtv-bar" id="jtvBar"></div>';
|
| 3619 |
+
const v = document.getElementById('vid'), bar = document.getElementById('jtvBar');
|
|
|
|
|
|
|
| 3620 |
attachPlayerEnhancements(v); ensureSound(v);
|
| 3621 |
+
const bad = new Set(); let cur = null, epg = false, hideT = null, last = null;
|
| 3622 |
const hhmm = ms => new Date(ms).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });
|
|
|
|
| 3623 |
const show = (ms) => { bar.classList.remove('hide'); clearTimeout(hideT); if (ms) hideT = setTimeout(() => { if (!epg) bar.classList.add('hide'); }, ms); };
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3624 |
const draw = (n, it) => {
|
| 3625 |
+
const nx = n.upcoming[0];
|
| 3626 |
+
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>' : '') +
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3627 |
'<button class="jtv-b" data-j="epg">' + esc(T('jtv_schedule')) + '</button></div>' +
|
| 3628 |
+
'<div class="jtv-m">' + esc(T('jtv_next')) + ': ' + esc(nx.item.title) + ' Β· ' + hhmm(nx.at) + '</div>' +
|
| 3629 |
+
(epg ? '<div class="jtv-epg">' + n.upcoming.map(u => '<div><span>' + hhmm(u.at) + '</span> ' + esc(u.item.title) + '</div>').join('') + '</div>' : '');
|
|
|
|
|
|
|
| 3630 |
};
|
| 3631 |
const sync = () => {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3632 |
const n = jtvNow(s, Date.now()), isBad = bad.has(n.idx);
|
| 3633 |
const it = isBad ? n.upcoming[0].item : n.item, off = isBad ? 0 : n.offset; // video mbovu: cheza inayofuata kutoka mwanzo
|
| 3634 |
if (cur !== it.url) {
|
| 3635 |
+
cur = it.url; v.src = it.url + '#t=' + Math.floor(off);
|
| 3636 |
+
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 });
|
| 3637 |
+
show(8000);
|
| 3638 |
} else if (!isBad && v.readyState > 1 && Math.abs(v.currentTime - off) > 4) v.currentTime = off; // rudi kwenye 'live'
|
| 3639 |
last = n; draw(n, it);
|
| 3640 |
};
|
| 3641 |
+
bar.onclick = (e) => { if (e.target.closest('[data-j="epg"]')) { epg = !epg; draw(last, last.item); show(epg ? 0 : 6000); } };
|
|
|
|
| 3642 |
v.addEventListener('ended', sync);
|
| 3643 |
v.addEventListener('play', sync);
|
| 3644 |
+
v.addEventListener('seeked', () => { if (last && Math.abs(v.currentTime - last.offset) > 6) sync(); });
|
| 3645 |
+
v.addEventListener('error', () => { if (last) { bad.add(last.idx); sync(); } });
|
| 3646 |
v.addEventListener('click', () => show(6000));
|
| 3647 |
+
const timer = setInterval(sync, 15000);
|
| 3648 |
+
window.__jtvStop = () => { clearInterval(timer); clearTimeout(hideT); };
|
|
|
|
| 3649 |
sync();
|
| 3650 |
}
|
| 3651 |
|
|
|
|
| 3713 |
}
|
| 3714 |
|
| 3715 |
// ββ ZILIZOTAFSIRIWA (movies/series za DJ β mradi tofauti) ββ
|
| 3716 |
+
async function fetchTranslatedList() {
|
| 3717 |
const card = (m, idPrefix) => ({
|
| 3718 |
id: idPrefix + m.id, kind: 'translated', title: m.title,
|
| 3719 |
poster: m.thumb_url || m.poster_url || null, imgFallbackOn: true,
|
| 3720 |
subtitle: normDj(m.dj) || (m.category === 'series' ? 'Series' : 'Movie'), isSeries: m.category === 'series',
|
| 3721 |
+
added: m.created_at || m.updated_at || '',
|
| 3722 |
});
|
| 3723 |
const r = await fetch(TRANSLATED_ALL, { headers: { apikey: SUPABASE_ANON_KEY } });
|
| 3724 |
if (!r.ok) throw new Error(T('err_generic') + ' ' + r.status);
|
|
|
|
| 3727 |
return (d.movies || []).map(m => card(m, ''));
|
| 3728 |
}
|
| 3729 |
|
| 3730 |
+
// ββ KUPANGA ZILIZOTAFSIRIWA: Zote / Mpya / Movie Zinazovuma / Series Zinazovuma / Movie / Series / Kwa DJ ββ
|
| 3731 |
+
let trCache = null; // { items, model } -- kubadilisha kichujio hakupakui upya
|
| 3732 |
+
let trFilter = 'all', trDj = '';
|
| 3733 |
+
try { trFilter = sessionStorage.getItem('jm_tr_filter') || 'all'; trDj = sessionStorage.getItem('jm_tr_dj') || ''; } catch (_) {}
|
| 3734 |
+
const TR_FILTERS = ['all', 'new', 'trend_movie', 'trend_series', 'movie', 'series', 'dj'];
|
| 3735 |
+
function trDjGroups(items) { // DJ Mack kwanza, kisha DJ mwenye kazi nyingi zaidi
|
| 3736 |
+
const g = {};
|
| 3737 |
+
items.forEach(it => {
|
| 3738 |
+
const dj = String(it.subtitle || '').trim();
|
| 3739 |
+
if (!dj || dj === 'Series' || dj === 'Movie') return;
|
| 3740 |
+
(g[dj.toLowerCase()] = g[dj.toLowerCase()] || { name: dj, list: [] }).list.push(it);
|
| 3741 |
+
});
|
| 3742 |
+
return Object.values(g).sort((a, b) => (/mack/i.test(b.name) - /mack/i.test(a.name)) || b.list.length - a.list.length || a.name.localeCompare(b.name));
|
| 3743 |
+
}
|
| 3744 |
+
function trByNewest(list) { // mpya -> zamani kwa tarehe ya kuongezwa; zisizo na tarehe zinabaki kwa mpangilio wa seva
|
| 3745 |
+
return list.map((it, i) => [it, i])
|
| 3746 |
+
.sort((a, b) => (Date.parse(b[0].added) || 0) - (Date.parse(a[0].added) || 0) || a[1] - b[1]).map(x => x[0]);
|
| 3747 |
+
}
|
| 3748 |
+
function trTrending(list, model) { // kwa uchezaji halisi (model); zikipungua, zinaongezwa mpya zaidi bila namba
|
| 3749 |
+
const pop = (model && model.popularity) || {};
|
| 3750 |
+
const hot = list.filter(it => pop[it.id] > 0).sort((a, b) => pop[b.id] - pop[a.id]);
|
| 3751 |
+
const ranked = hot.slice(0, 30).map((it, i) => ({ ...it, rank: i < 10 ? i + 1 : undefined }));
|
| 3752 |
+
if (ranked.length < 10) {
|
| 3753 |
+
const have = new Set(ranked.map(it => it.id));
|
| 3754 |
+
trByNewest(list).forEach(it => { if (ranked.length < 30 && !have.has(it.id)) ranked.push(it); });
|
| 3755 |
+
}
|
| 3756 |
+
return { items: ranked, fallback: hot.length === 0 };
|
| 3757 |
+
}
|
| 3758 |
+
function renderTranslatedView() {
|
| 3759 |
+
if (!trCache) return;
|
| 3760 |
+
const { items, model } = trCache;
|
| 3761 |
+
const movies = items.filter(it => !it.isSeries), series = items.filter(it => it.isSeries);
|
| 3762 |
+
const groups = trDjGroups(items);
|
| 3763 |
+
if (!TR_FILTERS.includes(trFilter)) trFilter = 'all';
|
| 3764 |
+
if (trFilter === 'dj' && !groups.some(g => g.name === trDj)) trDj = groups[0] ? groups[0].name : '';
|
| 3765 |
+
let sections, grid = true, note = '';
|
| 3766 |
+
if (trFilter === 'new') sections = [{ title: t('tr_new'), items: trByNewest(items) }];
|
| 3767 |
+
else if (trFilter === 'trend_movie' || trFilter === 'trend_series') {
|
| 3768 |
+
const r = trTrending(trFilter === 'trend_movie' ? movies : series, model);
|
| 3769 |
+
if (r.fallback) note = t('trf_no_trend');
|
| 3770 |
+
sections = [{ title: t('trf_' + trFilter), items: r.items }];
|
| 3771 |
+
}
|
| 3772 |
+
else if (trFilter === 'movie') sections = [{ title: t('trf_movie'), items: trByNewest(movies) }];
|
| 3773 |
+
else if (trFilter === 'series') sections = [{ title: t('trf_series'), items: trByNewest(series) }];
|
| 3774 |
+
else if (trFilter === 'dj') {
|
| 3775 |
+
const g = groups.find(x => x.name === trDj);
|
| 3776 |
+
sections = g ? [{ title: g.name, items: trByNewest(g.list).map(it => ({ ...it, subtitle: it.isSeries ? 'Series' : '' })) }] : [];
|
| 3777 |
+
}
|
| 3778 |
+
else { sections = buildTranslatedSections(items, model); grid = false; }
|
| 3779 |
+
renderRails(sections, grid);
|
| 3780 |
+
const bar = document.createElement('div');
|
| 3781 |
+
bar.className = 'tr-filter';
|
| 3782 |
+
bar.innerHTML = `<div class="tabs">${TR_FILTERS.map(f => `<div class="tab-chip${f === trFilter ? ' active' : ''}" data-trf="${f}">${esc(t('trf_' + f))}</div>`).join('')}</div>` +
|
| 3783 |
+
(trFilter === 'dj' && groups.length ? `<div class="tabs tr-dj-row">${groups.map(g => `<div class="tab-chip${g.name === trDj ? ' active' : ''}" data-trdj="${esc(g.name)}">${esc(g.name)} Β· ${g.list.length}</div>`).join('')}</div>` : '') +
|
| 3784 |
+
(note ? `<div class="tr-filter-note">${esc(note)}</div>` : '');
|
| 3785 |
+
bar.onclick = (e) => {
|
| 3786 |
+
const c = e.target.closest('.tab-chip'); if (!c) return;
|
| 3787 |
+
if (c.dataset.trf) trFilter = c.dataset.trf; else if (c.dataset.trdj != null) trDj = c.dataset.trdj; else return;
|
| 3788 |
+
try { sessionStorage.setItem('jm_tr_filter', trFilter); sessionStorage.setItem('jm_tr_dj', trDj); } catch (_) {}
|
| 3789 |
+
renderTranslatedView();
|
| 3790 |
+
};
|
| 3791 |
+
railsEl.insertBefore(bar, railsEl.firstChild);
|
| 3792 |
+
bar.querySelectorAll('.tab-chip.active').forEach(c => { const row = c.parentElement; row.scrollLeft = c.offsetLeft - (row.clientWidth - c.offsetWidth) / 2; });
|
| 3793 |
+
}
|
| 3794 |
+
|
| 3795 |
async function loadTranslated() {
|
| 3796 |
heroWrap.innerHTML = ''; tabsEl.style.display = 'none';
|
| 3797 |
renderSkeletonRails(2);
|
| 3798 |
try {
|
| 3799 |
const [items, model] = await Promise.all([fetchTranslatedList(), loadTrModel()]);
|
| 3800 |
+
trCache = { items, model };
|
| 3801 |
+
renderTranslatedView();
|
| 3802 |
} catch (e) {
|
| 3803 |
railsEl.innerHTML = '';
|
| 3804 |
railsEl.appendChild(stateBox(t('err_failed', { e: esc(e.message || t('err_generic')) }), true, loadTranslated));
|
|
|
|
| 4122 |
const base = location.origin + location.pathname.replace(/index\.html$/i, '');
|
| 4123 |
const shareUrl = base + '#/translated/' + d.id;
|
| 4124 |
const text = d.title + ' β JAYNES MAX (' + T('nav_translated') + ')';
|
| 4125 |
+
if (navigator.share) { try { await navigator.share({ title: text, text, url: shareUrl }); } catch (_) {} return; }
|
| 4126 |
+
try { await navigator.clipboard.writeText(shareUrl); showToast(t('link_copied')); }
|
| 4127 |
+
catch (_) { prompt('Nakili kiungo:', shareUrl); }
|
| 4128 |
}
|
| 4129 |
|
| 4130 |
// ββ MICHEZO (fawa-live) ββ
|