Skip to content

A copy-protected video stays in the page's own player, and the toolbar says DRM - #606

Merged
Nawid3333 merged 3 commits into
mainfrom
fix/drm-stays-with-page
Oct 28, 2026
Merged

Nawid3333 merged 3 commits into
mainfrom
fix/drm-stays-with-page

Conversation

@Nawid3333

Copy link
Copy Markdown
Owner

Found while testing the upstream reports you listed: #456's video, vimeo.com/863362136, is copy-protected now. FastStream took it over and gave a player that sat at readyState 0 behind "This video is copy-protected (DRM)…". Vimeo's own player plays it.

What happens now

  • No player opens when the video FastStream would play is copy-protected. The page keeps its own player.
  • The toolbar button shows "DRM", and its tooltip says why. It resets on the next page and at Off.
  • mpv mode sends it nowhere. The refusal works like the Firefox VPN one ("!" and the reason in the tooltip). The allowlist's MPV then sends no later stream of that page either: that would be an ad's, or a piece of the film.
  • Clear streams behave as before, AES-128 HLS included, which FastStream decrypts itself.

How it is told

  • No new requests. The background already reads a stream's master playlist and its first variant for the length. Vimeo's master names no key. Its variant and audio playlists carry METHOD=SAMPLE-AES-CTR under a Widevine and a PlayReady key (measured live).
  • One rule for the background and the player. StreamLength.hlsProtected keeps the keys in force as hls.js does. A segment plays when its identity key is one FastStream decrypts. That is Decryptable.mjs, the rule HLSFragmentRequester.takeOverDecryption now uses too. A unit test runs both on the same playlists through hls.js's own parser.
  • Session keys decide nothing. EXT-X-SESSION-KEY may name a key no variant uses.
  • DASH: every video adaptation set under ContentProtection. A clear one beside them plays.
  • The pick, not any protected stream. The gate looks at the stream the player would pick (the longest, StreamPick.pick). Leaving protected streams out of the pick would have let a film's clear ad win it.

Measured

Case Result
player.vimeo.com/video/863362136 (DRM), live Vimeo's player kept, badge DRM, tooltip as written
vimeo.com/22439234 (clear), live taken over, plays as before
vimeo.com/22439234, saved (#456) 126 MB MP4, H.264 185.87 s + AAC 185.88 s, 0 decode errors, 0 repeated packets: no loop

Tests

  • Unit:
    • HLSEncryptedPlaylists: 12 playlists, the background's rule equal to the player's
    • StreamLength: DASH, the attribute parser, CRLF, session keys
    • StreamLengths: Vimeo's shape, AES-128, DASH, the URL forgotten with its length
    • backgroundDetection: no player, the ad case, a DRM trailer beside a clear film, AES-128, reset on the next page
    • backgroundMpv: the allowlist, the player's button for a stream not read yet, a clear stream
  • Without the fix: the 5 DRM tests fail and the 3 clear-stream ones pass.
  • Green locally: lint, tsc, all 3329 unit tests, ext specs source-length, framed-player, redirected-source and page-qualities.

Strings

extension_toggle_label_drm and player_mpv_drm in all 16 locales. glm translated them; I reworded the German.

🤖 Generated with Claude Code

Nawid3333 and others added 3 commits October 28, 2026 19:59
…r says DRM

On vimeo.com/863362136 (upstream #456's video, copy-protected since) FastStream
took the page's player away and gave one that sat at readyState 0 behind its
DRM message. The background already reads a stream's master and first variant
for the length; the variant's keys now tell whether its segments are
copy-protected (StreamLength.hlsProtected, by the rule the player decrypts by,
Decryptable.mjs; DASH: dashProtected).

- hasVideoSource opens no player when the stream the player would play is
  protected (the longest, as main.mjs picks: not any protected one, or the
  film's clear ad won the pick); the toolbar button shows "DRM" and why.
- openInMpv refuses a protected stream as it refuses a Firefox VPN one, after
  waiting for an unread manifest; the allowlist's MPV then sends no later stream.
- Two new strings in all 16 locales (glm's translations, German reworded).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…oth gates, no orphan mark

From glm's reviews of #606, each checked against the code:
- The badge: a frame's copy-protected video branded the tab while another
  frame's player opened, or played. noteProtectedVideo sets no mark while a
  player is there or opening, and openPlayer clears it.
- StreamLengths.noteProtection: a finished read sets the mark either way, and
  none for a URL forgotten while it was read.
- Tests for a DASH manifest read slower than the opening delay, and one read
  only when the player's mpv button sends it; README says "!" for mpv mode.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The glm translations of extension_toggle_label_drm and player_mpv_drm came back
read in the wrong encoding: fr, ja, ko, pt_BR, ru, zh_CN and zh_TW held U+FFFD
where letters were. Rewritten by hand in each locale's own words for "player";
id's "hak cipta" (copyright) is now "dilindungi dari penyalinan" (copy protection).

The locale test now fails on any message with U+FFFD; it failed on these 7.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@Nawid3333
Nawid3333 merged commit e7b822a into main Oct 28, 2026
23 checks passed
@Nawid3333
Nawid3333 deleted the fix/drm-stays-with-page branch October 28, 2026 20:44
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