diff --git a/plugins/love-resources/src/components/CamSettingPopup.svelte b/plugins/love-resources/src/components/CamSettingPopup.svelte deleted file mode 100644 index aa0986eb39..0000000000 --- a/plugins/love-resources/src/components/CamSettingPopup.svelte +++ /dev/null @@ -1,79 +0,0 @@ - - -
-
- - diff --git a/plugins/love-resources/src/components/ControlBar.svelte b/plugins/love-resources/src/components/ControlBar.svelte index afe5dbc022..a5711ce818 100644 --- a/plugins/love-resources/src/components/ControlBar.svelte +++ b/plugins/love-resources/src/components/ControlBar.svelte @@ -29,20 +29,18 @@ SplitButton, eventToHTMLElement, showPopup, - type AnySvelteComponent, TooltipInstance } from '@hcengineering/ui' import view, { Action } from '@hcengineering/view' import { getActions } from '@hcengineering/view-resources' + import { toggleCamState, toggleMicState } from '@hcengineering/media-resources' import love from '../plugin' import { currentRoom, myInfo, myOffice } from '../stores' import { - isCamAllowed, isCameraEnabled, isConnected, isFullScreen, - isMicAllowed, isMicEnabled, isRecording, isRecordingAvailable, @@ -53,20 +51,16 @@ leaveRoom, record, screenSharing, - setCam, - setMic, setShare, startTranscription, stopTranscription } from '../utils' - import CamSettingPopup from './CamSettingPopup.svelte' + import CamSettingPopup from './meeting/CamSettingPopup.svelte' import ControlBarContainer from './ControlBarContainer.svelte' - import MicSettingPopup from './MicSettingPopup.svelte' + import MicSettingPopup from './meeting/MicSettingPopup.svelte' import RoomAccessPopup from './RoomAccessPopup.svelte' - import RoomLanguageSelector from './RoomLanguageSelector.svelte' import RoomModal from './RoomModal.svelte' import ShareSettingPopup from './ShareSettingPopup.svelte' - import { Room as LKRoom } from 'livekit-client' export let room: Room export let canMaximize: boolean = true @@ -81,14 +75,6 @@ $: allowCam = $currentRoom?.type === RoomType.Video $: allowLeave = $myInfo?.room !== ($myOffice?._id ?? love.ids.Reception) - async function changeMute (): Promise { - await setMic(!$isMicEnabled) - } - - async function changeCam (): Promise { - await setCam(!$isCameraEnabled) - } - async function changeShare (): Promise { const newValue = !$isSharingEnabled const audio = newValue && $isShareWithSound @@ -183,11 +169,10 @@ size={'large'} icon={$isMicEnabled ? love.icon.MicEnabled : love.icon.MicDisabled} showTooltip={{ - label: !$isMicAllowed ? love.string.MicPermission : $isMicEnabled ? love.string.Mute : love.string.UnMute, + label: $isMicEnabled ? love.string.Mute : love.string.UnMute, keys: micKeys }} - action={changeMute} - disabled={!$isMicAllowed} + action={toggleMicState} secondIcon={IconUpOutline} secondAction={micSettings} separate @@ -197,15 +182,10 @@ size={'large'} icon={$isCameraEnabled ? love.icon.CamEnabled : love.icon.CamDisabled} showTooltip={{ - label: !$isCamAllowed - ? love.string.CamPermission - : $isCameraEnabled - ? love.string.StopVideo - : love.string.StartVideo, + label: $isCameraEnabled ? love.string.StopVideo : love.string.StartVideo, keys: camKeys }} - disabled={!$isCamAllowed} - action={changeCam} + action={toggleCamState} secondIcon={IconUpOutline} secondAction={camSettings} separate diff --git a/plugins/love-resources/src/components/MicSettingPopup.svelte b/plugins/love-resources/src/components/MicSettingPopup.svelte deleted file mode 100644 index c1eabe09bb..0000000000 --- a/plugins/love-resources/src/components/MicSettingPopup.svelte +++ /dev/null @@ -1,120 +0,0 @@ - - -
-
- - diff --git a/plugins/love-resources/src/components/RoomPopup.svelte b/plugins/love-resources/src/components/RoomPopup.svelte index 0381991ff2..f2a3d413e8 100644 --- a/plugins/love-resources/src/components/RoomPopup.svelte +++ b/plugins/love-resources/src/components/RoomPopup.svelte @@ -45,28 +45,25 @@ import { getClient } from '@hcengineering/presentation' import view from '@hcengineering/view' import { getObjectLinkFragment } from '@hcengineering/view-resources' + import { toggleCamState, toggleMicState } from '@hcengineering/media-resources' import { createEventDispatcher } from 'svelte' import love from '../plugin' import { currentMeetingMinutes, currentRoom, infos, invites, myInfo, myOffice, myRequests, rooms } from '../stores' import { endMeeting, getRoomName, - isCamAllowed, isCameraEnabled, isConnected, - isMicAllowed, isMicEnabled, isShareWithSound, isSharingEnabled, leaveRoom, screenSharing, - setCam, - setMic, setShare, tryConnect } from '../utils' - import CamSettingPopup from './CamSettingPopup.svelte' - import MicSettingPopup from './MicSettingPopup.svelte' + import CamSettingPopup from './meeting/CamSettingPopup.svelte' + import MicSettingPopup from './meeting/MicSettingPopup.svelte' import RoomAccessPopup from './RoomAccessPopup.svelte' import ShareSettingPopup from './ShareSettingPopup.svelte' @@ -98,14 +95,6 @@ const dispatch = createEventDispatcher() - async function changeMute (): Promise { - await setMic(!$isMicEnabled) - } - - async function changeCam (): Promise { - await setCam(!$isCameraEnabled) - } - async function changeShare (): Promise { const newValue = !$isSharingEnabled const audio = newValue && $isShareWithSound @@ -206,10 +195,9 @@ icon={$isMicEnabled ? love.icon.MicEnabled : love.icon.MicDisabled} label={$isMicEnabled ? love.string.Mute : love.string.UnMute} showTooltip={{ - label: !$isMicAllowed ? love.string.MicPermission : $isMicEnabled ? love.string.Mute : love.string.UnMute + label: $isMicEnabled ? love.string.Mute : love.string.UnMute }} - disabled={!$isMicAllowed} - action={changeMute} + action={toggleMicState} secondIcon={IconUpOutline} secondAction={micSettings} separate @@ -220,14 +208,9 @@ icon={$isCameraEnabled ? love.icon.CamEnabled : love.icon.CamDisabled} label={$isCameraEnabled ? love.string.StopVideo : love.string.StartVideo} showTooltip={{ - label: !$isCamAllowed - ? love.string.CamPermission - : $isCameraEnabled - ? love.string.StopVideo - : love.string.StartVideo + label: $isCameraEnabled ? love.string.StopVideo : love.string.StartVideo }} - disabled={!$isCamAllowed} - action={changeCam} + action={toggleCamState} secondIcon={IconUpOutline} secondAction={camSettings} separate diff --git a/plugins/love-resources/src/components/meeting/CamSettingPopup.svelte b/plugins/love-resources/src/components/meeting/CamSettingPopup.svelte new file mode 100644 index 0000000000..b362da2088 --- /dev/null +++ b/plugins/love-resources/src/components/meeting/CamSettingPopup.svelte @@ -0,0 +1,52 @@ + + +
+ + {#if blurProcessor !== undefined} +
+
+ + diff --git a/plugins/love-resources/src/components/meeting/MicSettingPopup.svelte b/plugins/love-resources/src/components/meeting/MicSettingPopup.svelte new file mode 100644 index 0000000000..d681758959 --- /dev/null +++ b/plugins/love-resources/src/components/meeting/MicSettingPopup.svelte @@ -0,0 +1,61 @@ + + +
+ + + {#if isKrispNoiseFilterSupported()} +
+
+ {/if} +
+ + diff --git a/plugins/love-resources/src/utils.ts b/plugins/love-resources/src/utils.ts index c17c1d72f7..422440e82b 100644 --- a/plugins/love-resources/src/utils.ts +++ b/plugins/love-resources/src/utils.ts @@ -40,8 +40,8 @@ import { RoomType, TranscriptionStatus } from '@hcengineering/love' -import { getSelectedCamId, getSelectedMicId, getSelectedSpeakerId } from '@hcengineering/media' -import { useMedia } from '@hcengineering/media-resources' +import { getSelectedSpeakerId } from '@hcengineering/media' +import { getSelectedCam, getSelectedMic, getSelectedSpeaker, useMedia } from '@hcengineering/media-resources' import { getEmbeddedLabel, getMetadata, getResource, type IntlString } from '@hcengineering/platform' import presentation, { copyTextToClipboard, @@ -50,14 +50,7 @@ import presentation, { getClient, type ObjectSearchResult } from '@hcengineering/presentation' -import { - closePanel, - type DropdownTextItem, - getCurrentLocation, - navigate, - panelstore, - showPopup -} from '@hcengineering/ui' +import { closePanel, getCurrentLocation, navigate, panelstore, showPopup } from '@hcengineering/ui' import view from '@hcengineering/view' import { getObjectLinkFragment } from '@hcengineering/view-resources' import { type Widget, type WidgetTab } from '@hcengineering/workbench' @@ -192,8 +185,6 @@ export const isCameraEnabled = writable(false) export const isSharingEnabled = writable(false) export const isFullScreen = writable(false) export const isShareWithSound = writable(false) -export const isMicAllowed = writable(false) -export const isCamAllowed = writable(false) export const currentRoomAudioLevels = writable, number>>(new Map()) @@ -640,21 +631,16 @@ export async function setCam (value: boolean): Promise { if ($isCurrentInstanceConnected) { try { const opt: VideoCaptureOptions = {} - const selectedDevice = getSelectedCamId() - const devices = await LKRoom.getLocalDevices('videoinput') - isCamAllowed.set(devices.length > 0) - if (selectedDevice !== null) { - const available = devices.find((p) => p.deviceId === selectedDevice) - if (available !== undefined) { - // We need to use exact device id to avoid issues with Firefox - // that can switch to another device when using just deviceId - opt.deviceId = { exact: available.deviceId } - } + const selectedDevice = await getSelectedCam() + if (selectedDevice !== null && selectedDevice !== undefined) { + // We need to use exact device id to avoid issues with Firefox + // that can switch to another device when using just deviceId + opt.deviceId = { exact: selectedDevice.deviceId } + await lk.switchActiveDevice('videoinput', selectedDevice.deviceId) } await lk.localParticipant.setCameraEnabled(value, opt) } catch (err) { console.error(err) - isCamAllowed.set(false) } } else { sendMessage({ type: 'set_cam', value }) @@ -664,34 +650,24 @@ export async function setCam (value: boolean): Promise { export async function setMic (value: boolean): Promise { if ($isCurrentInstanceConnected) { try { - const speaker = getSelectedSpeakerId() - if (speaker !== undefined) { - const devices = await LKRoom.getLocalDevices('audiooutput') - const available = devices.find((p) => p.deviceId === speaker) - if (available !== undefined) { - await lk.switchActiveDevice('audiooutput', speaker) - } + const speaker = await getSelectedSpeaker() + if (speaker !== null && speaker !== undefined) { + await lk.switchActiveDevice('audiooutput', speaker.deviceId) } } catch (err) { console.error(err) } try { const opt: AudioCaptureOptions = {} - const selectedDevice = getSelectedMicId() - const devices = await LKRoom.getLocalDevices('audioinput') - isMicAllowed.set(devices.length > 0) - if (selectedDevice !== null) { - const available = devices.find((p) => p.deviceId === selectedDevice) - if (available !== undefined) { - // We need to use exact device id to avoid issues with Firefox - // that can switch to another device when using just deviceId - opt.deviceId = { exact: available.deviceId } - } + const selectedDevice = await getSelectedMic() + if (selectedDevice !== null && selectedDevice !== undefined) { + // We need to use exact device id to avoid issues with Firefox + // that can switch to another device when using just deviceId + opt.deviceId = { exact: selectedDevice.deviceId } } await lk.localParticipant.setMicrophoneEnabled(value, opt) } catch (err) { console.error(err) - isMicAllowed.set(false) } } else { sendMessage({ type: 'set_mic', value }) @@ -973,22 +949,6 @@ export async function invite (person: Ref, room: Ref | undefined): }) } -export function getActive ( - devices: DropdownTextItem[], - activeId: string | undefined, - prevId?: string | null -): DropdownTextItem { - if (activeId !== undefined) { - const res = devices.find((p) => p.id === activeId) - if (res !== undefined) return res - } - if (prevId != null) { - const res = devices.find((p) => p.id === prevId) - if (res !== undefined) return res - } - return devices[0] -} - export async function toggleMic (): Promise { await setMic(!get(isMicEnabled)) } diff --git a/plugins/media-resources/src/components/CamStateButton.svelte b/plugins/media-resources/src/components/CamStateButton.svelte index 49721d34d9..695dd374d4 100644 --- a/plugins/media-resources/src/components/CamStateButton.svelte +++ b/plugins/media-resources/src/components/CamStateButton.svelte @@ -17,26 +17,19 @@ import { Icon, tooltip } from '@hcengineering/ui' import media from '../plugin' - import { sessions } from '../stores' import IconCamOn from './icons/CamOn.svelte' import IconCamOff from './icons/CamOff.svelte' + import { toggleCamState } from '../utils' export let state: CamState | undefined - - function handleClick (): void { - const enabled = state?.enabled ?? false - for (const session of $sessions) { - session.emit('camera', !enabled) - } - } {#if state != null} diff --git a/plugins/media-resources/src/components/MicStateButton.svelte b/plugins/media-resources/src/components/MicStateButton.svelte index 4dc174ec19..f68cc75f1b 100644 --- a/plugins/media-resources/src/components/MicStateButton.svelte +++ b/plugins/media-resources/src/components/MicStateButton.svelte @@ -17,26 +17,19 @@ import { Icon, tooltip } from '@hcengineering/ui' import media from '../plugin' - import { sessions } from '../stores' import IconMicOn from './icons/MicOn.svelte' import IconMicOff from './icons/MicOff.svelte' + import { toggleMicState } from '../utils' export let state: MicState | undefined - - function handleClick (): void { - const enabled = state?.enabled ?? false - for (const session of $sessions) { - session.emit('microphone', !enabled) - } - } {#if state != null} diff --git a/plugins/media-resources/src/index.ts b/plugins/media-resources/src/index.ts index aa37aa7475..86a854dda6 100644 --- a/plugins/media-resources/src/index.ts +++ b/plugins/media-resources/src/index.ts @@ -17,6 +17,9 @@ import { type Resources } from '@hcengineering/platform' import MediaExt from './components/MediaExt.svelte' import WorkbenchExtension from './components/WorkbenchExtension.svelte' +import MediaPopupCamSelector from './components/MediaPopupCamSelector.svelte' +import MediaPopupMicSelector from './components/MediaPopupMicSelector.svelte' +import MediaPopupSpkSelector from './components/MediaPopupSpkSelector.svelte' export * from './stores' export * from './utils' @@ -24,6 +27,9 @@ export * from './utils' export default async (): Promise => ({ component: { MediaExt, + MediaPopupCamSelector, + MediaPopupMicSelector, + MediaPopupSpkSelector, WorkbenchExtension } }) diff --git a/plugins/media-resources/src/utils.ts b/plugins/media-resources/src/utils.ts index 36d7e922b7..bdc728a38b 100644 --- a/plugins/media-resources/src/utils.ts +++ b/plugins/media-resources/src/utils.ts @@ -29,7 +29,8 @@ import { type IntlString, getEmbeddedLabel } from '@hcengineering/platform' import EventEmitter from 'events' import { onDestroy } from 'svelte' import type TypedEventEmitter from 'typed-emitter' -import { registerSession, unregisterSession } from './stores' +import { registerSession, sessions, state, unregisterSession } from './stores' +import { get } from 'svelte/store' /** @public */ export async function getSelectedMic (): Promise { @@ -67,6 +68,16 @@ export function getDeviceLabel (device: MediaDeviceInfo): IntlString { return getEmbeddedLabel(cleanupDeviceLabel(device.label)) } +/** @public */ +export function toggleCamState (): void { + toggleDeviceState('camera') +} + +/** @public */ +export function toggleMicState (): void { + toggleDeviceState('microphone') +} + export interface UseMediaOptions { state: MediaState autoDestroy?: boolean @@ -86,6 +97,14 @@ export function useMedia (options: UseMediaOptions): MediaSession { return session } +function toggleDeviceState (kind: 'camera' | 'microphone'): void { + const deviceState: CamState | MicState | undefined = kind === 'camera' ? get(state).camera : get(state).microphone + const enabled = deviceState?.enabled ?? false + for (const session of get(sessions)) { + session.emit(kind, !enabled) + } +} + class MediaSessionImpl extends (EventEmitter as unknown as new () => TypedEventEmitter) implements MediaSession { diff --git a/plugins/media/src/plugin.ts b/plugins/media/src/plugin.ts index 19baf44b7e..9946d27f13 100644 --- a/plugins/media/src/plugin.ts +++ b/plugins/media/src/plugin.ts @@ -15,12 +15,17 @@ import type { Plugin, Asset, IntlString } from '@hcengineering/platform' import { plugin } from '@hcengineering/platform' -import { type ComponentExtensionId } from '@hcengineering/ui' +import { type AnyComponent, type ComponentExtensionId } from '@hcengineering/ui' /** @public */ export const mediaId = 'media' as Plugin export const mediaPlugin = plugin(mediaId, { + component: { + MediaPopupCamSelector: '' as AnyComponent, + MediaPopupMicSelector: '' as AnyComponent, + MediaPopupSpkSelector: '' as AnyComponent + }, extension: { StateIndicator: '' as ComponentExtensionId, StateContext: '' as ComponentExtensionId