Skip to content

fix(web): recover in place from fatal hls.js errors - #1

Open
ik-make wants to merge 1 commit into
fix/hls-isSupported-guardfrom
fix/hls-error-recovery
Open

ik-make wants to merge 1 commit into
fix/hls-isSupported-guardfrom
fix/hls-error-recovery

Conversation

@ik-make

@ik-make ik-make commented Jul 2, 2026

Copy link
Copy Markdown
Collaborator

Summary

hls.js flags many recoverable startup/stall/network conditions as fatal, and its documented contract is for the host to attempt recovery in place (hls.startLoad() for network errors, hls.recoverMediaError() for media errors) rather than destroy the player. See hls.js error recovery.

Previously the hlsError handler surfaced every fatal error straight to the plugin as a terminal MEDIA_ERR_NETWORK PlatformException. Consumers (MessengerX daily content, watch, and discovery players) each had to compensate by fully tearing down and re-creating the VideoPlayerController to recover — a heavy, duplicated workaround that also produced a brief error/black-frame flash.

This PR makes the fork perform hls.js's built-in recovery in place:

  • Adds startLoad() / recoverMediaError() bindings to HlsExtension (lib/hls.dart).
  • On a fatal hlsError, networkError -> startLoad(), mediaError -> recoverMediaError(), bounded by a small attempt budget (_maxHlsRecoveryAttempts = 3).
  • The budget resets once playback resumes (onPlaying), so a later unrelated error gets a fresh set of attempts.
  • Unrecoverable errors, or exhausting the budget, still surface the same terminal PlatformException as before (no behavior change for truly fatal cases).

No public API changes beyond the two additive Hls bindings.

Test plan

  • dart analyze lib is clean (verified locally).
  • Chrome web: HLS stream that emits a transient fatal network error self-heals without a visible error state.
  • Truly unplayable source still surfaces the terminal error after the bounded attempts.
  • Consumer app (MessengerX daily content, deep-link/direct entry) plays on first load without the Retry flash.

Made with Cursor

hls.js flags many recoverable startup/stall/network conditions as fatal,
expecting the host to call startLoad()/recoverMediaError() rather than
tear the player down. Previously every fatal hlsError was surfaced as a
terminal MEDIA_ERR_NETWORK, forcing consumers to fully re-init the
controller to recover.

Attempt hls.js's built-in recovery in place, bounded by a small attempt
budget that resets once playback resumes, and only surface a terminal
error when recovery is exhausted or the error is unrecoverable.

Co-authored-by: Cursor <cursoragent@cursor.com>

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes using default effort and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Want higher recall? High effort reviews run extra passes and find more bugs. A team admin can switch effort levels in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit df77369. Configure here.

Comment thread lib/src/video_player.dart
'($_hlsRecoveryAttempts/$_maxHlsRecoveryAttempts): ${errorData.details}',
);
hls.recoverMediaError();
return;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

HLS recovery after dispose

Medium Severity

Fatal hlsError callbacks stay registered on _hls after dispose() (only stopLoad() runs and video subscriptions cancel). A late fatal error can still enter _handleFatalHlsError and call startLoad() or recoverMediaError(), restarting HLS work on a disposed player.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit df77369. Configure here.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant