работаем бля работаем

This commit is contained in:
2026-05-09 03:21:44 +06:00
parent f845777bac
commit 0b148c6a7d
169 changed files with 15816 additions and 1005 deletions

Binary file not shown.

54
client/.zed/settings.json Normal file
View File

@@ -0,0 +1,54 @@
{
// Use ESLint's --fix:
"code_actions_on_format": {
"source.fixAll.eslint": true,
},
"formatter": [],
// Enable eslint for all supported languages
// Defaults only include https://github.com/search?q=repo%3Azed-industries%2Fzed%20eslint_languages&type=code
"languages": {
"HTML": {
"language_servers": ["...", "eslint"],
},
"Markdown": {
"language_servers": ["...", "eslint"],
},
"Markdown-Inline": {
"language_servers": ["...", "eslint"],
},
"JSON": {
"language_servers": ["...", "eslint"],
},
"JSONC": {
"language_servers": ["...", "eslint"],
},
"YAML": {
"language_servers": ["...", "eslint"],
},
"CSS": {
"language_servers": ["...", "eslint"],
},
// Add other languages as needed
},
"lsp": {
"eslint": {
"settings": {
"workingDirectories": ["./"],
// Silent the stylistic rules in your IDE, but still auto fix them
"rulesCustomizations": [
{ "rule": "style/*", "severity": "off", "fixable": true },
{ "rule": "format/*", "severity": "off", "fixable": true },
{ "rule": "*-indent", "severity": "off", "fixable": true },
{ "rule": "*-spacing", "severity": "off", "fixable": true },
{ "rule": "*-spaces", "severity": "off", "fixable": true },
{ "rule": "*-order", "severity": "off", "fixable": true },
{ "rule": "*-dangle", "severity": "off", "fixable": true },
{ "rule": "*-newline", "severity": "off", "fixable": true },
{ "rule": "*quotes", "severity": "off", "fixable": true },
{ "rule": "*semi", "severity": "off", "fixable": true },
],
},
},
},
}

View File

@@ -21,7 +21,7 @@
</PrimeAvatar>
<p class="flex-1 text-sm leading-5 font-medium text-color truncate w-0">
{{ client.displayName || client.username }}
{{ client.displayName || client.username || client.socketId }}
</p>
<Component :is="expanded ? ChevronUp : ChevronDown" v-if="!isMe" :size="20" class="text-muted-color" />

View File

@@ -85,7 +85,7 @@ export const useApp = createGlobalState(() => {
await muteInput()
await signaling.socket.value?.emitWithAck('updateClient', {
await signaling.socket.value?.emitWithAck('update-client', {
outputMuted: true,
})
@@ -98,7 +98,7 @@ export const useApp = createGlobalState(() => {
if (!previousInputMuted.value)
await unmuteInput()
await signaling.socket.value?.emitWithAck('updateClient', {
await signaling.socket.value?.emitWithAck('update-client', {
outputMuted: false,
})

View File

@@ -14,7 +14,7 @@ export const useClients = createGlobalState(() => {
if (!socket)
return
socket.on('clientChanged', (clientId: ChadClient['socketId'], updatedClient: UpdatedClient) => {
socket.on('client-updated', (clientId: ChadClient['socketId'], updatedClient: UpdatedClient) => {
const client = getClient(clientId)
if (!client)

View File

@@ -26,15 +26,15 @@ const ICE_SERVERS: RTCIceServer[] = [
]
export const useMediasoup = createSharedComposable(() => {
const { emit } = useEventBus()
const eventBus = useEventBus()
const signaling = useSignaling()
const { addClient, removeClient, me } = useClients()
const { addClient, removeClient, me, clients, updateClient } = useClients()
const preferences = usePreferences()
const { getShareStream } = useDevices()
const device = shallowRef<mediasoupClient.Device>()
const rtpCapabilities = shallowRef<mediasoupClient.types.RtpCapabilities>()
const routerRtpCapabilities = shallowRef<mediasoupClient.types.RtpCapabilities>()
const sendTransport = shallowRef<mediasoupClient.types.Transport>()
const recvTransport = shallowRef<mediasoupClient.types.Transport>()
@@ -79,18 +79,30 @@ export const useMediasoup = createSharedComposable(() => {
if (!socket)
return
socket.on('authenticated', async () => {
socket.on('new-client', (client) => {
addClient(client)
eventBus.emit('client:added', client)
})
socket.on('client-switched-channel', (client) => {
updateClient(client.socketId, client)
})
socket.on('initialized', async (initData) => {
if (!signaling.socket.value)
return
device.value = new mediasoupClient.Device()
rtpCapabilities.value = await signaling.socket.value.emitWithAck('getRtpCapabilities')
routerRtpCapabilities.value = initData.rtpCapabilities
await device.value.load({ routerRtpCapabilities: rtpCapabilities.value! })
clients.value = initData.clients
await device.value.load({ routerRtpCapabilities: routerRtpCapabilities.value! })
// Send transport
{
const transportInfo = await signaling.socket.value.emitWithAck('createTransport', { producing: true, consuming: false })
const transportInfo = await signaling.socket.value.emitWithAck('create-transport', { producing: true, consuming: false })
sendTransport.value = device.value.createSendTransport({
...transportInfo,
iceServers: [
@@ -101,7 +113,7 @@ export const useMediasoup = createSharedComposable(() => {
sendTransport.value.on('connect', async ({ dtlsParameters }, callback, errback) => {
try {
await signaling.socket.value!.emitWithAck('connectTransport', {
await signaling.socket.value!.emitWithAck('connect-transport', {
transportId: sendTransport.value!.id,
dtlsParameters,
})
@@ -135,7 +147,7 @@ export const useMediasoup = createSharedComposable(() => {
// Recv Transport
{
const transportInfo = await signaling.socket.value.emitWithAck('createTransport', { producing: false, consuming: true })
const transportInfo = await signaling.socket.value.emitWithAck('create-transport', { producing: false, consuming: true })
recvTransport.value = device.value.createRecvTransport({
...transportInfo,
iceServers: [
@@ -146,7 +158,7 @@ export const useMediasoup = createSharedComposable(() => {
recvTransport.value.on('connect', async ({ dtlsParameters }, callback, errback) => {
try {
await signaling.socket.value!.emitWithAck('connectTransport', {
await signaling.socket.value!.emitWithAck('connect-transport', {
transportId: recvTransport.value!.id,
dtlsParameters,
})
@@ -160,36 +172,33 @@ export const useMediasoup = createSharedComposable(() => {
}
})
}
//
// const joinedClients = (await signaling.socket.value.emitWithAck('join', {
// rtpCapabilities: routerRtpCapabilities.value,
// }))
//
// addClient(...joinedClients)
//
// if (me.value)
// eventBus.emit('socket:authenticated', { socketId: me.value.socketId })
//
const joinedClients = (await signaling.socket.value.emitWithAck('join', {
rtpCapabilities: rtpCapabilities.value,
}))
addClient(...joinedClients)
if (me.value)
emit('socket:authenticated', { socketId: me.value.socketId })
// TODO: при переподключении проверять inputMuted
await enableMic()
})
socket.on('newPeer', (client) => {
addClient(client)
emit('client:added', client)
})
socket.on('peerClosed', (id) => {
socket.on('client-disconnected', (id) => {
const { getClient } = useClients()
const client = getClient(id)
removeClient(id)
if (client)
emit('client:removed', client)
eventBus.emit('client:removed', client)
})
socket.on(
'newConsumer',
'new-consumer',
async (
{ id, producerId, kind, rtpParameters, socketId, appData, producerPaused },
cb,
@@ -216,18 +225,18 @@ export const useMediasoup = createSharedComposable(() => {
raw: markRaw(consumer),
}
emit('consumer:added', consumers.value[consumer.id]!)
eventBus.emit('consumer:added', consumers.value[consumer.id]!)
consumer.observer.on('resume', () => {
consumers.value[consumer.id]!.paused = false
emit('consumer:resumed', consumers.value[consumer.id]!)
eventBus.emit('consumer:resumed', consumers.value[consumer.id]!)
})
consumer.observer.on('pause', () => {
consumers.value[consumer.id]!.paused = true
emit('consumer:paused', consumers.value[consumer.id]!)
eventBus.emit('consumer:paused', consumers.value[consumer.id]!)
})
consumer.observer.on('close', () => {
@@ -236,7 +245,7 @@ export const useMediasoup = createSharedComposable(() => {
delete consumers.value[consumer.id]
if (consumerData)
emit('consumer:removed', consumerData)
eventBus.emit('consumer:removed', consumerData)
})
consumer.on('trackended', () => {
@@ -248,7 +257,7 @@ export const useMediasoup = createSharedComposable(() => {
)
socket.on(
'consumerClosed',
'consumer-closed',
async (
{ consumerId },
) => {
@@ -261,7 +270,7 @@ export const useMediasoup = createSharedComposable(() => {
},
)
socket.on('consumerPaused', ({ consumerId }) => {
socket.on('consumer-paused', ({ consumerId }) => {
const consumer = consumers.value[consumerId]
if (!consumer)
@@ -270,7 +279,7 @@ export const useMediasoup = createSharedComposable(() => {
consumer.raw.pause()
})
socket.on('consumerResumed', ({ consumerId }) => {
socket.on('consumer-resumed', ({ consumerId }) => {
const consumer = consumers.value[consumerId]
if (!consumer)
@@ -279,13 +288,13 @@ export const useMediasoup = createSharedComposable(() => {
consumer.raw.resume()
})
socket.on('speakingPeers', (value: SpeakingClient[]) => {
socket.on('speaking-clients', (value: SpeakingClient[]) => {
speakingClients.value = value
})
socket.on('disconnect', () => {
device.value = undefined
rtpCapabilities.value = undefined
routerRtpCapabilities.value = undefined
sendTransport.value?.close()
sendTransport.value = undefined
@@ -317,27 +326,28 @@ export const useMediasoup = createSharedComposable(() => {
raw: markRaw(producer),
}
emit('producer:added', producers.value[producer.id]!)
eventBus.emit('producer:added', producers.value[producer.id]!)
producer.observer.on('pause', () => {
producers.value[producer.id]!.paused = true
emit('producer:paused', producers.value[producer.id]!)
eventBus.emit('producer:paused', producers.value[producer.id]!)
})
producer.observer.on('resume', () => {
producers.value[producer.id]!.paused = false
emit('producer:resumed', producers.value[producer.id]!)
eventBus.emit('producer:resumed', producers.value[producer.id]!)
})
producer.observer.on('close', () => {
console.log('producer closed')
const producerData = producers.value[producer.id]
delete producers.value[producer.id]
if (producerData)
emit('producer:removed', producerData)
eventBus.emit('producer:removed', producerData)
})
producer.on('trackended', () => {
@@ -352,7 +362,7 @@ export const useMediasoup = createSharedComposable(() => {
try {
producer.raw.close()
await signaling.socket.value.emitWithAck('closeProducer', {
await signaling.socket.value.emitWithAck('close-producer', {
producerId: producer.id,
})
}
@@ -455,7 +465,7 @@ export const useMediasoup = createSharedComposable(() => {
await createProducer({
track,
streamId: 'share',
codec: device.value.rtpCapabilities.codecs?.find(
codec: device.value.sendRtpCapabilities.codecs?.find(
c => c.mimeType.toLowerCase() === 'video/AV1',
),
codecOptions: {
@@ -478,7 +488,7 @@ export const useMediasoup = createSharedComposable(() => {
try {
producer.raw.pause()
await signaling.socket.value.emitWithAck('pauseProducer', {
await signaling.socket.value.emitWithAck('pause-producer', {
producerId: producer.id,
})
}
@@ -494,7 +504,7 @@ export const useMediasoup = createSharedComposable(() => {
try {
producer.raw.resume()
await signaling.socket.value.emitWithAck('resumeProducer', {
await signaling.socket.value.emitWithAck('resume-producer', {
producerId: producer.id,
})
}
@@ -526,7 +536,7 @@ export const useMediasoup = createSharedComposable(() => {
speakingClients,
sendTransport,
recvTransport,
rtpCapabilities,
rtpCapabilities: routerRtpCapabilities,
device,
micProducer,
videoProducer,
@@ -536,5 +546,7 @@ export const useMediasoup = createSharedComposable(() => {
enableVideo,
enableShare,
disableProducer,
consumersArray,
producersArray,
}
})

View File

@@ -52,11 +52,39 @@
<PrimeScrollPanel class="bg-surface-900 rounded-xl overflow-hidden" style="min-height: 0">
<div v-auto-animate class="p-3 space-y-1">
<ClientRow v-for="client of clients" :key="client.userId" :client="client" />
<template v-for="channel in channels" :key="channel.id">
<PrimeDivider>
<PrimeButton size="small" variant="text" @click="joinChannel(channel)">
{{ channel.name }}
</PrimeButton>
</PrimeDivider>
<ClientRow v-for="client in clients.filter(_client => _client.channelId === channel.id)" :key="client.socketId" :client="client">
{{ client.userId }}
</ClientRow>
</template>
<!-- <ClientRow v-for="client of clients" :key="client.userId" :client="client" /> -->
</div>
</PrimeScrollPanel>
<div class="bg-surface-900 rounded-xl overflow-hidden p-3 flex flex-col min-h-full">
<dl>
<dt>Socket ID</dt>
<dd>{{ socket?.id }}</dd>
<br>
<dt>Producers</dt>
<dd v-for="producer in producersArray" :key="producer.id">
{{ producer.id }}
{{ producer.appData }}
</dd>
<br>
<dl>Consumers</dl>
<dd v-for="consumer in consumersArray" :key="consumer.id">
{{ consumer.id }}
{{ consumer.appData }}
</dd>
</dl>
<slot />
</div>
</div>
@@ -65,6 +93,7 @@
</template>
<script setup lang="ts">
import chadApi from '#shared/chad-api'
import {
Camera,
CameraOff,
@@ -80,6 +109,13 @@ import {
VolumeOff,
} from 'lucide-vue-next'
const channels = shallowRef<any[]>([])
;(async () => {
channels.value = await chadApi<any[]>('/channels', { method: 'GET' })
})()
const { me } = useClients()
const {
version,
clients,
@@ -93,7 +129,8 @@ const {
toggleVideo,
toggleShare,
} = useApp()
const { connect, connected } = useSignaling()
const { connect, connected, socket } = useSignaling()
const { consumersArray, producersArray } = useMediasoup()
interface Tab {
id: string
@@ -150,4 +187,30 @@ watch(activeTab, (activeTab) => {
})
connect()
async function joinChannel(channel) {
socket.value?.emit('join-channel', { channelId: channel.id })
}
watch(socket, (socket) => {
if (!socket)
return
socket.on('channel-removed', (channelId) => {
const idx = channels.value.findIndex(channel => channel.id === channelId)
if (idx === -1)
return
channels.value.splice(idx, 1)
triggerRef(channels)
})
socket.on('channel-created', (channel) => {
channels.value.push(channel)
triggerRef(channels)
})
}, { immediate: true })
</script>

View File

@@ -25,7 +25,7 @@
"linkify-string": "^4.3.2",
"linkifyjs": "^4.3.2",
"lucide-vue-next": "^0.562.0",
"mediasoup-client": "^3.18.6",
"mediasoup-client": "^3.19.0",
"mitt": "^3.0.1",
"nuxt": "^4.2.2",
"postcss": "^8.5.6",

View File

@@ -3,8 +3,7 @@ import type { Consumer as MediasoupConsumer, Producer as MediasoupProducer } fro
export interface ChadClient {
socketId: string
userId: string
username: string
displayName: string
channelId: string
inputMuted?: boolean
outputMuted?: boolean

View File

@@ -3004,7 +3004,7 @@ __metadata:
languageName: node
linkType: hard
"@types/debug@npm:^4.0.0, @types/debug@npm:^4.1.12":
"@types/debug@npm:^4.0.0":
version: 4.1.12
resolution: "@types/debug@npm:4.1.12"
dependencies:
@@ -3013,6 +3013,15 @@ __metadata:
languageName: node
linkType: hard
"@types/debug@npm:^4.1.13":
version: 4.1.13
resolution: "@types/debug@npm:4.1.13"
dependencies:
"@types/ms": "npm:*"
checksum: 10c0/e5e124021bbdb23a82727eee0a726ae0fc8a3ae1f57253cbcc47497f259afb357de7f6941375e773e1abbfa1604c1555b901a409d762ec2bb4c1612131d4afb7
languageName: node
linkType: hard
"@types/estree@npm:*, @types/estree@npm:1.0.8, @types/estree@npm:^1.0.0, @types/estree@npm:^1.0.6, @types/estree@npm:^1.0.8":
version: 1.0.8
resolution: "@types/estree@npm:1.0.8"
@@ -4082,7 +4091,7 @@ __metadata:
linkify-string: "npm:^4.3.2"
linkifyjs: "npm:^4.3.2"
lucide-vue-next: "npm:^0.562.0"
mediasoup-client: "npm:^3.18.6"
mediasoup-client: "npm:^3.19.0"
mitt: "npm:^3.0.1"
nuxt: "npm:^4.2.2"
postcss: "npm:^8.5.6"
@@ -7355,11 +7364,11 @@ __metadata:
languageName: node
linkType: hard
"mediasoup-client@npm:^3.18.6":
version: 3.18.6
resolution: "mediasoup-client@npm:3.18.6"
"mediasoup-client@npm:^3.19.0":
version: 3.19.0
resolution: "mediasoup-client@npm:3.19.0"
dependencies:
"@types/debug": "npm:^4.1.12"
"@types/debug": "npm:^4.1.13"
"@types/events-alias": "npm:@types/events@^3.0.3"
awaitqueue: "npm:^3.3.0"
debug: "npm:^4.4.3"
@@ -7368,7 +7377,7 @@ __metadata:
h264-profile-level-id: "npm:^2.3.2"
sdp-transform: "npm:^3.0.0"
supports-color: "npm:^10.2.2"
checksum: 10c0/f5baff9139afccf88de5db767c1139efa5cdd68f4871e2fa9d6ff94d2e71d2365dc40e9ba6e903cde5fbb51a2d82972e738da656be9f6fc7006640fdd82dd5da
checksum: 10c0/9fde5ec5daec91d43a88796f49e2b1b7a018c8100a3f99786966678a0e0b5328e88f6e6af36d50f9eed93889b84f23a164865c7177c0767ee805c7a8c7a51eb2
languageName: node
linkType: hard