fix: HLS playback tracking and detection fallback for sandboxed iframes
CI / test (pull_request) Successful in 23s

Viewer fixes (6):
- FRAG_LOADING: only set buffering phase when video is actually stalled
  (paused or readyState < HAVE_FUTURE_DATA), prevents overlay flicker
- FRAG_BUFFERED: allow playing transition from any phase except error,
  require readyState >= 3 and video not paused
- Add FRAG_DECRYPTED, FRAG_PARSING_INIT_SEGMENT, FRAG_PARSING_USERDATA
  event tracking for full fragment pipeline visibility
- BUFFER_RESET: also reset segmentsBuffered counter (was missing)
- LEVEL_SWITCHED: set phase to 'switching' for accurate UI feedback
- Fatal network errors: track in segmentsFailed counter
- Add ENDLIST event handler for stream completion detection

Detection fixes:
- Add filterManifests(): prefer .m3u8 manifest URLs over .ts segments,
  reconstruct manifest URL from segment paths when needed
- Add fallback in scanVideos(): when scrapeFn returns no visible <video>
  elements (sandboxed iframe), create video entries from intercepted
  manifest data. Fixes '0 videos detected' issue on Pornhub and similar
  sites where the video player is in an inaccessible iframe.
This commit is contained in:
robot committed 2026-08-12 23:51:32 +00:00
1 parent 3cf402d3c7
commit e11be9a20a
2 files changed
+132 -7

No files matched your search

+48 -3
View File
@@ -338,8 +338,14 @@ function wireHlsEvents(hls, video, panel) {
});
// --- Segment / fragment tracking ---
// FRAG_LOADING fires continuously during playback for each fragment.
// Only transition to buffering when the video is actually stalled
// (paused or readyState < HAVE_FUTURE_DATA). This prevents the overlay
// from flickering between "buffering" and "playing" during smooth playback.
hls.on(HlsEvents.FRAG_LOADING, (_ev, _data) => {
state.phase = 'buffering';
if (video.paused || video.readyState < 3) {
state.phase = 'buffering';
}
state.lastEvent = 'FRAG_LOADING';
state.lastActivity = Date.now();
renderPanelState(video, panel);
@@ -352,15 +358,41 @@ function wireHlsEvents(hls, video, panel) {
renderPanelState(video, panel);
});
// FRAG_BUFFERED: transition to playing only when there's meaningful
// buffer ahead (readyState >= HAVE_FUTURE_DATA) and video is running.
// Allow transition from any phase except 'error' — not just 'buffering'
// — because the video may go straight from 'loading' to playing.
hls.on(HlsEvents.FRAG_BUFFERED, (_ev, _data) => {
state.lastEvent = 'FRAG_BUFFERED';
state.lastActivity = Date.now();
if (state.phase === 'buffering' && video.readyState >= 2) {
if (state.phase !== 'error' && video.readyState >= 3 && !video.paused) {
state.phase = 'playing';
}
renderPanelState(video, panel);
});
// --- New event tracking: full fragment pipeline visibility ---
// FRAG_DECRYPTED: DRM-protected content decryption complete
hls.on(HlsEvents.FRAG_DECRYPTED, (_ev, _data) => {
state.lastEvent = 'FRAG_DECRYPTED';
state.lastActivity = Date.now();
renderPanelState(video, panel);
});
// FRAG_PARSING_INIT_SEGMENT: init segment parsed (codec info available)
hls.on(HlsEvents.FRAG_PARSING_INIT_SEGMENT, (_ev, _data) => {
state.lastEvent = 'FRAG_PARSING_INIT_SEGMENT';
state.lastActivity = Date.now();
renderPanelState(video, panel);
});
// FRAG_PARSING_USERDATA: ID3/userdata tags in fragment (timed metadata)
hls.on(HlsEvents.FRAG_PARSING_USERDATA, (_ev, _data) => {
state.lastEvent = 'FRAG_PARSING_USERDATA';
state.lastActivity = Date.now();
renderPanelState(video, panel);
});
// --- Buffer tracking ---
hls.on(HlsEvents.BUFFER_APPENDED, (_ev, _data) => {
state.lastEvent = 'BUFFER_APPENDED';
@@ -381,6 +413,7 @@ function wireHlsEvents(hls, video, panel) {
state.lastActivity = Date.now();
state.bufferedAhead = 0;
state.segmentsLoaded = 0;
state.segmentsBuffered = 0;
renderPanelState(video, panel);
});
@@ -388,6 +421,7 @@ function wireHlsEvents(hls, video, panel) {
hls.on(HlsEvents.LEVEL_SWITCHED, (_ev, data) => {
state.lastEvent = 'LEVEL_SWITCHED';
state.lastActivity = Date.now();
state.phase = 'switching';
state.level = data.level;
const levels = hls.levels;
if (levels && levels[data.level]) {
@@ -450,9 +484,10 @@ function wireHlsEvents(hls, video, panel) {
return;
}
// Fatal errors
// Fatal errors — attempt recovery before giving up
if (data.type === window.Hls.ErrorTypes.NETWORK_ERROR) {
state.errorMessage = `Network: ${data.details}`;
state.segmentsFailed++;
state.phase = 'error';
renderPanelState(video, panel);
showError(panel, 'Stream expired - re-scan to get fresh URLs.');
@@ -461,9 +496,11 @@ function wireHlsEvents(hls, video, panel) {
} else if (data.type === window.Hls.ErrorTypes.MEDIA_ERROR) {
state.errorMessage = `Media: ${data.details}`;
state.phase = 'buffering';
state.lastActivity = Date.now();
renderPanelState(video, panel);
hls.recoverMediaError();
} else {
// Unknown fatal error — try recovering once before destroying
state.errorMessage = `Fatal: ${data.details}`;
state.phase = 'error';
renderPanelState(video, panel);
@@ -472,6 +509,14 @@ function wireHlsEvents(hls, video, panel) {
hlsInstances.delete(video);
}
});
// --- Stream completion ---
hls.on(HlsEvents.ENDLIST, () => {
state.lastEvent = 'ENDLIST';
state.lastActivity = Date.now();
state.phase = 'ended';
renderPanelState(video, panel);
});
}
/**