Skip to content

fix: make iOS/Safari media player work (#27)#51

Merged
ralyodio merged 7 commits intomasterfrom
fix/ios-safari-media-player-27
Mar 19, 2026
Merged

fix: make iOS/Safari media player work (#27)#51
ralyodio merged 7 commits intomasterfrom
fix/ios-safari-media-player-27

Conversation

@Preshy
Copy link
Copy Markdown
Contributor

@Preshy Preshy commented Mar 19, 2026

Summary

Fixes three root causes that prevented iOS/Safari HLS video playback from ever working. Desktop transcoding + ffmpeg streaming works fine, but iOS was broken end-to-end.

Root Causes & Fixes

1. Player mount gated behind 20MB buffer threshold (primary blocker)

  • shouldMountPlayer for HLS required fileReady which demands 20MB of video data downloaded
  • This threshold is designed for the direct byte-range streaming path, not HLS
  • The HLS endpoint has its own buffering (FFmpeg reads from the file incrementally and produces segments)
  • Fix: For HLS streams, only wait for torrent.ready (metadata available) instead of the full 20MB fileReady threshold

2. Subscription check broke Safari playlist re-fetches

  • Safari's native HLS player periodically re-fetches the m3u8 playlist URL to discover new segments
  • These requests from Safari's internal media engine may not carry session cookies
  • The subscription check rejected these cookie-less requests, aborting playback
  • Fix: Moved subscription check to only run for new HLS sessions. Existing session re-fetches skip auth. Added full CORS headers + OPTIONS handler.

3. Native video element initialization issues

  • Missing crossOrigin = 'anonymous' — required for Safari to fetch CORS-enabled HLS segments
  • Missing preload = 'auto'
  • play() called before loadedmetadata — iOS rejects early play() calls
  • Event listeners attached after src was set, missing early events
  • Fix: Added crossOrigin and preload attributes, moved listeners before src, defer play() to after loadedmetadata

Files Changed

  • src/components/media/media-player-modal.tsx — HLS mount gating + loading spinner logic
  • src/app/api/stream/hls/route.ts — Auth check ordering, CORS headers, OPTIONS handler
  • src/components/video/video-player.tsx — Native video element init for iOS HLS

Testing

  • TypeScript compiles cleanly
  • Build passes
  • 168/169 test files pass (1 pre-existing ProfileSelector failure unrelated to this PR)

Closes #27

Preshy and others added 7 commits March 4, 2026 19:59
…rt (#27)

- Use native <video> element for HLS on iOS/Safari instead of Video.js
  (VHS tech fights Safari's native HLS player causing corruption/abort)
- Add playsinline/webkit-playsinline attributes for inline playback in modals
- Defer VideoPlayer mount until SSE confirms stream readiness (prevents
  hitting HLS endpoint before torrent has peers)
- Rewrite HLS segment endpoint with Range request support (206 Partial
  Content), HEAD/OPTIONS handlers, and full CORS headers for iOS Safari
- Increase file wait timeout from 10s to 30s and require 2MB minimum for
  reliable codec detection on slow torrents
- Add FFmpeg error tolerance flags for partial file streaming

Closes #27
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
The native video src-update short-circuit was not gated behind
the Safari/HLS condition, preventing the component from switching
back to Video.js when the source changed to a non-HLS format.
Now tears down the native element and resets isNativePlayerRef
when the source is no longer HLS-on-Safari.

Also deduplicates the iOS/Safari detection (single declaration
used throughout initializePlayer).
The native HLS video path passes null to onReady since there is no
Video.js Player instance. Update the prop type from (player: Player)
to (player: Player | null) to fix the TypeScript error.
…ideo init

Three root causes prevented iOS/Safari HLS video playback from working:

1. shouldMountPlayer required fileReady (20MB buffer) before mounting the
   VideoPlayer for HLS streams. The HLS endpoint has its own buffering via
   FFmpeg, so this gate caused infinite loading. Now only waits for torrent
   metadata (torrent.ready).

2. Subscription check on HLS playlist route rejected Safari's periodic
   playlist re-fetches (media engine may not send cookies). Moved auth
   check to only run for new sessions; existing session re-fetches skip it.
   Added full CORS headers and OPTIONS handler.

3. Native <video> element for iOS HLS was missing crossOrigin attribute,
   preload hint, and called play() before loadedmetadata. Fixed init order:
   attach listeners before setting src, defer autoplay to loadedmetadata,
   add crossOrigin='anonymous' for CORS segment fetches.

Closes #27
@ralyodio ralyodio merged commit 732cf26 into master Mar 19, 2026
6 checks passed
@ralyodio ralyodio deleted the fix/ios-safari-media-player-27 branch March 19, 2026 09:05
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.

make ios/safari media player work

2 participants