The liveness probe already tries all of a video's sources, but actual playback
only ever tried the single primary URL and then fell straight to WebTorrent. Now
playback fails over across hosted mirrors first.
- PlaybackSession builds an ordered, de-duped hostedSourceCandidates (primary URL
first, then the video's imeta `sources`; accepts string or {url} entries).
- attemptHostedPlayback takes a candidateUrl; the url-first execution loop tries
each candidate and advances to the next mirror on probe-fail/stall/timeout,
cleaning up watchdogs + resetting the <video> between candidates, only calling
WebTorrent after every hosted mirror fails.
- `sources` plumbed coordinator -> strategy -> session. Single-source videos
(no `sources`) behave exactly as before.
Tests: tests/services/playbackService_multisource.test.mjs (candidate
construction; the full async fail-over flow is covered by the existing
playbackService_order suite in CI — that mock-timers harness doesn't run in this
sandbox). ROLLBACK: revert the loop to the single attemptHostedPlayback() call
and drop the sources plumbing.
Co-Authored-By: Claude Opus 4.8 <[email protected]>
Extracted the complex playback logic from `js/app/playbackCoordinator.js` into a new `js/services/playbackStrategyService.js`.
This improves maintainability by separating the orchestration logic from the application glue code.
The coordinator now delegates to the service, passing a context object for UI interactions and state synchronization.
Co-authored-by: PR0M3TH3AN <[email protected]>