bep40 commited on
Commit
4b7fba2
·
verified ·
1 Parent(s): 0de88ac

v11: Fix DVR snap-back — override hls.startLoad

Browse files

Gốc rễ: HLS.js startLoad(-1) tự động seek về live edge mỗi khi load segment → user seek back bị snap về LIVE liên tục.

Giải pháp: Override hls.startLoad method:
- Lần đầu load: startLoad(-1) bình thường → stream phát từ live edge
- Sau khi user seek back (_dvrHasUserSeeked=true): startLoad(cur-5) thay vì startLoad(-1) → load segment từ vị trí hiện tại, KHÔNG seek về live
- Nút LIVE: reset _dvrHasUserSeeked=false → lần sau startLoad(-1) hoạt động bình thường
- timeupdate: tự động clear _dvrHasUserSeeked khi playhead chạm live edge

→ Không cần intercept seeked (tránh loop), không cần liveSyncDurationCount nhỏ (tránh snap). Chặn ngay từ gốc.

Files changed (1) hide show
  1. static/yt_live.js +51 -28
static/yt_live.js CHANGED
@@ -1,6 +1,6 @@
1
- // === VNEWS — VTV LIVE + Inline Recorder v10 ===
2
  // Features: DVR 5-min rewind buffer (seek & stay), PiP, mini-player, INLINE RECORDER with ratio crop + AI title + Short AI upload
3
- // v10: Fix DVR — keep liveSync for stream start, but intercept HLS auto-snap via seeking/seeked events to preserve user position
4
 
5
  (function(){
6
  if(window._ytLiveLoaded) return;
@@ -23,6 +23,8 @@
23
  let _dvrSeekBarTimer = null;
24
  let _dvrUserSeeking = false; // true while user is dragging DVR seek handle
25
  let _dvrLastSeenEdge = false; // track if playhead was at live edge before seek
 
 
26
 
27
  // ===== RECORDER STATE =====
28
  const _rec = {
@@ -684,12 +686,14 @@
684
  const bufferStart = Math.max(0, duration - DVR_BUFFER_SECONDS);
685
  const seekTime = bufferStart + pct * (duration - bufferStart);
686
  _dvrUserSeeking = !isRelease;
 
 
 
 
687
  video.currentTime = seekTime;
688
  if (isRelease) {
689
- // Resume playback after seek release
690
  video.play().catch(()=>{});
691
  } else {
692
- // Pause while dragging for smooth scrubbing
693
  video.pause();
694
  }
695
  updateDVRUI();
@@ -711,6 +715,7 @@
711
  const video = document.getElementById('vtv-player');
712
  if (video && _hls) {
713
  _dvrUserSeeking = false;
 
714
  video.currentTime = video.duration;
715
  video.play().catch(()=>{});
716
  updateDVRUI();
@@ -721,13 +726,17 @@
721
  if (_dvrSeekBarTimer) clearInterval(_dvrSeekBarTimer);
722
  _dvrSeekBarTimer = setInterval(updateDVRUI, 1000);
723
 
724
- // v10: Track whether playhead is at live edge (for seeked event guard)
725
  video.addEventListener('timeupdate', function() {
726
  if (!_hls) return;
727
  const dur = video.duration;
728
  if (!dur || !isFinite(dur)) return;
729
  const behind = dur - video.currentTime;
730
  _dvrLastSeenEdge = (behind < 2);
 
 
 
 
731
  });
732
  }
733
 
@@ -844,46 +853,60 @@
844
  if(idx>=urls.length){loadEl.style.display='none';errEl.style.display='flex';errMsg.textContent=name+': Tất cả nguồn lỗi.';return;}
845
  const src=urls[idx]; loadEl.innerHTML='<div class="vtv-spinner"></div>Kết nối '+name+' ('+(idx+1)+'/'+urls.length+')...';
846
  if(typeof Hls!=='undefined'&&Hls.isSupported()){
847
- // v10: DVR 5min — keep liveSync for edge tracking, but override seek snap-back
848
  const hls=new Hls({
849
  enableWorker:true,lowLatencyMode:false,startLevel:0,capLevelToPlayerSize:true,
850
  maxBufferLength:120,maxMaxBufferLength:180,
851
  liveBackBufferLength:DVR_BUFFER_SECONDS,
852
- liveSyncDurationCount:3,liveMaxLatencyDurationCount:6,
853
  fragLoadingTimeOut:10000,manifestLoadingTimeOut:10000
854
  });
855
  _hls=hls; hls.loadSource(src); hls.attachMedia(video);
856
- // v10: After manifest loaded, seek to live edge first, then play
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
857
  hls.on(Hls.Events.MANIFEST_PARSED,(ev,data)=>{
858
  loadEl.style.display='none';video.style.display='block';
859
- // Start at live edge so stream plays immediately
860
  const dur = video.duration;
861
  if(dur && isFinite(dur)){ video.currentTime = dur; }
862
  video.play().catch(()=>{});
863
  });
864
- // (live-snap interception handled via video seeking/seeked events below)
865
- // v10: Block HLS auto-live-snap — restore user position when HLS fights back
866
- let _preSeekTime = 0;
867
- let _dvrRestoreLock = false; // prevent restore loop
868
- video.addEventListener('seeking',function(){
869
- if(_dvrRestoreLock) return;
870
- _preSeekTime = video.currentTime;
871
- });
872
- video.addEventListener('seeked',function(){
873
- if(_dvrUserSeeking || _dvrRestoreLock) return;
874
  const dur = video.duration;
875
- if(!dur || !isFinite(dur)) return;
876
  const behind = dur - video.currentTime;
877
- // If HLS.js snapped to live (behind < 0.5s) but user was >3s behind, restore
878
- if(behind < 0.5 && _preSeekTime > 0){
879
- const userBehind = dur - _preSeekTime;
880
- if(userBehind > 3){
881
- _dvrRestoreLock = true;
882
- video.currentTime = _preSeekTime;
883
- setTimeout(()=>{ _dvrRestoreLock = false; },500);
884
- }
885
  }
886
  });
 
887
  let rec=0; hls.on(Hls.Events.ERROR,(ev,data)=>{if(data.fatal){if(data.type===Hls.ErrorTypes.NETWORK_ERROR){rec++;if(rec<=3)setTimeout(()=>hls.startLoad(),2000);else{hls.destroy();_hls=null;_tryPlay(video,urls,idx+1,name,loadEl,errEl,errMsg);}}else if(data.type===Hls.ErrorTypes.MEDIA_ERROR){try{hls.recoverMediaError();}catch(e){}}else{hls.destroy();_hls=null;_tryPlay(video,urls,idx+1,name,loadEl,errEl,errMsg);}}});
888
  }else if(video.canPlayType('application/vnd.apple.mpegurl')){
889
  video.src=src;
 
1
+ // === VNEWS — VTV LIVE + Inline Recorder v11 ===
2
  // Features: DVR 5-min rewind buffer (seek & stay), PiP, mini-player, INLINE RECORDER with ratio crop + AI title + Short AI upload
3
+ // v11: Fix DVR snap-back override hls.startLoad to block auto-live-snap. Only seek to live on initial load or LIVE btn click
4
 
5
  (function(){
6
  if(window._ytLiveLoaded) return;
 
23
  let _dvrSeekBarTimer = null;
24
  let _dvrUserSeeking = false; // true while user is dragging DVR seek handle
25
  let _dvrLastSeenEdge = false; // track if playhead was at live edge before seek
26
+ let _dvrHasUserSeeked = false; // true once user manually seeks back from live
27
+ let _dvrStartLoadOrig = null; // original hls.startLoad reference
28
 
29
  // ===== RECORDER STATE =====
30
  const _rec = {
 
686
  const bufferStart = Math.max(0, duration - DVR_BUFFER_SECONDS);
687
  const seekTime = bufferStart + pct * (duration - bufferStart);
688
  _dvrUserSeeking = !isRelease;
689
+ // v11: Mark as user-seeked if not at live edge
690
+ if (duration - seekTime > 5) {
691
+ _dvrHasUserSeeked = true;
692
+ }
693
  video.currentTime = seekTime;
694
  if (isRelease) {
 
695
  video.play().catch(()=>{});
696
  } else {
 
697
  video.pause();
698
  }
699
  updateDVRUI();
 
715
  const video = document.getElementById('vtv-player');
716
  if (video && _hls) {
717
  _dvrUserSeeking = false;
718
+ _dvrHasUserSeeked = false; // reset — allow auto-live again
719
  video.currentTime = video.duration;
720
  video.play().catch(()=>{});
721
  updateDVRUI();
 
726
  if (_dvrSeekBarTimer) clearInterval(_dvrSeekBarTimer);
727
  _dvrSeekBarTimer = setInterval(updateDVRUI, 1000);
728
 
729
+ // v11: Track live edge state; auto-clear _dvrHasUserSeeked when buffer catches up
730
  video.addEventListener('timeupdate', function() {
731
  if (!_hls) return;
732
  const dur = video.duration;
733
  if (!dur || !isFinite(dur)) return;
734
  const behind = dur - video.currentTime;
735
  _dvrLastSeenEdge = (behind < 2);
736
+ // If playhead reached live edge naturally, allow auto-live again
737
+ if (behind < 1 && _dvrHasUserSeeked) {
738
+ _dvrHasUserSeeked = false;
739
+ }
740
  });
741
  }
742
 
 
853
  if(idx>=urls.length){loadEl.style.display='none';errEl.style.display='flex';errMsg.textContent=name+': Tất cả nguồn lỗi.';return;}
854
  const src=urls[idx]; loadEl.innerHTML='<div class="vtv-spinner"></div>Kết nối '+name+' ('+(idx+1)+'/'+urls.length+')...';
855
  if(typeof Hls!=='undefined'&&Hls.isSupported()){
856
+ // v11: DVR 5min — override hls.startLoad to prevent auto-live-snap
857
  const hls=new Hls({
858
  enableWorker:true,lowLatencyMode:false,startLevel:0,capLevelToPlayerSize:true,
859
  maxBufferLength:120,maxMaxBufferLength:180,
860
  liveBackBufferLength:DVR_BUFFER_SECONDS,
861
+ liveSyncDurationCount:999999,liveMaxLatencyDurationCount:999999,
862
  fragLoadingTimeOut:10000,manifestLoadingTimeOut:10000
863
  });
864
  _hls=hls; hls.loadSource(src); hls.attachMedia(video);
865
+
866
+ // v11: Override startLoad — only seek to live on initial load, never auto-snap back
867
+ _dvrStartLoadOrig = hls.startLoad.bind(hls);
868
+ hls.startLoad = function(position) {
869
+ // First call (initial load): seek to live edge
870
+ if (!_dvrHasUserSeeked && position === -1) {
871
+ _dvrStartLoadOrig(position);
872
+ return;
873
+ }
874
+ // After user sought back: load segments but DON'T seek to live edge
875
+ // Pass a large negative value to load from buffer start instead
876
+ if (_dvrHasUserSeeked && position === -1) {
877
+ // Load from current buffer position, not live edge
878
+ const dur = video.duration;
879
+ const cur = video.currentTime;
880
+ if (dur && isFinite(dur) && cur && isFinite(cur)) {
881
+ // Load from slightly behind current position to ensure smooth playback
882
+ _dvrStartLoadOrig(cur - 5);
883
+ } else {
884
+ _dvrStartLoadOrig(position);
885
+ }
886
+ return;
887
+ }
888
+ _dvrStartLoadOrig(position);
889
+ };
890
+
891
+ // v11: After manifest loaded, seek to live edge first, then play
892
  hls.on(Hls.Events.MANIFEST_PARSED,(ev,data)=>{
893
  loadEl.style.display='none';video.style.display='block';
 
894
  const dur = video.duration;
895
  if(dur && isFinite(dur)){ video.currentTime = dur; }
896
  video.play().catch(()=>{});
897
  });
898
+
899
+ // v11: Track when user manually seeks back from live
900
+ video.addEventListener('seeking', function() {
901
+ if (_dvrUserSeeking) return;
 
 
 
 
 
 
902
  const dur = video.duration;
903
+ if (!dur || !isFinite(dur)) return;
904
  const behind = dur - video.currentTime;
905
+ if (behind > 5) {
906
+ _dvrHasUserSeeked = true;
 
 
 
 
 
 
907
  }
908
  });
909
+
910
  let rec=0; hls.on(Hls.Events.ERROR,(ev,data)=>{if(data.fatal){if(data.type===Hls.ErrorTypes.NETWORK_ERROR){rec++;if(rec<=3)setTimeout(()=>hls.startLoad(),2000);else{hls.destroy();_hls=null;_tryPlay(video,urls,idx+1,name,loadEl,errEl,errMsg);}}else if(data.type===Hls.ErrorTypes.MEDIA_ERROR){try{hls.recoverMediaError();}catch(e){}}else{hls.destroy();_hls=null;_tryPlay(video,urls,idx+1,name,loadEl,errEl,errMsg);}}});
911
  }else if(video.canPlayType('application/vnd.apple.mpegurl')){
912
  video.src=src;