If you follow along with Udemy courses while coding in VS Code (or any other editor), you've probably hit this annoyance: the F7/F8/F9 media keys don't work properly with the Udemy video player. Play/pause fights with itself, and skip forward/back do nothing at all.
Here's why, and two ways to fix it.
The Problem
Udemy's player has a function called syncPlayStateToPlayer that constantly tries to reconcile its internal play/pause state with the actual video element. When you press F8 (play/pause) from another application, the browser's Media Session API toggles the video - but then Udemy's sync function fires and reverts it. The result: nothing happens, or the video flickers.
F7 and F9 (previous/next track) simply aren't wired up at all.
Fix 1: Chrome DevTools Local Override
This is the "proper" local hack. It patches the source file directly so the fix survives page navigation within the course.
- Open DevTools (or Safari's Web Inspector)
- In the Sources panel, search for a file starting with
course-taking-udlite-app...js - Right-click the file and select Override content (Chrome) or add it to Local Overrides
- Find the function
syncPlayStateToPlayerand addreturnas the first statement:
this.syncPlayStateToPlayer = () => {
return // <-- added: short-circuits the sync
clearTimeout(this.playStateSyncTimeout);
try {
if (!this.playingStateNeedsSyncing) {
return
}
if (this.isVideoPlayerInitialized && this.store.isSourceReady) {
this.isPlaying ? this.play() : this.pause()
}
this.playStateSyncTimeout = setTimeout(this.syncPlayStateToPlayer, O.Yj)
} catch (e) {}
}
The early return kills the sync loop entirely. The rest of the function becomes dead code.
Fix 2: Console Snippet (full version with all handlers)
This is lighter and doesn't require digging through minified source. It does three things:
- Walks the React fiber tree from the
<video>element to find the component instance - Replaces
syncPlayStateToPlayerwith a no-op - Registers Media Session handlers for F7 (rewind) and F9 (skip forward)
(() => {
const v = document.querySelector('video');
const k = Object.keys(v).find(k => k.startsWith('__reactFiber$'));
let f = v[k];
while (f) {
const i = f.stateNode;
if (i && i.syncPlayStateToPlayer) {
i.syncPlayStateToPlayer = () =>
i.isPlaying = !document.querySelector('video').paused;
console.log('Patched');
break;
}
f = f.return;
}
// F9 media button
navigator.mediaSession.setActionHandler('previoustrack', () => {
document.querySelector('[data-purpose="rewind-skip-button"]')?.click();
});
// F7 media button
navigator.mediaSession.setActionHandler('nexttrack', () => {
document.querySelector('[data-purpose="forward-skip-button"]')?.click();
});
function toggle(e) {
e.preventDefault();
e.stopImmediatePropagation();
const v = document.querySelector('video');
v.paused ? v.play() : v.pause();
}
// sync space key if video is played/paused with F8
document.addEventListener('keydown', (e) => {
if (e.code === 'Space' && e.target.tagName !== 'INPUT' && e.target.tagName !== 'TEXTAREA') {
toggle(e);
}
}, true);
// sync click on video if video is played/paused with F8
document.addEventListener('click', (e) => {
const el = e.target;
const isOverlay = el.tagName === 'VIDEO' ||
[...el.classList].some(c => c.startsWith('shaka-control-bar--popover-area-'));
if (isOverlay) {
toggle(e);
}
}, true);
})();
How to save it
In Chrome DevTools, go to Sources > Snippets, create a new snippet, paste the code, and save. Run it once after each page load (right-click the snippet > Run, or Ctrl+Enter).
Result
If you're coding along with a Udemy course in VS Code, you can control playback without switching to the browser tab:
- F8 - play/pause (works after patching the sync)
- F7 - skip back (mapped to Udemy's rewind button)
- F9 - skip forward (mapped to Udemy's forward button)
No alt-tabbing and hunting for the browser. Just press a key and keep coding.
Caveats
- The snippet needs to be re-run on each full page refresh (navigating between lectures within a course will in some cases require it)
- This relies on Udemy's current React internals and
data-purposeattributes - it could break if they refactor
Easy fix with a bookmark when you need it
I use minify to and produce this minified version clicking Terser Defaults:
javascript:void((()=>{const e=document.querySelector("video");const t=Object.keys(e).find((e=>e.startsWith("__reactFiber$")));let a=e[t];while(a){const e=a.stateNode;if(e&&e.syncPlayStateToPlayer){e.syncPlayStateToPlayer=()=>e.isPlaying=!document.querySelector("video").paused;console.log("Patched");break}a=a.return}navigator.mediaSession.setActionHandler("previoustrack",(()=>{document.querySelector('[data-purpose="rewind-skip-button"]')?.click()}));navigator.mediaSession.setActionHandler("nexttrack",(()=>{document.querySelector('[data-purpose="forward-skip-button"]')?.click()}));function o(e){e.preventDefault();e.stopImmediatePropagation();const t=document.querySelector("video");t.paused?t.play():t.pause()}document.addEventListener("keydown",(e=>{if(e.code==="Space"&&e.target.tagName!=="INPUT"&&e.target.tagName!=="TEXTAREA"){o(e)}}),true);document.addEventListener("click",(e=>{const t=e.target;const a=t.tagName==="VIDEO"||[...t.classList].some((e=>e.startsWith("shaka-control-bar--popover-area-")));if(a){o(e)}}),true)})())
Make new bookmark and paste this code in the URL field. Each time a new lecture starts, click a fav and that's it.
Top comments (0)