如何添加音频
让 <audio> 元素指向一个声音文件,再加上 controls:
<audio src="song.mp3" controls></audio>
controls 会显示浏览器的播放器:播放和暂停按钮、进度条、时间和音量控制。没有 controls 时,这个元素完全不占空间。预览无法加载你电脑上的文件,所以这个示例用脚本在浏览器里生成一段很短的 WAV 文件,并把它用作 src。点击播放:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Audio</title>
</head>
<body style="font-family: system-ui, sans-serif;">
<audio id="player" controls></audio>
<p>Four notes, 1.8 seconds.</p>
<script>
// Builds a tiny WAV file in the browser (stand-in for song.mp3)
function makeWav(notes) {
const rate = 8000, samples = [];
for (const [freq, secs] of notes) {
const n = secs * rate;
for (let i = 0; i < n; i++) {
samples.push(Math.sin(2 * Math.PI * freq * i / rate) * 0.4 * Math.min(1, (n - i) / 400));
}
}
const view = new DataView(new ArrayBuffer(44 + samples.length * 2));
const text = (at, s) => [...s].forEach((c, i) => view.setUint8(at + i, c.charCodeAt(0)));
text(0, 'RIFF'); view.setUint32(4, 36 + samples.length * 2, true); text(8, 'WAVEfmt ');
view.setUint32(16, 16, true); view.setUint16(20, 1, true); view.setUint16(22, 1, true);
view.setUint32(24, rate, true); view.setUint32(28, rate * 2, true);
view.setUint16(32, 2, true); view.setUint16(34, 16, true);
text(36, 'data'); view.setUint32(40, samples.length * 2, true);
samples.forEach((s, i) => view.setInt16(44 + i * 2, s * 32767, true));
return URL.createObjectURL(new Blob([view], { type: 'audio/wav' }));
}
document.getElementById('player').src = makeWav([[523, 0.4], [659, 0.4], [784, 0.4], [1047, 0.6]]);
</script>
</body>
</html>
这个播放器在 Chrome、Firefox 和 Safari 中看起来各不相同,因为每个浏览器都会自己绘制。删掉 controls,播放器就会消失,但音频依然存在,可以由 JavaScript 来播放。
audio 的属性
| 属性 | 作用 |
|---|
controls | 显示浏览器的播放器 |
autoplay | 尝试在加载时开始播放(带声音时会被拦截,见下文) |
loop | 文件结束后从头开始 |
muted | 开始时静音 |
preload | none(播放前什么都不下载)、metadata(只获取时长)或 auto(由浏览器决定) |
src | 要播放的文件;也可以用 <source> 子元素提供多种格式 |
对于有很多播放器的页面,比如播客单集列表,使用 preload="none" 或 "metadata",这样页面就不会一开始就下载所有文件。
格式与后备内容
用 <source> 列出多个文件,浏览器会播放它支持的第一个。元素里面的文字只会在不支持 <audio> 的浏览器中显示:
<audio controls preload="metadata">
<source src="episode.opus" type="audio/ogg; codecs=opus">
<source src="episode.mp3" type="audio/mpeg">
<p>Your browser cannot play audio. <a href="episode.mp3">Download the episode</a>.</p>
</audio>
| 格式 | MIME 类型 | 说明 |
|---|
| MP3 | audio/mpeg | 所有现行浏览器都能播放;只选一种时最稳妥 |
AAC(.m4a) | audio/mp4 | 支持广泛,文件小而音质好 |
Opus 或 Vorbis(.ogg、.webm) | audio/ogg、audio/webm | 文件小;要把 MP3 列为后备,因为 Safari 的支持情况因版本而异 |
| WAV | audio/wav | 未压缩,文件大;适合简短的音效 |
为什么自动播放会失败
浏览器不允许页面自己开始发出声音。使用 autoplay,或者在用户点击或轻触之前调用 play(),都会被拒绝。下面这个示例尝试在加载时播放,并打印出结果,然后提供一个按钮:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Autoplay</title>
<style>
body { font-family: system-ui, sans-serif; margin: 12px; }
button { font: inherit; padding: 6px 14px; }
</style>
</head>
<body>
<p id="result">Trying to play...</p>
<button id="play">Play with a click</button>
<script>
// Builds a short beep as a WAV file (stand-in for a real sound file)
function beep(freq, secs) {
const rate = 8000, n = secs * rate;
const view = new DataView(new ArrayBuffer(44 + n * 2));
const text = (at, s) => [...s].forEach((c, i) => view.setUint8(at + i, c.charCodeAt(0)));
text(0, 'RIFF'); view.setUint32(4, 36 + n * 2, true); text(8, 'WAVEfmt ');
view.setUint32(16, 16, true); view.setUint16(20, 1, true); view.setUint16(22, 1, true);
view.setUint32(24, rate, true); view.setUint32(28, rate * 2, true);
view.setUint16(32, 2, true); view.setUint16(34, 16, true);
text(36, 'data'); view.setUint32(40, n * 2, true);
for (let i = 0; i < n; i++) view.setInt16(44 + i * 2, Math.sin(2 * Math.PI * freq * i / rate) * 12000, true);
return URL.createObjectURL(new Blob([view], { type: 'audio/wav' }));
}
const sound = new Audio(beep(440, 0.5));
const result = document.getElementById('result');
sound.play()
.then(() => { result.textContent = 'Autoplay worked this time.'; })
.catch((err) => { result.textContent = 'Autoplay was blocked: ' + err.name; });
document.getElementById('play').onclick = () => {
sound.currentTime = 0;
sound.play()
.then(() => { result.textContent = 'Playing, because you clicked.'; })
.catch((err) => { result.textContent = 'Still blocked: ' + err.name; });
};
</script>
</body>
</html>
你通常会看到 NotAllowedError,这是浏览器拦截播放时给出的错误。按钮可以正常工作,因为一次点击就相当于用户许可。每个浏览器还有各自的额外规则(例如 Chrome 会记住你经常播放媒体的网站),所以永远不要依赖自动播放:由用户操作触发声音,并始终处理 play() 返回的 promise。
new Audio(url) 会在 JavaScript 中创建一个 audio 元素,但不会把它添加到页面上,用来播放音效非常方便。
自己做控件
省略 controls 来隐藏自带的播放器,然后通过 JavaScript API 控制这个元素:play()、pause()、currentTime、duration、volume,以及 timeupdate 和 ended 等事件:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Custom player</title>
<style>
body { font-family: system-ui, sans-serif; margin: 12px; }
.player { display: flex; align-items: center; gap: 10px; max-width: 360px; padding: 10px; border: 2px solid #e5e7eb; border-radius: 10px; }
.player button { font: inherit; width: 72px; padding: 6px 0; border: 0; border-radius: 6px; background: #2563eb; color: #fff; cursor: pointer; }
progress { flex: 1; }
#time { font-variant-numeric: tabular-nums; font-size: 14px; color: #374151; }
</style>
</head>
<body>
<div class="player">
<button id="toggle" aria-label="Play">Play</button>
<progress id="bar" value="0" max="1"></progress>
<span id="time">0.0 s</span>
</div>
<audio id="audio"></audio>
<script>
function makeWav(notes) {
const rate = 8000, samples = [];
for (const [freq, secs] of notes) {
const n = secs * rate;
for (let i = 0; i < n; i++) {
samples.push(Math.sin(2 * Math.PI * freq * i / rate) * 0.4 * Math.min(1, (n - i) / 400));
}
}
const view = new DataView(new ArrayBuffer(44 + samples.length * 2));
const text = (at, s) => [...s].forEach((c, i) => view.setUint8(at + i, c.charCodeAt(0)));
text(0, 'RIFF'); view.setUint32(4, 36 + samples.length * 2, true); text(8, 'WAVEfmt ');
view.setUint32(16, 16, true); view.setUint16(20, 1, true); view.setUint16(22, 1, true);
view.setUint32(24, rate, true); view.setUint32(28, rate * 2, true);
view.setUint16(32, 2, true); view.setUint16(34, 16, true);
text(36, 'data'); view.setUint32(40, samples.length * 2, true);
samples.forEach((s, i) => view.setInt16(44 + i * 2, s * 32767, true));
return URL.createObjectURL(new Blob([view], { type: 'audio/wav' }));
}
const audio = document.getElementById('audio');
const toggle = document.getElementById('toggle');
audio.src = makeWav([[392, 0.5], [440, 0.5], [494, 0.5], [523, 1]]);
toggle.onclick = () => {
if (audio.paused) audio.play().catch((err) => { toggle.textContent = err.name; });
else audio.pause();
};
audio.addEventListener('play', () => { toggle.textContent = 'Pause'; toggle.setAttribute('aria-label', 'Pause'); });
audio.addEventListener('pause', () => { toggle.textContent = 'Play'; toggle.setAttribute('aria-label', 'Play'); });
audio.addEventListener('timeupdate', () => {
document.getElementById('bar').value = audio.currentTime / audio.duration || 0;
document.getElementById('time').textContent = audio.currentTime.toFixed(1) + ' s';
});
</script>
</body>
</html>
这个按钮是真正的 <button>,所以可以用键盘操作,它的文字会在 Play 和 Pause 之间切换。文件播放到末尾时,浏览器会暂停元素并触发 ended,所以按钮会自动变回 Play。如果你自己做控件,原本自带控件免费提供的功能也要由你来负责:键盘支持、标签和音量控制。
无障碍
- 永远不要自动开始播放声音。 它会打断屏幕阅读器,还会吓人一跳。在点击时再开始播放音频。
- 为播客和语音音频提供文字稿。 在播放器下方放一个文字版本的链接就够了,搜索引擎也能读取它。
- 保留
controls,或者用真正的按钮来做控件,这样就能用键盘暂停音频。
带声音的视频、字幕和封面图请参阅 video 标签页面;<audio> 和它共用大部分属性,以及全部的 JavaScript API。
常见错误
- 忘了写
controls。 没有它元素就是不可见的,看起来像是什么都没发生。
- 指望
autoplay 能生效。 在点击时开始播放,并处理 play() 返回的 promise。
- 文件路径或类型写错。 直接打开文件的 URL 检查一下,并让
type 与真实格式一致(MP3 用 audio/mpeg)。
- 长列表里加载所有文件。 使用
preload="none" 或 "metadata"。
- 无法停止的背景音乐。 一定要给用户一个看得见的暂停方式。