Web TippsUse custom web fonts in Google Sheets charts(08.09.2026 um 17:05 Uhr)
Web TippsIntroducing the new 1Password App for Google Chat(08.09.2026 um 18:02 Uhr)
Web TippsUse custom web fonts in Google Sheets charts(08.09.2026 um 17:05 Uhr)
Web TippsIntroducing the new 1Password App for Google Chat(08.09.2026 um 18:02 Uhr)

🔧 Programmierung 🕛 vor 2 Monaten 5 Min Lesezeit
0

I thought building a video speed controller would take a weekend. The analytics nearly broke me.

↗ Quelle (dev.to)
🗣️ Stimme:
📑 Inhaltsübersicht

It was 2 AM on a Tuesday, and I was staring at my CourseSpeed dashboard looking at a graph that claimed I had just finished a 14-hour AWS certification course in 47 minutes.



I hadn't. I was just testing the 16x speed toggle. But my analytics engine thought I was a god.



When I started building CourseSpeed—a browser extension to inject custom playback speeds and track learning analytics across Udemy, Coursera, LinkedIn Learning, and Skillshare—I thought the hard part would be the UI. It wasn't. Injecting a floating control panel and setting document.querySelector('video').playbackRate = 2.5 takes about ten lines of JavaScript.



The actual nightmare was the learning analytics. Specifically, accurately tracking effective watch time versus wall-clock time across wildly different Single Page Applications (SPAs).






The naive approach that burned me



My first pass at the analytics tracker was straight out of MDN. I listened to the standard HTML5 video events.




CODE
// The approach that worked perfectly in my head
const video = document.querySelector('video');

video.addEventListener('ratechange', (e) => {
sendAnalytics({ type: 'speed_change', rate: e.target.playbackRate });
});

video.addEventListener('timeupdate', () => {
logWatchTime(video.currentTime, video.playbackRate);
});






This worked flawlessly on Udemy. Then I opened LinkedIn Learning. The dashboard flatlined. Then I tried Coursera. The time spent was wildly inaccurate, drifting by minutes over an hour.



I spent three days debugging this, tearing my hair out over console logs.



Here is what I missed: modern learning platforms don't just drop a raw <video> tag on the page and leave it alone. They wrap it in custom players, throttle events to save CPU, and dynamically destroy and recreate the DOM node when you skip chapters or when the SPA router transitions.



My event listeners were getting orphaned. Or worse, they were firing with stale data because the platform's custom wrapper was dispatching synthetic timeupdate events that didn't reflect the underlying HTML5 video element's true state when the playback rate was manipulated.






Stop trusting the DOM events



I had to rip out the event listeners. If I wanted reliable analytics, I needed to observe the DOM for element swaps and poll the video state myself.



I built a VideoTracker class. Instead of waiting for the platform to tell me the video changed, I used a MutationObserver to watch the specific player wrapper containers.




CODE
const observer = new MutationObserver((mutations) => {
for (const mutation of mutations) {
if (mutation.type === 'childList') {
const newVideo = mutation.target.querySelector('video');
if (newVideo && newVideo !== this.currentVideo) {
this.attachToVideo(newVideo);
}
}
}
});

// We observe the highest stable container, not the whole document body
const playerRoot = document.querySelector('[data-player-root]') || document.body;
observer.observe(playerRoot, { childList: true, subtree: true });






This solved the orphaned listener problem. When Coursera swapped the video node during a module transition, my tracker caught it, detached the old state, and latched onto the new <video> element within milliseconds.



But then I hit the background tab wall.






The background tab throttling trap



Here’s the real kicker. When you switch tabs to read documentation while a course plays in the background, Chrome (specifically around version 120 and later) aggressively throttles setInterval and setTimeout in background tabs to save battery and CPU.



My analytics pings were getting delayed. The browser would batch them up and send massive, inaccurate chunks of "watch time" when the tab regained focus. If a user left a 2x speed video running in the background for an hour, my extension might only log 15 minutes of actual progress because the setInterval ticking my internal clock was being put to sleep.



I tried switching the internal clock to requestAnimationFrame. That paused entirely in background tabs.



The fix was moving the ticking clock to a Web Worker. Web Workers run on a separate thread and aren't subjected to the same aggressive throttling as the main thread's timers.



The architecture ended up looking like this:




  1. The Web Worker ticks every 1000ms and posts a message to the main thread.

  2. The main thread receives the tick, reads video.currentTime and video.playbackRate directly from the DOM.

  3. The main thread calculates the delta and updates the local analytics state.




CODE
// worker.js
let intervalId;

self.onmessage = function(e) {
if (e.data === 'start') {
intervalId = setInterval(() => {
self.postMessage('tick');
}, 1000);
} else if (e.data === 'stop') {
clearInterval(intervalId);
}
};






It feels like overkill for a browser extension, but it completely eliminated the background-tab drift. The analytics dashboard finally matched reality. A 14-hour course watched at 2x speed now correctly logged as 7 hours of effective learning time, regardless of whether the user was actively staring at the tab or reading MDN docs in another window.



Building tools that live on top of other people's SPAs means you are constantly at the mercy of their DOM updates and browser performance optimizations. You can't just assume the standard APIs will behave the way the spec says they should in a vacuum. You have to build defensively, assume the DOM will mutate out from under you, and never trust a setInterval in a background tab.

Vollständiger Original-Bericht
Ausführliche Details, Code-Beispiele & Hersteller-Stellungnahme auf dev.to.
↗ Original-Artikel auf dev.to lesen
Wie bewertest du diesen Beitrag?
1 Klick Feedback
Teilen mit Netzwerk & Team:

Community-Analysen & Experten-Meinungen 0

Verfasse deine eigene Analyse, teile Workarounds oder diskutiere diesen Vorfall im Blog.
Noch keine Community-Analyse verfasst. Markiere einen Textabschnitt oder klicke oben auf Eigene Analyse verfassen“!
Community Pulse: Relevanz-Einschätzung
1 Klick Experten-Votum
🔴 Akute Relevanz 0%
🟡 In Evaluierung 0%
🟢 Keine Auswirkung 0%
Spannende Innovation 0%
Verwandte Story-Cluster & Quellen (Vektor-KI)
Port 8095 Engine
3 Quellen
Use custom web fonts in Google Sheets charts
2 Quellen
Introducing the new 1Password App for Google Chat
1 Quelle
Context-aware access controls are available for Gemini Enterprise in the Admin console
Ähnliche Beiträge
🔍 Verwandte News

Auch interessante Nachrichten I thought building a video speed controller would take a weekend. The analytics nearly broke me.

Thematisch verwandte Begriffe: thought, building, video, speed · 6 Treffer

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...