DEV Community

Andrew Elans
Andrew Elans

Posted on • Edited on

Make media buttons F7,F8,F9 to work with Udemy in browser on MacOS

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.

  1. Open DevTools (or Safari's Web Inspector)
  2. In the Sources panel, search for a file starting with course-taking-udlite-app...js
  3. Right-click the file and select Override content (Chrome) or add it to Local Overrides
  4. Find the function syncPlayStateToPlayer and add return as 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) {}
}
Enter fullscreen mode Exit fullscreen mode

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:

  1. Walks the React fiber tree from the <video> element to find the component instance
  2. Replaces syncPlayStateToPlayer with a no-op
  3. 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);
})();
Enter fullscreen mode Exit fullscreen mode

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-purpose attributes - 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)