Spaces:
Running
Running
Upload static/vtv_init.js
Browse files- 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;
|
| 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(
|
| 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(
|
| 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 =
|
| 206 |
video.play().catch(function(){});
|
| 207 |
}
|
| 208 |
});
|
| 209 |
} else {
|
| 210 |
-
|
| 211 |
-
video.src = m3u8ProxyUrl;
|
| 212 |
}
|
| 213 |
}
|
| 214 |
|
| 215 |
-
// =====
|
| 216 |
-
|
| 217 |
-
|
| 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 |
-
|
| 226 |
-
|
| 227 |
-
|
| 228 |
-
|
| 229 |
-
|
| 230 |
-
|
| 231 |
-
|
| 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 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 273 |
|
| 274 |
-
// Reset toggle button
|
| 275 |
var toggleBtn = document.getElementById('vtv-pro-toggle-btn');
|
| 276 |
-
if (toggleBtn
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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/' +
|
| 293 |
xhr.timeout = 15000;
|
| 294 |
xhr.onload = function() {
|
| 295 |
try {
|
| 296 |
var data = JSON.parse(xhr.responseText);
|
| 297 |
-
|
| 298 |
-
|
| 299 |
-
|
| 300 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 301 |
}
|
| 302 |
-
|
| 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 |
-
|
|
|
|
| 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 |
+
})();
|