mirror of
https://github.com/hcengineering/platform.git
synced 2026-09-12 12:47:45 +02:00
Refactor device management in the office (#9656)
Signed-off-by: Anton Alexeyev <alexeyev.anton@gmail.com>
This commit is contained in:
@@ -1,79 +0,0 @@
|
||||
<script lang="ts">
|
||||
import { getSelectedCamId, updateSelectedCamId } from '@hcengineering/media'
|
||||
import { translate } from '@hcengineering/platform'
|
||||
import { DropdownLabels, DropdownTextItem, Label, Progress, Toggle } from '@hcengineering/ui'
|
||||
import { Room } from 'livekit-client'
|
||||
import love from '../plugin'
|
||||
import { myPreferences } from '../stores'
|
||||
import { blurProcessor, getActive, lk, updateBlurRadius } from '../utils'
|
||||
|
||||
void Room.getLocalDevices().then(async (devices) => {
|
||||
devices.forEach((device) => {
|
||||
if (device.kind === 'videoinput') {
|
||||
cameras.push({ label: device.label, id: device.deviceId })
|
||||
}
|
||||
})
|
||||
if (cameras.length === 0) {
|
||||
cameras.push({ label: await translate(love.string.DefaultDevice, {}), id: 'default' })
|
||||
}
|
||||
cameras = cameras
|
||||
})
|
||||
|
||||
let cameras: DropdownTextItem[] = []
|
||||
|
||||
$: blurRadius = $myPreferences?.blurRadius ?? 0
|
||||
$: activeCamera = getActive(cameras, lk.getActiveDevice('videoinput'), getSelectedCamId())
|
||||
</script>
|
||||
|
||||
<div class="antiPopup p-4 grid">
|
||||
<Label label={love.string.Camera} />
|
||||
<DropdownLabels
|
||||
items={cameras}
|
||||
placeholder={love.string.Camera}
|
||||
enableSearch={false}
|
||||
justify={'left'}
|
||||
width={'100%'}
|
||||
disabled={cameras.length === 0}
|
||||
selected={activeCamera?.id}
|
||||
on:selected={async (item) => {
|
||||
if (item.detail != null && item.detail !== 'default') {
|
||||
await lk.switchActiveDevice('videoinput', item.detail)
|
||||
updateSelectedCamId(item.detail)
|
||||
activeCamera = cameras.find((p) => p.id === item.detail) ?? cameras[0]
|
||||
}
|
||||
}}
|
||||
/>
|
||||
{#if blurProcessor !== undefined}
|
||||
<Label label={love.string.Blur} />
|
||||
<Toggle
|
||||
showTooltip={{ label: love.string.BlurTooltip }}
|
||||
on={blurRadius >= 0.5}
|
||||
on:change={(e) => {
|
||||
updateBlurRadius(e.detail ? 0.5 : 0)
|
||||
}}
|
||||
/>
|
||||
{#if blurRadius >= 0.5}
|
||||
<Label label={love.string.BlurRadius} />
|
||||
<Progress
|
||||
editable
|
||||
max={10}
|
||||
min={0.5}
|
||||
value={blurRadius}
|
||||
on:change={(e) => {
|
||||
const value = Math.round(e.detail * 2) / 2
|
||||
updateBlurRadius(value)
|
||||
}}
|
||||
/>
|
||||
{/if}
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<style lang="scss">
|
||||
.grid {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr auto;
|
||||
row-gap: 1rem;
|
||||
column-gap: 1rem;
|
||||
align-items: center;
|
||||
}
|
||||
</style>
|
||||
@@ -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<void> {
|
||||
await setMic(!$isMicEnabled)
|
||||
}
|
||||
|
||||
async function changeCam (): Promise<void> {
|
||||
await setCam(!$isCameraEnabled)
|
||||
}
|
||||
|
||||
async function changeShare (): Promise<void> {
|
||||
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
|
||||
|
||||
@@ -1,120 +0,0 @@
|
||||
<script lang="ts">
|
||||
import core, { getCurrentAccount } from '@hcengineering/core'
|
||||
import { DevicesPreference } from '@hcengineering/love'
|
||||
import {
|
||||
getSelectedMicId,
|
||||
getSelectedSpeakerId,
|
||||
updateSelectedMicId,
|
||||
updateSelectedSpeakerId
|
||||
} from '@hcengineering/media'
|
||||
import { translate } from '@hcengineering/platform'
|
||||
import { getClient } from '@hcengineering/presentation'
|
||||
import { DropdownLabels, DropdownTextItem, Label, Toggle } from '@hcengineering/ui'
|
||||
import { isKrispNoiseFilterSupported } from '@livekit/krisp-noise-filter'
|
||||
import { Room } from 'livekit-client'
|
||||
import love from '../plugin'
|
||||
import { myPreferences } from '../stores'
|
||||
import { getActive, krispProcessor, lk } from '../utils'
|
||||
|
||||
void Room.getLocalDevices().then(async (devices) => {
|
||||
devices.forEach((device) => {
|
||||
if (device.deviceId !== 'default') {
|
||||
if (device.kind === 'audiooutput') {
|
||||
speakers.push({ label: device.label, id: device.deviceId })
|
||||
} else if (device.kind === 'audioinput') {
|
||||
mics.push({ label: device.label, id: device.deviceId })
|
||||
}
|
||||
}
|
||||
})
|
||||
if (speakers.length === 0) {
|
||||
speakers.push({ label: await translate(love.string.DefaultDevice, {}), id: 'default' })
|
||||
}
|
||||
if (mics.length === 0) {
|
||||
mics.push({ label: await translate(love.string.DefaultDevice, {}), id: 'default' })
|
||||
}
|
||||
speakers = speakers
|
||||
mics = mics
|
||||
})
|
||||
|
||||
let speakers: DropdownTextItem[] = []
|
||||
let mics: DropdownTextItem[] = []
|
||||
|
||||
$: activeSpeaker = getActive(speakers, lk.getActiveDevice('audiooutput'), getSelectedSpeakerId())
|
||||
$: activeMic = getActive(mics, lk.getActiveDevice('audioinput'), getSelectedMicId())
|
||||
|
||||
const client = getClient()
|
||||
|
||||
async function saveNoiseCancellationPreference (
|
||||
myPreferences: DevicesPreference | undefined,
|
||||
value: boolean
|
||||
): Promise<void> {
|
||||
if (myPreferences !== undefined) {
|
||||
await client.update(myPreferences, { noiseCancellation: value })
|
||||
} else {
|
||||
const acc = getCurrentAccount().uuid
|
||||
await client.createDoc(love.class.DevicesPreference, core.space.Workspace, {
|
||||
attachedTo: acc,
|
||||
noiseCancellation: value,
|
||||
camEnabled: true,
|
||||
micEnabled: true,
|
||||
blurRadius: 0
|
||||
})
|
||||
}
|
||||
await krispProcessor.setEnabled(value)
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="antiPopup p-4 grid">
|
||||
<Label label={love.string.Speaker} />
|
||||
<DropdownLabels
|
||||
selected={activeSpeaker?.id}
|
||||
items={speakers}
|
||||
enableSearch={false}
|
||||
justify={'left'}
|
||||
width={'100%'}
|
||||
disabled={speakers.length === 0}
|
||||
on:selected={async (item) => {
|
||||
if (item.detail != null && item.detail !== 'default') {
|
||||
await lk.switchActiveDevice('audiooutput', item.detail)
|
||||
updateSelectedSpeakerId(item.detail)
|
||||
activeSpeaker = speakers.find((p) => p.id === item.detail) ?? speakers[0]
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<Label label={love.string.Microphone} />
|
||||
<DropdownLabels
|
||||
items={mics}
|
||||
placeholder={love.string.Microphone}
|
||||
selected={activeMic?.id}
|
||||
enableSearch={false}
|
||||
justify={'left'}
|
||||
disabled={mics.length === 0}
|
||||
width={'100%'}
|
||||
on:selected={async (item) => {
|
||||
if (item.detail != null && item.detail !== 'default') {
|
||||
await lk.switchActiveDevice('audioinput', item.detail)
|
||||
updateSelectedMicId(item.detail)
|
||||
activeMic = mics.find((p) => p.id === item.detail) ?? mics[0]
|
||||
}
|
||||
}}
|
||||
/>
|
||||
{#if isKrispNoiseFilterSupported()}
|
||||
<Label label={love.string.NoiseCancellation} />
|
||||
<Toggle
|
||||
on={$myPreferences?.noiseCancellation ?? true}
|
||||
on:change={(e) => {
|
||||
saveNoiseCancellationPreference($myPreferences, e.detail)
|
||||
}}
|
||||
/>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<style lang="scss">
|
||||
.grid {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr auto;
|
||||
row-gap: 1rem;
|
||||
column-gap: 1rem;
|
||||
align-items: center;
|
||||
}
|
||||
</style>
|
||||
@@ -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<void> {
|
||||
await setMic(!$isMicEnabled)
|
||||
}
|
||||
|
||||
async function changeCam (): Promise<void> {
|
||||
await setCam(!$isCameraEnabled)
|
||||
}
|
||||
|
||||
async function changeShare (): Promise<void> {
|
||||
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
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
<script lang="ts">
|
||||
import { Component, Label, Progress, Toggle } from '@hcengineering/ui'
|
||||
import love from '../../plugin'
|
||||
import { myPreferences } from '../../stores'
|
||||
import { blurProcessor, updateBlurRadius } from '../../utils'
|
||||
import mediaPlugin from '@hcengineering/media'
|
||||
|
||||
$: blurRadius = $myPreferences?.blurRadius ?? 0
|
||||
</script>
|
||||
|
||||
<div class="antiPopup mediaPopup">
|
||||
<Component is={mediaPlugin.component.MediaPopupCamSelector} />
|
||||
{#if blurProcessor !== undefined}
|
||||
<div class="grid p-3">
|
||||
<Label label={love.string.Blur} />
|
||||
<Toggle
|
||||
showTooltip={{ label: love.string.BlurTooltip }}
|
||||
on={blurRadius >= 0.5}
|
||||
on:change={(e) => {
|
||||
updateBlurRadius(e.detail ? 0.5 : 0)
|
||||
}}
|
||||
/>
|
||||
{#if blurRadius >= 0.5}
|
||||
<Label label={love.string.BlurRadius} />
|
||||
<div class="separator" />
|
||||
<Progress
|
||||
editable
|
||||
max={10}
|
||||
min={0.5}
|
||||
value={blurRadius}
|
||||
on:change={(e) => {
|
||||
const value = Math.round(e.detail * 2) / 2
|
||||
updateBlurRadius(value)
|
||||
}}
|
||||
/>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<style lang="scss">
|
||||
.mediaPopup {
|
||||
width: 20rem;
|
||||
}
|
||||
.grid {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr auto;
|
||||
row-gap: 1rem;
|
||||
column-gap: 1rem;
|
||||
align-items: center;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,61 @@
|
||||
<script lang="ts">
|
||||
import core, { getCurrentAccount } from '@hcengineering/core'
|
||||
import { DevicesPreference } from '@hcengineering/love'
|
||||
import { getClient } from '@hcengineering/presentation'
|
||||
import { Component, Label, Toggle } from '@hcengineering/ui'
|
||||
import { isKrispNoiseFilterSupported } from '@livekit/krisp-noise-filter'
|
||||
import love from '../../plugin'
|
||||
import { myPreferences } from '../../stores'
|
||||
import { krispProcessor } from '../../utils'
|
||||
import mediaPlugin from '@hcengineering/media'
|
||||
|
||||
const client = getClient()
|
||||
|
||||
async function saveNoiseCancellationPreference (
|
||||
myPreferences: DevicesPreference | undefined,
|
||||
value: boolean
|
||||
): Promise<void> {
|
||||
if (myPreferences !== undefined) {
|
||||
await client.update(myPreferences, { noiseCancellation: value })
|
||||
} else {
|
||||
const acc = getCurrentAccount().uuid
|
||||
await client.createDoc(love.class.DevicesPreference, core.space.Workspace, {
|
||||
attachedTo: acc,
|
||||
noiseCancellation: value,
|
||||
camEnabled: true,
|
||||
micEnabled: true,
|
||||
blurRadius: 0
|
||||
})
|
||||
}
|
||||
await krispProcessor.setEnabled(value)
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="antiPopup mediaPopup">
|
||||
<Component is={mediaPlugin.component.MediaPopupMicSelector} />
|
||||
<Component is={mediaPlugin.component.MediaPopupSpkSelector} />
|
||||
{#if isKrispNoiseFilterSupported()}
|
||||
<div class="grid p-3">
|
||||
<Label label={love.string.NoiseCancellation} />
|
||||
<Toggle
|
||||
on={$myPreferences?.noiseCancellation ?? true}
|
||||
on:change={(e) => {
|
||||
saveNoiseCancellationPreference($myPreferences, e.detail)
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<style lang="scss">
|
||||
.mediaPopup {
|
||||
width: 20rem;
|
||||
}
|
||||
.grid {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr auto;
|
||||
row-gap: 1rem;
|
||||
column-gap: 1rem;
|
||||
align-items: center;
|
||||
}
|
||||
</style>
|
||||
@@ -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<boolean>(false)
|
||||
export const isSharingEnabled = writable<boolean>(false)
|
||||
export const isFullScreen = writable<boolean>(false)
|
||||
export const isShareWithSound = writable<boolean>(false)
|
||||
export const isMicAllowed = writable<boolean>(false)
|
||||
export const isCamAllowed = writable<boolean>(false)
|
||||
|
||||
export const currentRoomAudioLevels = writable<Map<Ref<Person>, number>>(new Map())
|
||||
|
||||
@@ -640,21 +631,16 @@ export async function setCam (value: boolean): Promise<void> {
|
||||
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<void> {
|
||||
export async function setMic (value: boolean): Promise<void> {
|
||||
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<Person>, room: Ref<Room> | 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<void> {
|
||||
await setMic(!get(isMicEnabled))
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
{#if state != null}
|
||||
<button
|
||||
class="hulyStatusBarButton mini positive {state.enabled ? 'positiveContent' : 'negativeContent'}"
|
||||
use:tooltip={{ label: state.enabled ? media.string.TurnOffCam : media.string.TurnOnCam, direction: 'bottom' }}
|
||||
on:click={handleClick}
|
||||
on:click={toggleCamState}
|
||||
>
|
||||
<Icon icon={state.enabled ? IconCamOn : IconCamOff} size={'small'} />
|
||||
</button>
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
{#if state != null}
|
||||
<button
|
||||
class="hulyStatusBarButton mini positive {state.enabled ? 'positiveContent' : 'negativeContent'}"
|
||||
use:tooltip={{ label: state.enabled ? media.string.TurnOffMic : media.string.TurnOnMic, direction: 'bottom' }}
|
||||
on:click={handleClick}
|
||||
on:click={toggleMicState}
|
||||
>
|
||||
<Icon icon={state.enabled ? IconMicOn : IconMicOff} size={'small'} />
|
||||
</button>
|
||||
|
||||
@@ -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<Resources> => ({
|
||||
component: {
|
||||
MediaExt,
|
||||
MediaPopupCamSelector,
|
||||
MediaPopupMicSelector,
|
||||
MediaPopupSpkSelector,
|
||||
WorkbenchExtension
|
||||
}
|
||||
})
|
||||
|
||||
@@ -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<MediaDeviceInfo | undefined> {
|
||||
@@ -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<MediaSessionEvents>)
|
||||
implements MediaSession {
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user