import type { type Consumer, Producer } from 'mediasoup-client/types' 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/webrtc', { 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('producers', async (producers) => { watch(connected, async () => { if (!connected.value) return for (const producer of producers) { await consume(producer.producerId) } }, { immediate: true }) }) socket.on('newProducer', async ({ producerId }) => { await consume(producerId) }) socket.on('producerClosed', async (producerId: Producer['id']) => { delete streams.value[producerId] triggerRef(streams) }) async function consume(producerId: number) { 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, id: params.consumerId, }) const stream = new MediaStream([consumer.track]) streams.value[producerId] = 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 (error) { if (error instanceof Error) { errback(error) } } }) sendTransport.on('produce', async ({ kind, rtpParameters }, callback, errback) => { try { const { producerId } = await socket.emitWithAck('produce', { transportId: sendTransport.id, kind, rtpParameters, }) callback({ id: producerId }) } catch (error) { if (error instanceof Error) { errback(error) } } }) } async function publishMic() { const devices = await navigator.mediaDevices.enumerateDevices() console.log(devices) const stream = await navigator.mediaDevices.getUserMedia({ audio: { autoGainControl: false, noiseSuppression: true, echoCancellation: false, }, }) 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 (error) { if (error instanceof Error) { errback(error) } } }) } (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, } })