brutalism design
This commit is contained in:
7
new-client/.claude/settings.local.json
Normal file
7
new-client/.claude/settings.local.json
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"permissions": {
|
||||||
|
"allow": [
|
||||||
|
"Bash(python -c \"import sys,json; p=json.load\\(sys.stdin\\); deps={**p.get\\('dependencies',{}\\), **p.get\\('devDependencies',{}\\)}; [print\\(k,v\\) for k in sorted\\(deps\\) if any\\(x in k for x in ['mediasoup','socket','vueuse','vue']\\)]\")"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
137
new-client/src/app/bootstrap/mediasoup.ts
Normal file
137
new-client/src/app/bootstrap/mediasoup.ts
Normal file
@@ -0,0 +1,137 @@
|
|||||||
|
import { useMediasoup } from '@shared/composables/use-mediasoup'
|
||||||
|
import { useSignaling } from '@shared/composables/use-signaling'
|
||||||
|
import { Device } from 'mediasoup-client'
|
||||||
|
import { markRaw, watch } from 'vue'
|
||||||
|
|
||||||
|
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 default function () {
|
||||||
|
const { socket } = useSignaling()
|
||||||
|
const ms = useMediasoup()
|
||||||
|
|
||||||
|
watch(socket, (socket) => {
|
||||||
|
if (!socket)
|
||||||
|
return
|
||||||
|
|
||||||
|
socket.on('initialized', async (initData) => {
|
||||||
|
ms.device.value = new Device()
|
||||||
|
await ms.device.value.load({ routerRtpCapabilities: initData.rtpCapabilities })
|
||||||
|
|
||||||
|
const sendInfo = await socket.emitWithAck('create-transport', { producing: true, consuming: false })
|
||||||
|
const sendTransport = ms.device.value.createSendTransport({
|
||||||
|
...sendInfo,
|
||||||
|
iceServers: [...ICE_SERVERS, ...(sendInfo.iceServers ?? [])],
|
||||||
|
})
|
||||||
|
ms.sendTransport.value = sendTransport
|
||||||
|
await ms.transports.add({
|
||||||
|
id: sendTransport.id,
|
||||||
|
ref: markRaw(sendTransport),
|
||||||
|
})
|
||||||
|
|
||||||
|
sendTransport.on('connect', async ({ dtlsParameters }, callback, errback) => {
|
||||||
|
try {
|
||||||
|
await socket.emitWithAck('connect-transport', { transportId: sendTransport.id, dtlsParameters })
|
||||||
|
callback()
|
||||||
|
}
|
||||||
|
catch (error) {
|
||||||
|
if (error instanceof Error)
|
||||||
|
errback(error)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
sendTransport.on('produce', async ({ kind, rtpParameters, appData }, callback, errback) => {
|
||||||
|
try {
|
||||||
|
const { id } = await socket.emitWithAck('produce', {
|
||||||
|
transportId: sendTransport.id,
|
||||||
|
kind,
|
||||||
|
rtpParameters,
|
||||||
|
appData,
|
||||||
|
})
|
||||||
|
callback({ id })
|
||||||
|
}
|
||||||
|
catch (error) {
|
||||||
|
if (error instanceof Error)
|
||||||
|
errback(error)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const recvInfo = await socket.emitWithAck('create-transport', { producing: false, consuming: true })
|
||||||
|
const recvTransport = ms.device.value.createRecvTransport({
|
||||||
|
...recvInfo,
|
||||||
|
iceServers: [...ICE_SERVERS, ...(recvInfo.iceServers ?? [])],
|
||||||
|
})
|
||||||
|
ms.recvTransport.value = recvTransport
|
||||||
|
await ms.transports.add({
|
||||||
|
id: recvTransport.id,
|
||||||
|
ref: markRaw(recvTransport),
|
||||||
|
})
|
||||||
|
|
||||||
|
recvTransport.on('connect', async ({ dtlsParameters }, callback, errback) => {
|
||||||
|
try {
|
||||||
|
await socket.emitWithAck('connect-transport', { transportId: recvTransport.id, dtlsParameters })
|
||||||
|
callback()
|
||||||
|
}
|
||||||
|
catch (error) {
|
||||||
|
if (error instanceof Error)
|
||||||
|
errback(error)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
socket.on('new-consumer', async ({ id, producerId, kind, rtpParameters, socketId, appData, producerPaused }, cb) => {
|
||||||
|
const rt = ms.recvTransport.value
|
||||||
|
if (!rt)
|
||||||
|
return
|
||||||
|
|
||||||
|
const consumer = await rt.consume({
|
||||||
|
id,
|
||||||
|
producerId,
|
||||||
|
kind,
|
||||||
|
rtpParameters,
|
||||||
|
streamId: `${socketId}-${appData.source ?? 'stream'}`,
|
||||||
|
appData: { ...appData, socketId },
|
||||||
|
})
|
||||||
|
|
||||||
|
if (producerPaused)
|
||||||
|
consumer.pause()
|
||||||
|
|
||||||
|
await ms.consumers.add({
|
||||||
|
id: consumer.id,
|
||||||
|
paused: consumer.paused,
|
||||||
|
kind: consumer.kind,
|
||||||
|
appData: consumer.appData as Record<string, unknown>,
|
||||||
|
track: consumer.track,
|
||||||
|
ref: markRaw(consumer),
|
||||||
|
})
|
||||||
|
|
||||||
|
cb()
|
||||||
|
})
|
||||||
|
|
||||||
|
socket.on('consumer-closed', ({ consumerId }) => {
|
||||||
|
ms.consumers.get(consumerId)?.ref.close()
|
||||||
|
})
|
||||||
|
|
||||||
|
socket.on('consumer-paused', ({ consumerId }) => {
|
||||||
|
ms.consumers.get(consumerId)?.ref.pause()
|
||||||
|
})
|
||||||
|
|
||||||
|
socket.on('consumer-resumed', ({ consumerId }) => {
|
||||||
|
ms.consumers.get(consumerId)?.ref.resume()
|
||||||
|
})
|
||||||
|
|
||||||
|
socket.on('disconnect', () => {
|
||||||
|
ms.clearAll()
|
||||||
|
})
|
||||||
|
}, { immediate: true })
|
||||||
|
}
|
||||||
50
new-client/src/app/bootstrap/signaling.ts
Normal file
50
new-client/src/app/bootstrap/signaling.ts
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
import type { ChatMessage } from '@shared/api/generated-chad-api.ts'
|
||||||
|
import { useAuth } from '@shared/composables/use-auth.ts'
|
||||||
|
import { useClients } from '@shared/composables/use-clients'
|
||||||
|
import { useEventBus } from '@shared/composables/use-event-bus.ts'
|
||||||
|
import { useSignaling } from '@shared/composables/use-signaling'
|
||||||
|
import { watch, watchEffect } from 'vue'
|
||||||
|
|
||||||
|
export default function () {
|
||||||
|
const { authorized } = useAuth()
|
||||||
|
const signaling = useSignaling()
|
||||||
|
const { addClient, updateClient, removeClient, clear: clearClients } = useClients()
|
||||||
|
const eventBus = useEventBus()
|
||||||
|
|
||||||
|
watch(signaling.connected, (connected) => {
|
||||||
|
if (!connected)
|
||||||
|
return
|
||||||
|
|
||||||
|
const socket = signaling.socket.value!
|
||||||
|
|
||||||
|
socket.on('initialized', async ({ clients }) => {
|
||||||
|
addClient(...clients)
|
||||||
|
})
|
||||||
|
|
||||||
|
socket.on('new-client', (client) => {
|
||||||
|
addClient(client)
|
||||||
|
})
|
||||||
|
|
||||||
|
socket.on('client-switched-channel', (client) => {
|
||||||
|
updateClient(client.socketId, client)
|
||||||
|
})
|
||||||
|
|
||||||
|
socket.on('client-disconnected', (socketId) => {
|
||||||
|
removeClient(socketId)
|
||||||
|
})
|
||||||
|
|
||||||
|
socket.on('disconnect', () => {
|
||||||
|
clearClients()
|
||||||
|
})
|
||||||
|
|
||||||
|
socket.on('chat:new-message', (message: ChatMessage) => {
|
||||||
|
eventBus.emit('chat:new-message', message)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
watchEffect(() => {
|
||||||
|
if (authorized.value) {
|
||||||
|
signaling.connect()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
74
new-client/src/shared/composables/use-media-controls.ts
Normal file
74
new-client/src/shared/composables/use-media-controls.ts
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
import { createGlobalState } from '@vueuse/core'
|
||||||
|
import { computed, ref, watch } from 'vue'
|
||||||
|
import { useMediasoup } from './use-mediasoup'
|
||||||
|
import { useProducers } from './use-producers'
|
||||||
|
|
||||||
|
export const useMediaControls = createGlobalState(() => {
|
||||||
|
const { producers } = useMediasoup()
|
||||||
|
const { enableMic, enableVideo, disableVideo, enableShare, disableShare, pauseProducer, resumeProducer } = useProducers()
|
||||||
|
|
||||||
|
const micProducer = computed(() => producers.values.value.find(p => p.appData.source === 'mic'))
|
||||||
|
const cameraProducer = computed(() => producers.values.value.find(p => p.appData.source === 'camera'))
|
||||||
|
const shareProducer = computed(() => producers.values.value.find(p => p.appData.source === 'share'))
|
||||||
|
|
||||||
|
const isSoundEnabled = ref(true)
|
||||||
|
|
||||||
|
const isMicEnabled = computed(() => isSoundEnabled.value && !!micProducer.value && !micProducer.value.paused)
|
||||||
|
const isCameraEnabled = computed(() => !!cameraProducer.value && !cameraProducer.value.paused)
|
||||||
|
const isShareEnabled = computed(() => !!shareProducer.value)
|
||||||
|
|
||||||
|
watch(isSoundEnabled, async (enabled) => {
|
||||||
|
if (!enabled) {
|
||||||
|
if (micProducer.value && !micProducer.value.paused)
|
||||||
|
await pauseProducer(micProducer.value.id)
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
if (micProducer.value?.paused)
|
||||||
|
await resumeProducer(micProducer.value.id)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
async function toggleMic() {
|
||||||
|
if (!isSoundEnabled.value)
|
||||||
|
return
|
||||||
|
|
||||||
|
if (isMicEnabled.value) {
|
||||||
|
await pauseProducer(micProducer.value!.id)
|
||||||
|
}
|
||||||
|
else if (micProducer.value?.paused) {
|
||||||
|
await resumeProducer(micProducer.value.id)
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
await enableMic()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function toggleSound() {
|
||||||
|
isSoundEnabled.value = !isSoundEnabled.value
|
||||||
|
}
|
||||||
|
|
||||||
|
async function toggleCamera() {
|
||||||
|
if (isCameraEnabled.value)
|
||||||
|
await disableVideo()
|
||||||
|
else
|
||||||
|
await enableVideo()
|
||||||
|
}
|
||||||
|
|
||||||
|
async function toggleShare() {
|
||||||
|
if (isShareEnabled.value)
|
||||||
|
await disableShare()
|
||||||
|
else
|
||||||
|
await enableShare()
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
isMicEnabled,
|
||||||
|
isSoundEnabled,
|
||||||
|
isCameraEnabled,
|
||||||
|
isShareEnabled,
|
||||||
|
toggleMic,
|
||||||
|
toggleSound,
|
||||||
|
toggleCamera,
|
||||||
|
toggleShare,
|
||||||
|
}
|
||||||
|
})
|
||||||
119
new-client/src/shared/composables/use-mediasoup.ts
Normal file
119
new-client/src/shared/composables/use-mediasoup.ts
Normal file
@@ -0,0 +1,119 @@
|
|||||||
|
import type { Consumer, Device, Producer, Transport } from 'mediasoup-client/types'
|
||||||
|
import type { Ref } from 'vue'
|
||||||
|
import { createEventHook, createGlobalState } from '@vueuse/core'
|
||||||
|
import { computed, ref, shallowRef } from 'vue'
|
||||||
|
|
||||||
|
interface Snapshot<T> {
|
||||||
|
id: string
|
||||||
|
ref: T
|
||||||
|
[key: string]: any
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ProducerSnapshot extends Snapshot<Producer> {
|
||||||
|
paused: boolean
|
||||||
|
kind: string
|
||||||
|
appData: Record<string, unknown>
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ConsumerSnapshot extends Snapshot<Consumer> {
|
||||||
|
paused: boolean
|
||||||
|
kind: string
|
||||||
|
appData: Record<string, unknown>
|
||||||
|
track: MediaStreamTrack
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface TransportSnapshot extends Snapshot<Transport> {
|
||||||
|
}
|
||||||
|
|
||||||
|
function createEntityStore<T extends { id: string }>() {
|
||||||
|
const store = ref(new Map<string, T>()) as unknown as Ref<Map<string, T>>
|
||||||
|
|
||||||
|
const onAddEvent = createEventHook<T>()
|
||||||
|
const onRemoveEvent = createEventHook<string>()
|
||||||
|
|
||||||
|
const values = computed(() => Array.from(store.value.values()))
|
||||||
|
|
||||||
|
async function add(entity: T) {
|
||||||
|
store.value.set(entity.id, entity)
|
||||||
|
// eslint-disable-next-line ts/ban-ts-comment
|
||||||
|
// @ts-expect-error
|
||||||
|
await onAddEvent.trigger(entity)
|
||||||
|
}
|
||||||
|
|
||||||
|
async function remove(id: string) {
|
||||||
|
store.value.delete(id)
|
||||||
|
await onRemoveEvent.trigger(id)
|
||||||
|
}
|
||||||
|
|
||||||
|
function get(id: string) {
|
||||||
|
return store.value.get(id)
|
||||||
|
}
|
||||||
|
|
||||||
|
function clear() {
|
||||||
|
store.value.clear()
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
store,
|
||||||
|
values,
|
||||||
|
onAdd: onAddEvent.on,
|
||||||
|
onRemove: onRemoveEvent.on,
|
||||||
|
add,
|
||||||
|
remove,
|
||||||
|
get,
|
||||||
|
clear,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useMediasoup = createGlobalState(() => {
|
||||||
|
const device = shallowRef<Device>()
|
||||||
|
const sendTransport = shallowRef<Transport>()
|
||||||
|
const recvTransport = shallowRef<Transport>()
|
||||||
|
|
||||||
|
const producers = createEntityStore<ProducerSnapshot>()
|
||||||
|
producers.onAdd((producer) => {
|
||||||
|
producer.ref.observer.on('pause', () => {
|
||||||
|
producers.store.value.set(producer.id, { ...producer, paused: true })
|
||||||
|
})
|
||||||
|
producer.ref.observer.on('resume', () => {
|
||||||
|
producers.store.value.set(producer.id, { ...producer, paused: false })
|
||||||
|
})
|
||||||
|
producer.ref.observer.on('close', () => producers.remove(producer.id))
|
||||||
|
})
|
||||||
|
|
||||||
|
const consumers = createEntityStore<ConsumerSnapshot>()
|
||||||
|
consumers.onAdd((consumer) => {
|
||||||
|
consumer.ref.on('trackended', () => consumer.ref.close())
|
||||||
|
consumer.ref.observer.on('pause', () => {
|
||||||
|
consumers.store.value.set(consumer.id, { ...consumer, paused: true })
|
||||||
|
})
|
||||||
|
consumer.ref.observer.on('resume', () => {
|
||||||
|
consumers.store.value.set(consumer.id, { ...consumer, paused: false })
|
||||||
|
})
|
||||||
|
consumer.ref.observer.on('close', () => consumers.remove(consumer.id))
|
||||||
|
})
|
||||||
|
|
||||||
|
const transports = createEntityStore<TransportSnapshot>()
|
||||||
|
transports.onAdd((transport) => {
|
||||||
|
transport.ref.observer.on('close', () => transports.remove(transport.id))
|
||||||
|
})
|
||||||
|
|
||||||
|
function clearAll() {
|
||||||
|
device.value = undefined
|
||||||
|
sendTransport.value = undefined
|
||||||
|
recvTransport.value = undefined
|
||||||
|
producers.clear()
|
||||||
|
consumers.clear()
|
||||||
|
transports.clear()
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
device,
|
||||||
|
sendTransport,
|
||||||
|
recvTransport,
|
||||||
|
producers,
|
||||||
|
consumers,
|
||||||
|
transports,
|
||||||
|
clearAll,
|
||||||
|
}
|
||||||
|
})
|
||||||
164
new-client/src/shared/composables/use-producers.ts
Normal file
164
new-client/src/shared/composables/use-producers.ts
Normal file
@@ -0,0 +1,164 @@
|
|||||||
|
import type { MediaKind, ProducerOptions } from 'mediasoup-client/types'
|
||||||
|
import { createGlobalState } from '@vueuse/core'
|
||||||
|
import { markRaw } from 'vue'
|
||||||
|
import { useMediasoup } from './use-mediasoup'
|
||||||
|
import { useSignaling } from './use-signaling'
|
||||||
|
|
||||||
|
export const useProducers = createGlobalState(() => {
|
||||||
|
const { socket } = useSignaling()
|
||||||
|
const { device, sendTransport, producers } = useMediasoup()
|
||||||
|
|
||||||
|
async function createProducer(options: ProducerOptions) {
|
||||||
|
if (!sendTransport.value || !device.value || !options.track)
|
||||||
|
return
|
||||||
|
|
||||||
|
if (!device.value.canProduce(options.track.kind as MediaKind))
|
||||||
|
return
|
||||||
|
|
||||||
|
const producer = await sendTransport.value.produce({ disableTrackOnPause: true, ...options })
|
||||||
|
|
||||||
|
producer.observer.on('trackended', () => disableProducer(producer.id))
|
||||||
|
|
||||||
|
await producers.add({
|
||||||
|
id: producer.id,
|
||||||
|
paused: producer.paused,
|
||||||
|
kind: producer.kind,
|
||||||
|
appData: producer.appData as Record<string, unknown>,
|
||||||
|
ref: markRaw(producer),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
async function disableProducer(producerId: string) {
|
||||||
|
const snap = producers.get(producerId)
|
||||||
|
if (!snap)
|
||||||
|
return
|
||||||
|
|
||||||
|
try {
|
||||||
|
snap.ref.close()
|
||||||
|
await socket.value?.emitWithAck('close-producer', { producerId })
|
||||||
|
}
|
||||||
|
catch {}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function pauseProducer(producerId: string) {
|
||||||
|
const snap = producers.get(producerId)
|
||||||
|
if (!snap || snap.paused)
|
||||||
|
return
|
||||||
|
|
||||||
|
try {
|
||||||
|
snap.ref.pause()
|
||||||
|
await socket.value?.emitWithAck('pause-producer', { producerId })
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
snap.ref.resume()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function resumeProducer(producerId: string) {
|
||||||
|
const snap = producers.get(producerId)
|
||||||
|
if (!snap || !snap.paused)
|
||||||
|
return
|
||||||
|
|
||||||
|
try {
|
||||||
|
snap.ref.resume()
|
||||||
|
await socket.value?.emitWithAck('resume-producer', { producerId })
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
snap.ref.pause()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function enableMic() {
|
||||||
|
for (const snap of producers.store.value.values()) {
|
||||||
|
if (snap.kind === 'audio')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const stream = await navigator.mediaDevices.getUserMedia({ audio: true })
|
||||||
|
const track = stream.getAudioTracks()[0]
|
||||||
|
if (!track)
|
||||||
|
return
|
||||||
|
|
||||||
|
await createProducer({
|
||||||
|
track,
|
||||||
|
streamId: 'mic-video',
|
||||||
|
codecOptions: { opusStereo: true, opusDtx: true },
|
||||||
|
appData: { source: 'mic' },
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
async function disableMic() {
|
||||||
|
for (const snap of producers.store.value.values()) {
|
||||||
|
if (snap.kind === 'audio')
|
||||||
|
await disableProducer(snap.id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function enableVideo() {
|
||||||
|
for (const snap of producers.store.value.values()) {
|
||||||
|
if (snap.kind === 'video' && snap.appData.source !== 'share')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const stream = await navigator.mediaDevices.getUserMedia({
|
||||||
|
video: { width: { ideal: 1920 }, height: { ideal: 1080 }, frameRate: { ideal: 60 } },
|
||||||
|
})
|
||||||
|
const track = stream.getVideoTracks()[0]
|
||||||
|
if (!track)
|
||||||
|
return
|
||||||
|
|
||||||
|
await createProducer({
|
||||||
|
track,
|
||||||
|
streamId: 'mic-video',
|
||||||
|
appData: { source: 'camera' },
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
async function disableVideo() {
|
||||||
|
for (const snap of producers.store.value.values()) {
|
||||||
|
if (snap.kind === 'video' && snap.appData.source !== 'share')
|
||||||
|
await disableProducer(snap.id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function enableShare() {
|
||||||
|
for (const snap of producers.store.value.values()) {
|
||||||
|
if (snap.appData.source === 'share')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const stream = await navigator.mediaDevices.getDisplayMedia({
|
||||||
|
audio: false,
|
||||||
|
video: { displaySurface: 'monitor' },
|
||||||
|
})
|
||||||
|
const track = stream.getVideoTracks()[0]
|
||||||
|
if (!track)
|
||||||
|
return
|
||||||
|
|
||||||
|
await createProducer({
|
||||||
|
track,
|
||||||
|
streamId: 'share',
|
||||||
|
zeroRtpOnPause: true,
|
||||||
|
appData: { source: 'share' },
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
async function disableShare() {
|
||||||
|
for (const snap of producers.store.value.values()) {
|
||||||
|
if (snap.appData.source === 'share')
|
||||||
|
await disableProducer(snap.id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
enableMic,
|
||||||
|
disableMic,
|
||||||
|
enableVideo,
|
||||||
|
disableVideo,
|
||||||
|
enableShare,
|
||||||
|
disableShare,
|
||||||
|
pauseProducer,
|
||||||
|
resumeProducer,
|
||||||
|
disableProducer,
|
||||||
|
}
|
||||||
|
})
|
||||||
@@ -0,0 +1,101 @@
|
|||||||
|
PRAGMA defer_foreign_keys=ON;
|
||||||
|
PRAGMA foreign_keys=OFF;
|
||||||
|
|
||||||
|
-- =====================
|
||||||
|
-- Channel
|
||||||
|
-- =====================
|
||||||
|
CREATE TABLE "new_Channel" (
|
||||||
|
"id" TEXT NOT NULL PRIMARY KEY,
|
||||||
|
"ownerId" TEXT,
|
||||||
|
"ownerUsername" TEXT,
|
||||||
|
"name" TEXT NOT NULL,
|
||||||
|
"persistent" BOOLEAN NOT NULL,
|
||||||
|
CONSTRAINT "Channel_ownerUsername_fkey" FOREIGN KEY ("ownerUsername") REFERENCES "User" ("username") ON DELETE CASCADE ON UPDATE CASCADE
|
||||||
|
);
|
||||||
|
INSERT INTO "new_Channel" ("id", "name", "ownerId", "ownerUsername", "persistent")
|
||||||
|
SELECT c."id", c."name", c."ownerId",
|
||||||
|
(SELECT u."username" FROM "User" u WHERE u."id" = c."ownerId"),
|
||||||
|
c."persistent"
|
||||||
|
FROM "Channel" c;
|
||||||
|
DROP TABLE "Channel";
|
||||||
|
ALTER TABLE "new_Channel" RENAME TO "Channel";
|
||||||
|
|
||||||
|
-- =====================
|
||||||
|
-- Message
|
||||||
|
-- =====================
|
||||||
|
CREATE TABLE "new_Message" (
|
||||||
|
"id" TEXT NOT NULL PRIMARY KEY,
|
||||||
|
"text" TEXT NOT NULL,
|
||||||
|
"senderId" TEXT,
|
||||||
|
"senderUsername" TEXT,
|
||||||
|
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
"updatedAt" DATETIME NOT NULL,
|
||||||
|
CONSTRAINT "Message_senderUsername_fkey" FOREIGN KEY ("senderUsername") REFERENCES "User" ("username") ON DELETE SET NULL ON UPDATE CASCADE
|
||||||
|
);
|
||||||
|
INSERT INTO "new_Message" ("id", "text", "senderId", "senderUsername", "createdAt", "updatedAt")
|
||||||
|
SELECT m."id", m."text", m."senderId",
|
||||||
|
(SELECT u."username" FROM "User" u WHERE u."id" = m."senderId"),
|
||||||
|
m."createdAt", m."updatedAt"
|
||||||
|
FROM "Message" m;
|
||||||
|
DROP TABLE "Message";
|
||||||
|
ALTER TABLE "new_Message" RENAME TO "Message";
|
||||||
|
|
||||||
|
-- =====================
|
||||||
|
-- Session
|
||||||
|
-- =====================
|
||||||
|
CREATE TABLE "new_Session" (
|
||||||
|
"id" TEXT NOT NULL PRIMARY KEY,
|
||||||
|
"userId" TEXT NOT NULL,
|
||||||
|
"username" TEXT NOT NULL,
|
||||||
|
"expiresAt" DATETIME NOT NULL,
|
||||||
|
CONSTRAINT "Session_username_fkey" FOREIGN KEY ("username") REFERENCES "User" ("username") ON DELETE CASCADE ON UPDATE CASCADE
|
||||||
|
);
|
||||||
|
INSERT INTO "new_Session" ("id", "userId", "username", "expiresAt")
|
||||||
|
SELECT s."id", s."userId",
|
||||||
|
(SELECT u."username" FROM "User" u WHERE u."id" = s."userId"),
|
||||||
|
s."expiresAt"
|
||||||
|
FROM "Session" s;
|
||||||
|
DROP TABLE "Session";
|
||||||
|
ALTER TABLE "new_Session" RENAME TO "Session";
|
||||||
|
CREATE INDEX "Session_userId_idx" ON "Session"("userId");
|
||||||
|
|
||||||
|
-- =====================
|
||||||
|
-- User
|
||||||
|
-- =====================
|
||||||
|
CREATE TABLE "new_User" (
|
||||||
|
"id" TEXT NOT NULL,
|
||||||
|
"username" TEXT NOT NULL PRIMARY KEY,
|
||||||
|
"password" TEXT NOT NULL,
|
||||||
|
"displayName" TEXT NOT NULL,
|
||||||
|
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
"updatedAt" DATETIME NOT NULL
|
||||||
|
);
|
||||||
|
INSERT INTO "new_User" ("createdAt", "displayName", "id", "password", "updatedAt", "username")
|
||||||
|
SELECT "createdAt", "displayName", "id", "password", "updatedAt", "username"
|
||||||
|
FROM "User";
|
||||||
|
DROP TABLE "User";
|
||||||
|
ALTER TABLE "new_User" RENAME TO "User";
|
||||||
|
CREATE UNIQUE INDEX "User_username_key" ON "User"("username");
|
||||||
|
|
||||||
|
-- =====================
|
||||||
|
-- UserPreferences
|
||||||
|
-- =====================
|
||||||
|
CREATE TABLE "new_UserPreferences" (
|
||||||
|
"userId" TEXT NOT NULL,
|
||||||
|
"username" TEXT NOT NULL PRIMARY KEY,
|
||||||
|
"toggleInputHotkey" TEXT DEFAULT '',
|
||||||
|
"toggleOutputHotkey" TEXT DEFAULT '',
|
||||||
|
CONSTRAINT "UserPreferences_username_fkey" FOREIGN KEY ("username") REFERENCES "User" ("username") ON DELETE CASCADE ON UPDATE CASCADE
|
||||||
|
);
|
||||||
|
INSERT INTO "new_UserPreferences" ("userId", "username", "toggleInputHotkey", "toggleOutputHotkey")
|
||||||
|
SELECT up."userId",
|
||||||
|
(SELECT u."username" FROM "User" u WHERE u."id" = up."userId"),
|
||||||
|
up."toggleInputHotkey", up."toggleOutputHotkey"
|
||||||
|
FROM "UserPreferences" up;
|
||||||
|
DROP TABLE "UserPreferences";
|
||||||
|
ALTER TABLE "new_UserPreferences" RENAME TO "UserPreferences";
|
||||||
|
CREATE UNIQUE INDEX "UserPreferences_userId_key" ON "UserPreferences"("userId");
|
||||||
|
CREATE UNIQUE INDEX "UserPreferences_username_key" ON "UserPreferences"("username");
|
||||||
|
|
||||||
|
PRAGMA foreign_keys=ON;
|
||||||
|
PRAGMA defer_foreign_keys=OFF;
|
||||||
@@ -0,0 +1,67 @@
|
|||||||
|
/*
|
||||||
|
Warnings:
|
||||||
|
|
||||||
|
- You are about to drop the column `ownerId` on the `Channel` table. All the data in the column will be lost.
|
||||||
|
- You are about to drop the column `senderId` on the `Message` table. All the data in the column will be lost.
|
||||||
|
- You are about to drop the column `userId` on the `Session` table. All the data in the column will be lost.
|
||||||
|
- You are about to drop the column `id` on the `User` table. All the data in the column will be lost.
|
||||||
|
- You are about to drop the column `userId` on the `UserPreferences` table. All the data in the column will be lost.
|
||||||
|
|
||||||
|
*/
|
||||||
|
-- RedefineTables
|
||||||
|
PRAGMA defer_foreign_keys=ON;
|
||||||
|
PRAGMA foreign_keys=OFF;
|
||||||
|
CREATE TABLE "new_Channel" (
|
||||||
|
"id" TEXT NOT NULL PRIMARY KEY,
|
||||||
|
"ownerUsername" TEXT,
|
||||||
|
"name" TEXT NOT NULL,
|
||||||
|
"persistent" BOOLEAN NOT NULL,
|
||||||
|
CONSTRAINT "Channel_ownerUsername_fkey" FOREIGN KEY ("ownerUsername") REFERENCES "User" ("username") ON DELETE CASCADE ON UPDATE CASCADE
|
||||||
|
);
|
||||||
|
INSERT INTO "new_Channel" ("id", "name", "ownerUsername", "persistent") SELECT "id", "name", "ownerUsername", "persistent" FROM "Channel";
|
||||||
|
DROP TABLE "Channel";
|
||||||
|
ALTER TABLE "new_Channel" RENAME TO "Channel";
|
||||||
|
CREATE TABLE "new_Message" (
|
||||||
|
"id" TEXT NOT NULL PRIMARY KEY,
|
||||||
|
"text" TEXT NOT NULL,
|
||||||
|
"senderUsername" TEXT,
|
||||||
|
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
"updatedAt" DATETIME NOT NULL,
|
||||||
|
CONSTRAINT "Message_senderUsername_fkey" FOREIGN KEY ("senderUsername") REFERENCES "User" ("username") ON DELETE SET NULL ON UPDATE CASCADE
|
||||||
|
);
|
||||||
|
INSERT INTO "new_Message" ("createdAt", "id", "senderUsername", "text", "updatedAt") SELECT "createdAt", "id", "senderUsername", "text", "updatedAt" FROM "Message";
|
||||||
|
DROP TABLE "Message";
|
||||||
|
ALTER TABLE "new_Message" RENAME TO "Message";
|
||||||
|
CREATE TABLE "new_Session" (
|
||||||
|
"id" TEXT NOT NULL PRIMARY KEY,
|
||||||
|
"username" TEXT NOT NULL,
|
||||||
|
"expiresAt" DATETIME NOT NULL,
|
||||||
|
CONSTRAINT "Session_username_fkey" FOREIGN KEY ("username") REFERENCES "User" ("username") ON DELETE CASCADE ON UPDATE CASCADE
|
||||||
|
);
|
||||||
|
INSERT INTO "new_Session" ("expiresAt", "id", "username") SELECT "expiresAt", "id", "username" FROM "Session";
|
||||||
|
DROP TABLE "Session";
|
||||||
|
ALTER TABLE "new_Session" RENAME TO "Session";
|
||||||
|
CREATE INDEX "Session_username_idx" ON "Session"("username");
|
||||||
|
CREATE TABLE "new_User" (
|
||||||
|
"username" TEXT NOT NULL PRIMARY KEY,
|
||||||
|
"password" TEXT NOT NULL,
|
||||||
|
"displayName" TEXT NOT NULL,
|
||||||
|
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
"updatedAt" DATETIME NOT NULL
|
||||||
|
);
|
||||||
|
INSERT INTO "new_User" ("createdAt", "displayName", "password", "updatedAt", "username") SELECT "createdAt", "displayName", "password", "updatedAt", "username" FROM "User";
|
||||||
|
DROP TABLE "User";
|
||||||
|
ALTER TABLE "new_User" RENAME TO "User";
|
||||||
|
CREATE UNIQUE INDEX "User_username_key" ON "User"("username");
|
||||||
|
CREATE TABLE "new_UserPreferences" (
|
||||||
|
"username" TEXT NOT NULL PRIMARY KEY,
|
||||||
|
"toggleInputHotkey" TEXT DEFAULT '',
|
||||||
|
"toggleOutputHotkey" TEXT DEFAULT '',
|
||||||
|
CONSTRAINT "UserPreferences_username_fkey" FOREIGN KEY ("username") REFERENCES "User" ("username") ON DELETE CASCADE ON UPDATE CASCADE
|
||||||
|
);
|
||||||
|
INSERT INTO "new_UserPreferences" ("toggleInputHotkey", "toggleOutputHotkey", "username") SELECT "toggleInputHotkey", "toggleOutputHotkey", "username" FROM "UserPreferences";
|
||||||
|
DROP TABLE "UserPreferences";
|
||||||
|
ALTER TABLE "new_UserPreferences" RENAME TO "UserPreferences";
|
||||||
|
CREATE UNIQUE INDEX "UserPreferences_username_key" ON "UserPreferences"("username");
|
||||||
|
PRAGMA foreign_keys=ON;
|
||||||
|
PRAGMA defer_foreign_keys=OFF;
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
/*
|
||||||
|
Warnings:
|
||||||
|
|
||||||
|
- You are about to drop the column `username` on the `Session` table. All the data in the column will be lost.
|
||||||
|
- Added the required column `userId` to the `Session` table without a default value. This is not possible if the table is not empty.
|
||||||
|
|
||||||
|
*/
|
||||||
|
-- RedefineTables
|
||||||
|
PRAGMA defer_foreign_keys=ON;
|
||||||
|
PRAGMA foreign_keys=OFF;
|
||||||
|
CREATE TABLE "new_Session" (
|
||||||
|
"id" TEXT NOT NULL PRIMARY KEY,
|
||||||
|
"userId" TEXT NOT NULL,
|
||||||
|
"expiresAt" DATETIME NOT NULL,
|
||||||
|
CONSTRAINT "Session_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User" ("username") ON DELETE CASCADE ON UPDATE CASCADE
|
||||||
|
);
|
||||||
|
INSERT INTO "new_Session" ("expiresAt", "id") SELECT "expiresAt", "id" FROM "Session";
|
||||||
|
DROP TABLE "Session";
|
||||||
|
ALTER TABLE "new_Session" RENAME TO "Session";
|
||||||
|
PRAGMA foreign_keys=ON;
|
||||||
|
PRAGMA defer_foreign_keys=OFF;
|
||||||
Reference in New Issue
Block a user