对端通信

This commit is contained in:
2023-09-28 23:49:26 +08:00
parent a743a066be
commit 4f7741b341
5 changed files with 177 additions and 73 deletions

View File

@ -31,10 +31,10 @@ app.ws('/webrtc/:channel', (ws, req) => {
// 设备发送信令时转发给指定在线设备
ws.on('message', message => {
console.log(ws.id, '设备发送信令:', ws.channel, wsInstance.getWss().clients.size)
const { id } = JSON.parse(message)
const data = JSON.parse(message)
wsInstance.getWss().clients.forEach(client => {
if (client !== ws && client.readyState === 1 && client.channel === ws.channel && client.id === id) {
client.send(message)
if (client !== ws && client.readyState === 1 && client.channel === ws.channel && client.id === data.id) {
client.send(JSON.stringify({ ...data, id: ws.id }))
}
})
})
@ -43,7 +43,7 @@ app.ws('/webrtc/:channel', (ws, req) => {
wsInstance.getWss().clients.forEach(client => {
if (client !== ws && client.readyState === 1 && client.channel === ws.channel) {
client.send(JSON.stringify({ type: 'push', id: ws.id, channel: ws.channel }))
ws.send(JSON.stringify({ type: 'push', id: client.id, channel: client.channel }))
ws.send(JSON.stringify({ type: 'list', id: client.id, channel: client.channel }))
}
})
})

110
public/client.js Normal file
View File

@ -0,0 +1,110 @@
import { List, ListItem } from './weigets.js'
export default class ClientList {
constructor() {
const protocol = window.location.protocol === 'https:' ? 'wss' : 'ws'
const host = window.location.host
this.websocket = new WebSocket(`${protocol}://${host}/webrtc/music`)
this.clientlist = []
this.ul = List({})
document.body.appendChild(this.ul)
//this.websocket.onopen = event => {
// console.log('clientlist websocket: onopen')
// console.log('当连接建立时,服务器将逐一发送所有客户端信息')
//}
this.websocket.onmessage = async event => {
const data = JSON.parse(event.data)
if (data.type === 'list') {
console.log('取得在线对端列表:', data)
const webrtc = new RTCPeerConnection()
webrtc.createDataChannel('music')
//webrtc.onicecandidate = event => {
// console.log('clientlist onicecandidate E', event)
// if (event.candidate) {
// this.websocket.send(JSON.stringify({ type: 'candidate', id: data.id, candidate: event.candidate }))
// }
//}
console.log('发送给对方 offer')
const offer = await webrtc.createOffer()
await webrtc.setLocalDescription(offer)
this.clientlist.push({ id: data.id, name: data.name, webrtc })
this.websocket.send(JSON.stringify({ type: 'offer', id: data.id, offer }))
return this.add(data)
}
if (data.type === 'push') {
console.log('新上线客户端:', data)
return this.add(data)
}
if (data.type === 'pull') {
console.log('移除客户端:', data)
return this.remove(data)
}
if (data.type === 'offer') {
console.log('收到对方 offer', data)
const webrtc = new RTCPeerConnection()
webrtc.createDataChannel('music')
//webrtc.onicecandidate = event => {
// console.log('clientlist onicecandidate X', event)
// if (event.candidate) {
// this.websocket.send(JSON.stringify({ type: 'candidate', id: data.id, candidate: event.candidate }))
// }
//}
this.clientlist.push({ id: data.id, name: data.name, webrtc })
console.log('发送给对方 answer')
await webrtc.setRemoteDescription(data.offer)
const answer = await webrtc.createAnswer()
await webrtc.setLocalDescription(answer)
this.websocket.send(JSON.stringify({ type: 'answer', id: data.id, answer }))
return
}
if (data.type === 'answer') {
console.log('收到对方 answer', data)
const pc = this.clientlist.find(client => client.id === data.id).webrtc
await pc.setRemoteDescription(data.answer)
return
}
if (data.type === 'candidate') {
const pc = this.clientlist.find(client => client.id === data.id).webrtc
await pc.addIceCandidate(data.candidate)
return console.log('收到 candidate 并将其添加到远程端', data.candidate)
}
console.log('收到未知数据:', data)
}
}
add(item) {
//this.clientlist.push(item)
this.ul.appendChild(ListItem({
id: item.id,
innerText: item.name ?? item.id,
onclick: event => {
},
chidren: []
}))
//// 与对方建立连接(即使不传输数据)
//const webrtc = new RTCPeerConnection()
//const channel = webrtc.createDataChannel('music')
//channel.onopen = event => {
// console.log('clientlist music: channel.onopen')
// channel.send('hello')
//}
//channel.onmessage = event => {
// console.log('clientlist music: channel.onmessage', event.data)
//}
//// 监听 ICE 候选事件
//webrtc.onicecandidate = event => {
// if (event.candidate) {
// console.log('clientlist onicecandidate', event.candidate)
// // 发送给对方(通过服务器)
// }
//}
}
remove(item) {
this.clientlist = this.clientlist.filter(client => client.id !== item.id)
this.ul.removeChild(document.getElementById(item.id))
}
update(item) { }
get(id) { }
getAll() { }
clear() { }
on(event, callback) { }
}

View File

@ -15,7 +15,6 @@ export default class IndexedDB {
}
open() {
console.log('open')
return new Promise((resolve, reject) => {
const request = indexedDB.open(this.databaseName, this.databaseVersion)
request.onerror = (event) => {

View File

@ -9,24 +9,64 @@
<body>
<div>
<h1>webRTC</h1>
<p>message</p>
<p>选择音乐使频道内所有设备同步播放</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()
// 音乐列表
import ClientList from './client.js'
// 初始化音乐列表
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 })
//})
// 初始化客户端列表
const clientList = new ClientList()
//// 初始化音乐频道
//const protocol = window.location.protocol === 'https:' ? 'wss' : 'ws'
//const host = window.location.host
//const ws = new WebSocket(`${protocol}://${host}/webrtc/music`)
//ws.onopen = function () {
// console.log('music ws open')
//}
//ws.onmessage = function (event) {
// const data = JSON.parse(event.data)
// console.log('ws message:', data)
// if (data.type === 'push') {
// console.log('收到 type:push 将设备增加', data.id)
// clientList.add(data.id, data.channel)
// return
// }
// if (data.type === 'pull') {
// console.log('收到 type:pull 将设备删除', data.id)
// clientList.remove(data)
// return
// }
// if (data.type === 'error') {
// console.log('收到 type:error 没什么可操作的', data.id)
// return
// }
// if (data.offer) {
// console.log('收到 offer 并将其设置为远程描述')
// //clientList.setRemoteDescription(data)
// 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))
// }
//}
// webRTC 传递音乐(分别传输文件和操作事件能更流畅)
const music = async function () {
@ -71,7 +111,6 @@
audioSource?.stop()
audioSource = null
})
// 监听 ICE 候选事件
pc.onicecandidate = event => {
if (event.candidate) {
@ -130,7 +169,7 @@
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
ws.send(JSON.stringify({ id, answer: pc.localDescription })) // 发送给远程终端 answer
return
}
if (data.answer) {
@ -144,55 +183,8 @@
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)
})
}
music()
//music()
</script>
<!--script type="module">
// 创建 RTCPeerConnection

View File

@ -8,8 +8,7 @@ export default class MusicList {
this.store = new IndexedDB('musicDatabase', 1, 'musicObjectStore')
this.store.open().then(() => {
this.store.getAll().then((data) => {
console.log(data)
data.forEach(item => this.add(item))
data.forEach(item => this.__add(item))
})
})
document.body.appendChild(this.ul)
@ -29,7 +28,6 @@ export default class MusicList {
input.multiple = true
input.accept = 'audio/*'
input.onchange = event => {
console.log('event.target.files', event.target.files)
for (const file of event.target.files) {
const id = 'music' + Date.now()
const { name, size, type } = file
@ -43,8 +41,8 @@ export default class MusicList {
}
document.body.appendChild(input)
}
add(item) {
this.store.add(item)
// 仅添加UI
__add(item) {
const li = ListItem({
id: item.id,
innerText: `${item.name} - ${item.size} - ${item.type} - ${item.id}`,
@ -78,6 +76,11 @@ export default class MusicList {
})
this.ul.appendChild(li)
}
// 添加数据并添加UI
add(item) {
this.store.add(item)
this.__add(item)
}
delete(item) {
this.store.delete(item.id)
this.ul.removeChild(this.ul.querySelector(`#${item.id}`))