bep40 commited on
Commit
db5b1d2
·
verified ·
1 Parent(s): bbed4a8

Delete static/yt_live_v2.js with huggingface_hub

Browse files
Files changed (1) hide show
  1. static/yt_live_v2.js +0 -348
static/yt_live_v2.js DELETED
@@ -1,348 +0,0 @@
1
- // === VNEWS — VTV1-VTV10 + VTVPrime LIVE CHANNELS + EPG ===
2
- // Uses backend /api/vtv/streams for stream URLs
3
- // Default channel: VTV6 | No double-load | EPG schedule
4
-
5
- (function(){
6
- if(window._ytLiveLoaded) return;
7
- window._ytLiveLoaded = true;
8
-
9
- const CHANNELS = [
10
- {id:'vtv1', name:'VTV1', badge:'Tin tức'},
11
- {id:'vtv2', name:'VTV2', badge:'Khoa học'},
12
- {id:'vtv3', name:'VTV3', badge:'Giải trí'},
13
- {id:'vtv4', name:'VTV4', badge:'Quốc tế'},
14
- {id:'vtv5', name:'VTV5', badge:'Miền Nam'},
15
- {id:'vtv6', name:'VTV6', badge:'Thanh niên'},
16
- {id:'vtv7', name:'VTV7', badge:'Giáo dục'},
17
- {id:'vtv8', name:'VTV8', badge:'Miền Trung'},
18
- {id:'vtv9', name:'VTV9', badge:'Miền Bắc'},
19
- {id:'vtv10', name:'VTV10', badge:'VTV10'},
20
- {id:'vtvprime', name:'VTVPrime', badge:'Prime'},
21
- ];
22
-
23
- // ===== EPG — Lịch phát sóng mẫu cho từng kênh =====
24
- const EPG = {
25
- vtv1: [
26
- {t:'06:00',n:'Nhật ký ngày mai'},{t:'07:00',n:'Thời sự sáng'},{t:'09:00',n:'Thời sự'},
27
- {t:'12:00',n:'Thời sự trưa'},{t:'15:00',n:'Thời sự chiều'},{t:'19:00',n:'Thời sự tối'},
28
- {t:'21:00',n:'Thời sự đêm'},{t:'23:00',n:'Nhật ký ngày mai'},
29
- ],
30
- vtv2: [
31
- {t:'06:00',n:'Khoa học & Công nghệ'},{t:'08:00',n:'Thế giới tự nhiên'},{t:'10:00',n:'Khoa học 360'},
32
- {t:'12:00',n:'Đi tìm giải pháp'},{t:'14:00',n:'Sức khỏe & Cuộc sống'},{t:'16:00',n:'Khoa học cho mọi nhà'},
33
- {t:'18:00',n:'Thế giới động vật'},{t:'20:00',n:'Khoa học & Tương lai'},{t:'22:00',n:'Tài liệu khoa học'},
34
- ],
35
- vtv3: [
36
- {t:'06:00',n:'Sáng vui'},{t:'08:00',n:'Phim truyện'},{t:'10:00',n:'Gameshow'},
37
- {t:'12:00',n:'Âm nhạc'},{t:'14:00',n:'Phim truyện'},{t:'16:00',n:'Giải trí chiều'},
38
- {t:'18:00',n:'Tạp kỹ thuật số'},{t:'20:00',n:'Phim truyện đặc biệt'},{t:'22:00',n:'Đêm giải trí'},
39
- ],
40
- vtv4: [
41
- {t:'06:00',n:'News'},{t:'08:00',n:'World News'},{t:'10:00',n:'Culture'},
42
- {t:'12:00',n:'Midday News'},{t:'14:00',n:'Documentary'},{t:'16:00',n:'Sports'},
43
- {t:'18:00',n:'Evening News'},{t:'20:00',n:'World Today'},{t:'22:00',n:'Nightline'},
44
- ],
45
- vtv5: [
46
- {t:'06:00',n:'Thời sự miền Nam'},{t:'08:00',n:'Chương trình thiếu nhi'},{t:'10:00',n:'Phim truyện'},
47
- {t:'12:00',n:'Thời sự trưa'},{t:'14:00',n:'Giải trí'},{t:'16:00',n:'Thể thao miền Nam'},
48
- {t:'18:00',n:'Thời sự chiều'},{t:'20:00',n:'Phim truyện'},{t:'22:00',n:'Thời sự tối'},
49
- ],
50
- vtv6: [
51
- {t:'06:00',n:'Khởi động ngày mới'},{t:'08:00',n:'Thanh niên & Sáng tạo'},{t:'10:00',n:'Thế giới trẻ'},
52
- {t:'12:00',n:'Nhịp sống trẻ'},{t:'14:00',n:'Thể thao tuổi trẻ'},{t:'16:00',n:'Giải trí thanh niên'},
53
- {t:'18:00',n:'Thời sự trẻ'},{t:'20:00',n:'Đêm nhạc'},{t:'22:00',n:'Thanh niên & Đêm'},
54
- ],
55
- vtv7: [
56
- {t:'06:00',n:'Giáo dục sáng'},{t:'08:00',n:'Học mọi lúc'},{t:'10:00',n:'Kỹ năng sống'},
57
- {t:'12:00',n:'Giáo dục trưa'},{t:'14:00',n:'Học trực tuyến'},{t:'16:00',n:'Thiếu nhi'},
58
- {t:'18:00',n:'Giáo dục chiều'},{t:'20:00',n:'Tài liệu giáo dục'},{t:'22:00',n:'Học suốt đời'},
59
- ],
60
- vtv8: [
61
- {t:'06:00',n:'Thời sự miền Trung'},{t:'08:00',n:'Văn hóa miền Trung'},{t:'10:00',n:'Phim truyện'},
62
- {t:'12:00',n:'Thời sự trưa'},{t:'14:00',n:'Giải trí'},{t:'16:00',n:'Thể thao miền Trung'},
63
- {t:'18:00',n:'Thời sự chiều'},{t:'20:00',n:'Phim truyện'},{t:'22:00',n:'Thời sự tối'},
64
- ],
65
- vtv9: [
66
- {t:'06:00',n:'Thời sự miền Bắc'},{t:'08:00',n:'Văn hóa miền Bắc'},{t:'10:00',n:'Phim truyện'},
67
- {t:'12:00',n:'Thời sự trưa'},{t:'14:00',n:'Giải trí'},{t:'16:00',n:'Thể thao miền Bắc'},
68
- {t:'18:00',n:'Thời sự chiều'},{t:'20:00',n:'Phim truyện'},{t:'22:00',n:'Thời sự tối'},
69
- ],
70
- vtv10: [
71
- {t:'06:00',n:'Thời sự Tây Nam Bộ'},{t:'08:00',n:'Văn hóa đồng bằng'},{t:'10:00',n:'Phim truyện'},
72
- {t:'12:00',n:'Thời sự trưa'},{t:'14:00',n:'Giải trí'},{t:'16:00',n:'Thể thao Tây Nam Bộ'},
73
- {t:'18:00',n:'Thời sự chiều'},{t:'20:00',n:'Phim truyện'},{t:'22:00',n:'Thời sự tối'},
74
- ],
75
- vtvprime: [
76
- {t:'06:00',n:'Prime Morning'},{t:'08:00',n:'Prime Cinema'},{t:'10:00',n:'Prime Sports'},
77
- {t:'12:00',n:'Prime News'},{t:'14:00',n:'Prime Drama'},{t:'16:00',n:'Prime Entertainment'},
78
- {t:'18:00',n:'Prime Evening'},{t:'20:00',n:'Prime Night'},{t:'22:00',n:'Prime Late'},
79
- ],
80
- };
81
-
82
- // ALL external streams need proxy — VTVGo/fptplay CDNs don't send CORS headers
83
- const STREAMS = {};
84
- let _currentCh = null;
85
- let _hls = null;
86
- let _loading = false;
87
- let _epgVisible = false;
88
-
89
- const s = document.createElement('style');
90
- s.textContent = `
91
- .vtv-wrap{margin:6px 4px;background:#111;border:1px solid #0066cc;border-radius:10px;overflow:hidden}
92
- .vtv-head{display:flex;align-items:center;gap:8px;padding:8px 10px;background:linear-gradient(90deg,#003366,#1a1a1a)}
93
- .vtv-title{font-size:13px;font-weight:800;color:#00ccff}
94
- .vtv-badge{font-size:10px;font-weight:800;color:#00ccff;animation:vtvp 1.3s infinite}
95
- @keyframes vtvp{0%,100%{opacity:1}50%{opacity:.3}}
96
- .vtv-tabs{display:flex;gap:3px;padding:6px 8px;overflow-x:auto;scrollbar-width:none;background:#0d1a2a}
97
- .vtv-tabs::-webkit-scrollbar{display:none}
98
- .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}
99
- .vtv-tab:hover{background:#0b4a7a;color:#fff}
100
- .vtv-tab.on{background:#0066cc;border-color:#00ccff;color:#fff;font-weight:700}
101
- .vtv-tab.off{opacity:.35;pointer-events:none}
102
- .vtv-frame{position:relative;width:100%;aspect-ratio:16/9;background:#000;min-height:180px}
103
- .vtv-frame video{position:absolute;inset:0;width:100%;height:100%;object-fit:contain}
104
- .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}
105
- .vtv-err button{background:#0066cc;border:none;color:#fff;padding:6px 14px;border-radius:8px;font-size:11px;cursor:pointer}
106
- .vtv-load{display:flex;align-items:center;justify-content:center;height:180px;color:#00ccff;font-size:12px;flex-direction:column;gap:8px}
107
- .vtv-spinner{width:24px;height:24px;border:2px solid #333;border-top-color:#00ccff;border-radius:50%;animation:vtvspin .8s linear infinite}
108
- @keyframes vtvspin{to{transform:rotate(360deg)}}
109
- .vtv-epg{margin:0;padding:6px 10px;background:#0a1628;border-top:1px solid #1a2a3a}
110
- .vtv-epg-header{display:flex;align-items:center;justify-content:space-between;margin-bottom:4px}
111
- .vtv-epg-title{font-size:10px;font-weight:700;color:#00ccff}
112
- .vtv-epg-toggle{background:none;border:1px solid #2a3a4a;color:#8ab4d8;font-size:9px;padding:2px 8px;border-radius:6px;cursor:pointer}
113
- .vtv-epg-list{display:flex;gap:4px;overflow-x:auto;scrollbar-width:none;padding-bottom:4px}
114
- .vtv-epg-list::-webkit-scrollbar{display:none}
115
- .vtv-epg-item{flex:0 0 auto;padding:3px 6px;background:#1a2a3a;border-radius:4px;font-size:8px;color:#8ab4d8;white-space:nowrap}
116
- .vtv-epg-item.now{background:#0066cc;color:#fff;font-weight:700}
117
- .vtv-epg-item .epg-t{font-size:7px;color:#6a8aaa}
118
- .vtv-epg-item.now .epg-t{color:#aaccee}
119
- .vtv-epg-item .epg-n{color:#ccc;font-size:8px}
120
- .vtv-epg-item.now .epg-n{color:#fff}
121
- `;
122
- document.head.appendChild(s);
123
-
124
- function getCurrentHour(){
125
- return new Date().getHours();
126
- }
127
-
128
- function buildEPGHTML(chId){
129
- const epg = EPG[chId] || [];
130
- if(!epg.length) return '';
131
- const curH = getCurrentHour();
132
- let items = '';
133
- epg.forEach(item => {
134
- const itemH = parseInt(item.t.split(':')[0], 10);
135
- const isNow = itemH <= curH && (itemH + 2) > curH;
136
- items += `<div class="vtv-epg-item${isNow?' now':''}"><div class="epg-t">${item.t}</div><div class="epg-n">${item.n}</div></div>`;
137
- });
138
- return `<div class="vtv-epg" id="vtv-epg">' +
139
- '<div class="vtv-epg-header"><span class="vtv-epg-title">📋 Lịch phát sóng</span>' +
140
- '<button class="vtv-epg-toggle" onclick="window._vtvToggleEPG()">Ẩn/Hiện</button></div>' +
141
- '<div class="vtv-epg-list" id="vtv-epg-list">' + items + '</div></div>';
142
- }
143
-
144
- window._vtvToggleEPG = function(){
145
- const list = document.getElementById('vtv-epg-list');
146
- if(list) list.style.display = list.style.display === 'none' ? 'flex' : 'none';
147
- };
148
-
149
- async function loadAllStreams(){
150
- if(_loading) return;
151
- _loading = true;
152
- const loadEl = document.getElementById('vtv-load');
153
- if(loadEl) loadEl.innerHTML = '<div class="vtv-spinner"></div>Đang tải danh sách kênh...';
154
-
155
- try {
156
- const r = await fetch('/api/vtv/streams', {signal: AbortSignal.timeout(10000)});
157
- if(r.ok){
158
- const data = await r.json();
159
- CHANNELS.forEach(ch => {
160
- const info = data[ch.id];
161
- if(info && info.stream_url){
162
- // Always proxy through backend to avoid CORS issues
163
- const url = '/api/proxy/m3u8/vtv?url=' + encodeURIComponent(info.stream_url);
164
- STREAMS[ch.id] = [url];
165
- } else {
166
- STREAMS[ch.id] = [];
167
- }
168
- });
169
- }
170
- } catch(e) {
171
- console.warn('VTV API error:', e);
172
- }
173
-
174
- CHANNELS.forEach(ch => {
175
- const tab = document.getElementById('vtvt-'+ch.id);
176
- if(tab){
177
- if(STREAMS[ch.id] && STREAMS[ch.id].length > 0){
178
- tab.classList.remove('off');
179
- tab.textContent = ch.name;
180
- } else {
181
- tab.style.opacity = '0.35';
182
- tab.textContent = ch.name + ' ✕';
183
- }
184
- }
185
- });
186
- _loading = false;
187
- }
188
-
189
- function buildBlock(){
190
- const w = document.createElement('div');
191
- w.className = 'vtv-wrap';
192
- w.id = 'vtv-block';
193
- let tabs = '';
194
- CHANNELS.forEach(ch => {
195
- tabs += '<button class="vtv-tab off" id="vtvt-'+ch.id+'" onclick="window._vtvPlay(\''+ch.id+'\')">'+ch.name+'</button>';
196
- });
197
- w.innerHTML =
198
- '<div class="vtv-head"><span class="vtv-title">📺 VTV Trực Tuyến</span><span class="vtv-badge">● LIVE</span></div>' +
199
- '<div class="vtv-tabs">' + tabs + '</div>' +
200
- '<div class="vtv-frame">' +
201
- '<div class="vtv-load" id="vtv-load"><div class="vtv-spinner"></div>Đang tải danh sách kênh...</div>' +
202
- '<video id="vtv-player" playsinline muted controls preload="auto" style="display:none"></video>' +
203
- '<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>' +
204
- '</div>';
205
- return w;
206
- }
207
-
208
- // ===== PIN BLOCK — called only once via loadHome wrapper =====
209
- function pinBlock(){
210
- const h = document.getElementById('view-home');
211
- if(!h || document.getElementById('vtv-block')) return;
212
- h.insertBefore(buildBlock(), h.firstChild);
213
- loadAllStreams().then(() => {
214
- // Default to VTV6 if available, otherwise first available channel
215
- const tryOrder = ['vtv6','vtv1','vtv2','vtv3','vtv4','vtv5','vtv7','vtv8','vtv9','vtv10'];
216
- for(const chId of tryOrder){
217
- if(STREAMS[chId] && STREAMS[chId].length > 0){
218
- setTimeout(() => window._vtvPlay(chId), 300);
219
- return;
220
- }
221
- }
222
- });
223
- }
224
-
225
- window._vtvRetry = function(){
226
- if(_currentCh) window._vtvPlay(_currentCh);
227
- };
228
-
229
- window._vtvPlay = function(chId){
230
- const ch = CHANNELS.find(c => c.id === chId);
231
- if(!ch) return;
232
- _currentCh = chId;
233
- document.querySelectorAll('.vtv-tab').forEach(t => t.classList.remove('on'));
234
- const tab = document.getElementById('vtvt-'+chId);
235
- if(tab) tab.classList.add('on');
236
- const video = document.getElementById('vtv-player');
237
- const errEl = document.getElementById('vtv-err');
238
- const loadEl = document.getElementById('vtv-load');
239
- const errMsg = document.getElementById('vtv-err-msg');
240
- video.style.display = 'none';
241
- errEl.style.display = 'none';
242
- loadEl.style.display = 'flex';
243
- loadEl.innerHTML = '<div class="vtv-spinner"></div>Đang kết nối ' + ch.name + '...';
244
- if(_hls){ _hls.destroy(); _hls = null; }
245
- const urls = STREAMS[chId] || [];
246
- if(urls.length === 0){
247
- loadEl.style.display = 'none';
248
- errEl.style.display = 'flex';
249
- if(chId === 'vtvprime'){
250
- errMsg.textContent = 'VTVPrime: Kênh trả phí, không có luồng miễn phí.';
251
- } else {
252
- errMsg.textContent = ch.name + ': Không tìm thấy luồng. Thử lại sau.';
253
- }
254
- return;
255
- }
256
- // Update EPG
257
- const epgEl = document.getElementById('vtv-epg');
258
- if(epgEl) epgEl.remove();
259
- const frame = document.querySelector('.vtv-frame');
260
- if(frame){
261
- const epgDiv = document.createElement('div');
262
- epgDiv.innerHTML = buildEPGHTML(chId);
263
- frame.appendChild(epgDiv.firstElementChild);
264
- }
265
- _tryPlay(video, urls, 0, ch.name, loadEl, errEl, errMsg);
266
- };
267
-
268
- function _tryPlay(video, urls, idx, name, loadEl, errEl, errMsg){
269
- if(idx >= urls.length){
270
- loadEl.style.display = 'none';
271
- errEl.style.display = 'flex';
272
- errMsg.textContent = name + ': Tất cả nguồn đều lỗi. Thử lại sau.';
273
- return;
274
- }
275
- const src = urls[idx];
276
- const sourceLabel = ' (' + (idx+1) + '/' + urls.length + ')';
277
- loadEl.innerHTML = '<div class="vtv-spinner"></div>Đang kết nối ' + name + sourceLabel + '...';
278
- if(typeof Hls !== 'undefined' && Hls.isSupported()){
279
- const hls = new Hls({
280
- enableWorker: true,
281
- lowLatencyMode: true,
282
- startLevel: -1,
283
- capLevelToPlayerSize: true,
284
- maxBufferLength: 20,
285
- xhrSetup: function(xhr, url){
286
- if(url.includes('fptplay')){
287
- xhr.setRequestHeader('Referer', 'https://fptplay.vn/');
288
- xhr.setRequestHeader('Origin', 'https://fptplay.vn');
289
- }
290
- }
291
- });
292
- _hls = hls;
293
- hls.loadSource(src);
294
- hls.attachMedia(video);
295
- hls.on(Hls.Events.MANIFEST_PARSED, () => {
296
- video.play().catch(() => {});
297
- loadEl.style.display = 'none';
298
- video.style.display = 'block';
299
- });
300
- let recoverAttempts = 0;
301
- hls.on(Hls.Events.ERROR, (ev, data) => {
302
- if(data.fatal){
303
- if(data.type === Hls.ErrorTypes.NETWORK_ERROR){
304
- recoverAttempts++;
305
- if(recoverAttempts <= 3){
306
- setTimeout(() => hls.startLoad(), 2000);
307
- } else {
308
- hls.destroy();
309
- _hls = null;
310
- _tryPlay(video, urls, idx + 1, name, loadEl, errEl, errMsg);
311
- }
312
- } else if(data.type === Hls.ErrorTypes.MEDIA_ERROR){
313
- try { hls.recoverMediaError(); } catch(e) {}
314
- } else {
315
- hls.destroy();
316
- _hls = null;
317
- _tryPlay(video, urls, idx + 1, name, loadEl, errEl, errMsg);
318
- }
319
- }
320
- });
321
- } else if(video.canPlayType('application/vnd.apple.mpegurl')){
322
- video.src = src;
323
- video.addEventListener('loadedmetadata', () => {
324
- video.play().catch(() => {});
325
- loadEl.style.display = 'none';
326
- video.style.display = 'block';
327
- }, {once: true});
328
- video.addEventListener('error', () => {
329
- _tryPlay(video, urls, idx + 1, name, loadEl, errEl, errMsg);
330
- }, {once: true});
331
- } else {
332
- loadEl.style.display = 'none';
333
- errEl.style.display = 'flex';
334
- errMsg.textContent = 'Trình duyệt không hỗ trợ HLS';
335
- }
336
- }
337
-
338
- // ===== ONLY wrap loadHome — no DOMContentLoaded listener to avoid double-load =====
339
- const orig = window.loadHome;
340
- if(orig && !orig.__vtvWrapped){
341
- window.loadHome = async function(){
342
- const r = await orig.apply(this, arguments);
343
- try{ pinBlock(); }catch(e){}
344
- return r;
345
- };
346
- window.loadHome.__vtvWrapped = true;
347
- }
348
- })();