Skip to content

Commit

Permalink
Speaker button (#22)
Browse files Browse the repository at this point in the history
* speaker button

* npx eslint  --fix

* finish speaker button

* use atom to finish speaker button

* Fixed an alert that did not allow trailing Spaces

* Delete a blank line from player.tsx

* add disable/enable function

* el.muted = !SpeakerStatus
  • Loading branch information
xdzqyyds authored Nov 21, 2024
1 parent 975478e commit 002fbad
Show file tree
Hide file tree
Showing 4 changed files with 75 additions and 1 deletion.
52 changes: 52 additions & 0 deletions webapp/components/device.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
import useWhipClient from './use/whip'
import { useAtom } from 'jotai'
import { useEffect, useState } from 'react'
import {
Device,
deviceNone,
deviceScreen,
} from '../lib/device'
import { deviceSpeakerAtom, SpeakerStatusAtom } from './../store/atom'

import Loading from './svg/loading'
import SvgSpeaker from './svg/speaker'
import SvgAudio from './svg/audio'
import SvgVideo from './svg/video'
import { SvgPresentCancel, SvgPresentToAll } from './svg/present'
Expand All @@ -24,10 +27,14 @@ export default function DeviceBar(props: { streamId: string }) {
const [permissionAudio, setPermissionAudio] = useState('')
const [permissionVideo, setPermissionVideo] = useState('')

const [loadingSpeaker, setLoadingSpeaker] = useState(false)
const [loadingAudio, setLoadingAudio] = useState(false)
const [loadingVideo, setLoadingVideo] = useState(false)
const [loadingScreen, setLoadingScreen] = useState(false)

const [currentDeviceSpeaker, setCurrentDeviceSpeaker] = useAtom(deviceSpeakerAtom)
const [SpeakerStatus, setSpeakerStatus] = useAtom(SpeakerStatusAtom)

const {
userStatus,
currentDeviceAudio,
Expand All @@ -38,6 +45,7 @@ export default function DeviceBar(props: { streamId: string }) {
toggleEnableVideo,
} = useWhipClient(props.streamId)

const [deviceSpeaker, setDeviceSpeaker] = useState<Device[]>([deviceNone])
const [deviceAudio, setDeviceAudio] = useState<Device[]>([deviceNone])
const [deviceVideo, setDeviceVideo] = useState<Device[]>([deviceNone])

Expand Down Expand Up @@ -82,9 +90,15 @@ export default function DeviceBar(props: { streamId: string }) {

const devices = (await navigator.mediaDevices.enumerateDevices()).filter(i => !!i.deviceId)

const speakers = devices.filter(i => i.kind === 'audiooutput').map(toDevice)
const audios = devices.filter(i => i.kind === 'audioinput').map(toDevice)
const videos = devices.filter(i => i.kind === 'videoinput').map(toDevice)

if (currentDeviceSpeaker === deviceNone.deviceId) {
const device = speakers[0]
if (device) setCurrentDeviceSpeaker(device.deviceId)
}

if (currentDeviceAudio === deviceNone.deviceId) {
const device = audios[0]
if (device) await setCurrentDeviceAudio(device.deviceId)
Expand All @@ -95,6 +109,7 @@ export default function DeviceBar(props: { streamId: string }) {
if (device) await setCurrentDeviceVideo(device.deviceId)
}

setDeviceSpeaker([...speakers])
setDeviceAudio([...audios])
setDeviceVideo([...videos, deviceScreen])
}
Expand Down Expand Up @@ -130,6 +145,12 @@ export default function DeviceBar(props: { streamId: string }) {
return () => { navigator.mediaDevices.removeEventListener('devicechange', updateDeviceList) }
}, [])

const onChangedDeviceSpeaker = async (current: string) => {
setLoadingSpeaker(true)
setCurrentDeviceSpeaker(current)
setLoadingSpeaker(false)
}

const onChangedDeviceAudio = async (current: string) => {
setLoadingAudio(true)
await setCurrentDeviceAudio(current)
Expand All @@ -151,6 +172,37 @@ export default function DeviceBar(props: { streamId: string }) {
return (
<div className="flex flex-row flex-wrap justify-around p-xs">
<center className="flex flex-row flex-wrap justify-around">
<section className="m-1 p-1 flex flex-row justify-center rounded-md border-1 border-indigo-500">
<button className="text-rose-400 rounded-md w-8 h-8" onClick={async () => {
setLoadingSpeaker(true)
setSpeakerStatus((prev) => !prev)
setLoadingSpeaker(false)
}}>
<center>{ loadingSpeaker ? <Loading/> : <SvgSpeaker/> }</center>
</button>
<div className="flex flex-col justify-between w-1 pointer-events-none">
<div></div>
{SpeakerStatus
? <div></div>
: <div className="w-8 h-1 bg-red-500 rounded-full rotate-45"
style={{
position: 'relative',
right: '32px',
bottom: '14px',
}}></div>
}
</div>
<select
className="w-3.5 h-8 rounded-sm rotate-180"
value={currentDeviceSpeaker}
onChange={e => onChangedDeviceSpeaker(e.target.value)}
>
{deviceSpeaker.map(device =>
<option key={device.deviceId} value={device.deviceId}>{device.label}</option>
)}
</select>
</section>

<section className="m-1 p-1 flex flex-row justify-center rounded-md border-1 border-indigo-500">
<button className="text-rose-400 rounded-md w-8 h-8" onClick={async () => {
setLoadingAudio(true)
Expand Down
12 changes: 11 additions & 1 deletion webapp/components/player/player.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import { useEffect, useRef, useState } from 'react'
import { useAtom } from 'jotai'
import WaveSurfer from 'wavesurfer.js'
import RecordPlugin from 'wavesurfer.js/dist/plugins/record'
import { isWechat } from '../../lib/util'
import SvgProgress from '../svg/progress'
import { deviceSpeakerAtom, SpeakerStatusAtom } from '../../store/atom'

function AudioWave(props: { stream: MediaStream }) {
const refWave = useRef<HTMLDivElement>(null)
Expand Down Expand Up @@ -34,6 +36,8 @@ export default function Player(props: { stream: MediaStream, muted: boolean, aud
const [showAudio, setShowAudio] = useState(false)
const audioTrack = props.stream.getAudioTracks()[0]
const videoTrack = props.stream.getVideoTracks()[0]
const [currentDeviceSpeaker] = useAtom(deviceSpeakerAtom)
const [SpeakerStatus] = useAtom(SpeakerStatusAtom)

useEffect(() => {
if (audioTrack && !videoTrack) {
Expand All @@ -44,6 +48,12 @@ export default function Player(props: { stream: MediaStream, muted: boolean, aud
if (audioTrack && props.audio) {
const el = document.createElement('audio')
el.srcObject = new MediaStream([audioTrack])

if (el.setSinkId) {
el.setSinkId(currentDeviceSpeaker)
}

el.muted = !SpeakerStatus
el.play()

return () => {
Expand All @@ -52,7 +62,7 @@ export default function Player(props: { stream: MediaStream, muted: boolean, aud
el.remove()
}
}
}, [audioTrack, videoTrack])
}, [audioTrack, videoTrack, currentDeviceSpeaker, SpeakerStatus])

useEffect(() => {
if (refVideo.current && videoTrack) {
Expand Down
7 changes: 7 additions & 0 deletions webapp/components/svg/speaker.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export default function SvgSpeaker() {
return (
<svg focusable="false" width="24" height="24" viewBox="0 0 24 24">
<path fill="currentColor" d="M4 7h4l5-5v20l-5-5H4c-1.1 0-2-.9-2-2V9c0-1.1.9-2 2-2zm11.54 3.88l1.41-1.41c1.78 1.78 1.78 4.66 0 6.44l-1.41-1.41c1.17-1.17 1.17-3.07 0-4.24zm2.83-2.83l1.41-1.41c3.12 3.12 3.12 8.19 0 11.31l-1.41-1.41c2.34-2.34 2.34-6.13 0-8.49z"></path>
</svg>
)
}
5 changes: 5 additions & 0 deletions webapp/store/atom.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,13 +35,18 @@ presentationStreamAtom.debugLabel = 'presentationStream'
const enabledPresentationAtom = atom(get => get(presentationStreamAtom).stream.getVideoTracks().length !== 0)
enabledPresentationAtom.debugLabel = 'enabledPresentation'

const deviceSpeakerAtom = atom<string>('')
const SpeakerStatusAtom = atom<boolean>(true)

export {
locationAtom,
presentationStreamAtom,

meetingIdAtom,
meetingJoinedAtom,
enabledPresentationAtom,
deviceSpeakerAtom,
SpeakerStatusAtom,
}

export type {
Expand Down

0 comments on commit 002fbad

Please sign in to comment.