YouTube’s short-form player has a structural blind spot — and a single browser script is all it takes to walk straight through it.
YouTube Shorts sit in an awkward position architecturally. They’re built on top of YouTube’s enormous infrastructure, yet they’re designed to behave like TikTok or Instagram Reels: fast, looping, endlessly scrollable. To achieve that feel, YouTube’s engineers prioritize speed and caching over the heavy-duty protection they apply to 4K long-form movies.
That tradeoff has a practical consequence: Shorts are substantially easier to capture from the browser than standard YouTube videos.
Why Shorts are a Weaker Target than the Main Site
Long-form YouTube uses separated tracks (DASH)
Full YouTube videos are delivered via DASH (Dynamic Adaptive Streaming over HTTP). The video and audio tracks are sent as separate streams at multiple quality levels. Your browser requests the right quality tier based on your bandwidth, then reassembles the tracks. Attempting to capture this with captureStream produces an incomplete or audio-less recording, because the audio track isn’t attached to the <video> element in the normal way — it arrives separately and is only stitched together internally.
Shorts use a unified, simpler manifest
Because Shorts need to load and loop instantly — the same way TikTok and Instagram Reels do — YouTube frequently serves them as a unified stream or a simpler manifest. The audio and video arrive together, attached to the same <video> element. That’s the exact format captureStream and MediaRecorder were built for. There’s no complex DASH reassembly to defeat; the browser already has everything it needs.
Using it on Desktop
Step 1 — Open YouTube Shorts
Go to youtube.com/shorts (or navigate to any Short directly). Scroll until the Short you want is fully active — it should be playing and looping in the center of the screen.
Step 2 — Open the Browser Console
Windows / Linux: Press F12 or Ctrl + Shift + I, then click the Console tab.
Direct shortcut: Ctrl + Shift + J (Chrome) jumps straight to the Console.
Mac: Press Cmd + Option + I, then click the Console tab.
Direct shortcut: Cmd + Option + J (Chrome) jumps straight to the Console.
Step 3 — Paste and Run the Script
Paste the following code into the console and press Enter:
javascript:(function() {
const video = document.querySelector('ytd-reel-video-renderer[is-active] video, #shorts-player video');
if (!video) return alert("No active Short detected!");
video.pause();
video.currentTime = 0;
let previousTime = 0;
let isFinished = false;
const startCapture = () => {
const stream = video.captureStream ? video.captureStream() : video.mozCaptureStream();
const recorder = new MediaRecorder(stream, {
mimeType: 'video/webm;codecs=vp9,opus',
videoBitsPerSecond : 5000000
});
const chunks = [];
recorder.ondataavailable = e => chunks.push(e.data);
recorder.onstop = () => {
const blob = new Blob(chunks, { type: 'video/webm' });
const a = document.createElement('a');
a.href = URL.createObjectURL(blob);
a.download = `YT_Short_Delta_Safe_${Date.now()}.webm`;
a.click();
video.pause();
};
/* The Delta-Check Logic */
const trackProgress = () => {
/* If the current time is LESS than the previous time, the loop has occurred */
if (video.currentTime < previousTime && !isFinished && previousTime > 0) {
isFinished = true;
recorder.stop();
video.removeEventListener('timeupdate', trackProgress);
console.log("Loop detected via Time Delta. Capture finalized.");
}
previousTime = video.currentTime;
};
video.addEventListener('timeupdate', trackProgress);
recorder.start();
video.play();
};
video.addEventListener('seeked', () => {
setTimeout(startCapture, 500);
}, { once: true });
})();
The Short will rewind to the start, play through once, and your browser will automatically download the file as YT_Short_Delta_Safe_<timestamp>.webm.
How the Code Works
Finding the Active Short
The very first line is the key architectural insight:
const video = document.querySelector('ytd-reel-video-renderer[is-active] video, #shorts-player video');
YouTube’s Shorts player keeps several ytd-reel-video-renderer elements in the DOM at once — one for the Short above, one for the current Short, one below. Each is ready to play the moment you swipe. The active one carries an is-active attribute. The selector targets that active renderer’s <video> child directly. The fallback #shorts-player video catches edge cases where the attribute-based selector doesn’t match (older YouTube UI versions).
This is a fundamentally different approach from the Instagram Reel Sniper, which has to loop through every <video> on the page and check each one’s bounding rectangle to find the visible one. YouTube gives us a cleaner semantic handle with is-active.
The 500 ms Delay
video.addEventListener('seeked', () => {
setTimeout(startCapture, 500);
}, { once: true });
After setting video.currentTime = 0, the script waits for the seeked event (fired when the seek operation completes) before starting anything. It then waits an additional 500 ms. The extra half-second gives YouTube’s player time to stabilize the stream after the seek — without it, the first few frames can be dropped or corrupted in the recording.
The Delta-Check Loop Detector
This is the most novel part of the script, and the reason it works reliably on looping content.
Instagram Reels and other single-play videos fire a clean ended event when they finish. Shorts loop by default — they don’t end. If you waited for video.onended, you’d wait forever.
The Delta-Check solves this elegantly:
const trackProgress = () => {
/* If the current time is LESS than the previous time, the loop has occurred */
if (video.currentTime < previousTime && !isFinished && previousTime > 0) {
isFinished = true;
recorder.stop();
video.removeEventListener('timeupdate', trackProgress);
console.log("Loop detected via Time Delta. Capture finalized.");
}
previousTime = video.currentTime;
};
video.addEventListener('timeupdate', trackProgress);
The timeupdate event fires several times per second as the video plays. Under normal playback, video.currentTime always increases. The moment the Short loops back to the beginning, currentTime drops — it becomes less than previousTime. That backwards jump is the signal. The isFinished flag ensures the recorder stops exactly once even if timeupdate fires again before the listener is removed.
Capturing and Saving
The rest mirrors the Instagram approach: captureStream turns the <video> element into a live stream source, MediaRecorder encodes it as VP9 + Opus at 5 Mbps, and the accumulated chunks are assembled into a Blob that triggers a download via a temporary <a> element.
Differences from Instagram Reel Downloads
For a full breakdown of the captureStream + MediaRecorder pattern, see the Instagram Reel Sniper post. The core pipeline is identical. Here’s where the two scripts diverge:
| YouTube Shorts Sniper | Instagram Reel Sniper | |
|---|---|---|
| Finding the video | ytd-reel-video-renderer[is-active] video — semantic YouTube attribute |
getBoundingClientRect() loop — geometric viewport check |
| Stop condition | Delta-Check: currentTime < previousTime (loop detection) |
video.onended — fires once when the video finishes |
| Why different | Shorts loop indefinitely and never fire ended |
Reels play once and stop, firing ended cleanly |
| Seek delay | 500 ms (slightly longer for YouTube’s heavier player) | 400 ms |
| Mute handling | Not required — Shorts are not muted by default | Explicit video.muted = false needed |
| Multi-video cleanup | Not needed — is-active isolates the target |
Pauses all other videos to prevent audio bleed |
| Output filename | YT_Short_Delta_Safe_<timestamp>.webm |
IG_Targeted_Capture_<timestamp>.webm |
| Mobile bookmarklet | Not provided | Provided |
The core philosophy is the same: use captureStream to intercept what the browser is already rendering. The differences are all adaptations to each platform’s specific DOM structure and playback behavior.
Troubleshooting
| Symptom | Likely Cause | Fix |
|---|---|---|
| Alert: “No active Short detected!” | Script ran before a Short was fully loaded, or on a non-Shorts page | Navigate to a Short and wait for it to begin playing before running the script |
| Recording stops immediately | YouTube’s UI changed and is-active is no longer present |
Open DevTools, inspect the active video element, and update the selector |
| File downloads but has no audio | DASH mode may be active for this Short | Try on a different Short; this occasionally happens on very high-resolution Shorts |
| Stutter at the start of the saved file | CPU spike during seek + recorder initialization | The 500 ms delay usually handles this; enable Hardware Acceleration in browser settings if it persists |
timeupdate never fires |
Tab was backgrounded during recording | Keep the YouTube tab active and in the foreground while recording |
| Download doesn’t start | Browser blocked the <a>.click() |
Allow pop-ups / automatic downloads for youtube.com in browser settings |
Using the same browser-only approach, you can also download Instagram Reels, download Twitter/X videos, and save Instagram photos.
Disclaimer: This script is provided for educational purposes only. It demonstrates browser APIs (
captureStream,MediaRecorder,timeupdate) that are freely documented and publicly available. Downloading content from YouTube may be against YouTube’s Terms of Service. Only use this on content you own, have explicit permission to download, or that is explicitly licensed for offline use. Respect copyright and the work of content creators.