基于ICE-SERVER的P2P连接:无服务器实现双HTML页面音频传输
无服务器实现跨页面音频传输与播放
要实现同浏览器下两个标签页之间的音频收发,无需额外服务器,直接用浏览器原生的Broadcast Channel API就能搞定——它专门用于同源页面(本地file://协议的HTML文件属于同源)之间的通信。下面是完整的实现代码:
1. 音频发送页面(sender.html)
这个页面支持选择本地音频文件或录制音频,然后通过广播通道发送给接收页面。
<!DOCTYPE html> <html> <head> <title>音频发送端</title> </head> <body> <h3>选择或录制音频发送</h3> <input type="file" accept="audio/*" id="audioFile"> <button id="startRecord">开始录制</button> <button id="stopRecord">停止录制并发送</button> <script> // 创建广播通道,通道名需与接收端一致 const broadcastChannel = new BroadcastChannel('audio-channel'); let mediaRecorder; let audioChunks = []; // 选择本地音频文件发送 document.getElementById('audioFile').addEventListener('change', async (e) => { const file = e.target.files[0]; if (!file) return; // 将文件转为ArrayBuffer发送 const arrayBuffer = await file.arrayBuffer(); broadcastChannel.postMessage({ type: 'audio', data: arrayBuffer }); alert('音频已发送'); }); // 启动音频录制 document.getElementById('startRecord').addEventListener('click', async () => { const stream = await navigator.mediaDevices.getUserMedia({ audio: true }); mediaRecorder = new MediaRecorder(stream); mediaRecorder.ondataavailable = (e) => { audioChunks.push(e.data); }; mediaRecorder.start(); alert('开始录制'); }); // 停止录制并发送音频 document.getElementById('stopRecord').addEventListener('click', () => { if (!mediaRecorder) return; mediaRecorder.stop(); mediaRecorder.onstop = () => { const audioBlob = new Blob(audioChunks, { type: 'audio/webm' }); audioBlob.arrayBuffer().then(arrayBuffer => { broadcastChannel.postMessage({ type: 'audio', data: arrayBuffer }); alert('录制的音频已发送'); audioChunks = []; }); }; }); </script> </body> </html>
2. 音频接收页面(receiver.html)
这个页面监听广播通道,收到音频数据后解码并自动播放。
<!DOCTYPE html> <html> <head> <title>音频接收端</title> </head> <body> <h3>等待接收音频...</h3> <audio id="audioPlayer" controls></audio> <script> // 创建与发送端同名的广播通道 const broadcastChannel = new BroadcastChannel('audio-channel'); const audioContext = new (window.AudioContext || window.webkitAudioContext)(); const audioPlayer = document.getElementById('audioPlayer'); broadcastChannel.onmessage = async (e) => { if (e.data.type !== 'audio') return; // 解码音频数据 const audioBuffer = await audioContext.decodeAudioData(e.data.data); // 将AudioBuffer转为WAV格式Blob,适配audio元素播放 const wavBlob = audioBufferToWav(audioBuffer); const audioUrl = URL.createObjectURL(wavBlob); audioPlayer.src = audioUrl; audioPlayer.play().catch(err => console.error('播放失败:', err)); }; // 辅助函数:将AudioBuffer转为WAV格式Blob function audioBufferToWav(buffer) { const length = buffer.length * buffer.numberOfChannels * 2 + 44; const arrayBuffer = new ArrayBuffer(length); const view = new DataView(arrayBuffer); const channels = []; let offset = 0; let pos = 0; // 写入WAV文件头 const setUint16 = (data) => { view.setUint16(pos, data, true); pos += 2; }; const setUint32 = (data) => { view.setUint32(pos, data, true); pos += 4; }; setUint32(0x46464952); // "RIFF" setUint32(length - 8); // 文件总长度-8 setUint32(0x45564157); // "WAVE" setUint32(0x20746d66); // "fmt " setUint32(16); // PCM格式块大小 setUint16(1); // PCM编码类型 setUint16(buffer.numberOfChannels); // 声道数 setUint32(buffer.sampleRate); // 采样率 setUint32(buffer.sampleRate * 2 * buffer.numberOfChannels); // 字节率 setUint16(buffer.numberOfChannels * 2); // 块对齐 setUint16(16); // 位深度 setUint32(0x61746164); // "data" setUint32(length - pos - 4); // 音频数据长度 // 写入音频采样数据 for (let i = 0; i < buffer.numberOfChannels; i++) { channels.push(buffer.getChannelData(i)); } while (pos < length) { for (let i = 0; i < buffer.numberOfChannels; i++) { let sample = Math.max(-1, Math.min(1, channels[i][offset])); sample = sample < 0 ? sample * 0x8000 : sample * 0x7FFF; view.setInt16(pos, sample, true); pos += 2; } offset++; } return new Blob([arrayBuffer], { type: 'audio/wav' }); } </script> </body> </html>
关键注意事项
- 同源限制:两个页面必须在同一浏览器打开,且都是本地
file://协议(或同一域名),否则Broadcast Channel无法通信。 - 浏览器权限:
- 录制功能需要麦克风权限,首次打开会弹窗请求授权。
- 部分浏览器(如Chrome)本地打开文件时,需添加启动参数
--allow-file-access-from-files,否则可能无法读取本地文件或调用麦克风。
- 兼容性:Broadcast Channel API支持所有现代浏览器,无需额外依赖。
内容的提问来源于stack exchange,提问作者Michael Ranucci
相关产品推荐
相关产品推荐

