bep40 commited on
Commit
2eb76dd
·
verified ·
1 Parent(s): c78ede8

v11: Add toggle button JWPlayer ↔ HLS.js native player (via /api/proxy/m3u8)

Browse files
Files changed (1) hide show
  1. static/vtv_init.js +106 -3
static/vtv_init.js CHANGED
@@ -1,4 +1,4 @@
1
- // ===== VNEWS VTV Player v10 — SV2 iframe trực tiếp =====
2
  (function() {
3
  if (window._vtvInitLoaded) return;
4
  window._vtvInitLoaded = true;
@@ -17,6 +17,8 @@
17
  var _epgData = [];
18
  var _timeTimer = null;
19
  var _serverDate = '';
 
 
20
 
21
  // ===== CSS =====
22
  (function injectCSS() {
@@ -39,6 +41,7 @@
39
  '#vtv-player-section .vtv-pro-tab .b{font-size:7px;opacity:.5;display:block;margin-top:1px}',
40
  '#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)}',
41
  '#vtv-player-section .vtv-pro-frame iframe{position:absolute;inset:0;width:100%!important;height:100%!important;border:none;background:#000}',
 
42
  '#vtv-player-section .vtv-pro-load{display:flex;flex-direction:column;align-items:center;justify-content:center;height:100%;min-height:200px;gap:12px;color:#9aa0a6;font-size:12px}',
43
  '#vtv-player-section .vtv-pro-spinner{width:28px;height:28px;border:2px solid rgba(255,255,255,.06);border-top-color:#0066cc;border-radius:50%;animation:vtv-pro-spin .7s linear infinite}',
44
  '@keyframes vtv-pro-spin{to{transform:rotate(360deg)}}',
@@ -50,6 +53,9 @@
50
  '#vtv-player-section .vtv-pro-controls{display:flex;align-items:center;gap:6px;padding:6px 12px;background:rgba(0,0,0,.3);border-top:1px solid rgba(255,255,255,.04)}',
51
  '#vtv-player-section .vtv-pro-btn{background:rgba(255,255,255,.06);border:1px solid rgba(255,255,255,.08);color:#9aa0a6;font-size:10px;padding:4px 10px;border-radius:6px;cursor:pointer;transition:all .15s;display:flex;align-items:center;gap:4px}',
52
  '#vtv-player-section .vtv-pro-btn:hover{background:rgba(0,102,204,.12);color:#e8eaed}',
 
 
 
53
  '#vtv-player-section .vtv-pro-epg{border-top:1px solid rgba(255,255,255,.04);background:rgba(0,0,0,.15)}',
54
  '#vtv-player-section .vtv-pro-epg-hdr{display:flex;align-items:center;justify-content:space-between;padding:8px 12px 4px}',
55
  '#vtv-player-section .vtv-pro-epg-title{font-size:10px;font-weight:600;color:#9aa0a6;letter-spacing:.5px;text-transform:uppercase}',
@@ -166,18 +172,115 @@
166
  }
167
  }
168
 
169
- // ===== Switch channel SV2 iframe trực tiếp =====
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
170
  window._vtvProSwitch = function(chId) {
171
  _currentCh = chId;
 
172
 
173
  var tabs = document.querySelectorAll('#vtv-player-section .vtv-pro-tab');
174
  for (var i = 0; i < tabs.length; i++)
175
  tabs[i].className = 'vtv-pro-tab' + (tabs[i].getAttribute('data-ch') === chId ? ' on' : '');
176
 
 
 
 
 
177
  updateClock();
178
  if (_timeTimer) clearInterval(_timeTimer);
179
  _timeTimer = setInterval(updateClock, 60000);
180
 
 
 
181
  var frame = document.getElementById('vtv-pro-frame');
182
  if (!frame) return;
183
  frame.innerHTML = '<div class="vtv-pro-load"><div class="vtv-pro-spinner"></div><span>Đang kết nối ' + chId.toUpperCase() + '...</span></div>';
@@ -191,7 +294,6 @@
191
  xhr.onload = function() {
192
  try {
193
  var data = JSON.parse(xhr.responseText);
194
- // Dùng iframe_url trực tiếp từ SV2 — player thật, mobile/desktop đều được
195
  var iframeUrl = data.iframe_url;
196
  if (!iframeUrl) {
197
  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>';
@@ -234,6 +336,7 @@
234
  '</div>' +
235
  '<div class="vtv-pro-controls">' +
236
  '<span id="vtv-pro-time" class="vtv-pro-btn" style="background:none;border:none;font-size:10px;color:#5f6368;margin-right:auto"></span>' +
 
237
  '</div>' +
238
  '<div class="vtv-pro-epg">' +
239
  '<div class="vtv-pro-epg-hdr"><span class="vtv-pro-epg-title">Lịch phát sóng — nguồn VTV.vn</span></div>' +
 
1
+ // ===== VNEWS VTV Player v11 — SV2 iframe + HLS.js native player toggle =====
2
  (function() {
3
  if (window._vtvInitLoaded) return;
4
  window._vtvInitLoaded = true;
 
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() {
 
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}',
44
+ '#vtv-player-section .vtv-pro-frame video{position:absolute;inset:0;width:100%;height:100%;background:#000}',
45
  '#vtv-player-section .vtv-pro-load{display:flex;flex-direction:column;align-items:center;justify-content:center;height:100%;min-height:200px;gap:12px;color:#9aa0a6;font-size:12px}',
46
  '#vtv-player-section .vtv-pro-spinner{width:28px;height:28px;border:2px solid rgba(255,255,255,.06);border-top-color:#0066cc;border-radius:50%;animation:vtv-pro-spin .7s linear infinite}',
47
  '@keyframes vtv-pro-spin{to{transform:rotate(360deg)}}',
 
53
  '#vtv-player-section .vtv-pro-controls{display:flex;align-items:center;gap:6px;padding:6px 12px;background:rgba(0,0,0,.3);border-top:1px solid rgba(255,255,255,.04)}',
54
  '#vtv-player-section .vtv-pro-btn{background:rgba(255,255,255,.06);border:1px solid rgba(255,255,255,.08);color:#9aa0a6;font-size:10px;padding:4px 10px;border-radius:6px;cursor:pointer;transition:all .15s;display:flex;align-items:center;gap:4px}',
55
  '#vtv-player-section .vtv-pro-btn:hover{background:rgba(0,102,204,.12);color:#e8eaed}',
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}',
 
172
  }
173
  }
174
 
175
+ // ===== Destroy HLS player =====
176
+ function destroyHLS() {
177
+ if (_hls) {
178
+ try { _hls.destroy(); } catch(e) {}
179
+ _hls = null;
180
+ }
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');
285
  if (!frame) return;
286
  frame.innerHTML = '<div class="vtv-pro-load"><div class="vtv-pro-spinner"></div><span>Đang kết nối ' + chId.toUpperCase() + '...</span></div>';
 
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>';
 
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>' +