bep40 commited on
Commit
04fa19d
·
verified ·
1 Parent(s): 2b88633

Upload static/vtv_init.js

Browse files
Files changed (1) hide show
  1. static/vtv_init.js +158 -84
static/vtv_init.js CHANGED
@@ -1,4 +1,4 @@
1
- // ===== VNEWS VTV Player v11 — SV2 iframe + HLS.js native player toggle =====
2
  (function() {
3
  if (window._vtvInitLoaded) return;
4
  window._vtvInitLoaded = true;
@@ -10,6 +10,21 @@
10
  {id:'vtv7',name:'VTV7',badge:'Giáo Dục'},{id:'vtv8',name:'VTV8',badge:'Miền Trung'},
11
  {id:'vtv9',name:'VTV9',badge:'Miền Bắc'},{id:'vtv10',name:'VTV10',badge:'Cần Thơ'},
12
  {id:'vtvprime',name:'VTVPrime',badge:'Prime'},
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
13
  ];
14
 
15
  var _currentCh = null;
@@ -17,8 +32,9 @@
17
  var _epgData = [];
18
  var _timeTimer = null;
19
  var _serverDate = '';
20
- var _nativeMode = false; // false=iframe, true=HLSjs
21
  var _hls = null;
 
22
 
23
  // ===== CSS =====
24
  (function injectCSS() {
@@ -38,6 +54,8 @@
38
  '#vtv-player-section .vtv-pro-tab{padding:5px 10px;background:rgba(255,255,255,.04);border:1px solid rgba(255,255,255,.06);border-radius:8px;color:#9aa0a6;font-size:10px;font-weight:500;cursor:pointer;white-space:nowrap;flex-shrink:0;transition:all .2s ease}',
39
  '#vtv-player-section .vtv-pro-tab:hover{background:rgba(0,102,204,.1);border-color:rgba(0,102,204,.3);color:#e8eaed}',
40
  '#vtv-player-section .vtv-pro-tab.on{background:rgba(0,102,204,.2);border-color:#0066cc;color:#fff;font-weight:600}',
 
 
41
  '#vtv-player-section .vtv-pro-tab .b{font-size:7px;opacity:.5;display:block;margin-top:1px}',
42
  '#vtv-player-section .vtv-pro-frame{position:relative;width:100%;aspect-ratio:16/9;background:#000;min-height:200px;border-top:1px solid rgba(255,255,255,.04)}',
43
  '#vtv-player-section .vtv-pro-frame iframe{position:absolute;inset:0;width:100%!important;height:100%!important;border:none;background:#000}',
@@ -56,6 +74,9 @@
56
  '#vtv-player-section .vtv-pro-switch{font-size:9px;padding:3px 8px;background:rgba(0,150,255,.08);border:1px solid rgba(0,150,255,.2);color:#8ab4f8;border-radius:6px;cursor:pointer;transition:all .15s;display:flex;align-items:center;gap:3px;white-space:nowrap}',
57
  '#vtv-player-section .vtv-pro-switch:hover{background:rgba(0,150,255,.2)}',
58
  '#vtv-player-section .vtv-pro-switch.active{background:rgba(0,204,136,.12);border-color:rgba(0,204,136,.3);color:#00cc88}',
 
 
 
59
  '#vtv-player-section .vtv-pro-epg{border-top:1px solid rgba(255,255,255,.04);background:rgba(0,0,0,.15)}',
60
  '#vtv-player-section .vtv-pro-epg-hdr{display:flex;align-items:center;justify-content:space-between;padding:8px 12px 4px}',
61
  '#vtv-player-section .vtv-pro-epg-title{font-size:10px;font-weight:600;color:#9aa0a6;letter-spacing:.5px;text-transform:uppercase}',
@@ -109,6 +130,7 @@
109
 
110
  // ===== EPG =====
111
  function loadEPG(chId) {
 
112
  var epgEl = document.getElementById('vtv-pro-epg-body');
113
  if (!epgEl) return;
114
  epgEl.innerHTML = '<div class="vtv-pro-epg-load"><div class="vtv-pro-spinner" style="width:12px;height:12px;border-width:1.5px;flex-shrink:0"></div>Đang tải lịch...</div>';
@@ -161,7 +183,7 @@
161
 
162
  function startEpgRefresh() {
163
  if (_epgTimer) clearInterval(_epgTimer);
164
- _epgTimer = setInterval(function() { if (_currentCh) loadEPG(_currentCh); }, 60000);
165
  }
166
 
167
  function updateClock() {
@@ -181,104 +203,72 @@
181
  }
182
 
183
  // ===== Play native HLS via proxy =====
184
- function playNativeHLS(chId, m3u8ProxyUrl) {
185
  destroyHLS();
186
  var frame = document.getElementById('vtv-pro-frame');
187
  if (!frame) return;
188
  frame.innerHTML = '<video id="vtv-native-video" controls autoplay playsinline webkit-playsinline style="position:absolute;inset:0;width:100%;height:100%;background:#000"></video>';
189
-
190
  var video = document.getElementById('vtv-native-video');
191
  if (!video) return;
192
-
193
- // Try HLS.js if available (injected via index_v2.html)
194
  if (typeof Hls !== 'undefined' && Hls.isSupported()) {
195
  _hls = new Hls({ enableWorker: false });
196
- _hls.loadSource(m3u8ProxyUrl);
197
  _hls.attachMedia(video);
198
  _hls.on(Hls.Events.MANIFEST_PARSED, function() {
199
  video.play().catch(function(){});
200
  });
201
  _hls.on(Hls.Events.ERROR, function(evt, data) {
202
  if (data.fatal) {
203
- // Fallback: direct video src
204
  destroyHLS();
205
- video.src = m3u8ProxyUrl;
206
  video.play().catch(function(){});
207
  }
208
  });
209
  } else {
210
- // Native HLS fallback (Safari/iOS)
211
- video.src = m3u8ProxyUrl;
212
  }
213
  }
214
 
215
- // ===== Toggle iframe / native HLS =====
216
- window._vtvToggleNative = function(btn) {
217
- if (!_currentCh) return;
218
- _nativeMode = !_nativeMode;
219
- btn.classList.toggle('active', _nativeMode);
220
- btn.innerHTML = _nativeMode ? '📡 HLS' : '🔌 JWPlayer';
221
-
222
  var frame = document.getElementById('vtv-pro-frame');
223
  if (!frame) return;
224
-
225
- // Re-fetch stream data then switch
226
- var xhr = new XMLHttpRequest();
227
- xhr.open('GET', '/api/vtv/stream/' + _currentCh, true);
228
- xhr.timeout = 15000;
229
- xhr.onload = function() {
230
- try {
231
- var data = JSON.parse(xhr.responseText);
232
- if (_nativeMode) {
233
- var m3u8Url = data.m3u8_proxy_url || data.proxy_url_hls || data.stream_url;
234
- if (m3u8Url) {
235
- playNativeHLS(_currentCh, m3u8Url);
236
- } else {
237
- frame.innerHTML = '<div class="vtv-pro-err"><div class="icon">📡</div><div class="msg">Kênh không hỗ trợ HLS</div></div>';
238
- _nativeMode = false;
239
- btn.classList.remove('active');
240
- btn.innerHTML = '🔌 JWPlayer';
241
- }
242
- } else {
243
- var iframeUrl = data.iframe_url;
244
- if (iframeUrl) {
245
- destroyHLS();
246
- var iframe = document.createElement('iframe');
247
- iframe.src = iframeUrl;
248
- iframe.setAttribute('allowfullscreen', 'true');
249
- iframe.setAttribute('allow', 'autoplay; encrypted-media; fullscreen');
250
- iframe.style.cssText = 'position:absolute;inset:0;width:100%;height:100%;border:none;background:#000';
251
- frame.innerHTML = '';
252
- frame.appendChild(iframe);
253
- }
254
- }
255
- } catch(e) {
256
- frame.innerHTML = '<div class="vtv-pro-err"><div class="icon">⚠️</div></div>';
257
- }
258
- };
259
- xhr.onerror = function() {
260
- frame.innerHTML = '<div class="vtv-pro-err"><div class="icon">🔌</div><div class="msg">Lỗi kết nối</div></div>';
261
- };
262
- xhr.send();
263
- };
264
 
265
  // ===== Switch channel =====
266
  window._vtvProSwitch = function(chId) {
267
  _currentCh = chId;
268
  _nativeMode = false;
 
269
 
270
  var tabs = document.querySelectorAll('#vtv-player-section .vtv-pro-tab');
271
- for (var i = 0; i < tabs.length; i++)
272
- tabs[i].className = 'vtv-pro-tab' + (tabs[i].getAttribute('data-ch') === chId ? ' on' : '');
 
 
 
 
273
 
274
- // Reset toggle button
275
  var toggleBtn = document.getElementById('vtv-pro-toggle-btn');
276
- if (toggleBtn) { toggleBtn.classList.remove('active'); toggleBtn.innerHTML = '🔌 JWPlayer'; }
 
 
 
 
 
 
277
 
278
  updateClock();
279
  if (_timeTimer) clearInterval(_timeTimer);
280
  _timeTimer = setInterval(updateClock, 60000);
281
-
282
  destroyHLS();
283
 
284
  var frame = document.getElementById('vtv-pro-frame');
@@ -288,31 +278,110 @@
288
  loadEPG(chId);
289
  startEpgRefresh();
290
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
291
  var xhr = new XMLHttpRequest();
292
- xhr.open('GET', '/api/vtv/stream/' + chId, true);
293
  xhr.timeout = 15000;
294
  xhr.onload = function() {
295
  try {
296
  var data = JSON.parse(xhr.responseText);
297
- var iframeUrl = data.iframe_url;
298
- if (!iframeUrl) {
299
- frame.innerHTML = '<div class="vtv-pro-err"><div class="icon">📡</div><div class="msg">Kênh không khả dụng</div><button onclick="_vtvProSwitch(\''+chId+'\')">Thử lại</button></div>';
300
- return;
 
 
 
 
 
 
 
 
 
 
 
 
301
  }
302
- var iframe = document.createElement('iframe');
303
- iframe.src = iframeUrl;
304
- iframe.setAttribute('allowfullscreen', 'true');
305
- iframe.setAttribute('allow', 'autoplay; encrypted-media; fullscreen');
306
- iframe.style.cssText = 'position:absolute;inset:0;width:100%;height:100%;border:none;background:#000';
307
- frame.innerHTML = '';
308
- frame.appendChild(iframe);
309
- } catch(e) {
310
- frame.innerHTML = '<div class="vtv-pro-err"><div class="icon">⚠️</div><div class="msg">Lỗi tải dữ liệu</div><button onclick="_vtvProSwitch(\''+chId+'\')">Thử lại</button></div>';
311
- }
312
- };
313
- xhr.onerror = xhr.ontimeout = function() {
314
- frame.innerHTML = '<div class="vtv-pro-err"><div class="icon">🔌</div><div class="msg">Mất kết nối máy chủ</div><button onclick="_vtvProSwitch(\''+chId+'\')">Thử lại</button></div>';
315
  };
 
316
  xhr.send();
317
  };
318
 
@@ -321,7 +390,8 @@
321
  var tabs = '';
322
  for (var i = 0; i < CHANNELS.length; i++) {
323
  var ch = CHANNELS[i];
324
- tabs += '<span class="vtv-pro-tab" data-ch="'+ch.id+'" onclick="_vtvProSwitch(\''+ch.id+'\')">' +
 
325
  ch.name + '<span class="b">'+ch.badge+'</span></span>';
326
  }
327
  return '<div id="vtv-player-section">' +
@@ -336,8 +406,10 @@
336
  '</div>' +
337
  '<div class="vtv-pro-controls">' +
338
  '<span id="vtv-pro-time" class="vtv-pro-btn" style="background:none;border:none;font-size:10px;color:#5f6368;margin-right:auto"></span>' +
 
339
  '<span class="vtv-pro-switch" id="vtv-pro-toggle-btn" onclick="_vtvToggleNative(this)">🔌 JWPlayer</span>' +
340
  '</div>' +
 
341
  '<div class="vtv-pro-epg">' +
342
  '<div class="vtv-pro-epg-hdr"><span class="vtv-pro-epg-title">Lịch phát sóng — nguồn VTV.vn</span></div>' +
343
  '<div class="vtv-pro-epg-list" id="vtv-pro-epg-body"><div class="vtv-pro-epg-empty">Chọn kênh để xem lịch phát sóng</div></div>' +
@@ -345,6 +417,8 @@
345
  '</div>';
346
  }
347
 
 
 
348
  function inject() {
349
  var homeEl = document.getElementById('view-home');
350
  if (!homeEl || document.getElementById('vtv-player-section')) return;
@@ -367,4 +441,4 @@
367
  else if (!document.getElementById('vtv-player-section')) setTimeout(waitAndInject, 1000);
368
  })();
369
  }
370
- })();
 
1
+ // ===== VNEWS VTV Player v11 + FPT Play — SV2 iframe + HLS.js native player toggle + Stream Selector =====
2
  (function() {
3
  if (window._vtvInitLoaded) return;
4
  window._vtvInitLoaded = true;
 
10
  {id:'vtv7',name:'VTV7',badge:'Giáo Dục'},{id:'vtv8',name:'VTV8',badge:'Miền Trung'},
11
  {id:'vtv9',name:'VTV9',badge:'Miền Bắc'},{id:'vtv10',name:'VTV10',badge:'Cần Thơ'},
12
  {id:'vtvprime',name:'VTVPrime',badge:'Prime'},
13
+ {id:'fpt-the-thao',name:'FPT',badge:'Thể Thao'},
14
+ ];
15
+
16
+ var FPT_PLAY_IFRAMES = {
17
+ "fpt-the-thao": "https://www.fptplay.net/xem-online/kenh-the-thao",
18
+ "fpt-the-thao-2": "https://hd1.xemtv.net/kenh-fptplay-truc-tuyen-the-thao.html",
19
+ };
20
+
21
+ var FPT_PLAY_M3U8 = "https://live.fptplay53.net/epzhd1/htvcthethao_vhls.smil/chunklist.m3u8";
22
+
23
+ var FPT_PLAY_STREAMS = [
24
+ {label:"🌐 Web FPT Play", url:"https://www.fptplay.net/xem-online/kenh-the-thao", type:"iframe", allow:"autoplay; encrypted-media; fullscreen"},
25
+ {label:"📡 HLS Proxy", url:"/api/proxy/m3u8?url=https://live.fptplay53.net/epzhd1/htvcthethao_vhls.smil/chunklist.m3u8", type:"m3u8"},
26
+ {label:"🔗 HLS Direct", url:FPT_PLAY_M3U8, type:"m3u8"},
27
+ {label:"📺 HD1.xemtv.net", url:"https://hd1.xemtv.net/kenh-fptplay-truc-tuyen-the-thao.html", type:"iframe", allow:"autoplay; encrypted-media; fullscreen"},
28
  ];
29
 
30
  var _currentCh = null;
 
32
  var _epgData = [];
33
  var _timeTimer = null;
34
  var _serverDate = '';
35
+ var _nativeMode = false;
36
  var _hls = null;
37
+ var _fptPlayMode = null;
38
 
39
  // ===== CSS =====
40
  (function injectCSS() {
 
54
  '#vtv-player-section .vtv-pro-tab{padding:5px 10px;background:rgba(255,255,255,.04);border:1px solid rgba(255,255,255,.06);border-radius:8px;color:#9aa0a6;font-size:10px;font-weight:500;cursor:pointer;white-space:nowrap;flex-shrink:0;transition:all .2s ease}',
55
  '#vtv-player-section .vtv-pro-tab:hover{background:rgba(0,102,204,.1);border-color:rgba(0,102,204,.3);color:#e8eaed}',
56
  '#vtv-player-section .vtv-pro-tab.on{background:rgba(0,102,204,.2);border-color:#0066cc;color:#fff;font-weight:600}',
57
+ '#vtv-player-section .vtv-pro-tab.fpt{background:rgba(255,100,0,.04);border-color:rgba(255,120,0,.08);color:#ffa866}',
58
+ '#vtv-player-section .vtv-pro-tab.fpt.on{background:rgba(255,100,0,.2);border-color:#ff6400;color:#fff}',
59
  '#vtv-player-section .vtv-pro-tab .b{font-size:7px;opacity:.5;display:block;margin-top:1px}',
60
  '#vtv-player-section .vtv-pro-frame{position:relative;width:100%;aspect-ratio:16/9;background:#000;min-height:200px;border-top:1px solid rgba(255,255,255,.04)}',
61
  '#vtv-player-section .vtv-pro-frame iframe{position:absolute;inset:0;width:100%!important;height:100%!important;border:none;background:#000}',
 
74
  '#vtv-player-section .vtv-pro-switch{font-size:9px;padding:3px 8px;background:rgba(0,150,255,.08);border:1px solid rgba(0,150,255,.2);color:#8ab4f8;border-radius:6px;cursor:pointer;transition:all .15s;display:flex;align-items:center;gap:3px;white-space:nowrap}',
75
  '#vtv-player-section .vtv-pro-switch:hover{background:rgba(0,150,255,.2)}',
76
  '#vtv-player-section .vtv-pro-switch.active{background:rgba(0,204,136,.12);border-color:rgba(0,204,136,.3);color:#00cc88}',
77
+ '#vtv-player-section .vtv-pro-stream-select{font-size:9px;padding:3px 8px;background:rgba(255,100,0,.08);border:1px solid rgba(255,100,0,.2);color:#ffa866;border-radius:6px;cursor:pointer;transition:all .15s;display:flex;align-items:center;gap:3px;white-space:nowrap}',
78
+ '#vtv-player-section .vtv-pro-stream-select:hover{background:rgba(255,100,0,.2)}',
79
+ '#vtv-player-section .vtv-pro-stream-select.active{background:rgba(255,100,0,.15);border-color:#ff6400;color:#fff}',
80
  '#vtv-player-section .vtv-pro-epg{border-top:1px solid rgba(255,255,255,.04);background:rgba(0,0,0,.15)}',
81
  '#vtv-player-section .vtv-pro-epg-hdr{display:flex;align-items:center;justify-content:space-between;padding:8px 12px 4px}',
82
  '#vtv-player-section .vtv-pro-epg-title{font-size:10px;font-weight:600;color:#9aa0a6;letter-spacing:.5px;text-transform:uppercase}',
 
130
 
131
  // ===== EPG =====
132
  function loadEPG(chId) {
133
+ if (chId.indexOf('fpt') >= 0) return;
134
  var epgEl = document.getElementById('vtv-pro-epg-body');
135
  if (!epgEl) return;
136
  epgEl.innerHTML = '<div class="vtv-pro-epg-load"><div class="vtv-pro-spinner" style="width:12px;height:12px;border-width:1.5px;flex-shrink:0"></div>Đang tải lịch...</div>';
 
183
 
184
  function startEpgRefresh() {
185
  if (_epgTimer) clearInterval(_epgTimer);
186
+ _epgTimer = setInterval(function() { if (_currentCh && _currentCh.indexOf('fpt') < 0) loadEPG(_currentCh); }, 60000);
187
  }
188
 
189
  function updateClock() {
 
203
  }
204
 
205
  // ===== Play native HLS via proxy =====
206
+ function playNativeHLS(m3u8Url) {
207
  destroyHLS();
208
  var frame = document.getElementById('vtv-pro-frame');
209
  if (!frame) return;
210
  frame.innerHTML = '<video id="vtv-native-video" controls autoplay playsinline webkit-playsinline style="position:absolute;inset:0;width:100%;height:100%;background:#000"></video>';
 
211
  var video = document.getElementById('vtv-native-video');
212
  if (!video) return;
 
 
213
  if (typeof Hls !== 'undefined' && Hls.isSupported()) {
214
  _hls = new Hls({ enableWorker: false });
215
+ _hls.loadSource(m3u8Url);
216
  _hls.attachMedia(video);
217
  _hls.on(Hls.Events.MANIFEST_PARSED, function() {
218
  video.play().catch(function(){});
219
  });
220
  _hls.on(Hls.Events.ERROR, function(evt, data) {
221
  if (data.fatal) {
 
222
  destroyHLS();
223
+ video.src = m3u8Url;
224
  video.play().catch(function(){});
225
  }
226
  });
227
  } else {
228
+ video.src = m3u8Url;
 
229
  }
230
  }
231
 
232
+ // ===== Load iframe into frame =====
233
+ function loadIframe(iframeUrl) {
234
+ destroyHLS();
 
 
 
 
235
  var frame = document.getElementById('vtv-pro-frame');
236
  if (!frame) return;
237
+ var iframe = document.createElement('iframe');
238
+ iframe.src = iframeUrl;
239
+ iframe.setAttribute('allowfullscreen', 'true');
240
+ iframe.setAttribute('allow', 'autoplay; encrypted-media; fullscreen');
241
+ iframe.style.cssText = 'position:absolute;inset:0;width:100%;height:100%;border:none;background:#000';
242
+ frame.innerHTML = '';
243
+ frame.appendChild(iframe);
244
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
245
 
246
  // ===== Switch channel =====
247
  window._vtvProSwitch = function(chId) {
248
  _currentCh = chId;
249
  _nativeMode = false;
250
+ _fptPlayMode = null;
251
 
252
  var tabs = document.querySelectorAll('#vtv-player-section .vtv-pro-tab');
253
+ for (var i = 0; i < tabs.length; i++) {
254
+ var t = tabs[i];
255
+ var isFpt = t.getAttribute('data-fpt') === 'true';
256
+ var isActive = (t.getAttribute('data-ch') === chId);
257
+ t.className = 'vtv-pro-tab' + (isActive ? ' on' : '') + (isFpt ? ' fpt' : '') + (isFpt && isActive ? ' on' : '');
258
+ }
259
 
 
260
  var toggleBtn = document.getElementById('vtv-pro-toggle-btn');
261
+ if (toggleBtn && chId.indexOf('fpt') >= 0) {
262
+ toggleBtn.style.display = 'none';
263
+ } else if (toggleBtn) {
264
+ toggleBtn.style.display = '';
265
+ toggleBtn.classList.remove('active');
266
+ toggleBtn.innerHTML = '🔌 JWPlayer';
267
+ }
268
 
269
  updateClock();
270
  if (_timeTimer) clearInterval(_timeTimer);
271
  _timeTimer = setInterval(updateClock, 60000);
 
272
  destroyHLS();
273
 
274
  var frame = document.getElementById('vtv-pro-frame');
 
278
  loadEPG(chId);
279
  startEpgRefresh();
280
 
281
+ if (chId.indexOf('fpt') >= 0) {
282
+ loadFPTPlayUI(chId);
283
+ } else {
284
+ var xhr = new XMLHttpRequest();
285
+ xhr.open('GET', '/api/vtv/stream/' + chId, true);
286
+ xhr.timeout = 15000;
287
+ xhr.onload = function() {
288
+ try {
289
+ var data = JSON.parse(xhr.responseText);
290
+ var iframeUrl = data.iframe_url;
291
+ if (!iframeUrl) {
292
+ frame.innerHTML = '<div class="vtv-pro-err"><div class="icon">📡</div><div class="msg">Kênh không khả dụng</div><button onclick="_vtvProSwitch(\''+chId+'\')">Thử lại</button></div>';
293
+ return;
294
+ }
295
+ loadIframe(iframeUrl);
296
+ } catch(e) {
297
+ frame.innerHTML = '<div class="vtv-pro-err"><div class="icon">⚠️</div><div class="msg">Lỗi tải dữ liệu</div><button onclick="_vtvProSwitch(\''+chId+'\')">Thử lại</button></div>';
298
+ }
299
+ };
300
+ xhr.onerror = xhr.ontimeout = function() {
301
+ frame.innerHTML = '<div class="vtv-pro-err"><div class="icon">🔌</div><div class="msg">Mất kết nối máy chủ</div><button onclick="_vtvProSwitch(\''+chId+'\')">Thử lại</button></div>';
302
+ };
303
+ xhr.send();
304
+ }
305
+ };
306
+
307
+ // ===== Load FPT Play UI =====
308
+ function loadFPTPlayUI(chId) {
309
+ var frame = document.getElementById('vtv-pro-frame');
310
+ if (!frame) return;
311
+ frame.innerHTML = '<div class="vtv-pro-load"><div class="vtv-pro-spinner"></div><span>Đang tải ' + FPT_PLAY_STREAMS[0].label + '...</span></div>';
312
+
313
+ var streamSelectDiv = document.getElementById('vtv-pro-stream-select-div');
314
+ if (streamSelectDiv) {
315
+ var h = '<div style="display:flex;gap:4px;flex-wrap:wrap">';
316
+ for (var i = 0; i < FPT_PLAY_STREAMS.length; i++) {
317
+ h += '<button class="vtv-pro-switch fpt-stream-btn" onclick="_fptPlayLoadStream(' + i + ')" style="font-size:9px;padding:3px 8px;background:rgba(255,100,0,.06);border:1px solid rgba(255,100,0,.15);color:#ffa866;border-radius:6px;cursor:pointer;transition:all .15s" data-idx="' + i + '">' + FPT_PLAY_STREAMS[i].label + '</button>';
318
+ }
319
+ h += '</div>';
320
+ streamSelectDiv.innerHTML = h;
321
+ }
322
+
323
+ _fptPlayLoadStream(0);
324
+ }
325
+
326
+ // ===== Load specific FPT Play stream =====
327
+ window._fptPlayLoadStream = function(idx) {
328
+ if (idx < 0 || idx >= FPT_PLAY_STREAMS.length) return;
329
+ var stream = FPT_PLAY_STREAMS[idx];
330
+ _fptPlayMode = stream.type;
331
+
332
+ document.querySelectorAll('.fpt-stream-btn').forEach(function(btn, i) {
333
+ btn.style.background = (i === idx) ? 'rgba(255,100,0,.2)' : '';
334
+ btn.style.color = (i === idx) ? '#fff' : '#ffa866';
335
+ });
336
+
337
+ var frame = document.getElementById('vtv-pro-frame');
338
+ if (!frame) return;
339
+ frame.innerHTML = '<div class="vtv-pro-load"><div class="vtv-pro-spinner"></div><span>Đang tải: ' + stream.label + '</span></div>';
340
+
341
+ if (stream.type === 'iframe') {
342
+ setTimeout(function() { loadIframe(stream.url); }, 500);
343
+ } else if (stream.type === 'm3u8') {
344
+ destroyHLS();
345
+ playNativeHLS(stream.url);
346
+ }
347
+ };
348
+
349
+ // ===== Toggle iframe / native HLS (VTV only) =====
350
+ window._vtvToggleNative = function(btn) {
351
+ if (!_currentCh || _currentCh.indexOf('fpt') >= 0) return;
352
+ _nativeMode = !_nativeMode;
353
+ btn.classList.toggle('active', _nativeMode);
354
+ btn.innerHTML = _nativeMode ? '📡 HLS' : '🔌 JWPlayer';
355
+
356
+ var frame = document.getElementById('vtv-pro-frame');
357
+ if (!frame) return;
358
+
359
  var xhr = new XMLHttpRequest();
360
+ xhr.open('GET', '/api/vtv/stream/' + _currentCh, true);
361
  xhr.timeout = 15000;
362
  xhr.onload = function() {
363
  try {
364
  var data = JSON.parse(xhr.responseText);
365
+ if (_nativeMode) {
366
+ var m3u8Url = data.m3u8_proxy_url || data.proxy_url_hls || data.stream_url;
367
+ if (m3u8Url) {
368
+ playNativeHLS(m3u8Url);
369
+ } else {
370
+ frame.innerHTML = '<div class="vtv-pro-err"><div class="icon">📡</div><div class="msg">Kênh không hỗ trợ HLS</div></div>';
371
+ _nativeMode = false;
372
+ btn.classList.remove('active');
373
+ btn.innerHTML = '🔌 JWPlayer';
374
+ }
375
+ } else {
376
+ var iframeUrl = data.iframe_url;
377
+ if (iframeUrl) {
378
+ destroyHLS();
379
+ loadIframe(iframeUrl);
380
+ }
381
  }
382
+ } catch(e) { frame.innerHTML = '<div class="vtv-pro-err"><div class="icon">⚠️</div></div>'; }
 
 
 
 
 
 
 
 
 
 
 
 
383
  };
384
+ xhr.onerror = function() { frame.innerHTML = '<div class="vtv-pro-err"><div class="icon">🔌</div><div class="msg">Lỗi kết nối</div></div>'; };
385
  xhr.send();
386
  };
387
 
 
390
  var tabs = '';
391
  for (var i = 0; i < CHANNELS.length; i++) {
392
  var ch = CHANNELS[i];
393
+ var isFpt = ch.id.indexOf('fpt') >= 0;
394
+ tabs += '<span class="vtv-pro-tab' + (isFpt ? ' fpt' : '') + '" data-ch="'+ch.id+'" onclick="_vtvProSwitch(\''+ch.id+'\')" ' + (isFpt ? 'data-fpt="true"' : '') + '>' +
395
  ch.name + '<span class="b">'+ch.badge+'</span></span>';
396
  }
397
  return '<div id="vtv-player-section">' +
 
406
  '</div>' +
407
  '<div class="vtv-pro-controls">' +
408
  '<span id="vtv-pro-time" class="vtv-pro-btn" style="background:none;border:none;font-size:10px;color:#5f6368;margin-right:auto"></span>' +
409
+ '<span class="vtv-pro-stream-select" id="vtv-pro-stream-select" style="display:none" onclick="toggleStreamList()">📡 Stream ▼</span>' +
410
  '<span class="vtv-pro-switch" id="vtv-pro-toggle-btn" onclick="_vtvToggleNative(this)">🔌 JWPlayer</span>' +
411
  '</div>' +
412
+ '<div class="vtv-pro-stream-list" id="vtv-pro-stream-select-div" style="display:none;padding:4px 12px 6px"></div>' +
413
  '<div class="vtv-pro-epg">' +
414
  '<div class="vtv-pro-epg-hdr"><span class="vtv-pro-epg-title">Lịch phát sóng — nguồn VTV.vn</span></div>' +
415
  '<div class="vtv-pro-epg-list" id="vtv-pro-epg-body"><div class="vtv-pro-epg-empty">Chọn kênh để xem lịch phát sóng</div></div>' +
 
417
  '</div>';
418
  }
419
 
420
+ function toggleStreamList() {}
421
+
422
  function inject() {
423
  var homeEl = document.getElementById('view-home');
424
  if (!homeEl || document.getElementById('vtv-player-section')) return;
 
441
  else if (!document.getElementById('vtv-player-section')) setTimeout(waitAndInject, 1000);
442
  })();
443
  }
444
+ })();