Network API
Inspect and modify every HTTP request and response the player performs with network interceptors.
Tip: open your browser's developer console and use the player and THEOplayer variables to interact with this demo.
Network log
No requests logged yet. Play the video to see network traffic.
player.network.addRequestInterceptor() fires for every outgoing request; addResponseInterceptor() for every incoming response. This example logs each one, and — while the checkbox is on — reverses the text of every .vtt subtitle response body with response.respondWith(), so enable subtitles to see the effect. The interceptor only affects subtitle files loaded after it runs.
player.network.addRequestInterceptor((request) => {
console.log('Request', request.method, request.url);
});
player.network.addResponseInterceptor((response) => {
console.log('Response', response.status, response.url);
if (response.url.indexOf('.vtt') !== -1 && typeof response.body === 'string') {
response.respondWith({ body: transform(response.body) });
}
});
See the request and response interceptors guide.
Code
demo.html
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Network API</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover" />
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link href="https://fonts.googleapis.com/css2?family=Noto+Sans:ital,wght@0,400;0,700;1,400;1,700&display=swap" rel="stylesheet" />
<style>
html,
body {
margin: 0;
padding: 0;
}
theoplayer-default-ui:not(:defined) {
display: inline-block;
box-sizing: border-box;
}
theoplayer-default-ui {
width: 100%;
aspect-ratio: 16 / 9;
font-family: 'Noto Sans', sans-serif;
background: #000;
}
</style>
<script src="https://cdn.theoplayer.com/dash/theoplayer/THEOplayer.js"></script>
<script nomodule src="https://unpkg.com/@theoplayer/web-ui@2/polyfills"></script>
<script async src="https://unpkg.com/@theoplayer/web-ui@2"></script>
</head>
<body>
<!--
libraryLocation: For demonstration purposes, we use the THEOplayer CDN.
For production use, host the library yourself and update this URL and the script above.
licenseUrl: Point this to your license file, or replace it with a license property.
-->
<theoplayer-default-ui
id="player-ui"
configuration='{"libraryLocation":"https://cdn.theoplayer.com/dash/theoplayer/","licenseUrl":"../../../../theoplayer-license.txt"}'
source='{"sources":{"src":"https://cdn.theoplayer.com/video/tears_of_steel/index.m3u8","type":"application/x-mpegurl"},"poster":"https://cdn.theoplayer.com/video/tears_of_steel/poster.jpg","metadata":{"title":"Tears of Steel"}}'
></theoplayer-default-ui>
<script src="../utils.js"></script>
<script>
let reverseSubtitles = true;
function header(headers, name) {
if (!headers) return '—';
const key = Object.keys(headers).find((entry) => entry.toLowerCase() === name.toLowerCase());
return key ? headers[key] : '—';
}
function timestamp() {
const now = new Date();
const pad = (value, length) => String(value).padStart(length, '0');
return `${pad(now.getUTCHours(), 2)}:${pad(now.getUTCMinutes(), 2)}:${pad(now.getUTCSeconds(), 2)}.${pad(now.getUTCMilliseconds(), 3)}`;
}
function logEntry(entry) {
sendToParent('network-entry', { entry });
}
onPlayerReady((player) => {
// Intercept every request the player makes and report it to the demo page.
player.network.addRequestInterceptor((request) => {
logEntry({
kind: 'Request',
time: timestamp(),
method: request.method || '—',
type: request.type || '—',
subType: request.subType || '—',
url: request.url,
contentType: header(request.headers, 'Content-Type'),
contentLength: header(request.headers, 'Content-Length'),
});
});
// Intercept every response. For .vtt subtitle files, reverse each cue's text.
player.network.addResponseInterceptor((response) => {
logEntry({
kind: 'Response',
time: timestamp(),
status: `${response.status} ${response.statusText || ''}`.trim(),
type: (response.request && response.request.type) || '—',
subType: (response.request && response.request.subType) || '—',
url: response.url,
contentType: header(response.headers, 'Content-Type'),
contentLength: header(response.headers, 'Content-Length'),
});
if (
reverseSubtitles &&
response.url.indexOf('.vtt') !== -1 &&
(typeof response.body === 'string' || response.body instanceof ArrayBuffer)
) {
const text = typeof response.body === 'string' ? response.body : new TextDecoder().decode(response.body);
const newText = text.replace(/\r\n/g, '\n').replace(/.+ --> .+\n([\s\S]*?)\n\n/g, (match, cueText) => {
const newCueText = cueText
.split('\n')
.map((line) => line.split('').reverse().join(''))
.join('\n');
return match.replace(cueText, newCueText);
});
response.respondWith({
body: typeof response.body === 'string' ? newText : new TextEncoder().encode(newText).buffer,
});
}
});
onParentMessage('network-reverse', (message) => {
if (typeof message.enabled === 'boolean') reverseSubtitles = message.enabled;
});
});
</script>
</body>
</html>