bep40 commited on
Commit
4098f30
·
verified ·
1 Parent(s): b4e6564

Upload static/yt_live.js with huggingface_hub

Browse files
Files changed (1) hide show
  1. static/yt_live.js +127 -2
static/yt_live.js CHANGED
@@ -20,6 +20,13 @@
20
  let _currentCh = null, _hls = null, _loading = false, _blockInserted = false;
21
  let _streamsLoaded = false, _pipActive = false, _miniActive = false, _vtvPinned = false;
22
 
 
 
 
 
 
 
 
23
  // ===== RECORDER STATE =====
24
  const _rec = {
25
  active: false, startTime: null, endTime: null,
@@ -85,6 +92,13 @@
85
  .vtv-wrap.vtv-sticky .vtv-title{font-size:11px}
86
  .vtv-wrap.vtv-sticky .vtv-badge{font-size:8px}
87
  .vtv-wrap.vtv-sticky .vtv-pin-btn{top:4px;right:6px}
 
 
 
 
 
 
 
88
  .vtv-rec-btn{background:#660000;border:1px solid #990000;color:#ff6666;font-size:9px;padding:3px 8px;border-radius:6px;cursor:pointer;transition:all .2s;display:flex;align-items:center;gap:4px;font-weight:700}
89
  .vtv-rec-btn:hover{background:#990000;color:#fff}
90
  .vtv-rec-btn.recording{background:#cc0000;border-color:#ff0000;color:#fff;animation:vtv-rec-pulse 1s infinite}
@@ -239,6 +253,95 @@
239
  });
240
  }
241
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
242
  // ===== BUILD INLINE RECORDER =====
243
  function buildInlineRecorder() {
244
  const controls = document.querySelector('.vtv-controls');
@@ -790,7 +893,7 @@
790
  w.innerHTML='<div class="vtv-head"><span class="vtv-title">📺 VTV Trực Tuyến</span><span class="vtv-badge">● LIVE</span></div>'+
791
  '<div class="vtv-tabs">'+tabs+'</div>'+
792
  '<div class="vtv-frame"><div class="vtv-load" id="vtv-load"><div class="vtv-spinner"></div>Đang tải kênh...</div><video id="vtv-player" playsinline muted controls preload="auto" style="display:none"></video><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></div>'+
793
- '<div class="vtv-controls"><button class="vtv-pip-btn" id="vtv-pip-btn" onclick="window._vtvTogglePiP()"><svg viewBox="0 0 24 24"><rect x="2" y="4" width="20" height="16" rx="2" fill="none" stroke="currentColor" stroke-width="2"/><rect x="12" y="10" width="8" height="6" rx="1" fill="currentColor"/></svg>PiP</button><span style="flex:1"></span><span style="font-size:9px;color:#666" id="vtv-status"></span></div>'+
794
  '<div class="vtv-epg" id="vtv-epg"><div class="vtv-epg-header"><span class="vtv-epg-title">📋 Lịch phát sóng</span><button class="vtv-epg-toggle" id="vtv-epg-toggle" onclick="window._vtvToggleEpg()">Ẩn</button></div><div class="vtv-epg-list" id="vtv-epg-list"><div class="vtv-epg-loading"><div class="vtv-epg-sp"></div>Đang tải...</div></div></div>';
795
  return w;
796
  }
@@ -823,7 +926,12 @@
823
  if(_hls){_hls.destroy();_hls=null;}
824
  const urls=STREAMS[chId]||[];
825
  if(!urls.length){loadEl.style.display='none';errEl.style.display='flex';errMsg.textContent=chId==='vtvprime'?'VTVPrime: Kênh trả phí.':ch.name+': Không có luồng.';return;}
826
- _tryPlay(video,urls,0,ch.name,loadEl,errEl,errMsg);
 
 
 
 
 
827
  loadEpg(chId);
828
  if(_miniActive){document.getElementById('vtv-mini-ch').textContent=ch.name;fetch('/api/vtv/epg/'+_currentCh).then(r=>r.json()).then(d=>{const p=d.programs||[];const n=p.find(x=>x.now);document.getElementById('vtv-mini-epg').textContent=n?n.time+' '+n.title:(p[0]?p[0].time+' '+p[0].title:'');}).catch(()=>{});}
829
  };
@@ -842,6 +950,23 @@
842
  }else{loadEl.style.display='none';errEl.style.display='flex';errMsg.textContent='Không hỗ trợ HLS';}
843
  }
844
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
845
  // ===== NAV HOOKS =====
846
  const _origShowView=window.showView;
847
  window.showView=function(id){if(id==='view-home'&&_miniActive)closeMiniPlayer();else if(id!=='view-home'&&_currentCh&&STREAMS[_currentCh]&&STREAMS[_currentCh].length>0&&!_miniActive)activateMiniPlayer();return _origShowView.apply(this,arguments);};
 
20
  let _currentCh = null, _hls = null, _loading = false, _blockInserted = false;
21
  let _streamsLoaded = false, _pipActive = false, _miniActive = false, _vtvPinned = false;
22
 
23
+ // ===== DVR / TIMESHIFT STATE =====
24
+ const _dvr = {
25
+ active: false, // DVR buffer đang chạy cho kênh hiện tại?
26
+ bufferUrl: null, // URL playlist HLS từ server buffer
27
+ channel: null, // kênh đang DVR
28
+ };
29
+
30
  // ===== RECORDER STATE =====
31
  const _rec = {
32
  active: false, startTime: null, endTime: null,
 
92
  .vtv-wrap.vtv-sticky .vtv-title{font-size:11px}
93
  .vtv-wrap.vtv-sticky .vtv-badge{font-size:8px}
94
  .vtv-wrap.vtv-sticky .vtv-pin-btn{top:4px;right:6px}
95
+ .vtv-dvr-btn{background:#0a2a0a;border:1px solid #2d6a2d;color:#5cb87a;font-size:9px;padding:3px 8px;border-radius:6px;cursor:pointer;transition:all .2s;display:flex;align-items:center;gap:4px;font-weight:700}
96
+ .vtv-dvr-btn:hover{background:#1a4a1a;color:#fff}
97
+ .vtv-dvr-btn.on{background:#2d8659;border-color:#5cb87a;color:#fff}
98
+ .vtv-dvr-btn svg{width:11px;height:11px;fill:currentColor}
99
+ .vtv-dvr-sched-btn{background:#2a1a00;border:1px solid #6a4a2d;color:#f0c040;font-size:9px;padding:3px 8px;border-radius:6px;cursor:pointer;transition:all .2s;display:flex;align-items:center;gap:4px;font-weight:700;margin-left:2px}
100
+ .vtv-dvr-sched-btn:hover{background:#4a3a10;color:#fff}
101
+ .vtv-dvr-sched-btn svg{width:11px;height:11px;fill:currentColor}
102
  .vtv-rec-btn{background:#660000;border:1px solid #990000;color:#ff6666;font-size:9px;padding:3px 8px;border-radius:6px;cursor:pointer;transition:all .2s;display:flex;align-items:center;gap:4px;font-weight:700}
103
  .vtv-rec-btn:hover{background:#990000;color:#fff}
104
  .vtv-rec-btn.recording{background:#cc0000;border-color:#ff0000;color:#fff;animation:vtv-rec-pulse 1s infinite}
 
253
  });
254
  }
255
 
256
+ // ===== DVR / TIMESHIFT =====
257
+ window._vtvDvrStart = function(chId) {
258
+ if (!chId) chId = _currentCh;
259
+ if (!chId) return;
260
+ const statusEl = document.getElementById('vtv-status');
261
+ if (statusEl) statusEl.textContent = '⏳ Đang bật DVR buffer...';
262
+ fetch('/api/dvr/buffer/start/' + chId)
263
+ .then(r => r.json())
264
+ .then(d => {
265
+ if (d.error) { alert('DVR: ' + d.error); return; }
266
+ _dvr.active = true;
267
+ _dvr.channel = chId;
268
+ _dvr.bufferUrl = d.playlist;
269
+ const btn = document.getElementById('vtv-dvr-btn');
270
+ if (btn) { btn.classList.add('on'); btn.innerHTML = '<svg viewBox="0 0 24 24"><rect x="4" y="4" width="16" height="16" rx="2"/></svg> DVR 5\' ON'; }
271
+ if (statusEl) statusEl.textContent = '✅ DVR 5\' active — tua lùi được!';
272
+ if (_currentCh === chId) _switchToDvrStream();
273
+ })
274
+ .catch(e => { alert('DVR start error: ' + e.message); });
275
+ };
276
+
277
+ window._vtvDvrStop = function(chId) {
278
+ if (!chId) chId = _dvr.channel || _currentCh;
279
+ fetch('/api/dvr/buffer/stop/' + chId).catch(() => {});
280
+ _dvr.active = false;
281
+ _dvr.bufferUrl = null;
282
+ const btn = document.getElementById('vtv-dvr-btn');
283
+ if (btn) { btn.classList.remove('on'); btn.innerHTML = '<svg viewBox="0 0 24 24"><circle cx="12" cy="12" r="8"/><line x1="12" y1="8" x2="12" y2="12" stroke="currentColor" stroke-width="2"/></svg> DVR 5\''; }
284
+ if (_currentCh === chId) _switchToLiveStream();
285
+ const statusEl = document.getElementById('vtv-status');
286
+ if (statusEl) statusEl.textContent = 'Đã tắt DVR';
287
+ };
288
+
289
+ window._vtvDvrToggle = function() {
290
+ if (_dvr.active) window._vtvDvrStop();
291
+ else window._vtvDvrStart();
292
+ };
293
+
294
+ function _switchToDvrStream() {
295
+ if (!_dvr.bufferUrl) return;
296
+ const video = document.getElementById('vtv-player');
297
+ if (!video) return;
298
+ const wasMuted = video.muted;
299
+ if (_hls) { _hls.destroy(); _hls = null; }
300
+ const src = 'https://' + window.location.host + _dvr.bufferUrl;
301
+ if (typeof Hls !== 'undefined' && Hls.isSupported()) {
302
+ const hls = new Hls({enableWorker:true, lowLatencyMode:false, startLevel:0, capLevelToPlayerSize:true, maxBufferLength:30, liveSyncDurationCount:2});
303
+ _hls = hls;
304
+ hls.loadSource(src);
305
+ hls.attachMedia(video);
306
+ hls.on(Hls.Events.MANIFEST_PARSED, () => { video.play().catch(()=>{}); });
307
+ } else if (video.canPlayType('application/vnd.apple.mpegurl')) {
308
+ video.src = src;
309
+ video.play().catch(()=>{});
310
+ }
311
+ video.muted = wasMuted;
312
+ }
313
+
314
+ function _switchToLiveStream() {
315
+ if (!_currentCh) return;
316
+ const urls = STREAMS[_currentCh] || [];
317
+ const video = document.getElementById('vtv-player');
318
+ if (!video || !urls.length) return;
319
+ if (_hls) { _hls.destroy(); _hls = null; }
320
+ _tryPlay(video, urls, 0, _currentCh, document.getElementById('vtv-load'), document.getElementById('vtv-err'), document.getElementById('vtv-err-msg'));
321
+ }
322
+
323
+ // ===== SCHEDULED RECORD =====
324
+ window._vtvSchedRecord = function() {
325
+ const ch = _currentCh || 'vtv1';
326
+ const durStr = prompt('Thời lượng record (giây, max 3600):', '600');
327
+ if (!durStr) return;
328
+ const dur = parseInt(durStr);
329
+ if (!dur || dur < 5 || dur > 3600) { alert('Thời lượng 5-3600s'); return; }
330
+ const now = new Date();
331
+ const startIso = new Date(now.getTime() + 5000).toISOString();
332
+ fetch('/api/dvr/record/schedule', {
333
+ method: 'POST',
334
+ headers: {'Content-Type': 'application/json'},
335
+ body: JSON.stringify({channel: ch, start_iso: startIso, duration_sec: dur, prebuffer: true})
336
+ })
337
+ .then(r => r.json())
338
+ .then(d => {
339
+ if (d.error) alert('Error: ' + d.error);
340
+ else alert('✅ Đã hẹn giờ record!\nKênh: ' + ch.toUpperCase() + '\nThời lượng: ' + dur + 's\nBắt đầu: ' + new Date(startIso).toLocaleString('vi-VN'));
341
+ })
342
+ .catch(e => alert('Error: ' + e.message));
343
+ };
344
+
345
  // ===== BUILD INLINE RECORDER =====
346
  function buildInlineRecorder() {
347
  const controls = document.querySelector('.vtv-controls');
 
893
  w.innerHTML='<div class="vtv-head"><span class="vtv-title">📺 VTV Trực Tuyến</span><span class="vtv-badge">● LIVE</span></div>'+
894
  '<div class="vtv-tabs">'+tabs+'</div>'+
895
  '<div class="vtv-frame"><div class="vtv-load" id="vtv-load"><div class="vtv-spinner"></div>Đang tải kênh...</div><video id="vtv-player" playsinline muted controls preload="auto" style="display:none"></video><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></div>'+
896
+ '<div class="vtv-controls"><button class="vtv-dvr-btn" id="vtv-dvr-btn" onclick="window._vtvDvrToggle()" title="Bật DVR 5 phút - xem lại LIVE"><svg viewBox="0 0 24 24"><circle cx="12" cy="12" r="8"/><line x1="12" y1="8" x2="12" y2="12" stroke="currentColor" stroke-width="2"/></svg> DVR 5\'</button><button class="vtv-dvr-sched-btn" id="vtv-dvr-sched-btn" onclick="window._vtvSchedRecord()" title="Hẹn giờ record"><svg viewBox="0 0 24 24"><rect x="3" y="4" width="18" height="18" rx="2"/><line x1="16" y1="2" x2="16" y2="6" stroke="currentColor" stroke-width="2"/><line x1="8" y1="2" x2="8" y2="6" stroke="currentColor" stroke-width="2"/><line x1="3" y1="10" x2="21" y2="10" stroke="currentColor" stroke-width="2"/></svg> Record</button><button class="vtv-pip-btn" id="vtv-pip-btn" onclick="window._vtvTogglePiP()"><svg viewBox="0 0 24 24"><rect x="2" y="4" width="20" height="16" rx="2" fill="none" stroke="currentColor" stroke-width="2"/><rect x="12" y="10" width="8" height="6" rx="1" fill="currentColor"/></svg>PiP</button><span style="flex:1"></span><span style="font-size:9px;color:#666" id="vtv-status"></span></div>'+
897
  '<div class="vtv-epg" id="vtv-epg"><div class="vtv-epg-header"><span class="vtv-epg-title">📋 Lịch phát sóng</span><button class="vtv-epg-toggle" id="vtv-epg-toggle" onclick="window._vtvToggleEpg()">Ẩn</button></div><div class="vtv-epg-list" id="vtv-epg-list"><div class="vtv-epg-loading"><div class="vtv-epg-sp"></div>Đang tải...</div></div></div>';
898
  return w;
899
  }
 
926
  if(_hls){_hls.destroy();_hls=null;}
927
  const urls=STREAMS[chId]||[];
928
  if(!urls.length){loadEl.style.display='none';errEl.style.display='flex';errMsg.textContent=chId==='vtvprime'?'VTVPrime: Kênh trả phí.':ch.name+': Không có luồng.';return;}
929
+ // Nếu DVR active cho kênh này, dùng buffer thay vì live
930
+ if (_dvr.active && _dvr.channel === chId && _dvr.bufferUrl) {
931
+ _switchToDvrStream();
932
+ } else {
933
+ _tryPlay(video,urls,0,ch.name,loadEl,errEl,errMsg);
934
+ }
935
  loadEpg(chId);
936
  if(_miniActive){document.getElementById('vtv-mini-ch').textContent=ch.name;fetch('/api/vtv/epg/'+_currentCh).then(r=>r.json()).then(d=>{const p=d.programs||[];const n=p.find(x=>x.now);document.getElementById('vtv-mini-epg').textContent=n?n.time+' '+n.title:(p[0]?p[0].time+' '+p[0].title:'');}).catch(()=>{});}
937
  };
 
950
  }else{loadEl.style.display='none';errEl.style.display='flex';errMsg.textContent='Không hỗ trợ HLS';}
951
  }
952
 
953
+ // ===== SYNC DVR BUTTON STATE ON CHANNEL SWITCH =====
954
+ const _origVtvPlay = window._vtvPlay;
955
+ window._vtvPlay = function(chId) {
956
+ // Cập nhật trạng thái nút DVR theo kênh
957
+ const btn = document.getElementById('vtv-dvr-btn');
958
+ if (btn) {
959
+ if (_dvr.active && _dvr.channel === chId) {
960
+ btn.classList.add('on');
961
+ btn.innerHTML = '<svg viewBox="0 0 24 24"><rect x="4" y="4" width="16" height="16" rx="2"/></svg> DVR 5\' ON';
962
+ } else {
963
+ btn.classList.remove('on');
964
+ btn.innerHTML = '<svg viewBox="0 0 24 24"><circle cx="12" cy="12" r="8"/><line x1="12" y1="8" x2="12" y2="12" stroke="currentColor" stroke-width="2"/></svg> DVR 5\'';
965
+ }
966
+ }
967
+ return _origVtvPlay.apply(this, arguments);
968
+ };
969
+
970
  // ===== NAV HOOKS =====
971
  const _origShowView=window.showView;
972
  window.showView=function(id){if(id==='view-home'&&_miniActive)closeMiniPlayer();else if(id!=='view-home'&&_currentCh&&STREAMS[_currentCh]&&STREAMS[_currentCh].length>0&&!_miniActive)activateMiniPlayer();return _origShowView.apply(this,arguments);};