bep40 commited on
Commit
67bf0b8
·
verified ·
1 Parent(s): 9389745

Fix: EPG schedule, VTV6 default channel, no double-load

Browse files
Files changed (1) hide show
  1. static/yt_live.js +116 -12
static/yt_live.js CHANGED
@@ -1,6 +1,6 @@
1
- // === VNEWS — VTV1-VTV10 + VTVPrime LIVE CHANNELS ===
2
  // Uses backend /api/vtv/streams for stream URLs
3
- // VTV1-VTV5,VTV7-VTV9: VTVGo failover | VTV6,VTV10: xemtv.net scraping
4
 
5
  (function(){
6
  if(window._ytLiveLoaded) return;
@@ -20,13 +20,71 @@
20
  {id:'vtvprime', name:'VTVPrime', badge:'Prime'},
21
  ];
22
 
23
- // fptplay URLs that need proxying (can't play directly due to hotlink protection)
24
- const NEEDS_PROXY = /fptplay\.net/;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
25
 
 
26
  const STREAMS = {};
27
  let _currentCh = null;
28
  let _hls = null;
29
  let _loading = false;
 
30
 
31
  const s = document.createElement('style');
32
  s.textContent = `
@@ -48,9 +106,46 @@
48
  .vtv-load{display:flex;align-items:center;justify-content:center;height:180px;color:#00ccff;font-size:12px;flex-direction:column;gap:8px}
49
  .vtv-spinner{width:24px;height:24px;border:2px solid #333;border-top-color:#00ccff;border-radius:50%;animation:vtvspin .8s linear infinite}
50
  @keyframes vtvspin{to{transform:rotate(360deg)}}
 
 
 
 
 
 
 
 
 
 
 
 
51
  `;
52
  document.head.appendChild(s);
53
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
54
  async function loadAllStreams(){
55
  if(_loading) return;
56
  _loading = true;
@@ -65,7 +160,6 @@
65
  const info = data[ch.id];
66
  if(info && info.stream_url){
67
  let url = info.stream_url;
68
- // If it's a fptplay URL, route through our proxy
69
  if(NEEDS_PROXY.test(url)){
70
  url = '/api/proxy/m3u8/vtv?url=' + encodeURIComponent(url);
71
  }
@@ -79,7 +173,6 @@
79
  console.warn('VTV API error:', e);
80
  }
81
 
82
- // Update UI tabs
83
  CHANNELS.forEach(ch => {
84
  const tab = document.getElementById('vtvt-'+ch.id);
85
  if(tab){
@@ -114,15 +207,18 @@
114
  return w;
115
  }
116
 
 
117
  function pinBlock(){
118
  const h = document.getElementById('view-home');
119
  if(!h || document.getElementById('vtv-block')) return;
120
  h.insertBefore(buildBlock(), h.firstChild);
121
  loadAllStreams().then(() => {
122
- for(const ch of CHANNELS){
123
- if(STREAMS[ch.id] && STREAMS[ch.id].length > 0){
124
- setTimeout(() => window._vtvPlay(ch.id), 300);
125
- break;
 
 
126
  }
127
  }
128
  });
@@ -159,6 +255,15 @@
159
  }
160
  return;
161
  }
 
 
 
 
 
 
 
 
 
162
  _tryPlay(video, urls, 0, ch.name, loadEl, errEl, errMsg);
163
  };
164
 
@@ -232,6 +337,7 @@
232
  }
233
  }
234
 
 
235
  const orig = window.loadHome;
236
  if(orig && !orig.__vtvWrapped){
237
  window.loadHome = async function(){
@@ -241,6 +347,4 @@
241
  };
242
  window.loadHome.__vtvWrapped = true;
243
  }
244
- if(document.readyState === 'loading') document.addEventListener('DOMContentLoaded', () => setTimeout(pinBlock, 500));
245
- else setTimeout(pinBlock, 500);
246
  })();
 
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;
 
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
+ const NEEDS_PROXY = /fptplay\.net/;
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 = `
 
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;
 
160
  const info = data[ch.id];
161
  if(info && info.stream_url){
162
  let url = info.stream_url;
 
163
  if(NEEDS_PROXY.test(url)){
164
  url = '/api/proxy/m3u8/vtv?url=' + encodeURIComponent(url);
165
  }
 
173
  console.warn('VTV API error:', e);
174
  }
175
 
 
176
  CHANNELS.forEach(ch => {
177
  const tab = document.getElementById('vtvt-'+ch.id);
178
  if(tab){
 
207
  return w;
208
  }
209
 
210
+ // ===== PIN BLOCK — called only once via loadHome wrapper =====
211
  function pinBlock(){
212
  const h = document.getElementById('view-home');
213
  if(!h || document.getElementById('vtv-block')) return;
214
  h.insertBefore(buildBlock(), h.firstChild);
215
  loadAllStreams().then(() => {
216
+ // Default to VTV6 if available, otherwise first available channel
217
+ const tryOrder = ['vtv6','vtv1','vtv2','vtv3','vtv4','vtv5','vtv7','vtv8','vtv9','vtv10'];
218
+ for(const chId of tryOrder){
219
+ if(STREAMS[chId] && STREAMS[chId].length > 0){
220
+ setTimeout(() => window._vtvPlay(chId), 300);
221
+ return;
222
  }
223
  }
224
  });
 
255
  }
256
  return;
257
  }
258
+ // Update EPG
259
+ const epgEl = document.getElementById('vtv-epg');
260
+ if(epgEl) epgEl.remove();
261
+ const frame = document.querySelector('.vtv-frame');
262
+ if(frame){
263
+ const epgDiv = document.createElement('div');
264
+ epgDiv.innerHTML = buildEPGHTML(chId);
265
+ frame.appendChild(epgDiv.firstElementChild);
266
+ }
267
  _tryPlay(video, urls, 0, ch.name, loadEl, errEl, errMsg);
268
  };
269
 
 
337
  }
338
  }
339
 
340
+ // ===== ONLY wrap loadHome — no DOMContentLoaded listener to avoid double-load =====
341
  const orig = window.loadHome;
342
  if(orig && !orig.__vtvWrapped){
343
  window.loadHome = async function(){
 
347
  };
348
  window.loadHome.__vtvWrapped = true;
349
  }
 
 
350
  })();