Stream countdown timer

Set a break, write what your audience should see, and open a clean display window to capture. Pause it, edit it, or add a minute without restarting. A bell rings when it reaches zero.

How it works

A countdown on a stream has a job that a kitchen timer does not: it has to be visible to an audience while staying editable by you, and the editing must not appear on the broadcast. That is why this tool is two windows. The controls stay on this page, and the display opens separately with nothing on it but the clock and your message, which is the window you capture.

The two are kept in step inside your browser. Press pause here and the display pauses; change the message and it changes there. Nothing goes to a server to make that happen, and nothing about your stream is transmitted anywhere.

The clock counts to a fixed moment rather than counting ticks down one at a time. That distinction matters more than it sounds: browsers slow timers right down in tabs you are not looking at, which is exactly where a stream timer sits, and a timer built by subtracting a second at a time falls behind by minutes. Working back from a fixed end time means a slow tab affects how smoothly the number moves and never what it says.

The bell is scheduled the same way. It is handed to the audio system the moment you press start, with an instruction to sound at a specific time, so it rings on cue even if the tab has been asleep in the background for the whole break.

How to use it

  1. 1 Set how long the break is, and write the message your audience should see.
  2. 2 Open the display window, then add it in OBS as a Window Capture.
  3. 3 Press start. Pause, or add a minute, whenever you need to.
  4. 4 The bell rings at zero. Test it first so you know the level is right.

Good to know

  • Nothing is uploaded. The timer, the message and the bell all run in your browser.
  • Capture the display window, not this page, and your controls never reach the stream.
  • Running over is normal. Add a minute rather than restarting the timer.
  • Set the background to a key colour to composite the clock into an existing scene.
  • Your message and colours are remembered on this device for next time.

Browser support

  • Chrome 94 and later · full speed from 110
  • Firefox 93 and later · slower path
  • Safari 16.4 and later · slower path
  • Edge 94 and later · full speed from 110

Best in Chrome or Edge.

These are the oldest versions with everything the tool uses. They are worked out from the tool itself, so they change when it does.

Version numbers checked on 2026-09-04.

Why a stream timer counts to a moment instead of counting down, and why the bell is booked before it is needed

The obvious way to build a countdown is a variable holding the seconds left and an interval that subtracts one from it. That implementation is wrong in a way that only shows up in the exact situation a stream timer lives in. Browsers throttle timers in tabs you are not looking at: a hidden tab is held to roughly one wake per second, and after about five minutes in the background Chrome applies intensive throttling and drops it to one wake per minute. A timer built on subtraction believes it has counted one second every time it wakes, so a five minute break spent behind OBS finishes reporting minutes that never elapsed. The fix is to store the absolute instant the timer reaches zero and subtract the current clock from it, which is what core/timer/countdown does. Every read derives the remainder from that instant, so a throttled tab changes only how smoothly the digits move and never what they say. Pausing is the same idea inverted: the end instant is discarded, the remainder is frozen, and resuming computes a fresh end instant from whatever the clock says now, so a pause of ten seconds and a pause of ten minutes both give back exactly the time that was left.

The bell has the same problem and cannot use the same solution, because something has to be awake at zero to make a noise. A setTimeout scheduled for the end of the break is throttled exactly like the tick, so it fires late or, in a tab that has been backgrounded long enough, not until the tab is looked at again. The way out is that the Web Audio clock is not the page clock. An AudioContext runs on the audio thread with its own timeline, and every node can be told to start at a specific time on that timeline. So the strike is not fired at zero, it is booked at the moment you press start: eight oscillators are created immediately with start times several minutes in the future, and the audio thread sounds them on schedule whatever the page is doing. Any change to the countdown invalidates that booking, so pausing, resetting or adding a minute tears the scheduled nodes down and lays a fresh set, which is why the scheduling lives in one effect rather than being spread across the handlers.

There is a second reason a browser timer ends in silence, and it is a permission rather than a schedule. An AudioContext created outside a user gesture is born suspended, and every note scheduled on a suspended context is discarded without an error. The context is therefore created and resumed inside the click handler that starts the timer, not lazily when the bell is first needed, because by then the gesture is long over. The Test bell button exists for the same reason it is useful for checking levels: pressing it is itself a gesture, so it grants the permission and demonstrates the sound in one action. Choosing which output device the bell plays to uses AudioContext.setSinkId, which only Chromium implements, so the tool declares it as a preferred capability rather than a required one and says plainly what happens elsewhere. Device names are a separate honesty problem: the browser withholds them until the user has granted a media permission at least once, so unlabelled outputs are numbered rather than shown as blanks.

The display being a second window is a product decision with an architectural consequence. The point of the tool is that the streamer keeps editing while the audience keeps watching, and the editing must not be captured, so the controls and the display cannot be the same surface. They are the same route, distinguished by a query parameter, and the shell drops the site header and footer for the display so a window capture gets nothing but the clock. State crosses between them on a BroadcastChannel, and the control page sends its whole state on every change rather than sending events, which makes the display a pure function of what it was last told and removes any question of the two drifting out of order. A display that opens midway through a break announces itself and is sent the current state in reply. Everything arriving over that channel is validated before it is rendered, because it comes from another window rather than from this code: the phase has to be one of four known values, the clock fields have to be finite numbers, and the settings are run through the same normaliser that repairs whatever was loaded from storage.

The message field is where emoji stop being decoration and become a correctness problem. Capping a headline with slice would cut by UTF-16 code unit, and almost nothing interesting is one code unit: a flag is two regional indicator symbols, a skin toned wave is a base emoji plus a modifier, and a family is four people joined by zero width joiners. Cutting inside any of those leaves a stray joiner or half a flag on screen at forty points high. The cap is therefore applied with Intl.Segmenter in grapheme mode, which knows where the user perceived characters actually end, and falls back to iterating code points where Segmenter is missing, since that is still far better than code units. The same normaliser rejects a background that is not a six digit hex colour, clamps volume and text size into range, and coerces anything of the wrong type back to its default, which matters because the settings are read from localStorage and localStorage is editable by anyone sitting at the machine.

Frequently asked questions

How do I add a countdown timer to OBS?

Open the display window with the button above, then in OBS add a Window Capture source and pick that window. Crop it in OBS if you only want the clock. Keep this control page open on another monitor or behind OBS, since it is what drives the display.

Why can I not use a Browser Source instead?

You can, but only for a timer you never touch. A Browser Source runs inside its own browser in a separate process, so it cannot hear the start, pause or extra minute you press on this page. Window Capture is the way to keep control of a running timer.

Can I pause the timer and start it again?

Yes. Pause freezes whatever is left and resume gives you exactly that much time back, however long the pause lasted. You can also add or take off a minute while it runs, without restarting.

Will the timer keep time if I switch to another window?

Yes. It counts to a fixed end time rather than ticking down, so being in the background cannot make it drift. The bell is handed to the audio system in advance for the same reason, and rings on time from a sleeping tab.

Can I put emoji in the message?

Yes, anywhere in either line. Use your system emoji picker: the Windows key with a full stop on Windows, or Control Command Space on a Mac. Flags, skin tones and family emoji all stay whole.

Why is there no sound when the timer ends?

Browsers block audio until you have interacted with the page, so the bell is switched on by the same click that starts the timer. Press Test bell once and you will both grant that permission and hear the level. Check the volume and the output device too if you route stream audio separately.

How do I make the background transparent?

Use a key colour instead. Set the background to green and chroma key it out in OBS, which gets you the same result and works with any capture method. Green is the default choice unless you wear or show something green.

Related tools

Your files are processed on your device and are not uploaded. You can verify this in your browser’s developer tools under the Network tab.