# How the Read Frog `interceptor.content` Script Modifies YouTube Video Player Behavior

> Discover how the Read Frog interceptor.content script modifies YouTube player behavior. It captures subtitles, exposes player methods, and enables real-time translation features for Read Frog.

- Repository: [MengXi/read-frog](https://github.com/mengxi-ream/read-frog)
- Tags: how-to-guide
- Published: 2026-03-07

---

**The `interceptor.content` script injects a thin RPC layer into YouTube pages that monkey-patches `XMLHttpRequest` to capture subtitle URLs, exposes internal player methods via `window.postMessage`, and can forcibly enable subtitles to support Read Frog's real-time translation features.**

The Read Frog browser extension (mengxi-ream/read-frog) enhances language learning on YouTube by intercepting and augmenting the platform's native video player. At the core of this capability lies the `interceptor.content` script, a content script that silently installs observation hooks and exposes a controlled API for background and UI scripts to extract playback data and manipulate subtitle states.

## Content Script Registration and Injection

The interceptor declares itself in [`src/entrypoints/interceptor.content/index.ts`](https://github.com/mengxi-ream/read-frog/blob/main/src/entrypoints/interceptor.content/index.ts), targeting `*.youtube.com/*` pages with a `document_start` timing that ensures execution before the page finishes loading (lines 4-10). Upon initialization, it immediately invokes `injectPlayerApi()` to begin the setup process.

This early injection is critical because it allows the script to wrap native browser APIs before the YouTube player initializes its own network stack.

## Intercepting Timed-Text Requests via XHR Monkey-Patching

To capture subtitle data, the script modifies the browser's network layer. In [`src/entrypoints/interceptor.content/timedtext-observer.ts`](https://github.com/mengxi-ream/read-frog/blob/main/src/entrypoints/interceptor.content/timedtext-observer.ts), the `setupTimedtextObserver()` function overrides `XMLHttpRequest.prototype.open` and `send` to monitor every outgoing request (lines 45-60).

When a request URL matches the `/api/timedtext/` pattern, the interceptor extracts the video ID (`v`) and the timed-text token (`pot`), then stores the full URL in a `timedtextUrlCache` Map. This cache enables asynchronous waiting via `waitForTimedtextUrl()`, which resolves pending promises once the specific video's subtitle URL is captured.

## Message-Based Player Control Interface

The core communication mechanism resides in [`src/entrypoints/interceptor.content/inject-player-api.ts`](https://github.com/mengxi-ream/read-frog/blob/main/src/entrypoints/interceptor.content/inject-player-api.ts) (lines 43-57), where a `window.addEventListener('message')` handler exposes three distinct RPC operations to other extension contexts. The script listens for specific message types and posts responses back to the origin, creating a bidirectional bridge between the isolated extension background scripts and the privileged page context.

### Extracting Comprehensive Player Data

When receiving a `PLAYER_DATA_REQUEST`, the script invokes `getPlayerData()` (lines 79-110). This function locates the YouTube player element using selectors like `.html5-video-player`, then accesses undocumented internal methods including `getPlayerResponse()`, `getAudioTrack()`, and `getAudioTracks()` to extract:

- Current video ID and playback status
- Available caption tracks (normalized via `normalizeTracks()` from [`utils.ts`](https://github.com/mengxi-ream/read-frog/blob/main/utils.ts))
- Active audio track information (parsed via `parseAudioTracks()`)
- Device and player metadata
- The cached timed-text URL from the XHR observer

The aggregated data is returned via `PLAYER_DATA_RESPONSE` postMessage. Error states are handled using the `errorResponse()` helper.

### Asynchronous Subtitle URL Resolution

For components that need to fetch raw subtitle XML, the `WAIT_TIMEDTEXT_REQUEST` message triggers `waitForTimedtextUrl(videoId, timeout)` (lines 58-66). This returns a promise that resolves once the `timedtextUrlCache` captures the requested video's URL, enabling reliable subtitle fetching without race conditions. Upon resolution, the script posts a `WAIT_TIMEDTEXT_RESPONSE` containing the captured URL.

### Forcing Subtitle Activation

The `ENSURE_SUBTITLES_REQUEST` message addresses scenarios where captions are disabled. The `ensureSubtitlesEnabled()` function (lines 17-33 and 68-76) attempts to activate subtitles by either:

1. Calling the player's internal `toggleSubtitles()` method directly, or
2. Programmatically clicking the visible subtitle toggle button in the player UI

This guarantees that caption tracks are available for downstream processing, regardless of the user's initial player state.

## Implementation Examples

*Requesting player data from a background script:*

```typescript
// Send request
window.postMessage(
  { type: 'PLAYER_DATA_REQUEST', requestId: 'req-123', expectedVideoId: 'abc123' },
  window.location.origin,
);

// Handle response
window.addEventListener('message', (e) => {
  if (e.data?.type === 'PLAYER_DATA_RESPONSE' && e.data.requestId === 'req-123') {
    console.log('Player data:', e.data.data);
  }
});

```

*Waiting for timed-text URL availability:*

```typescript
window.postMessage(
  { type: 'WAIT_TIMEDTEXT_REQUEST', requestId: 'tt-789', videoId: 'abc123' },
  window.location.origin,
);

window.addEventListener('message', (e) => {
  if (e.data?.type === 'WAIT_TIMEDTEXT_RESPONSE' && e.data.requestId === 'tt-789') {
    const subtitleUrl = e.data.url; // https://www.youtube.com/api/timedtext?...
    // Fetch subtitle content
  }
});

```

*Ensuring subtitles are enabled before processing:*

```typescript
window.postMessage(
  { type: 'ENSURE_SUBTITLES_REQUEST', requestId: 'sub-456' },
  window.location.origin,
);
// The interceptor enables subtitles and acknowledges via postMessage

```

## Summary

- The `interceptor.content` script runs at `document_start` on all YouTube domains to establish early hooks.
- It monkey-patches `XMLHttpRequest` in [`timedtext-observer.ts`](https://github.com/mengxi-ream/read-frog/blob/main/timedtext-observer.ts) to cache timed-text URLs and video tokens.
- The script exposes a `postMessage` API in [`inject-player-api.ts`](https://github.com/mengxi-ream/read-frog/blob/main/inject-player-api.ts) allowing other extension contexts to request player data, wait for subtitle URLs, and force-enable captions.
- Internal player methods like `getPlayerResponse()` and `toggleSubtitles()` are accessed directly to extract state and modify behavior.
- This architecture enables Read Frog's real-time translation, audio-track caption extraction, and synchronized playback control features.

## Frequently Asked Questions

### How does the interceptor script communicate with other Read Frog extension components?

The script establishes a message-passing bridge using `window.postMessage`. Background and UI scripts send typed messages (e.g., `PLAYER_DATA_REQUEST`) to the page context, and the interceptor responds with corresponding response types (e.g., `PLAYER_DATA_RESPONSE`), enabling cross-context RPC without direct function calls.

### Is it safe to monkey-patch XMLHttpRequest in a browser extension?

While the technique modifies native browser APIs, the implementation in [`timedtext-observer.ts`](https://github.com/mengxi-ream/read-frog/blob/main/timedtext-observer.ts) preserves the original request flow and only inspects URLs for the `/api/timedtext/` pattern. The patch is applied immediately at `document_start` and is scoped to the YouTube page context, minimizing interference with other extensions or standard web functionality.

### What happens if the YouTube player hasn't loaded timed-text data yet?

The `waitForTimedtextUrl()` function returns a promise that resolves once the XHR observer captures the subtitle URL for the specific video ID. If the request hasn't occurred, the interceptor waits until the player initiates the network call, then immediately resolves pending waiters and caches the result for subsequent requests.

### Can the script enable subtitles on videos that don't have caption tracks?

No. The `ensureSubtitlesEnabled()` function only toggles the visibility of existing caption tracks or clicks the subtitle button. If the video lacks timed-text data entirely, the YouTube player will not generate subtitle URLs, and the `timedtextUrlCache` will not receive entries for that video ID. The interceptor cannot create captions where none exist in the source material.