Skip to the content.

Module Reference

src/lib/schemas.ts — Zod Validation Schemas

Types

Schema Type Description
VariantSchema Variant HLS variant with bandwidth, resolution, framerate, codecs, URL
GqlResponseSchema GqlResponse Validated Twitch GQL response
VolumeSchema number 0..1, finite
VariantIdxSchema number Non-negative integer, validated on storage read
SpeedIdxSchema number 0..8 integer, validated on storage read
ChatOverlayEnabledSchema "true" \| "false" String enum for localStorage
VodPositionSchema number Positive finite number
StoryboardInfoSchema StoryboardInfo Storyboard sprite sheet metadata
StoryboardEntrySchema StoryboardEntry Single storyboard sprite sheet entry

src/lib/utils.ts — Shared Utilities

Exported Functions

Function Signature Description
parseMasterManifest (m3u8: string) => Variant[] Parse HLS master manifest, validate with Zod, sort by resolution descending
getChannelName (url: string) => string \| undefined Extract channel name from URL pathname
isInChannel (url: string) => boolean Check if URL is a channel/VOD page for player installation
formatTime (secs: number) => string Format seconds to HH:MM:SS or MM:SS
getOauthToken (cookies: string[]) => string \| null Extract OAuth token from twilight-user cookie with Zod validation
parseGqlResponse (raw: unknown) => GqlResponse Validate GQL response with Zod, redact tokens in debug log
readSavedVolume () => number Read saved volume from localStorage (default: 0.5)
buildChatReplayUrl (embedSrc: string, videoId: string \| null) => string Build chat replay iframe URL

Constants

Name Value Description
HANDLE_RADIUS 7.25 Radius of seek/volume slider handle in pixels

Logger

dvrlog.info(...)    // console.info with [TwitchDVR] [INFO] prefix
dvrlog.warn(...)    // console.warn
dvrlog.error(...)   // console.error
dvrlog.debug(...)   // console.debug

src/lib/storage.ts — Centralized Storage

Constants

Name Description
KEYS Object mapping every storage key name to its twitch-dvr:-prefixed string

Functions

Function Description
getItem(key) Async read from chrome.storage.local, falls back to localStorage
setItem(key, value) Async write to chrome.storage.local, falls back to localStorage
removeItem(key) Async remove from chrome.storage.local, falls back to localStorage
getSync(key) Synchronous read from localStorage (for boot path)
setSync(key, value) Synchronous write to localStorage
removeSync(key) Synchronous remove from localStorage

src/lib/theater.ts — Theater Mode Helpers

Function Description
getTheaterIconActive(element) Returns true if element has .active class
computeTheaterAction(current, target) Returns { needsToggle: boolean }
applyTheaterState(element, target) Idempotent toggle: returns 0 if already in target state, 1 if toggled

src/content/videojs.d.ts — video.js Runtime Global Types

Ambient type declarations for the videojs global + VHS, injected by manifest.json content_scripts (video.min.js, plugins/quality-levels.min.js, plugins/hls-quality-selector.min.js). VHS is bundled inside video.min.js.

Why a custom .d.ts? video.js ships its own types, but they assume an ESM runtime import (import videojs from 'video.js'). We CANNOT use a runtime import — build-content.mjs runs esbuild with bundle: true, which would pull the entire ~580KB library into static/index.js. Instead the script is loaded as a separate content_script, exposing a global. So this file:

src/lib/videojs-install.ts — video.js Player Installation

Exports

Function Description
getLiveHlsUrl(channel) Resolve signed live HLS master URL via GQL token + usher
getVodHlsUrl(vodId) Resolve signed VOD HLS master URL via GQL token + usher
installVODPlayer(hlsUrl, changelog) Create video.js player for VOD with timer elements
installLivePlayer(hlsUrl, changelog) Create video.js player for live with custom seek bar
wasMutedPlaylistDetected() Returns true if muted segment playlist was detected
resetMutedSegmentToast() Reset the muted-toast-once-per-load flag
getVodCdnBaseUrl() Captured VOD CDN base URL from VHS onResponse
resetVodCdnBaseUrl() Reset the VOD CDN base URL (for channel switch)
getLiveTotalElapsed() Latest totalElapsed from live playlist parse
resetLiveTotalElapsed() Reset totalElapsed (for channel switch)

Internals

_createBasePlayer(hlsUrl, changelog, onReady) — Shared player creation for both live and VOD. Creates #vjs-player-container, calls disableNativePlayer, wires VHS onRequest/onResponse hooks (includes muted-segment detection via -muted.ts / index-muted- URL check with toast), creates the video.js player with fill/liveui/overrideNative/liveTracker, enables hlsQualitySelector, arms 1s native-disable + 2s diagnostic intervals (cleared on dispose). Full debug logging.

installVODPlayer — Calls _createBasePlayer with an onReady callback that injects custom timer elements (#vjs-timer, #vjs-timer-divider, #vjs-timer-end) into the control bar before the progressControl. The built-in progressControl is left visible for VOD seeking.

installLivePlayer — Calls _createBasePlayer with an onReady callback that adds the vjs-live CSS class (hides the native progressControl), injects custom timer elements + a full-width custom seek bar (#vjs-custom-seek-outer), and starts the 1s seek bar update tick.

src/lib/player-main.ts — Main Loop

runMain(cb) — Starts a 1s setInterval that detects URL changes via handleUrlChange() and routes to install/switch/uninstall callbacks. No playerTick — video.js handles all playback internally.

src/lib/url-watcher.ts — URL Change Detection

handleUrlChange(newPath, oldPath, wasInChannel, cb) — Detects channel navigation. Routes to install (no player found), switch (existing player found), or uninstall (left channel page). Always returns newPath as currentUrl so the 1s tick never re-detects the same change.

src/lib/native-player.ts — Native Player Suppression

Function Description
playNative() Play the native Twitch video (used when toggling to native player)
disableNativePlayer(reason) Mute, pause, hide, strip controls, unfocus all native .video-player__container video elements (skipping ours)

disableNativePlayer skip logic: checks v.id === "player" || v.id === "vjs-player" || v.closest("#vjs-player-container") || v.closest(".video-js"). This is critical — video.js renames the <video> element, so checking only id misses it.

src/lib/volume.ts — Volume Control

Function Description
setVolume(vol, player, volumeEl, handleRadius) Clamp [0,1], set player.volume, update width on BOTH #volume-slider track AND .slider-filled child, persist
toggleMute(player, volumeEl, handleRadius) Toggle mute, restore last known volume, fire toast
applySavedVolume(player) Read saved volume from storage and apply to player

src/lib/play-pause.ts — Play/Pause State

Function Description
pause() Track behind-live offset, call player.pause()
play() Play with optional ad-break skip for black-screen watchdog

src/lib/keyboard-handler.ts — Keyboard Shortcuts

Key Action
Space / K Toggle play/pause (uses player.paused — actual HTMLVideoElement state)
Left arrow / J Seek back seekBackStep (10s)
Right arrow / L Seek forward seekForwardStep (30s, VOD only)
Up arrow Volume up 5%
Down arrow Volume down 5%
M Toggle mute
F Toggle fullscreen
T Toggle theater mode
, Decrease playback speed
. Increase playback speed
C Toggle chat overlay (fullscreen only)

Uses capture-phase (true) to intercept keys before native handlers. Form-field blacklist (INPUT / TEXTAREA / role="textbox" / contenteditable) instead of a whitelist. Null-checks e.target before reading properties.

src/lib/seek-bar.ts — Seek Bar DOM Helpers

Function Description
getSeekWidth() Calculate seek bar pixel width (container width - 560px)
getTimeAtOffset(offsetX, maxTime, handleRadius) Convert pixel offset to time value, clamped to [0, maxTime]
updateSeekBar(offsetX, maxTime, handleRadius, seekSlider) Update seek slider element width

src/lib/switch-mode.ts — Mode Switching

switchMode(mode, videoMode, setVideoMode, setChatReplay, updateChatOverlayChannel, channel, vodId) — Switches between “live” and “vod” mode, updating the live indicator button, chat replay, and chat overlay.

src/lib/fetch-storyboard.ts — Storyboard Sprite Sheet Fetching

fetchStoryboard(vodBaseUrl, vodId, state) — Fetches {vodBaseUrl}../storyboards/{vodId}-info.json (Zod-parsed via StoryboardInfoSchema), preloads high-quality sprite sheets. Returns StoryboardSprite or null. A live stream’s storyboard doesn’t exist until the stream ends, so 403/404 is retried rather than treated as a hard error.

src/lib/resolve-vod-url.ts — VOD URL Resolution

resolveVodForChannel(channel, totalElapsed, state) — Resolves the most recent VOD for a channel via GQL, validates freshness against totalElapsed. Returns { vodId, hlsUrl } or null.

src/lib/get-m3u8.ts — HLS Master URL Resolution

getM3U8(isLive, channelOrVodId, clientId, authState) — Fetches GQL PlaybackAccessToken, then fetches the usher master.m3u8, parses with parseMasterManifest, validates with Zod. Returns the highest-resolution variant URL.

src/lib/overlays.ts — Error/Ad Overlays

Function Description
showErrorOverlay(message) Show error overlay with message (no external links)
showAdOverlay() Show ad-break overlay
hideAdOverlay() Hide ad-break overlay

src/lib/vod-position.ts — VOD Position Save/Restore

Function Description
getSavedVodPosition(vodId) Read saved position from chrome.storage.local
saveVodPosition(vodId, time) Write position to chrome.storage.local
clearVodPosition(vodId) Remove saved position from chrome.storage.local

src/lib/dom-helpers.ts — DOM Helpers

Function Description
addStyle(css) Inject <style> tag with given CSS content

src/lib/toast.ts — Toast Notifications

showToast(message) — Self-contained toast notification with internal timer. Shows at bottom-center of player, auto-hides after 2s.

src/lib/chat-replay.ts — Chat Replay Sync

setChatReplay(channel, vodId) — Toggles chat between live and VOD replay modes. 1s interval syncs hidden native player’s currentTime to DVR player’s adjustedTime in VOD mode.

src/lib/chat-overlay.ts — Fullscreen Chat Overlay

Function Description
createChatOverlay() Build DOM: #chat-overlay > #chat-overlay-header + #chat-overlay-iframe
updateChatOverlayChannel(channel, vodId) Sync iframe between live chat and VOD chat replay
toggleChatOverlay(enabled) Show/hide the chat overlay
isChatOverlayEnabled() Read from localStorage
setChatOverlayEnabled(v) Write to localStorage
loadChatOverlayPos() Read saved position from localStorage
saveChatOverlayPos(pos) Write position to localStorage

src/lib/controls-toggle.ts — Control Bar Show/Hide

Function Description
hideToggle() Hide control bar elements
showToggle() Show control bar elements
showToggleForAWhile() Show controls temporarily, then auto-hide

src/lib/parse-vod-playlist.ts — VOD Variant Playlist Parsing

parseVodVariantPlaylist(playlistUrl, vodId, state) — Parse VOD variant playlist, detect .ts vs .mp4 extension, build vodSegmentNames map for muted segments. Returns VodPlaylistState.

src/lib/clip.ts — Clip Creation

createClip(channel, clientId, adjustedTime, videoMode, vodId) — Create a Twitch clip via GQL.

src/lib/uninstall-player.ts — Player Cleanup

uninstallPlayer(container, player, ...state) — Cleanup: dispose video.js, clear timers, remove DOM, reset state.

src/lib/picker.ts — Quality Picker

togglePicker() — Toggle quality picker dropdown.

src/lib/speed-popover.ts — Speed Popover

Function Description
toggleSpeedPopover(player) Toggle speed popover visibility
closeSpeedPopover() Close speed popover

src/lib/settings.ts — Settings Menu

toggleSettings() — Toggle settings menu (chat overlay toggle, etc.).

src/lib/fullscreen.ts — Fullscreen

toggleFullscreen() — Enter/exit fullscreen, capture theater state for restore on exit.

src/lib/theater-mode.ts — Theater Mode State

Function Description
setTheaterMode(target) Idempotent setter: toggle .video-player__container--theatre class
restoreTheaterMode() Restore theater mode from saved preference
toggleTheaterMode() Flip theater mode

src/lib/controls-toggle.ts — Control Bar Toggle

Function Description
hideToggle() Hide control bar
showToggle() Show control bar
showToggleForAWhile() Show controls temporarily