Discord's video players, now just one · Morning Gist
Tuesday, November 31 story · 4 min
Discord's video players, now just one
Today's single story: how Discord consolidated duplicated video players in production by writing tests first and shipping behind a large experiment.
Picked from 66 new articles across 34 sources · arrow keys for other days
Today in 30 seconds
30s left
Discord merged its separate video players into one, writing 60 unit tests to pin the old Quest player's behavior before extraction. story 1 ↓
A million-user experiment then kept key metrics within 0.1%, giving a production check on the consolidation without a big-bang rewrite. story 1 ↓
1/1Discord· Nov 2
Discord merges separate video players into one, using tests before refactoring
Discord replaced its untested, duplicated video players with one shared player, extracting it piece by piece behind tests. Per-product player code fell by up to 94%, and a million-user test showed no meaningful metric change.
Why it matters · It is a concrete playbook for consolidating duplicated, untested frontend code in production without a big-bang rewrite, including how the team sequenced the risk.
Know this first
Context from the editor, not the article · 33 sec
1 · Characterization tests
Tests that record what existing code actually does, not what it should do. They let you refactor code with no specification or test suite, because any change in behavior shows up as a failure. The term comes from Michael Feathers' book on legacy code.
2 · How browsers report playback
The HTML video element fires a timeupdate event only a few times per second. A progress bar that moves only on those events advances in visible jumps. Smooth motion requires the interface to work out and draw intermediate positions itself.
3 · Video indexes and keyframes
An MP4 file holds an index of where each frame is stored and which frames are keyframes. A keyframe can be decoded alone; other frames depend on their neighbors. Using the index, a service can locate a keyframe without reading the entire file.
The shared interface design that both the old Quest player and the new DVP were built to target.1 / 4
Remember
Writing tests against old behavior first made the swap measurable: changes could be seen rather than guessed at.
A second real consumer midway through kept the shared component honest, even though it felt like a detour.
Per-product code reduction overstates total savings, because the shared code still has to be owned and maintained.
Discord has moved two products, 1 and 2, onto a single component called the 3, in its web and desktop apps. Its Media Infrastructure team says this cut the player code each product maintains by up to 94%. Attachment videos in chat, the most common use, are live for a small percentage of users, with more to follow over the coming month.
The problem
The team found four separate player implementations. The two largest, for chat attachments and for Quests (videos that reward viewers with a currency called Orbs), shared no code and had no tests. Every feature was built twice and every bug fixed twice. With no tests, nothing recorded what correct behavior was, so a replacement could break things nobody had written down.
The approach: no full rewrite
The team started with Quests. It had the richest feature set but less exposure than the player in conversations, so the hard parts could be solved quietly.
Before building anything new, the team wrote 60 unit tests and 7 tests in 4, a tool that drives a real browser. These pinned down what the old player did. It then designed one interface for both old and new players to implement.
Extraction went piece by piece: progress bar, playhead, controls. Each piece got its own tests and was reworked until both players could use it. Quest-specific customizations were rebuilt on top.
Midway, the Clips team asked to build its new embed on DVP, and reached early access seven weeks before Quests shipped. Author Richard Kuo says this felt like a detour but kept the code truly shared, since a second real product exposed gaps early.
The results
The final gate was an experiment on a million users, split 50/50 between old and new. Quest starts, completions and claims stayed within 0.1%. The post gives no confidence intervals. Test coverage is now over 400 unit tests and 40 Playwright tests.
A shared player also justified polish and new features:
Stable numerals: the CSS setting tabular-nums gives every digit equal width, so the time readout doesn't jiggle.
Smooth playhead: browsers report position about four times a second, so DVP runs its own clock to animate between updates.
The caveats
The 94% is an "up to" figure for code each product owns. Shared code still needs maintaining, and a bug in it can now reach every surface. The widest test, attachments, is only beginning, and the post doesn't mention mobile.
Discord has moved two products, 1 and 2, onto one shared component called the 3, in its web and desktop apps. In a post by staff engineer Richard Kuo, the Media Infrastructure team says this cut the player code each product maintains by up to 94%. A controlled experiment on a million users kept key metrics within 0.1% of the old player. Attachment videos in chat, the most common use, are live for only a small percentage of users, with a wider rollout planned over the coming month.
The player is ordinary enough. The migration method is the part worth studying: the team never did a full rewrite.
Four players and no tests
The team began by finding everywhere Discord plays video and counted four separate implementations. The two largest shared no code and had no tests:
The attachments player runs whenever someone uploads a video to a chat.
The Quest player plays Quests, videos that reward viewers with Discord Orbs, a reward currency. It had accumulated reward markers on the timeline, end cards, progress rings and custom animations.
Every feature had to be built twice and every bug fixed twice. The absence of tests was the harder problem for a migration, because nothing recorded what correct behavior was. Any replacement could break things nobody had written down.
Other surfaces use silent videos as effects and backdrops, such as the Orbs animation. These needed none of the player machinery, so the team gave them a small video effect component and took them out of scope. The post doesn't itemize all four implementations.
Why start with the harder player
The team started with Quests, for two reasons. It had the richest feature set, which the new player had to match from day one. It also had less exposure than the player in conversations, so the team could develop more quietly before touching the widely used surface.
The sequencing cuts against the usual advice to migrate the simplest case first. Solving the hardest feature set first means the shared design has already met the demanding requirements. The risk, in my reading rather than the post's, is an abstraction shaped too closely around one product. A second product arriving midway, described below, helped counter that.
Pin the behavior, then extract
The preparation came before any new player existed:
The team wrote 60 unit tests against the existing Quest player.
It put the player into the 4, a place to exercise a component in isolation, and added 7 5 tests. Playwright is a tool that drives a real browser for end-to-end testing.
It designed a single shared interface that both the old Quest player and the new DVP would implement.
Extraction then went one subcomponent at a time: progress bar, playhead indicators, playback controls. Each got more unit tests for its own behavior and end-to-end tests to confirm the pieces still worked together. Where a subcomponent could serve both players straight away, both used it. Where it couldn't, the team broke it down further until reusable pieces emerged, then rebuilt the Quest-specific customizations on top.
The result is that old and new players shared parts throughout the work, and each extracted part was running under test coverage before the final switch. A failure during extraction pointed to a small area rather than to a whole new player.
A second customer arrives early
Partway through, the Clips team asked to build its new embed on DVP. Clips reached early access in late March, seven weeks before Quests shipped. Its needs went straight into the shared player, including a placeholder thumbnail shown while a clip loads and a download button in the control bar.
Kuo says this felt like a detour but worked as a forcing function, keeping code shared across surfaces. With only one real consumer, a "shared" component can stay shared in name alone.
The final gate
When Quests switched over, Kuo reports that the team caught essentially every issue before it reached a user. The last check was an experiment with a million users, split 50/50 between DVP and the old Quest player. Quest starts, completions and claims all stayed within 0.1% across the two groups. The post doesn't give confidence intervals, or say whether 0.1% is relative or in absolute percentage points.
Details a shared player can afford
The team then turned to polish, on the argument that bespoke players tend to miss these details:
Playhead. Browsers report playback position about four times a second, and not every old player handled that well. DVP runs on its own clock and smooths the animation. The post doesn't describe the implementation.
Sizing. Controls work from a 240px tile up to fullscreen, with no target below 24px.
Timestamps. Text sits on dark pills so it stays readable over bright and dark scenes.
Numerals. In most fonts a "1" is narrower than an "8", so a running time readout jiggles. Monospaced fonts fix that but look like an old VCR, in Kuo's description. DVP sets the CSS property font-variant-numeric: tabular-nums, which gives every digit equal width. In Discord's brand font, gg sans, those figures looked padded, so the readout switches to Roboto, Helvetica Neue or Arial.
Timeline previews from the MP4 index
Hovering over a Clips timeline shows a thumbnail of that moment. The feature uses a 6, a single image holding a grid of small preview frames, plus a 7 file, the format also used for captions, which maps each time range to one tile.
As an illustration of the common convention, not Discord's actual output, a cue might read 00:00:10.000 --> 00:00:15.000 followed by sheet.jpg#xywh=320,0,160,90, meaning that during those five seconds, show the 160×90 region starting at x=320.
The backend work is a new endpoint on 8, Discord's Rust media service. Rather than reading the whole video, it scans the MP4 index and fetches the relevant keyframes directly. On a two-minute video, a full spritesheet and its VTT file took 1.6 seconds to return. That is one data point: the post gives no figures for longer videos, no percentiles, and nothing on caching.
Frontend adoption took two props, scrubPreviewVttUrl and scrubPreviewImageUrl. Previews reached all Clips playback and remain Clips-only for now.
Extending without forking
DVP offers products two extension points. 9, a React pattern, let a product supply its own UI for fixed slots in the player. An extendable button API adds product-specific buttons to the controls. Clips built timeline comments and customized behavior this way without forking. Base features such as the seven playback speeds are available to any surface that enables them.
What remains open
Test coverage grew from zero to more than 400 unit tests and 40 Playwright tests. The 94% figure is "up to" and per product, so it measures code a product team owns. Code moved into DVP still needs maintaining, and a bug there can now reach every surface at once, which is why the tests matter. The post's text gives no absolute line counts.
The largest test is still ahead: attachments have the broadest exposure, and only a small percentage of users have the new player. Mobile apps aren't mentioned.
Timeline previews: a 5, one image holding a grid of preview frames, is paired with a 6 file mapping time ranges to tiles. 7, Discord's Rust media service, reads the MP4 index and fetches keyframes directly instead of the whole video. A two-minute video took 1.6 seconds.