Spaces:
Running
Running
Fix VTV: VTV10→fptplay, VTV6 correct source, stable HLS config, no AbortSignal
Browse files- static/yt_live.js +4 -4
- vtv_api.py +10 -10
static/yt_live.js
CHANGED
|
@@ -778,7 +778,7 @@
|
|
| 778 |
function activateMiniPlayer(){
|
| 779 |
if(!_currentCh) return; createMiniPlayer(); _miniActive=true;
|
| 780 |
const mini=document.getElementById('vtv-mini'); const mv=document.getElementById('vtv-mini-vid'); const v=document.getElementById('vtv-player');
|
| 781 |
-
if(_hls){const h=new Hls({enableWorker:true,lowLatencyMode:
|
| 782 |
else if(v&&v.src){mv.src=v.src; mv.play().catch(()=>{});}
|
| 783 |
const ch=CHANNELS.find(c=>c.id===_currentCh); if(ch) document.getElementById('vtv-mini-ch').textContent=ch.name;
|
| 784 |
fetch('/api/vtv/epg/'+_currentCh).then(r=>r.json()).then(d=>{const p=d.programs||[]; const n=p.find(x=>x.now); document.getElementById('vtv-mini-epg').textContent=n?n.time+' '+n.title:(p[0]?p[0].time+' '+p[0].title:'');}).catch(()=>{});
|
|
@@ -789,7 +789,7 @@
|
|
| 789 |
async function loadAllStreams(){
|
| 790 |
if(_loading) return; _loading=true;
|
| 791 |
const el=document.getElementById('vtv-load'); if(el) el.innerHTML='<div class="vtv-spinner"></div>Đang tải kênh...';
|
| 792 |
-
try{const r=await fetch('/api/vtv/streams'
|
| 793 |
CHANNELS.forEach(ch=>{const t=document.getElementById('vtvt-'+ch.id); if(t){if(STREAMS[ch.id]&&STREAMS[ch.id].length>0){t.classList.remove('off'); t.textContent=ch.name;}else{t.style.opacity='0.35'; t.textContent=ch.name+' ✕';}}});
|
| 794 |
_loading=false; _streamsLoaded=true;
|
| 795 |
}
|
|
@@ -808,7 +808,7 @@
|
|
| 808 |
async function loadEpg(chId){
|
| 809 |
const el=document.getElementById('vtv-epg-list'); if(!el) return;
|
| 810 |
el.innerHTML='<div class="vtv-epg-loading"><div class="vtv-epg-sp"></div>Đang tải lịch...</div>';
|
| 811 |
-
try{const r=await fetch('/api/vtv/epg/'+chId
|
| 812 |
}
|
| 813 |
|
| 814 |
window._vtvToggleEpg=function(){const l=document.getElementById('vtv-epg-list'),b=document.getElementById('vtv-epg-toggle'); if(!l||!b)return; if(l.style.display==='none'){l.style.display='flex';b.textContent='Ẩn';}else{l.style.display='none';b.textContent='Hiện';}};
|
|
@@ -842,7 +842,7 @@
|
|
| 842 |
if(idx>=urls.length){loadEl.style.display='none';errEl.style.display='flex';errMsg.textContent=name+': Tất cả nguồn lỗi.';return;}
|
| 843 |
const src=urls[idx]; loadEl.innerHTML='<div class="vtv-spinner"></div>Kết nối '+name+' ('+(idx+1)+'/'+urls.length+')...';
|
| 844 |
if(typeof Hls!=='undefined'&&Hls.isSupported()){
|
| 845 |
-
const hls=new Hls({enableWorker:true,lowLatencyMode:
|
| 846 |
hls.on(Hls.Events.MANIFEST_PARSED,()=>{video.play().catch(()=>{});loadEl.style.display='none';video.style.display='block';});
|
| 847 |
let rec=0; hls.on(Hls.Events.ERROR,(ev,data)=>{if(data.fatal){if(data.type===Hls.ErrorTypes.NETWORK_ERROR){rec++;if(rec<=3)setTimeout(()=>hls.startLoad(),2000);else{hls.destroy();_hls=null;_tryPlay(video,urls,idx+1,name,loadEl,errEl,errMsg);}}else if(data.type===Hls.ErrorTypes.MEDIA_ERROR){try{hls.recoverMediaError();}catch(e){}}else{hls.destroy();_hls=null;_tryPlay(video,urls,idx+1,name,loadEl,errEl,errMsg);}}});
|
| 848 |
}else if(video.canPlayType('application/vnd.apple.mpegurl')){
|
|
|
|
| 778 |
function activateMiniPlayer(){
|
| 779 |
if(!_currentCh) return; createMiniPlayer(); _miniActive=true;
|
| 780 |
const mini=document.getElementById('vtv-mini'); const mv=document.getElementById('vtv-mini-vid'); const v=document.getElementById('vtv-player');
|
| 781 |
+
if(_hls){const h=new Hls({enableWorker:true,lowLatencyMode:false,startLevel:0,capLevelToPlayerSize:true,maxBufferLength:6}); h.loadSource(STREAMS[_currentCh][0]); h.attachMedia(mv); mv.play().catch(()=>{});}
|
| 782 |
else if(v&&v.src){mv.src=v.src; mv.play().catch(()=>{});}
|
| 783 |
const ch=CHANNELS.find(c=>c.id===_currentCh); if(ch) document.getElementById('vtv-mini-ch').textContent=ch.name;
|
| 784 |
fetch('/api/vtv/epg/'+_currentCh).then(r=>r.json()).then(d=>{const p=d.programs||[]; const n=p.find(x=>x.now); document.getElementById('vtv-mini-epg').textContent=n?n.time+' '+n.title:(p[0]?p[0].time+' '+p[0].title:'');}).catch(()=>{});
|
|
|
|
| 789 |
async function loadAllStreams(){
|
| 790 |
if(_loading) return; _loading=true;
|
| 791 |
const el=document.getElementById('vtv-load'); if(el) el.innerHTML='<div class="vtv-spinner"></div>Đang tải kênh...';
|
| 792 |
+
try{const r=await fetch('/api/vtv/streams'); if(r.ok){const d=await r.json(); CHANNELS.forEach(ch=>{const i=d[ch.id]; if(i&&i.stream_url){let u=i.stream_url; if(NEEDS_PROXY.test(u)) u='/api/proxy/m3u8/vtv?url='+encodeURIComponent(u); STREAMS[ch.id]=[u];} else STREAMS[ch.id]=[];});}}catch(e){}
|
| 793 |
CHANNELS.forEach(ch=>{const t=document.getElementById('vtvt-'+ch.id); if(t){if(STREAMS[ch.id]&&STREAMS[ch.id].length>0){t.classList.remove('off'); t.textContent=ch.name;}else{t.style.opacity='0.35'; t.textContent=ch.name+' ✕';}}});
|
| 794 |
_loading=false; _streamsLoaded=true;
|
| 795 |
}
|
|
|
|
| 808 |
async function loadEpg(chId){
|
| 809 |
const el=document.getElementById('vtv-epg-list'); if(!el) return;
|
| 810 |
el.innerHTML='<div class="vtv-epg-loading"><div class="vtv-epg-sp"></div>Đang tải lịch...</div>';
|
| 811 |
+
try{const r=await fetch('/api/vtv/epg/'+chId); if(!r.ok) throw new Error(); const d=await r.json(); const p=d.programs||[]; if(!p.length){el.innerHTML='<div class="vtv-epg-empty">Chưa có lịch</div>';return;} let h=''; p.forEach(x=>{const n=x.now?' now':''; h+='<div class="vtv-epg-item'+n+'"><span class="t">'+(x.time||'')+(x.end_time?'-'+x.end_time:'')+'</span> <span class="n">'+(x.title||'')+'</span></div>';}); el.innerHTML=h; const ni=el.querySelector('.vtv-epg-item.now'); if(ni) ni.scrollIntoView({behavior:'smooth',inline:'center',block:'nearest'});}catch(e){el.innerHTML='<div class="vtv-epg-empty">Không tải được lịch</div>';}
|
| 812 |
}
|
| 813 |
|
| 814 |
window._vtvToggleEpg=function(){const l=document.getElementById('vtv-epg-list'),b=document.getElementById('vtv-epg-toggle'); if(!l||!b)return; if(l.style.display==='none'){l.style.display='flex';b.textContent='Ẩn';}else{l.style.display='none';b.textContent='Hiện';}};
|
|
|
|
| 842 |
if(idx>=urls.length){loadEl.style.display='none';errEl.style.display='flex';errMsg.textContent=name+': Tất cả nguồn lỗi.';return;}
|
| 843 |
const src=urls[idx]; loadEl.innerHTML='<div class="vtv-spinner"></div>Kết nối '+name+' ('+(idx+1)+'/'+urls.length+')...';
|
| 844 |
if(typeof Hls!=='undefined'&&Hls.isSupported()){
|
| 845 |
+
const hls=new Hls({enableWorker:true,lowLatencyMode:false,startLevel:0,capLevelToPlayerSize:true,maxBufferLength:8,maxMaxBufferLength:12,liveSyncDurationCount:3,liveMaxLatencyDurationCount:5}); _hls=hls; hls.loadSource(src); hls.attachMedia(video);
|
| 846 |
hls.on(Hls.Events.MANIFEST_PARSED,()=>{video.play().catch(()=>{});loadEl.style.display='none';video.style.display='block';});
|
| 847 |
let rec=0; hls.on(Hls.Events.ERROR,(ev,data)=>{if(data.fatal){if(data.type===Hls.ErrorTypes.NETWORK_ERROR){rec++;if(rec<=3)setTimeout(()=>hls.startLoad(),2000);else{hls.destroy();_hls=null;_tryPlay(video,urls,idx+1,name,loadEl,errEl,errMsg);}}else if(data.type===Hls.ErrorTypes.MEDIA_ERROR){try{hls.recoverMediaError();}catch(e){}}else{hls.destroy();_hls=null;_tryPlay(video,urls,idx+1,name,loadEl,errEl,errMsg);}}});
|
| 848 |
}else if(video.canPlayType('application/vnd.apple.mpegurl')){
|
vtv_api.py
CHANGED
|
@@ -58,7 +58,7 @@ VTVGO_FAILOVER = {
|
|
| 58 |
"vtv7": "https://vtvgolive-failover.vtvdigital.vn/vtvgo/vtv7-manifest.m3u8",
|
| 59 |
"vtv8": "https://vtvgolive-failover.vtvdigital.vn/vtvgo/vtv8-manifest.m3u8",
|
| 60 |
"vtv9": "https://vtvgolive-failover.vtvdigital.vn/vtvgo/vtv9-manifest.m3u8",
|
| 61 |
-
|
| 62 |
}
|
| 63 |
|
| 64 |
XEMTV_ONLY_CHANNELS = set() # No channels are xemtv-only; all use VTVGo CDN first
|
|
@@ -73,7 +73,7 @@ FPTPLAY_URLS = {
|
|
| 73 |
"vtv7": "https://live.fptplay53.net/fnxhd1/vtv7hd_vhls.smil/chunklist_b5000000.m3u8",
|
| 74 |
"vtv8": "https://live.fptplay53.net/epzhd1/vtv8hd_vhls.smil/chunklist.m3u8",
|
| 75 |
"vtv9": "https://live.fptplay53.net/fnxhd1/vtv9hd_vhls.smil/chunklist.m3u8",
|
| 76 |
-
"vtv10": "https://live.fptplay53.net/
|
| 77 |
}
|
| 78 |
|
| 79 |
_vtv_cache = {}
|
|
@@ -161,24 +161,24 @@ def fetch_vtv_stream(channel_id):
|
|
| 161 |
_set_cache(channel_id, None)
|
| 162 |
return None
|
| 163 |
|
| 164 |
-
# Source 1: VTVGo CDN (primary for
|
| 165 |
vtvgourl = VTVGO_FAILOVER.get(channel_id)
|
| 166 |
if vtvgourl:
|
| 167 |
_set_cache(channel_id, vtvgourl)
|
| 168 |
return vtvgourl
|
| 169 |
|
| 170 |
-
# Source 2:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 171 |
xemtv_url = fetch_xemtv_stream(channel_id)
|
| 172 |
if xemtv_url:
|
| 173 |
_set_cache(channel_id, xemtv_url)
|
| 174 |
return xemtv_url
|
| 175 |
|
| 176 |
-
# Source 3: fptplay CDN (last resort)
|
| 177 |
-
fpt_url = FPTPLAY_URLS.get(channel_id)
|
| 178 |
-
if fpt_url:
|
| 179 |
-
_set_cache(channel_id, fpt_url)
|
| 180 |
-
return fpt_url
|
| 181 |
-
|
| 182 |
_set_cache(channel_id, None)
|
| 183 |
return None
|
| 184 |
|
|
|
|
| 58 |
"vtv7": "https://vtvgolive-failover.vtvdigital.vn/vtvgo/vtv7-manifest.m3u8",
|
| 59 |
"vtv8": "https://vtvgolive-failover.vtvdigital.vn/vtvgo/vtv8-manifest.m3u8",
|
| 60 |
"vtv9": "https://vtvgolive-failover.vtvdigital.vn/vtvgo/vtv9-manifest.m3u8",
|
| 61 |
+
# VTV10: VTVGo CDN returns 404, use fptplay instead
|
| 62 |
}
|
| 63 |
|
| 64 |
XEMTV_ONLY_CHANNELS = set() # No channels are xemtv-only; all use VTVGo CDN first
|
|
|
|
| 73 |
"vtv7": "https://live.fptplay53.net/fnxhd1/vtv7hd_vhls.smil/chunklist_b5000000.m3u8",
|
| 74 |
"vtv8": "https://live.fptplay53.net/epzhd1/vtv8hd_vhls.smil/chunklist.m3u8",
|
| 75 |
"vtv9": "https://live.fptplay53.net/fnxhd1/vtv9hd_vhls.smil/chunklist.m3u8",
|
| 76 |
+
"vtv10": "https://live.fptplay53.net/fnxhd1/vtv10hd_vhls.smil/chunklist.m3u8",
|
| 77 |
}
|
| 78 |
|
| 79 |
_vtv_cache = {}
|
|
|
|
| 161 |
_set_cache(channel_id, None)
|
| 162 |
return None
|
| 163 |
|
| 164 |
+
# Source 1: VTVGo CDN (primary for channels that have it)
|
| 165 |
vtvgourl = VTVGO_FAILOVER.get(channel_id)
|
| 166 |
if vtvgourl:
|
| 167 |
_set_cache(channel_id, vtvgourl)
|
| 168 |
return vtvgourl
|
| 169 |
|
| 170 |
+
# Source 2: fptplay CDN (primary for VTV10, fallback for others)
|
| 171 |
+
fpt_url = fetch_fptplay_stream(channel_id)
|
| 172 |
+
if fpt_url:
|
| 173 |
+
_set_cache(channel_id, fpt_url)
|
| 174 |
+
return fpt_url
|
| 175 |
+
|
| 176 |
+
# Source 3: xemtv.net PHP scraper (last resort, often unreachable)
|
| 177 |
xemtv_url = fetch_xemtv_stream(channel_id)
|
| 178 |
if xemtv_url:
|
| 179 |
_set_cache(channel_id, xemtv_url)
|
| 180 |
return xemtv_url
|
| 181 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 182 |
_set_cache(channel_id, None)
|
| 183 |
return None
|
| 184 |
|