fix webrtc and login
This commit is contained in:
@@ -0,0 +1,125 @@
|
||||
import {defineStore} from 'pinia'
|
||||
|
||||
export const useVoiceStore = defineStore('voice', {
|
||||
state: () => ({
|
||||
channelId: null as string | null,
|
||||
serverId: null as string | null,
|
||||
connecting: false,
|
||||
error: null as string | null,
|
||||
}),
|
||||
actions: {
|
||||
async join(serverId: string, channelId: string) {
|
||||
if (this.channelId === channelId) return
|
||||
this.leave()
|
||||
this.connecting = true
|
||||
this.error = null
|
||||
const generation = ++voiceGeneration
|
||||
try {
|
||||
const stream = await navigator.mediaDevices.getUserMedia({audio: true})
|
||||
if (generation !== voiceGeneration) {
|
||||
stream.getTracks().forEach(track => track.stop())
|
||||
return
|
||||
}
|
||||
microphone = stream
|
||||
const connection = new RTCPeerConnection()
|
||||
peer = connection
|
||||
stream.getAudioTracks().forEach(track => connection.addTrack(track, stream))
|
||||
const url = new URL(`/ws/rtc/${encodeURIComponent(channelId)}`, window.location.href)
|
||||
url.protocol = url.protocol === 'https:' ? 'wss:' : 'ws:'
|
||||
const ws = new WebSocket(url)
|
||||
socket = ws
|
||||
this.channelId = channelId
|
||||
this.serverId = serverId
|
||||
const pendingCandidates: string[] = []
|
||||
const tracks = new Map<string, MediaStreamTrack>()
|
||||
connection.ontrack = ({track, streams}) => {
|
||||
if (peer !== connection) return
|
||||
const audio = new Audio()
|
||||
audio.autoplay = true
|
||||
audio.srcObject = new MediaStream([track])
|
||||
audioElements.set(track, audio)
|
||||
if (streams[0]) tracks.set(streams[0].id, track)
|
||||
track.onended = () => removeAudio(track)
|
||||
void audio.play().catch(() => { this.error = 'Lecture du son distant impossible.' })
|
||||
}
|
||||
connection.onicecandidate = ({candidate}) => {
|
||||
if (candidate && ws.readyState === WebSocket.OPEN) {
|
||||
ws.send(JSON.stringify({action: 'ice-candidate', candidate: candidate.candidate}))
|
||||
}
|
||||
}
|
||||
ws.onopen = async () => {
|
||||
try {
|
||||
const offer = await connection.createOffer()
|
||||
await connection.setLocalDescription(offer)
|
||||
if (socket === ws && ws.readyState === WebSocket.OPEN) {
|
||||
ws.send(JSON.stringify({action: 'sdp-offer', sdp: connection.localDescription?.sdp}))
|
||||
}
|
||||
} catch (error) {
|
||||
if (socket === ws) { this.error = String(error); this.leave() }
|
||||
}
|
||||
}
|
||||
let queue = Promise.resolve()
|
||||
ws.onmessage = ({data}) => {
|
||||
queue = queue.then(async () => {
|
||||
if (socket !== ws) return
|
||||
const message = JSON.parse(data)
|
||||
if (message.action === 'answer') {
|
||||
await connection.setRemoteDescription({type: 'answer', sdp: message.sdp})
|
||||
for (const candidate of pendingCandidates) await connection.addIceCandidate({candidate, sdpMLineIndex: 0})
|
||||
pendingCandidates.length = 0
|
||||
} else if (message.action === 'sdp-offer') {
|
||||
await connection.setRemoteDescription({type: 'offer', sdp: message.sdp})
|
||||
const answer = await connection.createAnswer()
|
||||
await connection.setLocalDescription(answer)
|
||||
ws.send(JSON.stringify({action: 'sdp-answer', sdp: connection.localDescription?.sdp}))
|
||||
} else if (message.action === 'ice-candidate') {
|
||||
if (connection.remoteDescription) await connection.addIceCandidate({candidate: message.candidate, sdpMLineIndex: 0})
|
||||
else pendingCandidates.push(message.candidate)
|
||||
} else if (message.action === 'source-left') {
|
||||
const track = tracks.get(message.id)
|
||||
if (track) removeAudio(track)
|
||||
tracks.delete(message.id)
|
||||
} else if (message.action === 'error') {
|
||||
this.error = message.message
|
||||
this.leave()
|
||||
}
|
||||
}).catch(error => { if (socket === ws) { this.error = String(error); this.leave() } })
|
||||
}
|
||||
ws.onerror = () => { if (socket === ws) this.error = 'Connexion vocale impossible.' }
|
||||
ws.onclose = () => { if (socket === ws) this.leave() }
|
||||
} catch (error) {
|
||||
if (generation === voiceGeneration) this.error = `Microphone indisponible : ${String(error)}`
|
||||
} finally {
|
||||
if (generation === voiceGeneration) this.connecting = false
|
||||
}
|
||||
},
|
||||
leave() {
|
||||
voiceGeneration++
|
||||
if (socket?.readyState === WebSocket.OPEN) socket.send(JSON.stringify({action: 'leave'}))
|
||||
socket?.close()
|
||||
socket = null
|
||||
peer?.close()
|
||||
peer = null
|
||||
microphone?.getTracks().forEach(track => track.stop())
|
||||
microphone = null
|
||||
for (const track of audioElements.keys()) removeAudio(track)
|
||||
this.channelId = null
|
||||
this.serverId = null
|
||||
this.connecting = false
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
let voiceGeneration = 0
|
||||
let socket: WebSocket | null = null
|
||||
let peer: RTCPeerConnection | null = null
|
||||
let microphone: MediaStream | null = null
|
||||
const audioElements = new Map<MediaStreamTrack, HTMLAudioElement>()
|
||||
|
||||
function removeAudio(track: MediaStreamTrack) {
|
||||
const audio = audioElements.get(track)
|
||||
if (!audio) return
|
||||
audio.pause()
|
||||
audio.srcObject = null
|
||||
audioElements.delete(track)
|
||||
}
|
||||
Reference in New Issue
Block a user