Skip to content

feat: hls-stream: Enable extension to download live streams in HLS protocol#25

Merged
jvillegasd merged 16 commits intomainfrom
feat/hls-stream
Feb 22, 2026
Merged

feat: hls-stream: Enable extension to download live streams in HLS protocol#25
jvillegasd merged 16 commits intomainfrom
feat/hls-stream

Conversation

@jvillegasd
Copy link
Owner

@jvillegasd jvillegasd commented Feb 22, 2026

This pull request introduces several improvements and fixes to HLS/M3U8 video detection and download management, with a focus on deduplication, live stream detection, partial download handling, and codebase maintainability. The changes enhance reliability when detecting streams (especially live and variant playlists), allow saving partial downloads on cancellation, and improve header injection and chunk management for downloads.

HLS/M3U8 Detection Improvements:

  • Added deduplication of detected HLS URLs by using only the origin and pathname, ignoring query parameters. This prevents duplicate video cards for the same stream re-fetched with different tokens or timestamps. (src/core/detection/hls/hls-detection-handler.ts) [1] [2] [3] [4]
  • Improved detection of live streams:
    • For standalone M3U8 playlists, liveness is determined by the absence of #EXT-X-ENDLIST.
    • For HLS master playlists, the code now fetches the first variant playlist to check for liveness, defaulting to VOD if the check fails. (src/core/detection/hls/hls-detection-handler.ts) [1] [2] [3] [4] [5]

Download Management Enhancements:

  • Added support for saving partial downloads when cancelled, instead of discarding progress. This is now configurable via a shouldSaveOnCancel callback passed through DownloadManager and into download handlers. (src/core/downloader/download-manager.ts, src/core/downloader/hls/hls-download-handler.ts) [1] [2] [3] [4] [5]
  • Improved chunk management by importing and using getChunkCount for more robust handling, and improved header injection by importing addHeaderRules and removeHeaderRules for network requests. (src/core/downloader/hls/hls-download-handler.ts) [1] [2]

Refactoring and Maintainability:

  • Moved the generateDownloadId utility to a shared location and updated its usage in DownloadManager. (src/core/downloader/download-manager.ts) [1] [2] [3]
  • Propagated the pageUrl from video metadata to download handlers for improved context. (src/core/downloader/download-manager.ts) [1] [2]

Documentation:

I will start using Claude here, yay!

  • Added a comprehensive CLAUDE.md file describing the architecture, build commands, component interactions, and advanced features of the Chrome extension, serving as a guide for contributors and code assistants. (CLAUDE.md)

jvillegasd and others added 16 commits February 21, 2026 01:07
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds a REC button for HLS/M3U8 video cards that starts a live recording
session. The recording handler polls the manifest every 3 s, collects new
segments into IndexedDB, and merges them via FFmpeg when the user clicks
Stop (or when #EXT-X-ENDLIST is detected). Popup shows a red shimmer bar
and segment count during recording.

- Add RECORDING stage to DownloadStage and segmentsCollected to DownloadProgress
- Add START_RECORDING / STOP_RECORDING message types
- New HlsRecordingHandler with polling loop, concurrent segment download, and FFmpeg handoff
- Service worker handles START_RECORDING / STOP_RECORDING messages
- RECORDING stage is non-cancellable via standard cancel path
- Popup: red shimmer progress bar, REC badge, REC/Stop buttons

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- crypto-utils: cast Uint8Array iv to BufferSource to satisfy TS5 strict
  generic ArrayBufferView<ArrayBuffer> requirement in SubtleCrypto.decrypt
- options: cast getElementById result through unknown before SVGElement
  to satisfy non-overlapping type assertion check

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds isLive detection during manifest analysis so the REC button is not
shown on regular VOD streams.

- Add isLive? to VideoMetadata
- m3u8 (standalone media playlist): live when #EXT-X-ENDLIST is absent
- hls (master playlist): fetch first variant at detection time and check
  for #EXT-X-ENDLIST; defaults to false (VOD) if the fetch fails
- Popup gates REC button on video.isLive === true

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Allows users to stop an in-progress HLS or M3U8 download mid-way and
still receive a valid (shorter) MP4 from the segments already downloaded.

- Add STOP_AND_SAVE_DOWNLOAD message type
- Add shouldSaveOnCancel option to HlsDownloadHandler and M3u8DownloadHandler;
  on CancellationError, compute effective video/audio lengths from stored
  chunk count, then mux and save the partial file via the existing FFmpeg path
- Wire shouldSaveOnCancel through DownloadManager options
- Service worker: track URLs in savePartialDownloads Set, handle
  STOP_AND_SAVE_DOWNLOAD by marking the set and aborting the controller,
  clean up in cleanupDownloadResources
- Popup: add .btn-stop-save CSS and a "Stop & Save" button in the
  DOWNLOADING progress bar for HLS/M3U8 videos

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
URL.revokeObjectURL called from the service worker had no effect on blob
URLs created in the offscreen document — they are separate JS contexts
with separate blob URL registries, so the full MP4 blob was never freed.

Add a REVOKE_BLOB_URL message type. After Chrome finishes saving the file
(or on error), handlers send this message to the offscreen document, which
calls URL.revokeObjectURL in the same context that created the blob.
Applies to HlsDownloadHandler, M3u8DownloadHandler, and HlsRecordingHandler.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Object.setPrototypeOf(this, MediaBridgeError.prototype) in the base
error class broke prototype chains for all subclasses, causing
`instanceof CancellationError` to always return false. The partial
save code path was never reached. Fixed by using new.target.prototype.

Also allow progress updates through during partial save — the
onProgress callback was dropping MERGING/SAVING/COMPLETED state
transitions because the abort signal was already set.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…ards

The REC button had class "video-btn btn-rec", causing the regular
download handler (.video-btn selector) to also fire — pre-empting the
recording handler. Removed video-btn class from REC, stop-rec, and
stop-save buttons so only their specific handlers fire.

Also deduplicate HLS detection by origin+pathname to prevent duplicate
video cards when the player re-fetches playlists with different query
params (auth tokens, timestamps).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Show recording progress bar, REC badge, and Stop button in Downloads
  tab (matching the Videos tab card)
- Hide Download/Redownload button for live streams, keep Select Quality
  and REC
- Add "Live Stream Detected" banner in Manifest tab with Record button
  that routes to START_RECORDING
- Send tabTitle/website in REC payload for proper filename generation
- Persist FAILED state to IndexedDB when recording errors occur

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…est tab

Pass the selected variant URL instead of the master playlist URL when
starting a recording from the Manifest tab, so the recording handler
uses the chosen quality instead of always picking the highest bitrate.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…of every render

Move sorting from renderDownloads() (called on every progress event, poll,
and focus) to loadDownloadStates() using createdAt, preventing cards from
jumping around during active downloads.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Check HTTP status codes in fetchResource to reject non-2xx responses
  instead of silently storing error pages as video chunks
- Propagate actual error messages through fetchWithRetry instead of
  generic "Fetch error"
- Reset FFmpeg WASM instance after failures to prevent corrupted state
  from breaking subsequent downloads
- Use downloadId for FFmpeg temp filenames to avoid collisions between
  concurrent downloads
- Force .mp4 extension for HLS/M3U8 downloads since FFmpeg always
  outputs MP4
- Truncate long filenames at word boundaries instead of mid-word
- Add chunk concatenation logging for diagnosing download issues

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
… errors

Recording downloads use full URLs as their downloadId, which contain
characters (: / ? & =) invalid for filesystem paths in FFmpeg's MEMFS.
Also replace inline onerror handlers with addEventListener to comply
with extension CSP.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…nt ID generation

Recording downloads previously used raw URLs as IDs, causing FFmpeg path failures.
Now all download IDs are generated via a single shared function that produces
filesystem-safe IDs, removing the need for the sanitizeForFilename workaround.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…LS 404s

Browsers silently strip Origin and Referer from fetch() calls since they
are forbidden headers. CDNs like phncdn.com require these headers, causing
segment downloads to fail with 404. Use chrome.declarativeNetRequest
dynamic rules to inject headers at the network layer instead.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…eferences

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@jvillegasd jvillegasd self-assigned this Feb 22, 2026
@jvillegasd jvillegasd changed the title feat/hls-stream: Enable extension to download live streams in HLS protocol feat/hls-stream: Enable extension to download live streams in HLS protocol and start using Claude Feb 22, 2026
@github-actions github-actions bot changed the title feat/hls-stream: Enable extension to download live streams in HLS protocol and start using Claude feat: hls-stream: Enable extension to download live streams in HLS protocol Feb 22, 2026
@jvillegasd jvillegasd merged commit 074cd7b into main Feb 22, 2026
3 checks passed
@jvillegasd jvillegasd deleted the feat/hls-stream branch February 23, 2026 20:48
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