From 1c55850296ecd14c2f19d5227baf89a9dac8a9cf Mon Sep 17 00:00:00 2001 From: Florian Raith <37345813+FlorianRaith@users.noreply.github.com> Date: Sun, 3 Sep 2023 12:37:48 +0200 Subject: [PATCH] Add setting to mute everyone (#79) * Add setting to mute everyone * Format code --- src/components/Browser.vue | 1 - src/composables/channel/browser.ts | 4 -- src/composables/channel/channel.ts | 81 ++++++++++++++++++++++++++++-- src/composables/channel/webcam.ts | 16 +++++- src/views/RoomView.vue | 58 ++++++++++++++++++++- 5 files changed, 149 insertions(+), 11 deletions(-) diff --git a/src/components/Browser.vue b/src/components/Browser.vue index a597d76..5f9f666 100644 --- a/src/components/Browser.vue +++ b/src/components/Browser.vue @@ -291,7 +291,6 @@ watch( () => channel.browser.browserStream, (stream) => { - console.log(stream); if (stream) { isStreaming.value = true; browserVideo.value!.srcObject = stream; diff --git a/src/composables/channel/browser.ts b/src/composables/channel/browser.ts index 4732a13..22a593d 100644 --- a/src/composables/channel/browser.ts +++ b/src/composables/channel/browser.ts @@ -16,11 +16,9 @@ export const useBrowser = () => { const browserPeer: Peer = new Peer(); browserPeer.on('call', (call) => { - console.log('calling'); call.answer(); call.on('stream', (stream) => { - console.log('streaming'); browserStream.value = stream; }); }); @@ -33,8 +31,6 @@ export const useBrowser = () => { socket = newSocket; socket.on('open-website', (newPeerId: string) => { - console.log('connecting to peer', newPeerId); - peerId.value = newPeerId; loadBrowserStream(); }); diff --git a/src/composables/channel/channel.ts b/src/composables/channel/channel.ts index b5b6ec6..aabafc5 100644 --- a/src/composables/channel/channel.ts +++ b/src/composables/channel/channel.ts @@ -59,11 +59,12 @@ export interface Teacher extends ChannelUser { * @property students - The list of students that are connected to the room */ export interface JoinRoomResult { - room: Room; + room: Room & { channelId: string }; browserPeerId: string; browserUrl: string; teacher: Teacher; students: Student[]; + settings: Settings; } /** @@ -88,6 +89,15 @@ export interface ChannelState { hasName: boolean; notes: Notes | null; whiteboard: Whiteboard | null; + settings: Settings; +} + +/** + * The settings object that is used to represent the settings of the channel. + * @property globalMute - Whether all students are muted or not + */ +export interface Settings { + globalMute: boolean; } /** @@ -112,6 +122,9 @@ export const useChannel = defineStore('channel', () => { hasName: false, notes: null, whiteboard: null, + settings: { + globalMute: false, + }, } as ChannelState); /** @@ -191,6 +204,13 @@ export const useChannel = defineStore('channel', () => { }); } + function toggleGlobalMute(): void { + socket?.emit('update-settings', { + ...state.settings, + globalMute: !state.settings.globalMute, + }); + } + /** * Retrieves a user (either teacher or student) by their ID. * @param id - The ID of the user to retrieve. @@ -281,7 +301,7 @@ export const useChannel = defineStore('channel', () => { roomId: room.id, }; - socket?.emit('open-room', payload, async (result: any) => { + socket?.emit('open-room', payload, async (result: JoinRoomResult) => { state.connected = true; state.channelId = result.room.channelId; state.clientId = socket?.id || ''; @@ -295,6 +315,7 @@ export const useChannel = defineStore('channel', () => { }; state.hasName = true; state.whiteboard = new Whiteboard(socket!, result.room.whiteboardCanvas); + state.settings = result.settings; browser.peerId.value = ''; browser.setUrl('www.google.com'); @@ -332,11 +353,15 @@ export const useChannel = defineStore('channel', () => { */ async function joinAsStudent(id: string, password?: string): Promise<void> { await connect(); - return join(id, 'join-room-as-student', { + await join(id, 'join-room-as-student', { channelId: id, name: 'Verbinden...', password, }); + + if (state.settings.globalMute && currentUser().audio) { + webcam.toggleAudio(); + } } /** @@ -364,6 +389,7 @@ export const useChannel = defineStore('channel', () => { state.room = data.room; state.hasName = false; state.whiteboard = new Whiteboard(socket!, data.room.whiteboardCanvas); + state.settings = data.settings; browser.peerId.value = data.browserPeerId; browser.setUrl(data.browserUrl); @@ -442,6 +468,9 @@ export const useChannel = defineStore('channel', () => { state.students = []; state.teacher = null; state.hasName = false; + state.settings = { + globalMute: false, + }; if (router.currentRoute.value.name === 'room') { if (auth.isLoggedIn) { @@ -541,6 +570,51 @@ export const useChannel = defineStore('channel', () => { } }, ); + + socket.on('disable-audio-for', (id: string) => { + const student = studentById(id); + + if (student) { + student.audio = false; + } + }); + + socket.on('update-settings', (settings: Settings) => { + const oldSettings = { ...state.settings }; + state.settings = settings; + + if (!oldSettings.globalMute && settings.globalMute) { + state.students + .filter((s) => s.audio) + .forEach((s) => webcam.disableAudioFor(s)); + + if (isStudent(currentUser())) { + alerts.primary( + 'Stummschaltung', + 'Der Lehrer hat alle Mikrofone stummgeschaltet.', + ); + } else { + alerts.primary( + 'Stummschaltung', + 'Sie haben alle Mikrofone stummgeschaltet.', + ); + } + } + + if (oldSettings.globalMute && !settings.globalMute) { + if (isStudent(currentUser())) { + alerts.primary( + 'Stummschaltung', + 'Der Lehrer hat erlaubt alle Mikrofone wieder zu aktivieren.', + ); + } else { + alerts.primary( + 'Stummschaltung', + 'Sie haben erlaubt alle Mikrofone wieder zu aktivieren.', + ); + } + } + }); } return { @@ -564,5 +638,6 @@ export const useChannel = defineStore('channel', () => { loadNotes, toggleHandSignal, updatePermission, + toggleGlobalMute, }; }); diff --git a/src/composables/channel/webcam.ts b/src/composables/channel/webcam.ts index 9c77cf7..f28fda4 100644 --- a/src/composables/channel/webcam.ts +++ b/src/composables/channel/webcam.ts @@ -1,7 +1,7 @@ import Peer from 'peerjs'; import { reactive } from 'vue'; import { Socket } from 'socket.io-client'; -import { ChannelUser } from '@/composables/channel/channel'; +import { ChannelUser, Student } from '@/composables/channel/channel'; export const useWebcam = () => { let webcamsLoaded = false; @@ -121,6 +121,19 @@ export const useWebcam = () => { }); } + function disableAudioFor(student: Student): void { + const stream = streams[student.id]; + + if (!student.audio) { + return; + } + + student.audio = false; + + stream?.getAudioTracks().forEach((track) => (track.enabled = false)); + socket?.emit('disable-audio-for', { userId: student.id }); + } + /** * Stops the webcam streaming of the current user and removes all other users' streams. */ @@ -145,6 +158,7 @@ export const useWebcam = () => { getStream, toggleVideo, toggleAudio, + disableAudioFor, stop, }; }; diff --git a/src/views/RoomView.vue b/src/views/RoomView.vue index 8a3b82a..c12367d 100644 --- a/src/views/RoomView.vue +++ b/src/views/RoomView.vue @@ -82,7 +82,39 @@ <i class="fa fa-link me-1"></i> Teilen </button> + <button + v-if=" + channel.state.teacher && channel.isSelf(channel.state.teacher) + " + class="btn btn-outline-primary btn-sm" + data-bs-toggle="dropdown" + aria-expanded="false" + > + <i class="fa fa-gear"></i> + </button> + + <ul class="dropdown-menu"> + <li> + <button + class="dropdown-item" + @click="channel.toggleGlobalMute()" + v-text=" + !channel.state.settings.globalMute + ? 'Schüler stummschalten' + : 'Stummschaltung aufheben' + " + ></button> + </li> + </ul> </div> + + <small + v-if="channel.state.settings.globalMute" + class="text-warning mt-2" + > + <i class="fa fa-circle-exclamation me-1"></i> + Alle Schüler sind stummgeschaltet + </small> </div> <div @@ -222,7 +254,13 @@ <button type="button" - class="btn text-primary w-100" + class="btn w-100" + :class=" + channel.state.settings.globalMute && + channel.isStudent(channel.currentUser()) + ? 'text-danger' + : 'text-primary' + " @click="toggleAudio()" > <i @@ -264,7 +302,9 @@ import BrowserComponent from '@/components/Browser.vue'; import { ask } from '@/composables/prompt'; import { useRouter } from 'vue-router'; + import { useAlerts } from '@/composables/alerts'; + const alerts = useAlerts(); const auth = useAuth(); const channel = useChannel(); const router = useRouter(); @@ -335,8 +375,19 @@ /** * Function that toggles the audio transmission status through the channel using 'channel.toggleAudio()'. */ - function toggleAudio() { + if ( + channel.state.settings.globalMute && + channel.isStudent(channel.currentUser()) + ) { + alerts.danger( + 'Du kannst dein Mikrofon nicht aktivieren', + 'Der Lehrer hat alle Mikrofone deaktiviert.', + ); + + return; + } + channel.webcam.toggleAudio(); } @@ -490,14 +541,17 @@ #webcam-wrapper { scrollbar-color: #999 #333; } + #webcam-wrapper::-webkit-scrollbar { width: 10px; } + #webcam-wrapper::-webkit-scrollbar-thumb { background: #999; border-radius: 12px; margin: 1rem; } + #webcam-wrapper::-webkit-scrollbar-track { background: #333; border-radius: 12px; -- GitLab