<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>webRTC</title> </head> <body> <div> <h1>webRTC</h1> <p>message</p> </div> <script type="module"> import IndexedDB from './database.js' import MusicList from './music.js' const musicObjectStore = new IndexedDB('musicDatabase', 1, 'musicObjectStore') await musicObjectStore.open() // 音乐列表 const musicList = new MusicList() console.log('musicList:', musicList) //musicList.on('add', async item => { // console.log('musicList add:', item) // const { name, size, type, lastModified, lastModifiedDate, arrayBuffer } = item // const id = Date.now() // // 存储到 IndexDB // await musicObjectStore.add({ id, name, size, type, lastModified, lastModifiedDate, arrayBuffer }) //}) // webRTC 传递音乐(分别传输文件和操作事件能更流畅) const music = async function () { const clients = [] // 客户端列表 // 对端设备 const ul = document.createElement('ul') document.body.appendChild(ul) const protocol = window.location.protocol === 'https:' ? 'wss' : 'ws' const host = window.location.host const ws = new WebSocket(`${protocol}://${host}/webrtc/music`) const pc = new RTCPeerConnection() // 监听 ICE 候选事件 pc.onicecandidate = event => { if (event.candidate) { const id = clients[0].id ws.send(JSON.stringify({ id, candidate: event.candidate })) // 发送 ICE 候选到远程终端 } } // 监听远程流事件 pc.ontrack = function (event) { console.log('pc ontrack:', event) const audio = document.createElement('audio') audio.srcObject = event.streams[0] audio.play() } ws.onmessage = async (event) => { const data = JSON.parse(event.data) if (data.type === 'push') { console.log('收到 type:push 将设备增加', data.id) clients.push({ id: data.id, channel: data.channel }) const li = document.createElement('li') li.innerText = `id:${data.id} channel:${data.channel}` li.id = data.id li.onclick = async () => { console.log('点击设备', data.id) // 清理所有选中状态 clients.forEach(client => { const li = document.getElementById(client.id) if (data.id === client.id) { li.style.backgroundColor = 'red' console.log('设置选中状态', data.id) return } li.style.backgroundColor = 'transparent' console.log('清理选中状态', client.id) }) } ul.appendChild(li) return } if (data.type === 'pull') { console.log('收到 type:pull 将设备删除', data.id) const index = clients.findIndex(client => client.id === data.id) if (index !== -1) { clients.splice(index, 1) const li = document.getElementById(data.id) li.remove() } return } if (data.type === 'error') { console.log('收到 type:error 没什么可操作的', data.id) return } if (data.offer) { const id = clients[0].id console.log('收到 offer 并将其设置为远程描述', data.offer) await pc.setRemoteDescription(new RTCSessionDescription(data.offer)) // 设置远程描述为 offer await pc.setLocalDescription(await pc.createAnswer()) // 设置本地描述为 answer ws.send(JSON.stringify({ id, answer: pc.localDescription })) // 发送给远程终端 answer return } if (data.answer) { console.log('收到 answer 并将其设置为远程描述', data.answer) await pc.setRemoteDescription(new RTCSessionDescription(data.answer)) return } if (data.candidate) { console.log('收到 candidate 并将其添加到远程端', data.candidate) await pc.addIceCandidate(new RTCIceCandidate(data.candidate)) return } } // 从数据库读取音乐文件 musicObjectStore.getAll().then(data => { console.log('从数据库读取音乐文件', data) const ol = document.createElement('ol') data.forEach(item => { const li = document.createElement('li') li.innerText = `${item.name} - ${item.size} - ${item.type} - ${item.id}` const start = document.createElement('button') start.innerText = '播放' start.onclick = async () => { console.log('播放音乐', item) // 传输音乐文件 const arrayBuffer = item.arrayBuffer const audioContext = new AudioContext() audioContext.decodeAudioData(arrayBuffer, async audioBuffer => { // 将音乐流添加到 RTCPeerConnection const mediaStreamDestination = audioContext.createMediaStreamDestination() mediaStreamDestination.stream.getAudioTracks().forEach(function (track) { pc.addTrack(track, mediaStreamDestination.stream) }) // 播放音乐(远程) const audioSource = audioContext.createBufferSource() audioSource.buffer = audioBuffer audioSource.connect(mediaStreamDestination) audioSource.start() // 播放音乐(本地) const audioPlayer = new Audio() audioPlayer.srcObject = mediaStreamDestination.stream audioPlayer.play() // 创建SDP offer并将其设置为本地描述, 发送给远程端 const id = clients[0].id await pc.setLocalDescription(await pc.createOffer()) // 设置本地描述为 offer ws.send(JSON.stringify({ id, offer: pc.localDescription })) // 发送给远程终端 offer }) } li.appendChild(start) const remove = document.createElement('button') remove.innerText = '移除' remove.onclick = async () => { await musicObjectStore.delete(item.id) li.remove() } li.appendChild(remove) ol.appendChild(li) }) document.body.appendChild(ol) }) // 从文件系统中选择音乐文件 const button = document.createElement('button') button.innerText = '选择音乐' button.addEventListener('click', async () => { const id = Date.now() const fileHandle = await window.showOpenFilePicker() const file = await fileHandle[0].getFile() const { name, size, type, lastModified, lastModifiedDate } = file const reader = new FileReader() reader.onload = async () => { const arrayBuffer = reader.result // 存储到 IndexDB musicObjectStore.add({ id, name, size, type, lastModified, lastModifiedDate, arrayBuffer }) // 更新列表 //// 传输音乐文件 //const audioContext = new AudioContext() //audioContext.decodeAudioData(arrayBuffer, async audioBuffer => { // // 将音乐流添加到 RTCPeerConnection // const mediaStreamDestination = audioContext.createMediaStreamDestination() // mediaStreamDestination.stream.getAudioTracks().forEach(function (track) { // pc.addTrack(track, mediaStreamDestination.stream) // }) // // 播放音乐 // const audioSource = audioContext.createBufferSource() // audioSource.buffer = audioBuffer // audioSource.connect(mediaStreamDestination) // audioSource.start() // // 创建本地音频播放器 // const audioPlayer = new Audio() // audioPlayer.srcObject = mediaStreamDestination.stream // audioPlayer.play() // // 创建SDP offer并将其设置为本地描述, 发送给远程端 // console.log('创建SDP offer并将其设置为本地描述, 发送给远程端') // console.log('clients:', clients) // const id = clients[0].id // await pc.setLocalDescription(await pc.createOffer()) // 设置本地描述为 offer // ws.send(JSON.stringify({ id, offer: pc.localDescription })) // 发送给远程终端 offer //}) } reader.readAsArrayBuffer(file) }) document.body.appendChild(button) } music() </script> <!--script type="module"> // 创建 RTCPeerConnection const pc = new RTCPeerConnection() // webSocket 连接服务器 const protocol = window.location.protocol === 'https:' ? 'wss' : 'ws' const host = window.location.host const ws = new WebSocket(`${protocol}://${host}/webrtc/default`) ws.onopen = function () { console.log('video ws open') } ws.onmessage = function (event) { const data = JSON.parse(event.data) console.log('ws message:', data) if (data.offer) { console.log('收到 offer 并将其设置为远程描述') pc.setRemoteDescription(new RTCSessionDescription(data.offer)) // 创建SDP answer并将其设置为本地描述, 发送给远程端 pc.createAnswer().then(function (answer) { pc.setLocalDescription(answer); ws.send(JSON.stringify({ answer })) }) return } if (data.answer) { console.log('收到 answer 并将其设置为远程描述') pc.setRemoteDescription(new RTCSessionDescription(data.answer)) return } if (data.candidate) { console.log('收到 candidate 并将其添加到远程端') pc.addIceCandidate(new RTCIceCandidate(data.candidate)) } } ws.onclose = function () { console.log('ws close') } setTimeout(() => { // 获取本地视频流 navigator.mediaDevices.getUserMedia({ audio: false, video: true }).then(stream => { // 创建本地视频元素 const localVideo = document.createElement('video'); localVideo.srcObject = stream; localVideo.autoplay = true; localVideo.muted = true; document.body.appendChild(localVideo); // 添加本地视频流到 RTCPeerConnection stream.getTracks().forEach(function (track) { pc.addTrack(track, stream); }); // 监听 ICE candidate 事件 pc.onicecandidate = function (event) { if (event.candidate) { // 发送 ICE candidate 到远程端 ws.send(JSON.stringify({ candidate: event.candidate })) } }; // 监听远程视频流事件 pc.ontrack = function (event) { // 创建远程视频元素 var remoteVideo = document.createElement('video'); remoteVideo.srcObject = event.streams[0]; remoteVideo.autoplay = true; document.body.appendChild(remoteVideo); } // 创建SDP offer并将其设置为本地描述, 发送给远程端 pc.createOffer().then(function (offer) { pc.setLocalDescription(offer); ws.send(JSON.stringify({ offer })) }) }).catch(error => { console.log(error) }) }, 1000) </script--> </body> </html>