import type { Socket } from 'socket.io-client' import { createGlobalState } from '@vueuse/core' import * as mediasoupClient from 'mediasoup-client' import { io } from 'socket.io-client' const ICE_SERVERS: RTCIceServer[] = [ { urls: 'stun:stun.l.google.com:19302' }, { urls: 'stun:stun.l.google.com:5349' }, { urls: 'stun:stun1.l.google.com:3478' }, { urls: 'stun:stun1.l.google.com:5349' }, { urls: 'stun:stun2.l.google.com:19302' }, { urls: 'stun:stun2.l.google.com:5349' }, { urls: 'stun:stun3.l.google.com:3478' }, { urls: 'stun:stun3.l.google.com:5349' }, { urls: 'stun:stun4.l.google.com:19302' }, { urls: 'stun:stun4.l.google.com:5349' }, ] export const useMediasoup = createGlobalState(() => { const socket: Socket = io('https://api.koptilnya.xyz', { path: '/chad/ws/', transports: ['websocket'], }) const initializing = ref(false) const connected = ref(false) const streams = shallowRef([]) let device: mediasoupClient.Device let sendTransport: mediasoupClient.types.Transport let recvTransport: mediasoupClient.types.Transport socket.on('newProducer', async ({ producerId }) => { const params = await socket.emitWithAck('consume', { producerId, transportId: recvTransport.id, rtpCapabilities: device.rtpCapabilities, }) if (params?.error) { console.error('consume error:', params.error) return } const consumer = await recvTransport.consume(params) const stream = new MediaStream([consumer.track]) streams.value.push(stream) triggerRef(streams) }) async function loadDevice() { device = new mediasoupClient.Device() const rtpCapabilities = await socket.emitWithAck('getRtpCapabilities') await device.load({ routerRtpCapabilities: rtpCapabilities }) } async function createSendTransport() { const params = await socket.emitWithAck('createTransport') sendTransport = device.createSendTransport({ ...params, iceServers: [ ...ICE_SERVERS, ...(params.iceServers ?? []), ], }) sendTransport.on('connect', async ({ dtlsParameters }, callback, errback) => { try { await socket.emitWithAck('connectTransport', { transportId: sendTransport.id, dtlsParameters, }) callback() } catch (err) { errback(err) } }) sendTransport.on('produce', async ({ kind, rtpParameters }, callback, errback) => { try { const { id } = await socket.emitWithAck('produce', { transportId: sendTransport.id, kind, rtpParameters, }) callback({ id }) } catch (err) { errback(err) } }) } async function publishMic() { const stream = await navigator.mediaDevices.getUserMedia({ audio: true }) const track = stream.getAudioTracks()[0] await sendTransport.produce({ track }) } async function createRecvTransport() { const params = await socket.emitWithAck('createTransport') recvTransport = device.createRecvTransport({ ...params, iceServers: [ ...ICE_SERVERS, ...(params.iceServers ?? []), ], }) recvTransport.on('connect', async ({ dtlsParameters }, callback, errback) => { try { await socket.emitWithAck('connectTransport', { transportId: recvTransport.id, dtlsParameters, }) callback() } catch (err) { errback(err) } }) } (async () => { if (initializing.value || connected.value) return initializing.value = true connected.value = false await loadDevice() await createSendTransport() await createRecvTransport() await publishMic() initializing.value = false connected.value = true })() return { connected, streams, } })