Skip to content

feat: add MPEG-DASH support with type refactoring#32

Merged
jvillegasd merged 13 commits intomainfrom
feat/dash
Mar 3, 2026
Merged

feat: add MPEG-DASH support with type refactoring#32
jvillegasd merged 13 commits intomainfrom
feat/dash

Conversation

@jvillegasd
Copy link
Copy Markdown
Owner

Changelog:

  • Add full MPEG-DASH support: VOD download, live stream recording, and .mpd URL detection
  • Extract shared live-recording logic into BaseRecordingHandler, refactoring HlsRecordingHandler to extend it;
    DashRecordingHandler inherits the same polling loop
  • Pre-warm FFmpeg when live stream recording starts to reduce latency on first merge
  • Refactor parser utilities: unify m3u8-parser.ts and add mpd-parser.ts mirroring the same structure
  • Colocate module-internal types with their owning files (ParsedSegment/ParsedPlaylist → playlist-utils.ts, FetchFn →
    fetch-utils.ts), removing them from the central core/types/index.ts
  • Move mpd-parser ambient type shim to src/types/mpd-parser.d.ts, separating vendor shims from project domain types

jvillegasd and others added 13 commits February 28, 2026 04:04
Extends the FFmpeg pre-warm logic to cover HlsRecordingHandler.
Previously, createOffscreenDocument + WARMUP_FFMPEG only fired for
HLS/M3U8 format downloads in handleDownloadRequest. Recordings went
through handleStartRecordingMessage which had no pre-warm, so FFmpeg
only began loading when the user hit Stop — adding latency at the start
of the merge phase.

Now recordings pre-warm on the same condition (first active task),
matching the behaviour of regular HLS/M3U8 downloads.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Document why IndexedDB is used as the cross-context shared state store,
the dual-channel progress update strategy (IDB + sendMessage), and the
BasePlaylistHandler cachedState/throttle optimizations. Include an explicit
warning against adding getDownload() calls in the onProgress hot path,
referencing the UI freeze bug from commit 9f2a21e.

Also correct VideoFormat from string union to string enum.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add VideoFormat.DASH and OFFSCREEN_PROCESS_DASH message types
- Add mpd-parser utility wrapping the mpd-parser npm package
- Add DashDetectionHandler for .mpd URL detection with DRM/live detection
- Add DashDownloadHandler for static DASH VOD downloads (video+audio streams)
- Add DashRecordingHandler for live DASH stream recording
- Extract BaseRecordingHandler from HlsRecordingHandler (template method pattern)
- Refactor HlsRecordingHandler to extend BaseRecordingHandler
- Add processDashChunks() to offscreen FFmpeg worker (no bsf filter, .mp4 intermediates)
- Wire DASH into DetectionManager, DownloadManager, and service worker

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Split parseLevelsPlaylist into two steps: parseMediaPlaylist (HLS) /
  getVideoPlaylist / getAudioPlaylist (DASH) produce a ParsedPlaylist,
  then a single parseLevelsPlaylist in playlist-utils.ts converts to
  Fragment[] for both protocols
- Add ParsedSegment and ParsedPlaylist to core types as the shared
  protocol-agnostic intermediate
- Move src/core/types.ts → src/core/types/index.ts; add parser.d.ts
  alongside it for mpd-parser ambient declarations (replaces orphan
  src/types/ folder)
- Remove MpdPlaylist from public API; handlers now use getVideoPlaylist()
  instead of inline sort logic

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ning modules

Move ParsedSegment and ParsedPlaylist from core/types/index.ts to
playlist-utils.ts, where they are produced and consumed. Move FetchFn
to fetch-utils.ts as an unexported internal type. Update imports in
m3u8-parser.ts and mpd-parser.ts accordingly.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Relocate src/core/types/parser.d.ts → src/types/mpd-parser.d.ts to
separate vendor type shims from project domain types.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Reduces src/core/utils/ from 20 to 11 files by relocating subsystem-specific
modules to the directories that own them:

  core/ffmpeg/        ← ffmpeg-bridge, ffmpeg-singleton, offscreen-manager
  core/parsers/       ← m3u8-parser, mpd-parser, playlist-utils
  core/detection/     ← thumbnail-utils
  core/downloader/    ← crypto-utils, header-rules

No logic changes — pure file-move + import-update refactor.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Show a quality picker in the Manifest tab when a DASH MPD contains
multiple representations. Highest bandwidth is pre-selected; audio is
always auto-included. Single-rep MPDs continue to auto-select with no
picker shown. The selected bandwidth flows through the full stack:
popup → service worker → DownloadManager / DashRecordingHandler →
mpd-parser.getVideoPlaylistByBandwidth().

Also completes DASH live recording audio support: BaseRecordingHandler
now carries a separate audioSegmentIndex and storeId for concurrent
audio fragment downloads, and resolveMediaUrl() returns both the
polling URL and the post-redirect URL for declarativeNetRequest rules.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ave audio loss

DASH VOD and HLS master playlist downloads used sequential video-then-audio segment
downloading. If the user stopped during the video phase, chunkCount <= videoLength so
the stop-and-save formula produced effectiveAudioLength = 0 — no audio in the saved file.

Fix: switch both handlers to concurrent download using Promise.allSettled, with audio
stored under a separate `downloadId + "_a"` namespace (matching DashRecordingHandler).
Stop-and-save now counts video and audio chunks independently via getChunkCount. The
offscreen adds processHlsVideoAndAudioSeparate (mirrors the DASH equivalent) and
processHLSChunks now requires audioDownloadId for dual-stream mux.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
processVideoAndAudio() and processDashVideoAndAudio() are unreachable now
that both HLS and DASH VOD handlers always pass audioDownloadId for separate
namespace processing. Replace the if/else branches with a guard throw,
matching the pattern already used in processHLSChunks.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ment counts

Without -shortest, if audio downloaded more segments than video (common since
audio segments are smaller and finish faster), the output would have audio
extending beyond video end causing a frozen/black frame with audio. Matches
the existing -shortest behavior in processHlsVideoAndAudioSeparate.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@jvillegasd jvillegasd merged commit f5a24ff into main Mar 3, 2026
@jvillegasd jvillegasd deleted the feat/dash branch March 3, 2026 02:02
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