When you finish a swim, Apple Watch can play a short, buzzy tone to push water out of its speaker. Phones don't have a button like that. Most "water eject" apps I found play a similar tone, and some put it behind a weekly subscription.
I wanted to know if a plain web page could do the same job on any phone, with nothing to install. It mostly can. Here's the physics, the code, and three iOS Safari problems I hit along the way.
Why a low tone, and why around 165 Hz
A speaker grille is a fine mesh. A droplet in the mesh is held there by surface tension, so it often won't drain out on its own.
The speaker sits right behind that mesh. At low pitches the speaker cone moves further with each cycle, so it pushes more air against the droplet. At high pitches it barely moves.
There's a limit, though. Small phone speakers can't play deep bass well. Their output drops off sharply below roughly 100–200 Hz, depending on the phone. So a tone in the 145–165 Hz range is a practical middle ground: low enough to move the cone a lot, high enough that the phone can still play it loudly. Treat that as a reasonable starting point, not a measured optimum for every phone.
Pulses instead of a steady tone
With a steady tone, the droplet tends to settle into a new shape and stay put. Short bursts with gaps keep shaking it. I use about 0.8 s on and 0.2 s off.
Here's the core of it in plain JavaScript:
function playWaterPulse(seconds = 10, freq = 165) {
const AudioCtx = window.AudioContext || window.webkitAudioContext;
const ctx = new AudioCtx();
const osc = ctx.createOscillator();
const gain = ctx.createGain();
osc.type = "sine";
osc.frequency.value = freq;
gain.gain.value = 0;
osc.connect(gain).connect(ctx.destination);
const t0 = ctx.currentTime;
for (let t = t0; t < t0 + seconds; t += 1) {
// Short ramps instead of hard on/off, so the speaker doesn't click
gain.gain.setTargetAtTime(0.8, t, 0.015);
gain.gain.setTargetAtTime(0, t + 0.8, 0.015);
}
osc.start(t0);
osc.stop(t0 + seconds);
return () => { osc.stop(); ctx.close(); };
}
Call it from a button's click handler (browsers block audio that starts without a tap). The returned function stops it early.
Gotcha 1: the iPhone silent switch mutes web audio
On iOS, web audio is treated like a game sound. If the phone is set to silent, the page plays nothing. Someone with a wet phone will just assume the page is broken.
Safari 17 added the Audio Session API. Setting the type to "playback" before starting tells iOS this is media, so it plays even in silent mode:
if ("audioSession" in navigator) {
navigator.audioSession.type = "playback";
}
On older iOS versions this doesn't exist, so I still show a note telling people to check the silent switch.
Gotcha 2: the first tap sometimes plays nothing
On some iPhones the first tap after loading created the audio context but no sound came out. What fixed it for me was playing a one-sample silent buffer right inside the tap handler, before anything async runs:
function unlock(ctx) {
const buf = ctx.createBuffer(1, 1, 22050);
const src = ctx.createBufferSource();
src.buffer = buf;
src.connect(ctx.destination);
src.start(0);
if (ctx.state === "suspended") ctx.resume();
}
The important part is the timing. If you await anything first, Safari may no longer count it as a user gesture.
Gotcha 3: vibration only works on Android
Shaking the phone along with the sound seemed like a nice extra. navigator.vibrate() works in Chrome on Android:
if ("vibrate" in navigator) {
navigator.vibrate([800, 200, 800, 200, 800, 200]);
}
Safari on iPhone doesn't support the Vibration API, and the call quietly does nothing. I label the toggle "Android only" so iPhone users don't think something is wrong.
What this can't do
This part matters more than the code:
- It can only help with water sitting in the speaker grille. It does nothing for water inside the phone.
- Salt water, pool water and sugary drinks can leave residue or cause corrosion. Rinse guidance and a repair shop are better options there.
- Don't use a hair dryer, compressed air or a cotton swab in the grille. Heat and air pressure can damage the speaker or the seals.
- If sound is still muffled after the phone has dried for a day, the speaker may be damaged.
Apple's own advice for a wet iPhone is to tap it gently, speaker side down, against your hand and let it dry. A tone is an extra step on top of that, not a replacement.
Try it
I put this into a free water eject tool that runs in the browser and never uses the microphone. Hold the phone speaker-down over a cloth, set the volume to around 75% (lower if it distorts), and run it two or three times.
I'm curious whether others have found better ways to handle Web Audio on iOS, especially for people on iOS 16 and earlier. Let me know in the comments.
— Nayeem, software developer
Top comments (1)
One more from the same family, with video instead of sound. A muted autoplay video on my site sat paused in Safari, play button showing, until Safari rechecked that it was on screen, which often only happened on a scroll or a hover.
Starting it from script fixed it (muted, so no tap needed), with the muted attribute also in the HTML and the first frame covering it until it really plays. With Low Power Mode on, Safari won't autoplay at all, so the still frame stays and the first tap starts it.