Spaces:
Running
Running
Upload index.html
Browse files- index.html +178 -119
index.html
CHANGED
|
@@ -117,6 +117,17 @@
|
|
| 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;}
|
|
@@ -1215,13 +1226,6 @@
|
|
| 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,24 +1496,34 @@ const PIXTVMAX_KEY = SUPABASE_ANON_KEY;
|
|
| 1492 |
const TRAILER_BASE = JM_CONFIG.CONTENT_URL + '/functions/v1/movie-trailer';
|
| 1493 |
const TRAILER_KEY = SUPABASE_ANON_KEY;
|
| 1494 |
|
| 1495 |
-
//
|
| 1496 |
-
const
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 |
-
|
| 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;
|
| 1509 |
function loadTrModel() {
|
| 1510 |
if (!trModelP) trModelP = fetch(TR_MODEL_URL, { cache: 'no-cache' }).then(r => r.ok ? r.json() : null).catch(() => 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();
|
|
@@ -1532,11 +1546,13 @@ function buildTranslatedSections(items, model) {
|
|
| 1532 |
if (more.length) sections.push({ title: t('tr_more'), items: more.slice(0, 40) });
|
| 1533 |
return sections;
|
| 1534 |
}
|
| 1535 |
-
async function fetchTranslatedMovie(id) { // -> { ok, error?, movie } kutoka
|
| 1536 |
-
const
|
| 1537 |
-
const
|
| 1538 |
-
|
| 1539 |
-
|
|
|
|
|
|
|
| 1540 |
}
|
| 1541 |
const PIXTVMAX_CATEGORY_SW = {
|
| 1542 |
'Sports': 'cat_sports', 'Movie Channels': 'cat_movies', 'News': 'cat_news',
|
|
@@ -1552,7 +1568,56 @@ function liveCategoryOf(name) {
|
|
| 1552 |
return 'cat_other';
|
| 1553 |
}
|
| 1554 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1555 |
async function rpc(op, args = {}) {
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1556 |
const res = await fetch(SUPABASE_FN_URL, {
|
| 1557 |
method: 'POST',
|
| 1558 |
headers: { 'Content-Type': 'application/json', 'apikey': SUPABASE_ANON_KEY },
|
|
@@ -2885,13 +2950,6 @@ const I18N_EXTRA10 = {
|
|
| 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() {
|
|
@@ -3075,7 +3133,7 @@ function safeImgHTML(urls, alt, lazy){
|
|
| 3075 |
* matokeo tayari yamechujwa, tunayabadilisha tu kuwa muundo wa
|
| 3076 |
* poster card (sawa na toCard()).
|
| 3077 |
*/
|
| 3078 |
-
async function
|
| 3079 |
const url = SEARCH_FN_URL + '?keyword=' + encodeURIComponent(keyword);
|
| 3080 |
const res = await fetch(url, { headers: { 'apikey': SUPABASE_ANON_KEY } });
|
| 3081 |
const arr = await res.json();
|
|
@@ -3592,7 +3650,7 @@ async function fetchChannelPosters() {
|
|
| 3592 |
|
| 3593 |
// Inarudisha orodha bapa (flat) ya channels zote za Live TV β inatumika
|
| 3594 |
// na loadLive() (kuonyesha kwa rails) na search (kuchuja kwa query).
|
| 3595 |
-
// ββ JAYNESTV:
|
| 3596 |
const JTV_URL = 'https://huggingface.co/datasets/jaynesmaxtv/movie-file/resolve/main/jaynestv/schedule.json';
|
| 3597 |
let jtvP = null, jtvAt = 0;
|
| 3598 |
function jtvSchedule(force) { // ratiba inahifadhiwa dakika 5; ikikosekana JaynesTV haionekani
|
|
@@ -3610,23 +3668,76 @@ function jtvNow(s, nowMs) { // kipindi cha sasa + vinavyofuata, kwa saa: kila mt
|
|
| 3610 |
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; }
|
| 3611 |
return { idx: i, item: s.items[i], offset: pos, left: s.items[i].dur - pos, upcoming };
|
| 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);
|
|
@@ -3634,7 +3745,7 @@ async function openJaynesTV() {
|
|
| 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 |
};
|
|
@@ -3645,18 +3756,22 @@ async function openJaynesTV() {
|
|
| 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 |
|
| 3652 |
-
async function
|
| 3653 |
const items = [];
|
| 3654 |
let anyOk = false, lastErr = null;
|
| 3655 |
const posters = await fetchChannelPosters();
|
| 3656 |
|
| 3657 |
try { // JaynesTV (ikiwa ratiba ipo): kadi inaonyesha kipindi kinachochezwa sasa
|
| 3658 |
const js = await jtvSchedule();
|
| 3659 |
-
if (js) {
|
|
|
|
|
|
|
|
|
|
| 3660 |
} catch (_) {}
|
| 3661 |
|
| 3662 |
try {
|
|
@@ -3713,83 +3828,30 @@ async function loadLive() {
|
|
| 3713 |
}
|
| 3714 |
|
| 3715 |
// ββ ZILIZOTAFSIRIWA (movies/series za DJ β mradi tofauti) ββ
|
| 3716 |
-
async function
|
| 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);
|
| 3725 |
-
const d = await r.json();
|
| 3726 |
-
if (!d.ok) throw new Error(d.error || 'Imekataa ombi.');
|
| 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 |
-
|
| 3743 |
-
}
|
| 3744 |
-
|
| 3745 |
-
|
| 3746 |
-
|
| 3747 |
-
|
| 3748 |
-
|
| 3749 |
-
const
|
| 3750 |
-
|
| 3751 |
-
|
| 3752 |
-
|
| 3753 |
-
|
| 3754 |
-
|
| 3755 |
-
|
| 3756 |
-
|
| 3757 |
-
|
| 3758 |
-
|
| 3759 |
-
|
| 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() {
|
|
@@ -3797,8 +3859,7 @@ async function loadTranslated() {
|
|
| 3797 |
renderSkeletonRails(2);
|
| 3798 |
try {
|
| 3799 |
const [items, model] = await Promise.all([fetchTranslatedList(), loadTrModel()]);
|
| 3800 |
-
|
| 3801 |
-
renderTranslatedView();
|
| 3802 |
} catch (e) {
|
| 3803 |
railsEl.innerHTML = '';
|
| 3804 |
railsEl.appendChild(stateBox(t('err_failed', { e: esc(e.message || t('err_generic')) }), true, loadTranslated));
|
|
@@ -4030,6 +4091,7 @@ function playTranslatedUrl(url, title, playlist, meta) {
|
|
| 4030 |
v.addEventListener('ended', () => { const c = window.__pcPlaylist; if (c && typeof c.next === 'function') c.next(); });
|
| 4031 |
// Tangazo la video kwanza (likishindikana/likiruka, filamu inaanza mara moja)
|
| 4032 |
v.src = url;
|
|
|
|
| 4033 |
// Route ya player: #/translated/ID/play?se=..&ep=..
|
| 4034 |
try {
|
| 4035 |
const id = currentTranslatedDetail && currentTranslatedDetail.id;
|
|
@@ -4130,7 +4192,7 @@ async function shareTranslatedTitle(d) {
|
|
| 4130 |
// ββ MICHEZO (fawa-live) ββ
|
| 4131 |
// Inarudisha orodha bapa (flat) ya mechi zote β inatumika na loadSports()
|
| 4132 |
// (kuonyesha kwa rails, kikundi kwa ligi) na search (kuchuja kwa query).
|
| 4133 |
-
async function
|
| 4134 |
const res = await fetch(FAWA_BASE + '/matches', { headers: { Authorization: 'Bearer ' + FAWA_KEY } });
|
| 4135 |
if (!res.ok) throw new Error(T('err_generic') + ' ' + res.status);
|
| 4136 |
const d = await res.json();
|
|
@@ -6935,11 +6997,7 @@ async function loadTrailer(subjectId){
|
|
| 6935 |
if (!wrap) return;
|
| 6936 |
wrap.innerHTML = '<div class="trailer-state">' + T('trailer_loading') + '</div>';
|
| 6937 |
try {
|
| 6938 |
-
const
|
| 6939 |
-
headers: { apikey: TRAILER_KEY, Authorization: 'Bearer ' + TRAILER_KEY },
|
| 6940 |
-
});
|
| 6941 |
-
if (!res.ok) throw new Error('http_' + res.status);
|
| 6942 |
-
const result = await res.json();
|
| 6943 |
if (result.code !== 0) throw new Error(result.message || 'trailer_error');
|
| 6944 |
// Kagua kila hatua kabla ya kutumia β data.trailer, .url zote zinaweza kukosekana
|
| 6945 |
const trailer = result.data && result.data.trailer;
|
|
@@ -10008,8 +10066,9 @@ async function muxJob(videoUrl, subtitles, filename, onProgress){
|
|
| 10008 |
if (!startRes.ok || !startData.jobId) throw new Error(startData.error || t('dq_err_mux_start'));
|
| 10009 |
const jobId = startData.jobId;
|
| 10010 |
|
| 10011 |
-
|
| 10012 |
-
|
|
|
|
| 10013 |
const st = await fetch(MUX_BASE + '?jobId=' + encodeURIComponent(jobId), {
|
| 10014 |
headers: { 'Authorization': 'Bearer ' + MUX_KEY, 'apikey': MUX_KEY },
|
| 10015 |
});
|
|
@@ -10606,7 +10665,7 @@ function nhSliderRender(box, sl){
|
|
| 10606 |
}
|
| 10607 |
}
|
| 10608 |
// Config ya Home inatoka Supabase (home-config); ikishindwa tunatumia nakala ya mwisho au chaguo-msingi
|
| 10609 |
-
async function
|
| 10610 |
try {
|
| 10611 |
const ctl = new AbortController(); const to = setTimeout(() => ctl.abort(), 3000);
|
| 10612 |
const r = await fetch(HOME_CFG_URL, { method: 'POST', signal: ctl.signal, headers: { 'Content-Type': 'application/json', Authorization: 'Bearer ' + SUPABASE_ANON_KEY, apikey: SUPABASE_ANON_KEY }, body: JSON.stringify({ action: 'get' }) });
|
|
|
|
| 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-bug{position:absolute; z-index:4; pointer-events:none; width:auto; max-width:30%; object-fit:contain; filter:drop-shadow(0 1px 4px rgba(0,0,0,.6));}
|
| 121 |
+
.jtv-top-right{top:12px; right:12px;} .jtv-top-left{top:12px; left:12px;} .jtv-bottom-right{bottom:64px; right:12px;} .jtv-bottom-left{bottom:64px; left:12px;}
|
| 122 |
+
.jtv-ident{position:absolute; inset:0; z-index:6; display:flex; flex-direction:column; align-items:center; justify-content:center; gap:10px; background:radial-gradient(circle at 50% 40%,#1b1640,#07070f); color:#fff; pointer-events:none; transition:opacity .6s;}
|
| 123 |
+
.jtv-ident img{height:34%; max-height:180px; width:auto; animation:jtvPop .9s ease-out;} .jtv-ident.out{opacity:0;}
|
| 124 |
+
.jtv-idn{font-weight:900; font-size:22px; letter-spacing:2px;} .jtv-idt{opacity:.75; font-size:13px;}
|
| 125 |
+
@keyframes jtvPop{from{transform:scale(.7); opacity:0;} to{transform:scale(1); opacity:1;}}
|
| 126 |
+
.jtv-lt{position:absolute; left:10px; bottom:64px; z-index:5; display:flex; gap:8px; align-items:center; background:rgba(0,0,0,.65); color:#fff; padding:6px 10px; border-radius:10px; font-size:13px; pointer-events:none; animation:jtvPop .5s ease-out;}
|
| 127 |
+
.jtv-ltk{border-radius:5px; padding:1px 6px; font-size:11px; font-weight:800; letter-spacing:.5px;}
|
| 128 |
+
.jtv-ticker{position:absolute; left:0; right:0; bottom:44px; z-index:4; overflow:hidden; background:rgba(0,0,0,.55); color:#fff; font-size:13px; padding:4px 0; pointer-events:none;}
|
| 129 |
+
.jtv-tk{display:inline-block; white-space:nowrap; padding-left:100%; animation:jtvTk linear infinite;}
|
| 130 |
+
@keyframes jtvTk{from{transform:translateX(0);} to{transform:translateX(-100%);}}
|
| 131 |
.jtv-live{background:#e5484d; border-radius:6px; padding:2px 7px; font-weight:800; font-size:11px; letter-spacing:.5px;}
|
| 132 |
.jtv-m{opacity:.8; font-size:12px; margin-top:2px;}
|
| 133 |
.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;}
|
|
|
|
| 1226 |
/* Nafasi ya kusogeza (scroll) ili kipengele chenye focus kisifichwe na topbar / nav */
|
| 1227 |
.poster,.cw-card,.tab-chip,.settings-row,.dq-item,.tr-ep,.cast-item{scroll-margin:110px 3vw 110px 3vw;}
|
| 1228 |
</style>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1229 |
</head>
|
| 1230 |
<body>
|
| 1231 |
<div id="gate">
|
|
|
|
| 1496 |
const TRAILER_BASE = JM_CONFIG.CONTENT_URL + '/functions/v1/movie-trailer';
|
| 1497 |
const TRAILER_KEY = SUPABASE_ANON_KEY;
|
| 1498 |
|
| 1499 |
+
// movie-zilizotafsiriwa β mradi TOFAUTI (DJ movies/series), function ya umma pekee (GET)
|
| 1500 |
+
const TRANSLATED_BASE = JM_CONFIG.TRANSLATED_URL + '/functions/v1/' + JM_CONFIG.TRANSLATED_FN;
|
| 1501 |
+
const TRANSLATED_KEY = JM_CONFIG.TRANSLATED_KEY;
|
| 1502 |
+
// movie-zilizotafsriwa2 β filamu zenye MP4 (draft/public kutoka admin); project ile ile ya CONTENT_URL.
|
| 1503 |
+
// Id zake zinaanza na 'n2-' ili zisigongane na za chanzo cha zamani.
|
| 1504 |
+
const TRANSLATED2_BASE = JM_CONFIG.CONTENT_URL + '/functions/v1/movie-zilizotafsriwa2';
|
| 1505 |
+
const TR2_PREFIX = 'n2-';
|
| 1506 |
+
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;
|
| 1507 |
function normDj(raw) { // jina safi la DJ (bila maneno ya ziada na bila tofauti za herufi kubwa/ndogo)
|
| 1508 |
const s = String(raw || '').replace(/\(.*?\)|\[.*?\]/g, ' ').replace(TR_DJ_NOISE, ' ').replace(/[|_\-\u2013\u2014:,.]+/g, ' ').replace(/\s+/g, ' ').trim();
|
| 1509 |
if (!s || /^dj$/i.test(s)) return '';
|
| 1510 |
+
return s.split(' ').map(w => /^dj$/i.test(w) ? 'DJ' : w.charAt(0).toUpperCase() + w.slice(1).toLowerCase()).join(' ');
|
|
|
|
| 1511 |
}
|
|
|
|
|
|
|
| 1512 |
// Model iliyofunzwa (train.py -> HF Model). Ikikosekana, app inafanya kazi kama kawaida (Trending = mpya zaidi).
|
|
|
|
| 1513 |
const TR_MODEL_URL = 'https://huggingface.co/jaynesmaxtv/translated-recommender/resolve/main/model.json';
|
| 1514 |
let trModelP = null;
|
| 1515 |
function loadTrModel() {
|
| 1516 |
if (!trModelP) trModelP = fetch(TR_MODEL_URL, { cache: 'no-cache' }).then(r => r.ok ? r.json() : null).catch(() => null);
|
| 1517 |
return trModelP;
|
| 1518 |
}
|
| 1519 |
+
function logTrPlay(id) { // rekodi uchezaji bila kuzuia player; kitambulisho cha kifaa ni cha nasibu (hakuna jina/email)
|
| 1520 |
+
if (!id) return;
|
| 1521 |
+
let dev = '';
|
| 1522 |
+
try { dev = localStorage.getItem('jm_dev') || ''; if (!dev) { dev = (crypto.randomUUID && crypto.randomUUID()) || (Date.now() + '-' + Math.random().toString(36).slice(2)); localStorage.setItem('jm_dev', dev); } } catch (_) {}
|
| 1523 |
+
fetch(TRANSLATED2_BASE + '?action=play', { method: 'POST', keepalive: true,
|
| 1524 |
+
headers: { apikey: SUPABASE_ANON_KEY, 'Content-Type': 'application/json' },
|
| 1525 |
+
body: JSON.stringify({ movie_id: String(id), device_id: dev }) }).catch(() => {});
|
| 1526 |
+
}
|
| 1527 |
// Rails za mlalo: Trending Now (Top 10, kwa model au mpya zaidi) -> New Releases -> kila DJ (DJ Mack kwanza) -> Series -> More to Watch
|
| 1528 |
function buildTranslatedSections(items, model) {
|
| 1529 |
const pop = (model && model.popularity) || {}, sections = [], covered = new Set();
|
|
|
|
| 1546 |
if (more.length) sections.push({ title: t('tr_more'), items: more.slice(0, 40) });
|
| 1547 |
return sections;
|
| 1548 |
}
|
| 1549 |
+
async function fetchTranslatedMovie(id) { // -> { ok, error?, movie } kutoka chanzo sahihi
|
| 1550 |
+
const isNew = String(id).startsWith(TR2_PREFIX);
|
| 1551 |
+
const url = isNew ? TRANSLATED2_BASE + '?id=' + encodeURIComponent(String(id).slice(TR2_PREFIX.length))
|
| 1552 |
+
: TRANSLATED_BASE + '?id=' + encodeURIComponent(id);
|
| 1553 |
+
const r = await fetch(url, { headers: { apikey: isNew ? SUPABASE_ANON_KEY : TRANSLATED_KEY } });
|
| 1554 |
+
const d = await r.json();
|
| 1555 |
+
return isNew ? { ok: r.ok && !d.error, error: d.error, movie: d } : d;
|
| 1556 |
}
|
| 1557 |
const PIXTVMAX_CATEGORY_SW = {
|
| 1558 |
'Sports': 'cat_sports', 'Movie Channels': 'cat_movies', 'News': 'cat_news',
|
|
|
|
| 1568 |
return 'cat_other';
|
| 1569 |
}
|
| 1570 |
|
| 1571 |
+
// ββ CACHE ya majibu ya Supabase functions (inapunguza invocations): kumbukumbu + localStorage, ombi moja kwa wote wanaoomba kwa pamoja,
|
| 1572 |
+
// na nakala ya zamani ikiwa seva imeshindwa. Majibu yenye hitilafu hayahifadhiwi. ββ
|
| 1573 |
+
const JM_MEM = new Map(), JM_INFLIGHT = new Map();
|
| 1574 |
+
const jmClone = v => { try { return structuredClone(v); } catch (_) { return JSON.parse(JSON.stringify(v)); } };
|
| 1575 |
+
async function jmCached(key, ttl, fn, opt = {}) {
|
| 1576 |
+
const persist = opt.persist !== false, ok = opt.ok || (v => v != null);
|
| 1577 |
+
let hit = JM_MEM.get(key);
|
| 1578 |
+
if (!hit && persist) { try { const j = JSON.parse(localStorage.getItem('jmc:' + key)); if (j && j.t) { hit = j; JM_MEM.set(key, j); } } catch (_) {} }
|
| 1579 |
+
if (hit && Date.now() - hit.t < ttl) return jmClone(hit.v);
|
| 1580 |
+
if (JM_INFLIGHT.has(key)) return jmClone(await JM_INFLIGHT.get(key));
|
| 1581 |
+
const p = (async () => {
|
| 1582 |
+
try {
|
| 1583 |
+
const v = await fn();
|
| 1584 |
+
if (ok(v)) {
|
| 1585 |
+
const rec = { t: Date.now(), v: jmClone(v) }; JM_MEM.set(key, rec);
|
| 1586 |
+
if (persist) { try { const sj = JSON.stringify(rec); if (sj.length < 300000) localStorage.setItem('jmc:' + key, sj); } catch (_) {} }
|
| 1587 |
+
}
|
| 1588 |
+
return v;
|
| 1589 |
+
} catch (e) { if (hit) return hit.v; throw e; } // seva imeshindwa: tumia nakala ya zamani kuliko kuonyesha kosa
|
| 1590 |
+
finally { JM_INFLIGHT.delete(key); }
|
| 1591 |
+
})();
|
| 1592 |
+
JM_INFLIGHT.set(key, p);
|
| 1593 |
+
return jmClone(await p);
|
| 1594 |
+
}
|
| 1595 |
+
try { for (let i = localStorage.length - 1; i >= 0; i--) { const k = localStorage.key(i); if (k && k.startsWith('jmc:')) { const j = JSON.parse(localStorage.getItem(k) || 'null'); if (!j || Date.now() - j.t > 2 * 86400000) localStorage.removeItem(k); } } } catch (_) {}
|
| 1596 |
+
const jmOkObj = r => r && typeof r === 'object' && !r.error && r.success !== false;
|
| 1597 |
+
const RPC_CACHE = { // op -> [ttl ms, hifadhi localStorage?]; movies.play haihifadhiwi kamwe (viungo vya kucheza)
|
| 1598 |
+
'movies.home': [600000, true], 'movies.tabs': [1800000, true], 'movies.tab': [600000, true],
|
| 1599 |
+
'movies.related': [1800000, false], 'movies.series': [600000, false], 'movies.seasons': [600000, false], 'movies.episodes': [600000, false],
|
| 1600 |
+
'movies.staffInfo': [3600000, false], 'movies.staffSubjectList': [3600000, false],
|
| 1601 |
+
};
|
| 1602 |
+
// Orodha zinazotumika na search, Home na tabs: ombi moja la mtandao kwa dakika chache badala ya kila mara
|
| 1603 |
+
function fetchAllLiveItems() { return jmCached('live', 300000, fetchAllLiveItemsRaw, { persist: false }); }
|
| 1604 |
+
function fetchAllSportsItems() { return jmCached('sports', 120000, fetchAllSportsItemsRaw, { persist: false }); }
|
| 1605 |
+
function fetchTranslatedList() { return jmCached('translated', 300000, fetchTranslatedListRaw, { persist: false }); }
|
| 1606 |
+
function searchMovieBox(keyword) { return jmCached('search:' + String(keyword).trim().toLowerCase(), 600000, () => searchMovieBoxRaw(keyword), { persist: false }); }
|
| 1607 |
+
function getHomeConfig() { return jmCached('homecfg', 600000, getHomeConfigRaw, { persist: false, ok: v => v && v !== HOME_CFG_DEFAULT }); }
|
| 1608 |
+
async function fetchTrailerJson(subjectId) {
|
| 1609 |
+
return jmCached('trailer:' + subjectId, 86400000, async () => {
|
| 1610 |
+
const res = await fetch(TRAILER_BASE + '?subjectId=' + encodeURIComponent(subjectId), { headers: { apikey: TRAILER_KEY, Authorization: 'Bearer ' + TRAILER_KEY } });
|
| 1611 |
+
if (!res.ok) throw new Error('http_' + res.status);
|
| 1612 |
+
return res.json();
|
| 1613 |
+
}, { persist: false, ok: r => r && r.code === 0 });
|
| 1614 |
+
}
|
| 1615 |
+
|
| 1616 |
async function rpc(op, args = {}) {
|
| 1617 |
+
const c = RPC_CACHE[op];
|
| 1618 |
+
return c ? jmCached('rpc:' + op + ':' + JSON.stringify(args), c[0], () => rpcRaw(op, args), { persist: c[1], ok: jmOkObj }) : rpcRaw(op, args);
|
| 1619 |
+
}
|
| 1620 |
+
async function rpcRaw(op, args = {}) {
|
| 1621 |
const res = await fetch(SUPABASE_FN_URL, {
|
| 1622 |
method: 'POST',
|
| 1623 |
headers: { 'Content-Type': 'application/json', 'apikey': SUPABASE_ANON_KEY },
|
|
|
|
| 2950 |
fr: { jtv_next: 'Ensuite', jtv_schedule: 'Programme', jtv_down: 'JaynesTV est indisponible', tr_trending: 'Tendances actuelles', tr_new: 'NouveautΓ©s', tr_more: 'Γ dΓ©couvrir' }
|
| 2951 |
};
|
| 2952 |
Object.keys(I18N_EXTRA10).forEach(l => { I18N[l] = Object.assign(I18N[l] || {}, I18N_EXTRA10[l]); });
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2953 |
function T(k, v) { return t(k, v); }
|
| 2954 |
// Maandishi yaliyoandikwa kwenye HTML kupitia data-i18n / data-i18n-ph / data-i18n-title
|
| 2955 |
function applyI18nAttrs() {
|
|
|
|
| 3133 |
* matokeo tayari yamechujwa, tunayabadilisha tu kuwa muundo wa
|
| 3134 |
* poster card (sawa na toCard()).
|
| 3135 |
*/
|
| 3136 |
+
async function searchMovieBoxRaw(keyword) {
|
| 3137 |
const url = SEARCH_FN_URL + '?keyword=' + encodeURIComponent(keyword);
|
| 3138 |
const res = await fetch(url, { headers: { 'apikey': SUPABASE_ANON_KEY } });
|
| 3139 |
const arr = await res.json();
|
|
|
|
| 3650 |
|
| 3651 |
// Inarudisha orodha bapa (flat) ya channels zote za Live TV β inatumika
|
| 3652 |
// na loadLive() (kuonyesha kwa rails) na search (kuchuja kwa query).
|
| 3653 |
+
// ββ JAYNESTV: TV yako (jina, nembo, rangi na vipengele vinatoka kwenye jaynestv/config.json; ratiba: build_jaynestv.py) ββ
|
| 3654 |
const JTV_URL = 'https://huggingface.co/datasets/jaynesmaxtv/movie-file/resolve/main/jaynestv/schedule.json';
|
| 3655 |
let jtvP = null, jtvAt = 0;
|
| 3656 |
function jtvSchedule(force) { // ratiba inahifadhiwa dakika 5; ikikosekana JaynesTV haionekani
|
|
|
|
| 3668 |
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; }
|
| 3669 |
return { idx: i, item: s.items[i], offset: pos, left: s.items[i].dur - pos, upcoming };
|
| 3670 |
}
|
| 3671 |
+
const JTV_DEFAULTS = { name: 'JaynesTV', tagline: '', logo: '', accent: '#7c5cff', card: 'logo',
|
| 3672 |
+
features: { bug: true, ident: true, lowerthird: true, ticker: false, epg: true },
|
| 3673 |
+
bug: { position: 'top-right', opacity: 0.85, size: 54 }, ticker: { messages: [], speed: 60 } };
|
| 3674 |
+
function jtvCfg(s) { // chaguo-msingi + config yako (funguo zisizojulikana zinapita ili vipengele vipya vizitumie)
|
| 3675 |
+
const c = (s && s.config) || {}, m = (a, b) => Object.assign({}, a, b || {});
|
| 3676 |
+
const cfg = Object.assign({}, JTV_DEFAULTS, c, { features: m(JTV_DEFAULTS.features, c.features), bug: m(JTV_DEFAULTS.bug, c.bug), ticker: m(JTV_DEFAULTS.ticker, c.ticker) });
|
| 3677 |
+
if (!/^#[0-9a-f]{3,8}$/i.test(String(cfg.accent))) cfg.accent = JTV_DEFAULTS.accent;
|
| 3678 |
+
if (!/^https:\/\//.test(String(cfg.logo || ''))) cfg.logo = '';
|
| 3679 |
+
return cfg;
|
| 3680 |
+
}
|
| 3681 |
+
// ββ MFUMO WA VIPENGELE (plugins): kuongeza kipengele kipya andika jtvFeature('jina', { open(c){}, program(c,n,it){}, close(c){} })
|
| 3682 |
+
// kisha kiwashe kwenye config.json: "features": { "jina": true }. 'c' ina: stage, v (video), bar, s (ratiba), cfg, own[] (vipengele vya DOM
|
| 3683 |
+
// vitakavyoondolewa kiotomatiki), timers[] (vitafutwa kiotomatiki). Kipengele chenye hitilafu hakiangushi TV.
|
| 3684 |
+
const JTV_FEATURES = {};
|
| 3685 |
+
function jtvFeature(name, def) { JTV_FEATURES[name] = def; }
|
| 3686 |
+
const jtvEl = (cls, html) => { const d = document.createElement('div'); d.className = cls; if (html) d.innerHTML = html; return d; };
|
| 3687 |
+
jtvFeature('bug', { // nembo ndogo kwenye kona ya video
|
| 3688 |
+
open(c) {
|
| 3689 |
+
if (!c.cfg.logo) return;
|
| 3690 |
+
const b = document.createElement('img'); b.className = 'jtv-bug jtv-' + c.cfg.bug.position; b.src = c.cfg.logo; b.alt = '';
|
| 3691 |
+
b.style.opacity = String(c.cfg.bug.opacity); b.style.height = (+c.cfg.bug.size || 54) + 'px';
|
| 3692 |
+
c.stage.appendChild(b); c.own.push(b);
|
| 3693 |
+
},
|
| 3694 |
+
});
|
| 3695 |
+
jtvFeature('ident', { // skrini ya utambulisho (nembo + jina + kauli mbiu) mwanzoni
|
| 3696 |
+
open(c) {
|
| 3697 |
+
const d = jtvEl('jtv-ident', (c.cfg.logo ? '<img src="' + esc(c.cfg.logo) + '" alt="">' : '') + '<div class="jtv-idn">' + esc(c.cfg.name) + '</div>' +
|
| 3698 |
+
(c.cfg.tagline ? '<div class="jtv-idt">' + esc(c.cfg.tagline) + '</div>' : ''));
|
| 3699 |
+
c.stage.appendChild(d); c.own.push(d);
|
| 3700 |
+
const t0 = Date.now(), done = () => { d.classList.add('out'); c.timers.push(setTimeout(() => d.remove(), 600)); };
|
| 3701 |
+
c.v.addEventListener('playing', () => c.timers.push(setTimeout(done, Math.max(0, 1500 - (Date.now() - t0)))), { once: true });
|
| 3702 |
+
c.timers.push(setTimeout(done, 4000));
|
| 3703 |
+
},
|
| 3704 |
+
});
|
| 3705 |
+
jtvFeature('lowerthird', { // "NOW: jina la filamu" kipindi kinapoanza
|
| 3706 |
+
program(c, n, it) {
|
| 3707 |
+
const d = jtvEl('jtv-lt', '<span class="jtv-ltk" style="background:' + esc(c.cfg.accent) + '">NOW</span><b>' + esc(it.title) + '</b>' + (it.dj ? ' <span class="jtv-m">' + esc(it.dj) + '</span>' : ''));
|
| 3708 |
+
c.stage.appendChild(d); c.own.push(d); c.timers.push(setTimeout(() => d.remove(), 7000));
|
| 3709 |
+
},
|
| 3710 |
+
});
|
| 3711 |
+
jtvFeature('ticker', { // maandishi yanayopita chini (config.ticker.messages)
|
| 3712 |
+
open(c) {
|
| 3713 |
+
const msgs = (c.cfg.ticker.messages || []).map(String).filter(Boolean); if (!msgs.length) return;
|
| 3714 |
+
const text = msgs.join(' β’ '), dur = Math.max(12, text.length / (Math.max(10, +c.cfg.ticker.speed || 60) / 10));
|
| 3715 |
+
const d = jtvEl('jtv-ticker', '<div class="jtv-tk" style="animation-duration:' + dur.toFixed(0) + 's">' + esc(text) + '</div>');
|
| 3716 |
+
c.stage.appendChild(d); c.own.push(d);
|
| 3717 |
+
},
|
| 3718 |
+
});
|
| 3719 |
async function openJaynesTV() {
|
| 3720 |
openPlayerShell('JaynesTV');
|
| 3721 |
const s = await jtvSchedule(true);
|
| 3722 |
if (!s) return statusMsg('<span style="color:#ffb4ab">' + esc(T('jtv_down')) + '</span>');
|
| 3723 |
+
const cfg = jtvCfg(s);
|
| 3724 |
+
try { ptitle.textContent = cfg.name; } catch (_) {}
|
| 3725 |
stage.style.position = 'relative';
|
| 3726 |
stage.innerHTML = '<video id="vid" controls autoplay playsinline></video><div class="jtv-bar" id="jtvBar"></div>';
|
| 3727 |
const v = document.getElementById('vid'), bar = document.getElementById('jtvBar');
|
| 3728 |
attachPlayerEnhancements(v); ensureSound(v);
|
| 3729 |
+
const ctx = { stage, v, bar, s, cfg, own: [], timers: [] };
|
| 3730 |
+
const on = name => cfg.features[name] === true || (cfg.features[name] !== false && name in JTV_DEFAULTS.features && JTV_DEFAULTS.features[name]);
|
| 3731 |
+
const hook = (h, ...a) => Object.keys(JTV_FEATURES).forEach(k => { if (on(k) && JTV_FEATURES[k][h]) { try { JTV_FEATURES[k][h](ctx, ...a); } catch (e) { console.warn('jtv feature', k, e); } } });
|
| 3732 |
const bad = new Set(); let cur = null, epg = false, hideT = null, last = null;
|
| 3733 |
const hhmm = ms => new Date(ms).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });
|
| 3734 |
const show = (ms) => { bar.classList.remove('hide'); clearTimeout(hideT); if (ms) hideT = setTimeout(() => { if (!epg) bar.classList.add('hide'); }, ms); };
|
| 3735 |
const draw = (n, it) => {
|
| 3736 |
+
const nx = n.upcoming[0], canEpg = on('epg');
|
| 3737 |
+
bar.innerHTML = '<div class="jtv-row"><span class="jtv-live" style="background:' + esc(cfg.accent) + '">β LIVE</span><b>' + esc(it.title) + '</b>' + (it.dj ? '<span class="jtv-m">' + esc(it.dj) + '</span>' : '') +
|
| 3738 |
+
(canEpg ? '<button class="jtv-b" data-j="epg">' + esc(T('jtv_schedule')) + '</button>' : '') + '</div>' +
|
| 3739 |
'<div class="jtv-m">' + esc(T('jtv_next')) + ': ' + esc(nx.item.title) + ' Β· ' + hhmm(nx.at) + '</div>' +
|
| 3740 |
+
(epg && canEpg ? '<div class="jtv-epg">' + n.upcoming.map(u => '<div><span>' + hhmm(u.at) + '</span> ' + esc(u.item.title) + '</div>').join('') + '</div>' : '');
|
| 3741 |
};
|
| 3742 |
const sync = () => {
|
| 3743 |
const n = jtvNow(s, Date.now()), isBad = bad.has(n.idx);
|
|
|
|
| 3745 |
if (cur !== it.url) {
|
| 3746 |
cur = it.url; v.src = it.url + '#t=' + Math.floor(off);
|
| 3747 |
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 });
|
| 3748 |
+
show(8000); hook('program', n, it);
|
| 3749 |
} else if (!isBad && v.readyState > 1 && Math.abs(v.currentTime - off) > 4) v.currentTime = off; // rudi kwenye 'live'
|
| 3750 |
last = n; draw(n, it);
|
| 3751 |
};
|
|
|
|
| 3756 |
v.addEventListener('error', () => { if (last) { bad.add(last.idx); sync(); } });
|
| 3757 |
v.addEventListener('click', () => show(6000));
|
| 3758 |
const timer = setInterval(sync, 15000);
|
| 3759 |
+
window.__jtvStop = () => { clearInterval(timer); clearTimeout(hideT); hook('close'); ctx.timers.forEach(clearTimeout); ctx.own.forEach(e => { try { e.remove(); } catch (_) {} }); };
|
| 3760 |
+
hook('open');
|
| 3761 |
sync();
|
| 3762 |
}
|
| 3763 |
|
| 3764 |
+
async function fetchAllLiveItemsRaw() {
|
| 3765 |
const items = [];
|
| 3766 |
let anyOk = false, lastErr = null;
|
| 3767 |
const posters = await fetchChannelPosters();
|
| 3768 |
|
| 3769 |
try { // JaynesTV (ikiwa ratiba ipo): kadi inaonyesha kipindi kinachochezwa sasa
|
| 3770 |
const js = await jtvSchedule();
|
| 3771 |
+
if (js) { // kadi ina nembo yako (au picha ya kipindi, kulingana na config.card) na jina la kipindi cha sasa
|
| 3772 |
+
const jc = jtvCfg(js), n = jtvNow(js, Date.now());
|
| 3773 |
+
items.push({ id: 'jaynestv', kind: 'live', title: 'β ' + n.item.title, poster: ((jc.card === 'program' ? (n.item.poster || jc.logo) : (jc.logo || n.item.poster)) || null), subtitle: jc.name, imgFallbackOn: true });
|
| 3774 |
+
}
|
| 3775 |
} catch (_) {}
|
| 3776 |
|
| 3777 |
try {
|
|
|
|
| 3828 |
}
|
| 3829 |
|
| 3830 |
// ββ ZILIZOTAFSIRIWA (movies/series za DJ β mradi tofauti) ββ
|
| 3831 |
+
async function fetchTranslatedListRaw() {
|
| 3832 |
const card = (m, idPrefix) => ({
|
| 3833 |
id: idPrefix + m.id, kind: 'translated', title: m.title,
|
| 3834 |
poster: m.thumb_url || m.poster_url || null, imgFallbackOn: true,
|
| 3835 |
subtitle: normDj(m.dj) || (m.category === 'series' ? 'Series' : 'Movie'), isSeries: m.category === 'series',
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3836 |
});
|
| 3837 |
+
const oldP = (async () => { // chanzo cha zamani
|
| 3838 |
+
const r = await fetch(TRANSLATED_BASE + '?limit=100', { headers: { apikey: TRANSLATED_KEY } });
|
| 3839 |
+
if (!r.ok) throw new Error(T('err_generic') + ' ' + r.status);
|
| 3840 |
+
const d = await r.json();
|
| 3841 |
+
if (!d.ok) throw new Error(d.error || 'Seva imekataa ombi.');
|
| 3842 |
+
return (d.movies || []).map(m => card(m, ''));
|
| 3843 |
+
})();
|
| 3844 |
+
const newP = (async () => { // movie-zilizotafsriwa2: public tu (seva inachuja)
|
| 3845 |
+
const r = await fetch(TRANSLATED2_BASE, { headers: { apikey: SUPABASE_ANON_KEY } });
|
| 3846 |
+
if (!r.ok) throw new Error(T('err_generic') + ' ' + r.status);
|
| 3847 |
+
const d = await r.json();
|
| 3848 |
+
return (Array.isArray(d) ? d : []).map(m => card(m, TR2_PREFIX));
|
| 3849 |
+
})();
|
| 3850 |
+
const [a, b] = await Promise.allSettled([oldP, newP]);
|
| 3851 |
+
if (a.status === 'rejected' && b.status === 'rejected') throw a.reason;
|
| 3852 |
+
const fresh = b.status === 'fulfilled' ? b.value : [], older = a.status === 'fulfilled' ? a.value : [];
|
| 3853 |
+
const seen = new Set(fresh.map(x => String(x.title || '').trim().toLowerCase()));
|
| 3854 |
+
return fresh.concat(older.filter(x => !seen.has(String(x.title || '').trim().toLowerCase())));
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3855 |
}
|
| 3856 |
|
| 3857 |
async function loadTranslated() {
|
|
|
|
| 3859 |
renderSkeletonRails(2);
|
| 3860 |
try {
|
| 3861 |
const [items, model] = await Promise.all([fetchTranslatedList(), loadTrModel()]);
|
| 3862 |
+
renderRails(buildTranslatedSections(items, model), false);
|
|
|
|
| 3863 |
} catch (e) {
|
| 3864 |
railsEl.innerHTML = '';
|
| 3865 |
railsEl.appendChild(stateBox(t('err_failed', { e: esc(e.message || t('err_generic')) }), true, loadTranslated));
|
|
|
|
| 4091 |
v.addEventListener('ended', () => { const c = window.__pcPlaylist; if (c && typeof c.next === 'function') c.next(); });
|
| 4092 |
// Tangazo la video kwanza (likishindikana/likiruka, filamu inaanza mara moja)
|
| 4093 |
v.src = url;
|
| 4094 |
+
try { logTrPlay(currentTranslatedDetail && currentTranslatedDetail.id); } catch (_) {}
|
| 4095 |
// Route ya player: #/translated/ID/play?se=..&ep=..
|
| 4096 |
try {
|
| 4097 |
const id = currentTranslatedDetail && currentTranslatedDetail.id;
|
|
|
|
| 4192 |
// ββ MICHEZO (fawa-live) ββ
|
| 4193 |
// Inarudisha orodha bapa (flat) ya mechi zote β inatumika na loadSports()
|
| 4194 |
// (kuonyesha kwa rails, kikundi kwa ligi) na search (kuchuja kwa query).
|
| 4195 |
+
async function fetchAllSportsItemsRaw() {
|
| 4196 |
const res = await fetch(FAWA_BASE + '/matches', { headers: { Authorization: 'Bearer ' + FAWA_KEY } });
|
| 4197 |
if (!res.ok) throw new Error(T('err_generic') + ' ' + res.status);
|
| 4198 |
const d = await res.json();
|
|
|
|
| 6997 |
if (!wrap) return;
|
| 6998 |
wrap.innerHTML = '<div class="trailer-state">' + T('trailer_loading') + '</div>';
|
| 6999 |
try {
|
| 7000 |
+
const result = await fetchTrailerJson(subjectId);
|
|
|
|
|
|
|
|
|
|
|
|
|
| 7001 |
if (result.code !== 0) throw new Error(result.message || 'trailer_error');
|
| 7002 |
// Kagua kila hatua kabla ya kutumia β data.trailer, .url zote zinaweza kukosekana
|
| 7003 |
const trailer = result.data && result.data.trailer;
|
|
|
|
| 10066 |
if (!startRes.ok || !startData.jobId) throw new Error(startData.error || t('dq_err_mux_start'));
|
| 10067 |
const jobId = startData.jobId;
|
| 10068 |
|
| 10069 |
+
const t0 = Date.now();
|
| 10070 |
+
for (let tries = 0; Date.now() - t0 < 900000; tries++) { // hadi dakika 15; muda kati ya maombi unaongezeka (5s -> 15s) kupunguza invocations
|
| 10071 |
+
await new Promise(r => setTimeout(r, Math.min(15000, 5000 + tries * 1000)));
|
| 10072 |
const st = await fetch(MUX_BASE + '?jobId=' + encodeURIComponent(jobId), {
|
| 10073 |
headers: { 'Authorization': 'Bearer ' + MUX_KEY, 'apikey': MUX_KEY },
|
| 10074 |
});
|
|
|
|
| 10665 |
}
|
| 10666 |
}
|
| 10667 |
// Config ya Home inatoka Supabase (home-config); ikishindwa tunatumia nakala ya mwisho au chaguo-msingi
|
| 10668 |
+
async function getHomeConfigRaw(){
|
| 10669 |
try {
|
| 10670 |
const ctl = new AbortController(); const to = setTimeout(() => ctl.abort(), 3000);
|
| 10671 |
const r = await fetch(HOME_CFG_URL, { method: 'POST', signal: ctl.signal, headers: { 'Content-Type': 'application/json', Authorization: 'Bearer ' + SUPABASE_ANON_KEY, apikey: SUPABASE_ANON_KEY }, body: JSON.stringify({ action: 'get' }) });
|