Skip to content

Commit b91294c

Browse files
authored
Merge pull request #45 from huanghuang358/main
2 parents eb98ae3 + c2d30f6 commit b91294c

3 files changed

Lines changed: 73 additions & 0 deletions

File tree

webapp/components/player/player.tsx

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,9 @@ import {
1616
SvgExitFullscreen,
1717
SvgPictureInPicture,
1818
SvgExitPictureInPicture,
19+
SvgMic,
1920
} from '../svg/player'
21+
import VolumeExtractor from '../use/volumeExtraction'
2022

2123
function AudioWave(props: { stream: MediaStream }) {
2224
const refWave = useRef<HTMLDivElement>(null)
@@ -43,6 +45,37 @@ function AudioWave(props: { stream: MediaStream }) {
4345
return <div ref={refWave}></div>
4446
}
4547

48+
function Mic(props: { stream: MediaStream }) {
49+
const [volumeValue, setVolumeValue] = useState(0)
50+
const rest = 100 - volumeValue
51+
useEffect(() => {
52+
let done = false
53+
if (props.stream.getAudioTracks().length) {
54+
const extractor = new VolumeExtractor(props.stream)
55+
function updateVolume() {
56+
const value = extractor.calculateVolume()
57+
setVolumeValue(value)
58+
if (!done) requestAnimationFrame(updateVolume)
59+
}
60+
updateVolume()
61+
}
62+
return(() => {
63+
done = true
64+
})
65+
}, [props.stream])
66+
return (
67+
<div
68+
className="absolute top-0 right-0 rounded-xl p-1 m-2 transition-opacity duration-300"
69+
style={{
70+
background: `linear-gradient(to bottom, white ${rest}%, red ${volumeValue}%)`,
71+
opacity: volumeValue > 3 ? '1' : '0'
72+
}}
73+
>
74+
<SvgMic />
75+
</div>
76+
)
77+
}
78+
4679
export default function Player(props: { stream: MediaStream, muted: boolean, audio?: boolean, video?: boolean, width: string }) {
4780
const refVideo = useRef<HTMLVideoElement>(null)
4881
const [showAudio, setShowAudio] = useState(false)
@@ -193,6 +226,7 @@ export default function Player(props: { stream: MediaStream, muted: boolean, aud
193226
? <AudioWave stream={props.stream} />
194227
: null
195228
}
229+
<Mic stream={props.stream} />
196230
<div
197231
className={`absolute bottom-0 left-0 right-0 rounded-b-xl px-4 py-3 flex justify-between items-center transition-opacity duration-300 ${
198232
showControls ? 'opacity-100' : 'opacity-0 pointer-events-none'

webapp/components/svg/player.tsx

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,4 +44,12 @@ export function SvgExitPictureInPicture() {
4444
<path d="M896 128a42.666667 42.666667 0 0 1 42.666667 42.666667v298.666666h-85.333334V213.333333H170.666667v597.333334h256v85.333333H128a42.666667 42.666667 0 0 1-42.666667-42.666667V170.666667a42.666667 42.666667 0 0 1 42.666667-42.666667h768z m0 426.666667a42.666667 42.666667 0 0 1 42.666667 42.666666v256a42.666667 42.666667 0 0 1-42.666667 42.666667h-341.333333a42.666667 42.666667 0 0 1-42.666667-42.666667v-256a42.666667 42.666667 0 0 1 42.666667-42.666666h341.333333z m-405.333333-256L403.498667 385.834667l96 96-60.330667 60.330666-96-96L256 533.333333V298.666667h234.666667z"></path>
4545
</svg>
4646
)
47+
}
48+
49+
export function SvgMic() {
50+
return(
51+
<svg viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" width="24" height="24">
52+
<path d="M512 128a128 128 0 0 0-128 128v170.666667a128 128 0 1 0 256 0V256a128 128 0 0 0-128-128z m0-85.333333a213.333333 213.333333 0 0 1 213.333333 213.333333v170.666667a213.333333 213.333333 0 1 1-426.666666 0V256a213.333333 213.333333 0 0 1 213.333333-213.333333zM130.346667 469.333333H216.32a298.752 298.752 0 0 0 591.274667 0h86.016A384.170667 384.170667 0 0 1 554.666667 808.32V981.333333h-85.333334v-173.013333A384.170667 384.170667 0 0 1 130.346667 469.333333z" p-id="3352"></path>
53+
</svg>
54+
)
4755
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
export default class VolumeExtractor {
2+
private audioContext: AudioContext
3+
private source: MediaStreamAudioSourceNode
4+
private analyser: AnalyserNode
5+
private dataArray: Uint8Array
6+
private bufferLength: number
7+
8+
constructor(stream: MediaStream) {
9+
this.audioContext = new AudioContext()
10+
this.source = this.audioContext.createMediaStreamSource(stream)
11+
this.analyser = this.audioContext.createAnalyser()
12+
this.analyser.fftSize = 512
13+
this.bufferLength = this.analyser.frequencyBinCount
14+
this.dataArray = new Uint8Array(this.bufferLength)
15+
this.source.connect(this.analyser)
16+
}
17+
18+
public calculateVolume = () => {
19+
this.analyser.getByteTimeDomainData(this.dataArray)
20+
let sum = 0
21+
for (let i = 0; i < this.bufferLength; i++) {
22+
const val = this.dataArray[i] - 128
23+
sum += val * val
24+
}
25+
const rms = Math.sqrt(sum / this.bufferLength)
26+
const normalized = rms / 127
27+
const perceptual = Math.log10(1 + 9 * normalized)
28+
const volumeValue = Math.round(perceptual * 100)
29+
return volumeValue
30+
}
31+
}

0 commit comments

Comments
 (0)