bep40 commited on
Commit
dfe8a67
·
verified ·
1 Parent(s): 2c56b85

Upload static/yt_live.js with huggingface_hub

Browse files
Files changed (1) hide show
  1. static/yt_live.js +88 -12
static/yt_live.js CHANGED
@@ -297,16 +297,47 @@
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
  }
@@ -321,25 +352,70 @@
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 =====
 
297
  if (!video) return;
298
  const wasMuted = video.muted;
299
  if (_hls) { _hls.destroy(); _hls = null; }
300
+ // Dùng relative URL để HLS.js resolve segment paths đúng
301
+ const src = _dvr.bufferUrl;
302
+ console.log('[DVR] Switching to buffer:', src);
303
  if (typeof Hls !== 'undefined' && Hls.isSupported()) {
304
+ const hls = new Hls({
305
+ enableWorker: true,
306
+ lowLatencyMode: false,
307
+ startLevel: 0,
308
+ capLevelToPlayerSize: true,
309
+ maxBufferLength: 30,
310
+ liveSyncDurationCount: 2,
311
+ fragLoadingTimeOut: 15000,
312
+ manifestLoadingTimeOut: 15000,
313
+ });
314
  _hls = hls;
315
  hls.loadSource(src);
316
  hls.attachMedia(video);
317
+ hls.on(Hls.Events.MANIFEST_PARSED, () => {
318
+ console.log('[DVR] Manifest parsed, playing...');
319
+ video.play().catch(e => console.warn('[DVR] play failed:', e));
320
+ });
321
+ hls.on(Hls.Events.ERROR, (ev, data) => {
322
+ console.error('[DVR] HLS error:', data.type, data.details, data.fatal);
323
+ if (data.fatal) {
324
+ if (data.type === Hls.ErrorTypes.NETWORK_ERROR) {
325
+ console.log('[DVR] Network error, retrying in 3s...');
326
+ setTimeout(() => { if (_dvr.active) _switchToDvrStream(); }, 3000);
327
+ } else {
328
+ console.log('[DVR] Fatal error, falling back to live stream');
329
+ _dvr.active = false;
330
+ const btn = document.getElementById('vtv-dvr-btn');
331
+ 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\''; }
332
+ _switchToLiveStream();
333
+ }
334
+ }
335
+ });
336
  } else if (video.canPlayType('application/vnd.apple.mpegurl')) {
337
  video.src = src;
338
+ video.play().catch(e => console.warn('[DVR] native play failed:', e));
339
+ } else {
340
+ alert('Trình duyệt không hỗ trợ HLS — cần hls.js');
341
  }
342
  video.muted = wasMuted;
343
  }
 
352
  }
353
 
354
  // ===== SCHEDULED RECORD =====
355
+ // Helper: format Date theo giờ VN (UTC+7) thành ISO string có timezone
356
+ function _toVNISO(date) {
357
+ // Tạo ISO string với offset +07:00
358
+ const tz = 7 * 60; // UTC+7 = +420 minutes
359
+ const d = new Date(date.getTime() + tz * 60000);
360
+ const iso = d.toISOString().replace('Z', '');
361
+ return iso + '+07:00';
362
+ }
363
+
364
  window._vtvSchedRecord = function() {
365
  const ch = _currentCh || 'vtv1';
366
+ // Bước 1: Hỏi thời lượng
367
+ const durStr = prompt('⏱️ Thời lượng record (giây):\n\n• 60 = 1 phút\n• 300 = 5 phút\n• 600 = 10 phút\n• 1800 = 30 phút\n• 3600 = 1 giờ', '600');
368
  if (!durStr) return;
369
  const dur = parseInt(durStr);
370
+ if (!dur || dur < 5 || dur > 3600) { alert('⚠️ Thời lượng từ 5 đến 3600 giây!'); return; }
371
+
372
+ // Bước 2: Hỏi thời điểm bắt đầu (giờ VN)
373
+ const nowVN = new Date(Date.now() + 7*3600000 - new Date().getTimezoneOffset()*60000);
374
+ const nowVNStr = nowVN.toISOString().slice(0, 16); // YYYY-MM-DDTHH:mm
375
+ const startStr = prompt('🕐 Thời điểm BẮT ĐẦU (giờ VN):\n\nĐịnh dạng: YYYY-MM-DD HH:MM\nVí dụ: ' + nowVNStr.replace('T', ' '), nowVNStr.replace('T', ' '));
376
+ if (!startStr) return;
377
+
378
+ // Parse giờ VN thành ISO
379
+ const startVN = new Date(startStr.replace(' ', 'T') + ':00+07:00');
380
+ if (isNaN(startVN.getTime())) { alert('⚠️ Giờ không hợp lệ! Dùng format: YYYY-MM-DD HH:MM'); return; }
381
+
382
+ // Bước 3: Hỏi thời điểm kết thúc (giờ VN)
383
+ const defaultEnd = new Date(startVN.getTime() + dur*1000);
384
+ const defaultEndStr = new Date(defaultEnd.getTime() + 7*3600000 - defaultEnd.getTimezoneOffset()*60000).toISOString().slice(0,16).replace('T',' ');
385
+ const endStr = prompt('🕐 Thời điểm KẾT THÚC (giờ VN):\n\nMặc định = bắt đầu + ' + dur + 's', defaultEndStr);
386
+ if (!endStr) return;
387
+ const endVN = new Date(endStr.replace(' ', 'T') + ':00+07:00');
388
+ if (isNaN(endVN.getTime()) || endVN <= startVN) { alert('⚠️ Giờ kết thúc phải sau giờ bắt đầu!'); return; }
389
+
390
+ // Confirm
391
+ const durActual = Math.round((endVN - startVN) / 1000);
392
+ const ok = confirm(
393
+ '📋 XÁC NHẬN HẸN GIỜ RECORD\n\n' +
394
+ '📺 Kênh: ' + ch.toUpperCase() + '\n' +
395
+ '🕐 Bắt đầu: ' + startStr + ' (VN)\n' +
396
+ '🕐 Kết thúc: ' + endStr + ' (VN)\n' +
397
+ '⏱️ Thời lượng: ' + durActual + 's (' + Math.floor(durActual/60) + 'p ' + (durActual%60) + 's)\n\n' +
398
+ 'OK để xác nhận?'
399
+ );
400
+ if (!ok) return;
401
+
402
  fetch('/api/dvr/record/schedule', {
403
  method: 'POST',
404
  headers: {'Content-Type': 'application/json'},
405
+ body: JSON.stringify({
406
+ channel: ch,
407
+ start_iso: startVN.toISOString(),
408
+ end_iso: endVN.toISOString(),
409
+ duration_sec: durActual,
410
+ prebuffer: true
411
+ })
412
  })
413
  .then(r => r.json())
414
  .then(d => {
415
+ if (d.error) alert('Error: ' + d.error);
416
+ else alert('✅ Đã hẹn giờ record!\n\n📺 Kênh: ' + ch.toUpperCase() + '\n🕐 ' + startStr + ' ' + endStr + ' (giờ VN)\n⏱️ ' + durActual + 's');
417
  })
418
+ .catch(e => alert('Error: ' + e.message));
419
  };
420
 
421
  // ===== BUILD INLINE RECORDER =====