bep40 commited on
Commit
044d29d
·
verified ·
1 Parent(s): d822818

Fix VTV mapping: VTV6=VTV Cần Thơ, VTV10=VTV10 (not Cần Thơ)

Browse files
Files changed (1) hide show
  1. static/yt_live.js +143 -2
static/yt_live.js CHANGED
@@ -1,7 +1,8 @@
1
  // === VNEWS — VTV LIVE CHANNELS ===
2
- // VTV6 = VTV Cần Thơ, VTV10 = VTV10 (riêng biệt)
3
  // All streams played directly from user's browser (server IP blocked by fptplay CDN)
4
  // Sources: iptv-org M3U, Ykcitus M3U, xemtv.net PHP fallback
 
5
 
6
  (function(){
7
  if(window._ytLiveLoaded) return;
@@ -31,4 +32,144 @@
31
  'https://hd.xemtv.net/kenh/vtv3.php',
32
  'https://hd.xemtv.net/kenh/vtv4.php',
33
  'https://hd.xemtv.net/kenh/vtv5.php',
34
- 'https:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  // === VNEWS — VTV LIVE CHANNELS ===
2
+ // VTV6 = VTV Cần Thơ, VTV10 = VTV10 (riêng biệt, không phải Cần Thơ)
3
  // All streams played directly from user's browser (server IP blocked by fptplay CDN)
4
  // Sources: iptv-org M3U, Ykcitus M3U, xemtv.net PHP fallback
5
+ // Uses HLS.js with auto-failover
6
 
7
  (function(){
8
  if(window._ytLiveLoaded) return;
 
32
  'https://hd.xemtv.net/kenh/vtv3.php',
33
  'https://hd.xemtv.net/kenh/vtv4.php',
34
  'https://hd.xemtv.net/kenh/vtv5.php',
35
+ 'https://hd.xemtv.net/kenh/vtv6.php',
36
+ 'https://hd.xemtv.net/kenh/vtv7.php',
37
+ 'https://hd.xemtv.net/kenh/vtv8.php',
38
+ 'https://hd.xemtv.net/kenh/vtv9.php',
39
+ 'https://hd.xemtv.net/kenh/vtv10.php',
40
+ ];
41
+
42
+ const STREAMS = {};
43
+ let _currentCh = null;
44
+ let _hls = null;
45
+ let _loading = false;
46
+
47
+ const s=document.createElement('style');
48
+ s.textContent=`
49
+ .vtv-wrap{margin:6px 4px;background:#111;border:1px solid #0066cc;border-radius:10px;overflow:hidden}
50
+ .vtv-head{display:flex;align-items:center;gap:8px;padding:8px 10px;background:linear-gradient(90deg,#003366,#1a1a1a)}
51
+ .vtv-title{font-size:13px;font-weight:800;color:#00ccff}
52
+ .vtv-badge{font-size:10px;font-weight:800;color:#00ccff;animation:vtvp 1.3s infinite}
53
+ @keyframes vtvp{0%,100%{opacity:1}50%{opacity:.3}}
54
+ .vtv-tabs{display:flex;gap:3px;padding:6px 8px;overflow-x:auto;scrollbar-width:none;background:#0d1a2a}
55
+ .vtv-tabs::-webkit-scrollbar{display:none}
56
+ .vtv-tab{padding:4px 8px;background:#1a2a3a;border:1px solid #2a3a4a;border-radius:10px;color:#8ab4d8;font-size:9px;cursor:pointer;white-space:nowrap;flex-shrink:0;transition:all .2s}
57
+ .vtv-tab:hover{background:#0b4a7a;color:#fff}
58
+ .vtv-tab.on{background:#0066cc;border-color:#00ccff;color:#fff;font-weight:700}
59
+ .vtv-tab.off{opacity:.35;pointer-events:none}
60
+ .vtv-frame{position:relative;width:100%;aspect-ratio:16/9;background:#000;min-height:180px}
61
+ .vtv-frame video{position:absolute;inset:0;width:100%;height:100%;object-fit:contain}
62
+ .vtv-err{display:flex;align-items:center;justify-content:center;height:180px;color:#888;font-size:12px;text-align:center;padding:20px;flex-direction:column;gap:8px}
63
+ .vtv-err button{background:#0066cc;border:none;color:#fff;padding:6px 14px;border-radius:8px;font-size:11px;cursor:pointer}
64
+ .vtv-load{display:flex;align-items:center;justify-content:center;height:180px;color:#00ccff;font-size:12px;flex-direction:column;gap:8px}
65
+ .vtv-spinner{width:24px;height:24px;border:2px solid #333;border-top-color:#00ccff;border-radius:50%;animation:vtvspin .8s linear infinite}
66
+ @keyframes vtvspin{to{transform:rotate(360deg)}}
67
+ `;
68
+ document.head.appendChild(s);
69
+
70
+ function parseM3U(text){
71
+ const lines=text.split('\n');const entries=[];let cur=null;
72
+ for(let i=0;i<lines.length;i++){
73
+ const line=lines[i].trim();
74
+ if(line.startsWith('#EXTINF:')){cur={info:line,url:''};const nm=line.match(/,(.+)$/);cur.name=nm?nm[1].trim():'';const tv=line.match(/tvg-id="([^"]*)"/);cur.tvgId=tv?tv[1]:'';}
75
+ else if(line&&!line.startsWith('#')&&cur){cur.url=line;entries.push(cur);cur=null;}
76
+ }
77
+ return entries;
78
+ }
79
+
80
+ function matchChannel(entry,channel){
81
+ const search=(entry.name+' '+entry.tvgId+' '+entry.info).toLowerCase();
82
+ for(const pat of channel.patterns){
83
+ if(search.includes(pat.toLowerCase())){
84
+ if(channel.id==='vtv5'&&(search.includes('tây')||search.includes('tay')))continue;
85
+ return true;
86
+ }
87
+ }
88
+ return false;
89
+ }
90
+
91
+ async function fetchM3U(url){
92
+ try{const r=await fetch(url,{signal:AbortSignal.timeout(10000)});if(r.ok)return await r.text();}catch(e){}
93
+ return null;
94
+ }
95
+
96
+ async function fetchXemtvPhp(phpUrl){
97
+ try{
98
+ const r=await fetch('/api/proxy/page?url='+encodeURIComponent(phpUrl),{signal:AbortSignal.timeout(10000)});
99
+ if(r.ok){const text=await r.text();const m=text.match(/file\s*:\s*['"]([^'"]*\.m3u8[^'"]*)['"]/i);if(m&&m[1].length>20)return m[1];}
100
+ }catch(e){}
101
+ return null;
102
+ }
103
+
104
+ async function loadAllStreams(){
105
+ if(_loading)return;_loading=true;
106
+ const loadEl=document.getElementById('vtv-load');
107
+ if(loadEl)loadEl.innerHTML='<div class="vtv-spinner"></div>Đang tải danh sách kênh...';
108
+ const results=await Promise.allSettled(M3U_SOURCES.map(src=>fetchM3U(src.url)));
109
+ CHANNELS.forEach(ch=>{STREAMS[ch.id]=[];});
110
+ results.forEach(result=>{
111
+ if(result.status==='fulfilled'&&result.value){
112
+ const entries=parseM3U(result.value);
113
+ CHANNELS.forEach(ch=>{for(const entry of entries){if(matchChannel(entry,ch)){if(entry.url&&!STREAMS[ch.id].includes(entry.url))STREAMS[ch.id].push(entry.url);break;}}});
114
+ }
115
+ });
116
+ await Promise.allSettled(CHANNELS.map(async(ch,idx)=>{
117
+ if(STREAMS[ch.id].length===0&&XEMTV_PHP[idx]){const url=await fetchXemtvPhp(XEMTV_PHP[idx]);if(url)STREAMS[ch.id].push(url);}
118
+ }));
119
+ CHANNELS.forEach(ch=>{const tab=document.getElementById('vtvt-'+ch.id);if(tab){if(STREAMS[ch.id].length>0)tab.classList.remove('off');else{tab.style.opacity='0.35';tab.textContent=ch.name+' ✕';}}});
120
+ _loading=false;
121
+ }
122
+
123
+ function buildBlock(){
124
+ const w=document.createElement('div');w.className='vtv-wrap';w.id='vtv-block';
125
+ let tabs='';
126
+ CHANNELS.forEach(ch=>{tabs+='<button class="vtv-tab off" id="vtvt-'+ch.id+'" onclick="window._vtvPlay(\''+ch.id+'\')">'+ch.name+'</button>';});
127
+ w.innerHTML='<div class="vtv-head"><span class="vtv-title">📺 VTV Trực Tuyến</span><span class="vtv-badge">● LIVE</span></div>'+'<div class="vtv-tabs">'+tabs+'</div>'+'<div class="vtv-frame">'+'<div class="vtv-load" id="vtv-load"><div class="vtv-spinner"></div>Đang tải danh sách kênh...</div>'+'<video id="vtv-player" playsinline muted controls preload="auto" style="display:none"></video>'+'<div class="vtv-err" id="vtv-err" style="display:none"><span id="vtv-err-msg">Không thể tải kênh</span><button onclick="window._vtvRetry()">Thử lại</button></div>'+'</div>';
128
+ return w;
129
+ }
130
+
131
+ function pinBlock(){
132
+ const h=document.getElementById('view-home');
133
+ if(!h||document.getElementById('vtv-block'))return;
134
+ h.insertBefore(buildBlock(),h.firstChild);
135
+ loadAllStreams().then(()=>{for(const ch of CHANNELS){if(STREAMS[ch.id]&&STREAMS[ch.id].length>0){setTimeout(()=>window._vtvPlay(ch.id),300);break;}}});
136
+ }
137
+
138
+ window._vtvRetry=function(){if(_currentCh)window._vtvPlay(_currentCh);};
139
+
140
+ window._vtvPlay=function(chId){
141
+ const ch=CHANNELS.find(c=>c.id===chId);if(!ch)return;
142
+ _currentCh=chId;
143
+ document.querySelectorAll('.vtv-tab').forEach(t=>t.classList.remove('on'));
144
+ const tab=document.getElementById('vtvt-'+chId);if(tab)tab.classList.add('on');
145
+ const video=document.getElementById('vtv-player');const errEl=document.getElementById('vtv-err');const loadEl=document.getElementById('vtv-load');const errMsg=document.getElementById('vtv-err-msg');
146
+ video.style.display='none';errEl.style.display='none';loadEl.style.display='flex';
147
+ loadEl.innerHTML='<div class="vtv-spinner"></div>Đang kết nối '+ch.name+'...';
148
+ if(_hls){_hls.destroy();_hls=null;}
149
+ const urls=STREAMS[chId]||[];
150
+ if(urls.length===0){loadEl.style.display='none';errEl.style.display='flex';errMsg.textContent=ch.name+': Không tìm thấy luồng. Thử lại sau.';return;}
151
+ _tryPlay(video,urls,0,ch.name,loadEl,errEl,errMsg);
152
+ };
153
+
154
+ function _tryPlay(video,urls,idx,name,loadEl,errEl,errMsg){
155
+ if(idx>=urls.length){loadEl.style.display='none';errEl.style.display='flex';errMsg.name+': Tất cả nguồn đều lỗi. Thử lại sau.';return;}
156
+ const src=urls[idx];const label=' ('+(idx+1)+'/'+urls.length+')';
157
+ loadEl.innerHTML='<div class="vtv-spinner"></div>Đang kết nối '+name+label+'...';
158
+ // All streams played directly from browser (server IP blocked by fptplay CDN)
159
+ if(typeof Hls!=='undefined'&&Hls.isSupported()){
160
+ const hls=new Hls({enableWorker:true,lowLatencyMode:true,startLevel:-1,capLevelToPlayerSize:true,maxBufferLength:20,xhrSetup:function(xhr,url){if(url.includes('fptplay')){xhr.setRequestHeader('Referer','https://fptplay.vn/');xhr.setRequestHeader('Origin','https://fptplay.vn');}}});
161
+ _hls=hls;hls.loadSource(src);hls.attachMedia(video);
162
+ hls.on(Hls.Events.MANIFEST_PARSED,()=>{video.play().catch(()=>{});loadEl.style.display='none';video.style.display='block';});
163
+ hls.on(Hls.Events.ERROR,(ev,data)=>{if(data.fatal){if(data.type===Hls.ErrorTypes.NETWORK_ERROR){setTimeout(()=>{hls.destroy();_hls=null;_tryPlay(video,urls,idx+1,name,loadEl,errEl,errMsg);},1500);}else if(data.type===Hls.ErrorTypes.MEDIA_ERROR){hls.recoverMediaError();}else{hls.destroy();_hls=null;_tryPlay(video,urls,idx+1,name,loadEl,errEl,errMsg);}}});
164
+ }else if(video.canPlayType('application/vnd.apple.mpegurl')){
165
+ video.src=src;
166
+ video.addEventListener('loadedmetadata',()=>{video.play().catch(()=>{});loadEl.style.display='none';video.style.display='block';},{once:true});
167
+ video.addEventListener('error',()=>{_tryPlay(video,urls,idx+1,name,loadEl,errEl,errMsg);},{once:true});
168
+ }else{loadEl.style.display='none';errEl.style.display='flex';errMsg.textContent='Trình duyệt không hỗ trợ HLS';}
169
+ }
170
+
171
+ const orig=window.loadHome;
172
+ if(orig&&!orig.__vtvWrapped){window.loadHome=async function(){const r=await orig.apply(this,arguments);try{pinBlock();}catch(e){}return r;};window.loadHome.__vtvWrapped=true;}
173
+ if(document.readyState==='loading')document.addEventListener('DOMContentLoaded',()=>setTimeout(pinBlock,500));
174
+ else setTimeout(pinBlock,500);
175
+ })();