Bookmarklets & oku3d://
Open streams directly from any web page into Oku3D - one click in your browser, watch in 3D in the player. The Quick Start link works out of the box; the reference scripts further down are starting points for building your own.
Table of Contents
Overview
Oku3D registers a Windows protocol handler for the oku3d:// URL scheme on first launch. Any web page or shell command that navigates to oku3d://open?url=... hands the request to the running Oku3D instance (or starts a new one), and the stream opens directly in the player.
That makes it possible to build browser bookmarklets that detect a video on the current page and open it in Oku3D in one click. This page is the reference for the URL format, the headers Oku3D can forward to the stream origin, and the safety constraints the parser enforces.
If you only want to watch streams - paste a URL into Oku3D's Open Stream dialog or drag a .m3u playlist onto the window. The bookmarklet workflow exists for browser-to-player handoff with header forwarding.
Quick Start
Drag the link below onto your browser's bookmarks bar - that's the whole install. Clicking the bookmark on any video page opens the stream in Oku3D.
Open in Oku3D ← drag to your bookmarks bar
How it works
- The bookmark is a tiny loader that fetches
https://oku3d.com/bookmarklet.jsand runs it on the current page. - The hosted script tries provider-specific handlers (Vimeo, arte.tv, ...) first, then falls back to scraping the first
<video>element. - On match it builds an
oku3d://URL with the stream source plusRefererandUser-Agentheaders. - Your browser prompts "Open Oku3D?" on each click; tick Always allow on that prompt to skip it on subsequent clicks from the same origin.
- Oku3D shows its own confirmation dialog with the destination URL and the headers being forwarded, then opens the stream.
Why the script is hosted
Bookmarks are static once installed. Hosting the dispatcher on oku3d.com means new provider support reaches every installed bookmark on the next click - no reinstall, no version drift. The trade-off: clicking the bookmark loads code from oku3d.com, so trusting the script means trusting the server. If that is not the trade-off you want, the reference scripts below are self-contained and never reach the network.
When the Quick Start link works: any site that embeds an HLS, DASH, or progressive-MP4 stream via a plain <video> tag (or one of the explicitly-supported providers). When it doesn't: DRM-protected services (Netflix, Disney+, ...) - their src is a blob: URL, not an extractable stream.
oku3d:// URI Spec
Shape
oku3d://open?url=<ENCODED_URL>
&header=Name:%20Value
&header=Name:%20Value
&referer=<ENCODED_URL>
&user_agent=<ENCODED_UA>
&cookie=<ENCODED_COOKIE> The open path segment is conventional and ignored by the parser; only the query string carries meaning. Build URLs with URLSearchParams in JS - manual percent-encoding is easy to get wrong, especially for header values with +, =, or non-ASCII characters.
Query Parameters
| Parameter | Repeatable | Purpose |
|---|---|---|
url | No | Required. The stream URL libavformat will open. Any scheme libavformat recognises (http, https, rtsp, rtmp, hls, dash, udp, srt, ...). |
header | Yes | One full Name: Value header line. Repeat for multiple headers. The colon separator is mandatory. |
referer | No | Convenience alias - same as header=Referer:%20<value>. |
user_agent | No | Convenience alias - same as header=User-Agent:%20<value>. Also accepts user-agent and useragent spellings. |
cookie | No | Convenience alias - same as header=Cookie:%20<value>. |
Unknown query parameters are silently ignored. This is intentional - future Oku3D versions may introduce additional optional fields, and existing bookmarklets keep working in the meantime.
Kodi Pipe Syntax
The same header-forwarding capability is exposed via the Kodi-style URL suffix convention used in IPTV playlists and the broader streaming community. Append |Name=Value pairs to the stream URL, separated by &:
https://example.com/stream.m3u8|Referer=https://example.com/&User-Agent=Mozilla/5.0 Recognised in three places:
- Pasted into the Open Stream dialog.
- Dropped onto the player window as part of a
.m3u/.m3u8playlist file. - Passed as a command-line argument:
oku3d.exe "https://stream/...|Referer=...".
The header block uses = to separate name and value (not : like in oku3d:// headers) and & as the inter-header separator. Values are URL-encoded. Same validation rules apply as for the oku3d:// form.
Note: No browser confirmation prompt fires for Kodi-style URLs, because they are not triggered by a web-page navigation - the user typed or pasted them deliberately into the player.
HTTP Headers
What gets forwarded
Headers in the parsed request are passed verbatim to libavformat's headers AVDictionary option, which prepends them to every HTTP-family request the demuxer makes (manifest fetch, segment fetch, key-URL fetch for AES-128 HLS). They override libavformat's defaults for the same names.
User-Agent handling
If your request includes a User-Agent header, it wins over Oku3D's built-in identifier (Oku3D/x.y.z (streaming)). Some origins reject non-browser-shaped UA strings outright, so forwarding the browser UA is the safer default for bookmarklets.
Validation
Every header is validated against RFC 7230 before being forwarded:
- CRLF injection blocked. Carriage return or line feed in either the name or the value is rejected outright - the classic attack vector that would let an attacker smuggle a second header (e.g.
Host: attacker.com) past the first one. - Name token. Header names must match RFC 7230's
tokenproduction ([!#$%&'*+\-.^_`|~0-9A-Za-z]+). Spaces, quotes, or non-ASCII characters in the name are rejected. - Length cap. 4 KiB per header line, 16 KiB total across all headers.
Deny-listed headers
A small set of headers is rejected because libavformat manages them itself and an override would either break the request or open a smuggling surface:
| Header | Why blocked |
|---|---|
Host | Derived from the URL by libavformat. TLS SNI keys off it. Override breaks HTTPS connections and shapes a request-smuggling attack surface. |
Content-Length | libavformat sets this from the request body it actually sends. |
Transfer-Encoding | libavformat owns request body framing. |
Connection | Hop-by-hop. libavformat manages the socket lifecycle. |
Upgrade | Hop-by-hop. |
Logging
When Oku3D is launched from a terminal (cmd / PowerShell / Windows Terminal), the values of Authorization and Cookie headers are redacted before being printed to the parent console. The full values are still passed to libavformat - only the diagnostic log line is masked, so session tokens do not leak through your shell history.
Reference Bookmarklets
These are standalone, self-contained bookmarklets you can fork and adapt for sites the Quick Start link does not cover. Unlike the Quick Start loader, they do not depend on oku3d.com - the full logic lives in the bookmark itself. Each one has a drag-to-bookmarks-bar link directly under its code block.
If you build a bookmarklet for another provider and the site's terms allow it, open a PR against the oku3d-website repo. Provider handlers that hold up are folded into the hosted Quick Start script so every user gets the support automatically.
Generic <video> scraper
Works on most sites that embed an HLS / DASH / progressive-MP4 stream via a plain <video> tag. Forwards the current page as Referer plus the browser User-Agent - covers the typical hotlink-protected setup.
javascript:(() => {
// Grab the first <video> on the page and forward its source URL
// to Oku3D along with the page Referer and browser User-Agent
// (the two headers most hotlink-protection setups gate on).
const video = document.querySelector('video');
const streamUrl = video?.src || video?.currentSrc;
if (!streamUrl) {
alert('No <video> source found on this page');
return;
}
const params = new URLSearchParams({
url: streamUrl,
referer: location.href,
user_agent: navigator.userAgent,
});
location.href = `oku3d://open?${params.toString()}`;
})(); Open in Oku3D ← drag to your bookmarks bar
Vimeo embed
Vimeo's player iframe only points at the HTML player page, not at the underlying stream. This bookmarklet extracts the video id and unlisted-hash token from the iframe URL, calls player.vimeo.com/video/<id>/config?h=<token> to resolve the HLS master playlist (with a progressive-MP4 fallback for legacy embeds), and forwards the result. Vimeo validates the Referer header against the video's privacy settings, so forwarding the current page URL is what lets the stream open at all.
javascript:(async () => {
// The Vimeo player iframe carries only the HTML player page,
// not the underlying stream. Extract the video id and the
// unlisted-hash token from the iframe URL, then call Vimeo's
// player-config endpoint to resolve the real stream URL:
// https://player.vimeo.com/video/<id>/config?h=<token>
// The response carries `request.files.hls.cdns` keyed by CDN
// name with a `default_cdn` pointer; fall back to the highest-
// quality entry of `request.files.progressive` if HLS is absent
// (some legacy embeds).
const iframe = document.querySelector('iframe[src*="player.vimeo.com"]');
if (!iframe) {
alert('No Vimeo player iframe found');
return;
}
const iframeUrlMatch = iframe.src.match(
/player\.vimeo\.com\/video\/(\d+)(?:\?|.*?[?&])?(?:.*&)?h=([a-f0-9]+)/,
);
if (!iframeUrlMatch) {
alert('Vimeo iframe URL is missing the video id / h-token');
return;
}
const [, videoId, hashToken] = iframeUrlMatch;
try {
const response = await fetch(
`https://player.vimeo.com/video/${videoId}/config?h=${hashToken}`,
);
if (!response.ok) throw new Error(`HTTP ${response.status}`);
const config = await response.json();
const hls = config?.request?.files?.hls;
const hlsCdn = hls?.cdns?.[hls.default_cdn] ?? Object.values(hls?.cdns ?? {})[0];
const progressive = config?.request?.files?.progressive;
const streamUrl = hlsCdn?.url ?? progressive?.at(-1)?.url;
if (!streamUrl) {
alert('No playable stream URL in Vimeo config');
return;
}
const params = new URLSearchParams({
url: streamUrl,
referer: location.href,
user_agent: navigator.userAgent,
});
location.href = `oku3d://open?${params.toString()}`;
} catch (error) {
alert(`Vimeo lookup failed: ${error.message}`);
}
})(); Open Vimeo in Oku3D ← drag to your bookmarks bar
arte.tv (HLS lookup)
arte.tv exposes a public player-config JSON endpoint that returns the underlying HLS URL for any video page. This bookmarklet calls that endpoint and forwards the result.
javascript:(async () => {
// arte.tv exposes a public player-config endpoint that resolves
// any /<lang>/videos/<id> page to its underlying HLS master
// playlist. All entries of `data.attributes.streams[]` point at
// the same manifest (one per audio version) and the m3u8 itself
// negotiates the right variant, so picking `streams[0]` is
// enough.
const pathMatch = location.pathname.match(/\/([a-z]+)\/videos\/(\d+-\d+-A)/);
if (!pathMatch) {
alert('Not an arte.tv video page');
return;
}
const [, language, videoId] = pathMatch;
try {
const response = await fetch(
`https://api.arte.tv/api/player/v2/config/${language}/${videoId}`,
);
const config = await response.json();
const streamUrl = config?.data?.attributes?.streams?.[0]?.url;
if (!streamUrl) {
alert('No playable stream URL in arte.tv config');
return;
}
const params = new URLSearchParams({
url: streamUrl,
referer: location.href,
user_agent: navigator.userAgent,
});
location.href = `oku3d://open?${params.toString()}`;
} catch (error) {
alert(`arte.tv lookup failed: ${error.message}`);
}
})(); Open arte.tv in Oku3D ← drag to your bookmarks bar
Limitations
DRM-protected streams
Netflix, Disney+, Amazon Prime Video, Apple TV+, Paramount+, HBO Max, Joyn+, ARD Plus, RTL+, and similar services use EME / Widevine / PlayReady DRM. Their video.src is a blob: URL backed by MSE (Media Source Extensions); there is no extractable stream URL. Bookmarklets fail cleanly here - no workaround exists short of breaking DRM, which Oku3D does not do.
YouTube
YouTube uses SABR streaming with per-segment signed URLs that require continuously-rotated PO tokens. No header forwarding scheme can produce playable segments; yt-dlp is the right tool for that workflow.
Live Twitch
Twitch low-latency HLS uses signed-cookie segment auth that rotates per segment. The initial manifest fetch works with a forwarded session cookie, but segment fetches fail once the token expires (~30 s).
HttpOnly session cookies
The browser-side document.cookie reader does not see HttpOnly cookies, which most modern auth sessions use. If a stream requires an authenticated session cookie, a bookmarklet alone cannot forward it - you need a browser extension that hooks the network layer instead.
Cross-origin video CDNs
When the page lives on site.com but the stream loads from cdn.example.net, the cookies you can read with document.cookie are not the cookies the browser actually sends to the CDN. The bookmarklet is operating on the wrong origin and the forwarded Cookie header is unlikely to be useful. Referer forwarding still works because libavformat sends it to the CDN as part of the GET, but cookie-based auth on the CDN itself is out of reach for the bookmarklet shape.