About

"Left Ear Only" Means Two Things — StereoPanner Moves, ChannelSplitter Picks

pan: -1 doesn't drop the right channel, it folds it into the left. Measured

You can make sound come out of only the left earphone from a web page. The catch is there are two ways and they behave differently. StereoPannerNode with pan: -1 moves the whole sound to the left; ChannelSplitter picks the original left channel. After this you'll know which one you need, and why you get bug reports like "the instrument on the right is playing in my left ear."

Earphones don't decide anything

Earphones play L on the left and R on the right. Left and right are decided before that, in one of four places.

Layer Who decides
① Media file Recording and mixing put different sounds in L/R (stereo, ASMR)
② Player / app Changes it during playback. On the web, the Web Audio API
③ OS Balance, mono audio (accessibility)
④ Earphones Play what they get

Developers work at ②. ③ is the user's setting; apps can't change it, and it can override ②. If the user turns on mono audio, the OS mixes left and right at the very end, so the app's "left only" should stop working. I haven't confirmed that on macOS yet.

Two ways to do it on the web

A. StereoPannerNode: move

const ctx = new AudioContext()
const src = ctx.createMediaElementSource(videoEl)
const panner = new StereoPannerNode(ctx, { pan: -1 })   // -1 left, 0 center, +1 right
src.connect(panner).connect(ctx.destination)

B. ChannelSplitter + Merger: pick

const splitter = ctx.createChannelSplitter(2)
const merger = ctx.createChannelMerger(2)
src.connect(splitter)
splitter.connect(merger, 0, 0)   // source L → output L. Source R goes nowhere
merger.connect(ctx.destination)

Measured: the difference is whether the left has 880Hz

I fed a test signal with 440Hz only on L and 880Hz only on R, and measured the output channels with an analyser. Chrome 152 / macOS, 2026-09-19.

Output L Output R
Source 440 880
pan = -1 440 + 880 silent
Left only (Splitter) 440 silent
Swapped 880 440
A. StereoPannerNode (move) new StereoPannerNode(ctx, { pan: -1 }) in L 440 Hz in R 880 Hz out L 440 + 880 Hz out R silent B. ChannelSplitter + Merger (pick) splitter.connect(merger, 0, 0) in L 440 Hz in R 880 Hz out L 440 Hz out R silent dropped

I first assumed pan -1 would just switch the right side off. The spec formula says otherwise. For stereo input with pan ≤ 0, gainL = cos(x·π/2) and outL = inL + inR × gainL, where x = pan + 1. At pan = -1, gainL = 1, so the right channel is added to the left in full.

You want Use
The whole track in the left ear (one earbud in) A. Panner pan: -1
Only the source's left channel (drop the right) B. Splitter
Original language left, interpreter right (two sources) One Panner per source, -1 / +1
Swap left and right B. Splitter wired 0→1, 1→0

When there's no sound at all

Sometimes createMediaElementSource makes everything silent, with no error.

  • Cross-origin media without CORS headers: per the spec, cross-origin audio isn't exposed to scripts and comes through as silence. I haven't reproduced it and captured the console message yet

  • DRM content (EME): protected audio can't be pulled into Web Audio

  • AudioContext created before a click: autoplay policy leaves it suspended. Call ctx.resume() inside the click

  • YouTube iframe: the <video> lives on someone else's page, so you can't reach it

So "Netflix in my left ear only" can't be done from a web page. That's an OS balance setting.

Side note: folding 5.1 into earphones drops the LFE

I put a 5.1 WAV with a different frequency per channel into the same demo. Center and surrounds were mixed into both sides at about -3dB (×0.707), and the LFE bass channel came out at -141dB, effectively discarded. That's the spec's speaker downmix formula. It's why film mixes also put important bass in the main channels.

Mono audio sits in the accessibility menu for a reason too. People who can't hear in one ear lose a whole instrument in a stereo mix. Don't design something that puts important information only on the left.

If you're building something like split-channel playback for a language app or an earphone L/R test, you can ask negabaro. The tables above are numbers we measured in our own demo.

Key Points

1

Earphones just play L/R. Left and right are decided by the file, the app or the OS

2

pan: -1 folds the right channel into the left (outL = inL + inR)

3

For only the source left channel, connect just output 0 of a ChannelSplitter

4

Cross-origin media without CORS, DRM and YouTube iframes are out of Web Audio's reach

Use Cases

Language apps: original on the left, translation on the right A "send everything to one side" button for one earbud, or an L/R check page
← Drums on setInterval Drift — Say "Play at 0.55s", Not "Play Now" Test Audio With AnalyserNode, Not the Screen — Why All 17 Demos Looked Silent in a Background Tab →