@fishjam-cloud/react-client 0.25.3 → 0.26.0-rc.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/hooks/internal/devices/useDeviceManager.d.ts.map +1 -1
- package/dist/hooks/internal/devices/useDeviceManager.js +11 -19
- package/dist/hooks/internal/devices/useMediaDevices.d.ts.map +1 -1
- package/dist/hooks/internal/devices/useMediaDevices.js +15 -5
- package/dist/hooks/useLocalVAD.d.ts +20 -0
- package/dist/hooks/useLocalVAD.d.ts.map +1 -0
- package/dist/hooks/useLocalVAD.js +59 -0
- package/dist/hooks/useVAD.d.ts +12 -4
- package/dist/hooks/useVAD.d.ts.map +1 -1
- package/dist/hooks/useVAD.js +32 -18
- package/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -0
- package/package.json +2 -2
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useDeviceManager.d.ts","sourceRoot":"","sources":["../../../../src/hooks/internal/devices/useDeviceManager.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,0BAA0B,CAAC;AACvD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,OAAO,CAAC;AAG5C,OAAO,KAAK,EAAE,WAAW,EAAE,UAAU,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAMtF,KAAK,kBAAkB,GAAG;IACxB,WAAW,EAAE,WAAW,GAAG,IAAI,CAAC;IAChC,cAAc,EAAE,CAAC,MAAM,EAAE,cAAc,CAAC,WAAW,GAAG,IAAI,CAAC,KAAK,IAAI,CAAC;IACrE,WAAW,EAAE,WAAW,GAAG,IAAI,CAAC;IAChC,cAAc,EAAE,CAAC,MAAM,EAAE,cAAc,CAAC,WAAW,GAAG,IAAI,CAAC,KAAK,IAAI,CAAC;IACrE,gBAAgB,EAAE,MAAM,OAAO,CAAC,WAAW,GAAG,IAAI,CAAC,CAAC;IACpD,UAAU,EAAE,OAAO,GAAG,OAAO,CAAC;IAC9B,cAAc,EAAE,eAAe,EAAE,CAAC;IAClC,WAAW,CAAC,EAAE,qBAAqB,GAAG,OAAO,CAAC;IAC9C,iBAAiB,EAAE,CAAC,MAAM,EAAE,eAAe,KAAK,IAAI,CAAC;IACrD,cAAc,EAAE,eAAe,GAAG,IAAI,CAAC;IACvC,MAAM,EAAE,MAAM,CAAC;CAChB,CAAC;AAEF,MAAM,MAAM,aAAa,GAAG;IAC1B,WAAW,EAAE,CAAC,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,KAAK,OAAO,CAAC,CAAC,gBAAgB,EAAE,IAAI,CAAC,GAAG,CAAC,IAAI,EAAE,WAAW,CAAC,CAAC,CAAC;IACnG,UAAU,EAAE,MAAM,IAAI,CAAC;IACvB,YAAY,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,CAAC,gBAAgB,EAAE,IAAI,CAAC,GAAG,CAAC,IAAI,EAAE,WAAW,CAAC,CAAC,GAAG,SAAS,CAAC;IACxG,YAAY,EAAE,UAAU,GAAG,IAAI,CAAC;IAChC,WAAW,EAAE,gBAAgB,GAAG,IAAI,CAAC;IACrC,UAAU,EAAE,UAAU,EAAE,CAAC;IACzB,aAAa,EAAE,OAAO,CAAC;IACvB,YAAY,EAAE,MAAM,IAAI,CAAC;IACzB,aAAa,EAAE,MAAM,IAAI,CAAC;IAC1B,iBAAiB,EAAE,eAAe,CAAC;IACnC,eAAe,EAAE,CAAC,UAAU,EAAE,eAAe,KAAK,OAAO,CAAC,gBAAgB,GAAG,IAAI,CAAC,CAAC;IACnF,WAAW,EAAE,WAAW,GAAG,IAAI,CAAC;IAChC,cAAc,EAAE,eAAe,GAAG,IAAI,CAAC;CACxC,CAAC;
|
|
1
|
+
{"version":3,"file":"useDeviceManager.d.ts","sourceRoot":"","sources":["../../../../src/hooks/internal/devices/useDeviceManager.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,0BAA0B,CAAC;AACvD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,OAAO,CAAC;AAG5C,OAAO,KAAK,EAAE,WAAW,EAAE,UAAU,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAMtF,KAAK,kBAAkB,GAAG;IACxB,WAAW,EAAE,WAAW,GAAG,IAAI,CAAC;IAChC,cAAc,EAAE,CAAC,MAAM,EAAE,cAAc,CAAC,WAAW,GAAG,IAAI,CAAC,KAAK,IAAI,CAAC;IACrE,WAAW,EAAE,WAAW,GAAG,IAAI,CAAC;IAChC,cAAc,EAAE,CAAC,MAAM,EAAE,cAAc,CAAC,WAAW,GAAG,IAAI,CAAC,KAAK,IAAI,CAAC;IACrE,gBAAgB,EAAE,MAAM,OAAO,CAAC,WAAW,GAAG,IAAI,CAAC,CAAC;IACpD,UAAU,EAAE,OAAO,GAAG,OAAO,CAAC;IAC9B,cAAc,EAAE,eAAe,EAAE,CAAC;IAClC,WAAW,CAAC,EAAE,qBAAqB,GAAG,OAAO,CAAC;IAC9C,iBAAiB,EAAE,CAAC,MAAM,EAAE,eAAe,KAAK,IAAI,CAAC;IACrD,cAAc,EAAE,eAAe,GAAG,IAAI,CAAC;IACvC,MAAM,EAAE,MAAM,CAAC;CAChB,CAAC;AAEF,MAAM,MAAM,aAAa,GAAG;IAC1B,WAAW,EAAE,CAAC,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,KAAK,OAAO,CAAC,CAAC,gBAAgB,EAAE,IAAI,CAAC,GAAG,CAAC,IAAI,EAAE,WAAW,CAAC,CAAC,CAAC;IACnG,UAAU,EAAE,MAAM,IAAI,CAAC;IACvB,YAAY,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,CAAC,gBAAgB,EAAE,IAAI,CAAC,GAAG,CAAC,IAAI,EAAE,WAAW,CAAC,CAAC,GAAG,SAAS,CAAC;IACxG,YAAY,EAAE,UAAU,GAAG,IAAI,CAAC;IAChC,WAAW,EAAE,gBAAgB,GAAG,IAAI,CAAC;IACrC,UAAU,EAAE,UAAU,EAAE,CAAC;IACzB,aAAa,EAAE,OAAO,CAAC;IACvB,YAAY,EAAE,MAAM,IAAI,CAAC;IACzB,aAAa,EAAE,MAAM,IAAI,CAAC;IAC1B,iBAAiB,EAAE,eAAe,CAAC;IACnC,eAAe,EAAE,CAAC,UAAU,EAAE,eAAe,KAAK,OAAO,CAAC,gBAAgB,GAAG,IAAI,CAAC,CAAC;IACnF,WAAW,EAAE,WAAW,GAAG,IAAI,CAAC;IAChC,cAAc,EAAE,eAAe,GAAG,IAAI,CAAC;CACxC,CAAC;AAiBF,eAAO,MAAM,gBAAgB,GAAI,qKAY9B,kBAAkB,KAAG,aA0IvB,CAAC"}
|
|
@@ -1,24 +1,8 @@
|
|
|
1
|
-
import { useCallback, useMemo, useState } from "react";
|
|
1
|
+
import { useCallback, useMemo, useRef, useState } from "react";
|
|
2
2
|
import { parseUserMediaError } from "../../../utils/errors";
|
|
3
3
|
import { getTrackFromStream, stopStream } from "../../../utils/track";
|
|
4
4
|
import { useTrackMiddleware } from "../useTrackMiddleware";
|
|
5
5
|
import { useHandleTrackEnd } from "./useHandleTrackEnd";
|
|
6
|
-
function getReplaceStreamAction(newStream, deviceType) {
|
|
7
|
-
return (oldStream) => {
|
|
8
|
-
if (oldStream) {
|
|
9
|
-
stopStream(oldStream, deviceType);
|
|
10
|
-
}
|
|
11
|
-
return newStream;
|
|
12
|
-
};
|
|
13
|
-
}
|
|
14
|
-
function getStopStreamAction(deviceType) {
|
|
15
|
-
return (oldStream) => {
|
|
16
|
-
if (oldStream) {
|
|
17
|
-
stopStream(oldStream, deviceType);
|
|
18
|
-
}
|
|
19
|
-
return null;
|
|
20
|
-
};
|
|
21
|
-
}
|
|
22
6
|
async function getDeviceStream(type, constraints, deviceId) {
|
|
23
7
|
constraints = typeof constraints === "object" ? constraints : {};
|
|
24
8
|
if (deviceId) {
|
|
@@ -30,6 +14,8 @@ async function getDeviceStream(type, constraints, deviceId) {
|
|
|
30
14
|
return stream;
|
|
31
15
|
}
|
|
32
16
|
export const useDeviceManager = ({ mediaStream, setMediaStream, getInitialStream, deviceType, constraints, allDevicesList, setSelectedDevice, deviceError, setDeviceError, selectedDevice, logger, }) => {
|
|
17
|
+
const mediaStreamRef = useRef(mediaStream);
|
|
18
|
+
mediaStreamRef.current = mediaStream;
|
|
33
19
|
const rawTrack = useMemo(() => mediaStream && getTrackFromStream(mediaStream, deviceType), [mediaStream, deviceType]);
|
|
34
20
|
const clearStream = useCallback(() => {
|
|
35
21
|
setMediaStream(null);
|
|
@@ -61,7 +47,10 @@ export const useDeviceManager = ({ mediaStream, setMediaStream, getInitialStream
|
|
|
61
47
|
}
|
|
62
48
|
try {
|
|
63
49
|
const stream = await getDeviceStream(deviceType, constraints, deviceId ?? null);
|
|
64
|
-
|
|
50
|
+
if (mediaStreamRef.current) {
|
|
51
|
+
stopStream(mediaStreamRef.current, deviceType);
|
|
52
|
+
}
|
|
53
|
+
setMediaStream(stream);
|
|
65
54
|
const retrievedTrack = stream && getTrackFromStream(stream, deviceType);
|
|
66
55
|
const retrievedTrackDeviceId = retrievedTrack.getSettings().deviceId;
|
|
67
56
|
if (retrievedTrackDeviceId) {
|
|
@@ -97,7 +86,10 @@ export const useDeviceManager = ({ mediaStream, setMediaStream, getInitialStream
|
|
|
97
86
|
}
|
|
98
87
|
}, [currentTrack, setSelectedDeviceId, startDevice]);
|
|
99
88
|
const stopDevice = useCallback(() => {
|
|
100
|
-
|
|
89
|
+
if (mediaStreamRef.current) {
|
|
90
|
+
stopStream(mediaStreamRef.current, deviceType);
|
|
91
|
+
}
|
|
92
|
+
setMediaStream(null);
|
|
101
93
|
}, [setMediaStream, deviceType]);
|
|
102
94
|
const enableDevice = useCallback(() => {
|
|
103
95
|
if (!currentTrack)
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useMediaDevices.d.ts","sourceRoot":"","sources":["../../../../src/hooks/internal/devices/useMediaDevices.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,0BAA0B,CAAC;AAKvD,OAAO,KAAK,EAAe,uBAAuB,EAAE,yBAAyB,EAAE,MAAM,uBAAuB,CAAC;AAG7G,UAAU,eAAe;IACvB,gBAAgB,CAAC,EAAE,qBAAqB,GAAG,OAAO,CAAC;IACnD,gBAAgB,CAAC,EAAE,qBAAqB,GAAG,OAAO,CAAC;IACnD,eAAe,CAAC,EAAE,yBAAyB,CAAC;IAC5C,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,MAAM,yBAAyB,GAAG;IAAE,WAAW,CAAC,EAAE,OAAO,CAAC;IAAC,WAAW,CAAC,EAAE,OAAO,CAAA;CAAE,CAAC;AAEzF,eAAO,MAAM,eAAe,GAAI,iEAAiE,eAAe;
|
|
1
|
+
{"version":3,"file":"useMediaDevices.d.ts","sourceRoot":"","sources":["../../../../src/hooks/internal/devices/useMediaDevices.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,0BAA0B,CAAC;AAKvD,OAAO,KAAK,EAAe,uBAAuB,EAAE,yBAAyB,EAAE,MAAM,uBAAuB,CAAC;AAG7G,UAAU,eAAe;IACvB,gBAAgB,CAAC,EAAE,qBAAqB,GAAG,OAAO,CAAC;IACnD,gBAAgB,CAAC,EAAE,qBAAqB,GAAG,OAAO,CAAC;IACnD,eAAe,CAAC,EAAE,yBAAyB,CAAC;IAC5C,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,MAAM,yBAAyB,GAAG;IAAE,WAAW,CAAC,EAAE,OAAO,CAAC;IAAC,WAAW,CAAC,EAAE,OAAO,CAAA;CAAE,CAAC;AAEzF,eAAO,MAAM,eAAe,GAAI,iEAAiE,eAAe;mCAoC1F,yBAAyB,KAAG,OAAO,CAAC,uBAAuB,CAAC;;;CAwHjF,CAAC"}
|
|
@@ -10,6 +10,7 @@ export const useMediaDevices = ({ videoConstraints, audioConstraints, persistHan
|
|
|
10
10
|
const [audioError, setAudioError] = useState(null);
|
|
11
11
|
const [selectedCamera, setSelectedCamera] = useState(persistHandlers?.getLastDevice("video") ?? null);
|
|
12
12
|
const [selectedMic, setSelectedMic] = useState(persistHandlers?.getLastDevice("audio") ?? null);
|
|
13
|
+
const isInitializedRef = useRef(false);
|
|
13
14
|
const initializationRef = useRef(null);
|
|
14
15
|
const selectCamera = useCallback((deviceInfo) => {
|
|
15
16
|
setSelectedCamera(deviceInfo);
|
|
@@ -20,12 +21,15 @@ export const useMediaDevices = ({ videoConstraints, audioConstraints, persistHan
|
|
|
20
21
|
persistHandlers?.saveLastDevice(deviceInfo, "audio");
|
|
21
22
|
}, [persistHandlers]);
|
|
22
23
|
const initializeDevices = useCallback(async (settings) => {
|
|
23
|
-
if (
|
|
24
|
+
if (isInitializedRef.current) {
|
|
24
25
|
return { stream: null, errors: null, status: "already_initialized" };
|
|
25
26
|
}
|
|
27
|
+
if (initializationRef.current) {
|
|
28
|
+
return initializationRef.current;
|
|
29
|
+
}
|
|
26
30
|
const lastUsed = {
|
|
27
|
-
audio:
|
|
28
|
-
video:
|
|
31
|
+
audio: persistHandlers?.getLastDevice("audio") ?? null,
|
|
32
|
+
video: persistHandlers?.getLastDevice("video") ?? null,
|
|
29
33
|
};
|
|
30
34
|
const constraints = {
|
|
31
35
|
video: settings?.enableVideo !== false && prepareConstraints(lastUsed.video?.deviceId, videoConstraints),
|
|
@@ -63,10 +67,16 @@ export const useMediaDevices = ({ videoConstraints, audioConstraints, persistHan
|
|
|
63
67
|
return { status: "initialized", errors: null, stream };
|
|
64
68
|
}
|
|
65
69
|
};
|
|
66
|
-
const initializePromise = intitialize()
|
|
70
|
+
const initializePromise = intitialize().then((result) => {
|
|
71
|
+
isInitializedRef.current = true;
|
|
72
|
+
return result;
|
|
73
|
+
}, (error) => {
|
|
74
|
+
initializationRef.current = null;
|
|
75
|
+
throw error;
|
|
76
|
+
});
|
|
67
77
|
initializationRef.current = initializePromise;
|
|
68
78
|
return await initializePromise;
|
|
69
|
-
}, [
|
|
79
|
+
}, [videoConstraints, audioConstraints, selectCamera, selectMic, persistHandlers]);
|
|
70
80
|
useEffect(() => {
|
|
71
81
|
const isInitialStreamIrrelevant = videoStream !== audioStream;
|
|
72
82
|
if (isInitialStreamIrrelevant) {
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { PeerId } from "../types/public";
|
|
2
|
+
/**
|
|
3
|
+
* Client-side voice activity detection for the local peer.
|
|
4
|
+
*
|
|
5
|
+
* Polls the local microphone's audio level every 100ms and derives a speech/silence
|
|
6
|
+
* state from it. A level above ~0.025 (approximately −32 dBov, scaled to [0, 1])
|
|
7
|
+
* is treated as speech. Silence is debounced over 2 consecutive ticks (~200ms)
|
|
8
|
+
* to prevent rapid flapping.
|
|
9
|
+
*
|
|
10
|
+
* This is purely client-side — it does not signal other peers. Remote participants
|
|
11
|
+
* receive the local peer's VAD status via backend `vadNotification` messages.
|
|
12
|
+
*
|
|
13
|
+
* @internal Used by `useVAD` when the local peer's id is included in `peerIds`.
|
|
14
|
+
* @returns A record mapping the local peer's id to its current speaking state,
|
|
15
|
+
* or an empty object if `options.disabled` is true, the local peer is not available, or no microphone track is found.
|
|
16
|
+
*/
|
|
17
|
+
export declare const useLocalVAD: (options: {
|
|
18
|
+
disabled: boolean;
|
|
19
|
+
}) => Record<PeerId, boolean>;
|
|
20
|
+
//# sourceMappingURL=useLocalVAD.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useLocalVAD.d.ts","sourceRoot":"","sources":["../../src/hooks/useLocalVAD.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,iBAAiB,CAAC;AAW9C;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,WAAW,GAAI,SAAS;IAAE,QAAQ,EAAE,OAAO,CAAA;CAAE,KAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAsClF,CAAC"}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { useContext, useEffect, useState } from "react";
|
|
2
|
+
import { FishjamClientContext } from "../contexts/fishjamClient";
|
|
3
|
+
import { usePeers } from "./usePeers";
|
|
4
|
+
// This is a dBov-to-linear conversion. -32 dBov number is taken from backend VAD threshold
|
|
5
|
+
// formula for dBov to linear conversion: linear = 10 ^ (dBov / 20)
|
|
6
|
+
// So -32 dBov = 10^(-32/20) ≈ 0.025. This is the minimum audio level considered "speech".
|
|
7
|
+
const THRESHOLD = 10 ** (-32 / 20);
|
|
8
|
+
// Number of consecutive "silence" ticks before we consider speech to have stopped. Helps with smoothing out brief pauses in speech.
|
|
9
|
+
const SILENCE_DEBOUNCE_TICKS = 2;
|
|
10
|
+
/**
|
|
11
|
+
* Client-side voice activity detection for the local peer.
|
|
12
|
+
*
|
|
13
|
+
* Polls the local microphone's audio level every 100ms and derives a speech/silence
|
|
14
|
+
* state from it. A level above ~0.025 (approximately −32 dBov, scaled to [0, 1])
|
|
15
|
+
* is treated as speech. Silence is debounced over 2 consecutive ticks (~200ms)
|
|
16
|
+
* to prevent rapid flapping.
|
|
17
|
+
*
|
|
18
|
+
* This is purely client-side — it does not signal other peers. Remote participants
|
|
19
|
+
* receive the local peer's VAD status via backend `vadNotification` messages.
|
|
20
|
+
*
|
|
21
|
+
* @internal Used by `useVAD` when the local peer's id is included in `peerIds`.
|
|
22
|
+
* @returns A record mapping the local peer's id to its current speaking state,
|
|
23
|
+
* or an empty object if `options.disabled` is true, the local peer is not available, or no microphone track is found.
|
|
24
|
+
*/
|
|
25
|
+
export const useLocalVAD = (options) => {
|
|
26
|
+
const fishjamClient = useContext(FishjamClientContext);
|
|
27
|
+
const [isSpeaking, setIsSpeaking] = useState(false);
|
|
28
|
+
const { localPeer } = usePeers();
|
|
29
|
+
const localPeerId = localPeer?.id;
|
|
30
|
+
const microphoneTrackId = localPeer?.microphoneTrack?.trackId;
|
|
31
|
+
useEffect(() => {
|
|
32
|
+
if (options.disabled || !localPeerId || !microphoneTrackId)
|
|
33
|
+
return;
|
|
34
|
+
let silenceTicks = 0;
|
|
35
|
+
let timeoutId;
|
|
36
|
+
const poll = async () => {
|
|
37
|
+
const trackAudio = await fishjamClient?.current?.getLocalTrackAudioLevel(microphoneTrackId);
|
|
38
|
+
if (trackAudio != null && trackAudio.level > THRESHOLD) {
|
|
39
|
+
silenceTicks = 0;
|
|
40
|
+
setIsSpeaking(true);
|
|
41
|
+
}
|
|
42
|
+
else {
|
|
43
|
+
silenceTicks += 1;
|
|
44
|
+
if (silenceTicks >= SILENCE_DEBOUNCE_TICKS) {
|
|
45
|
+
setIsSpeaking(false);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
timeoutId = setTimeout(poll, 100);
|
|
49
|
+
};
|
|
50
|
+
timeoutId = setTimeout(poll, 0);
|
|
51
|
+
return () => {
|
|
52
|
+
clearTimeout(timeoutId);
|
|
53
|
+
setIsSpeaking(false);
|
|
54
|
+
};
|
|
55
|
+
}, [options.disabled, fishjamClient, localPeerId, microphoneTrackId]);
|
|
56
|
+
if (!localPeerId || options.disabled || !microphoneTrackId)
|
|
57
|
+
return {};
|
|
58
|
+
return { [localPeerId]: isSpeaking };
|
|
59
|
+
};
|
package/dist/hooks/useVAD.d.ts
CHANGED
|
@@ -1,14 +1,21 @@
|
|
|
1
1
|
import type { PeerId } from "../types/public";
|
|
2
2
|
/**
|
|
3
|
-
* Voice activity detection. Use this hook to check if voice is detected in audio track for given peer(s).
|
|
3
|
+
* Voice activity detection. Use this hook to check if voice is detected in the audio track for given peer(s).
|
|
4
4
|
*
|
|
5
|
-
*
|
|
5
|
+
* Remote peer VAD is driven by `vadNotification` messages from the backend.
|
|
6
|
+
* If the local peer's id is included in `peerIds`, local VAD is determined client-side
|
|
7
|
+
* by polling the microphone's audio level (see `useLocalVAD`).
|
|
8
|
+
*
|
|
9
|
+
* @param options - Options object.
|
|
10
|
+
* @param options.peerIds - List of peer ids to subscribe to for VAD notifications.
|
|
11
|
+
* Include the local peer's id to also track whether the local user is speaking.
|
|
6
12
|
*
|
|
7
13
|
* Example usage:
|
|
8
14
|
* ```tsx
|
|
9
15
|
* import { useVAD, type PeerId } from "@fishjam-cloud/react-client";
|
|
16
|
+
*
|
|
10
17
|
* function WhoIsTalkingComponent({ peerIds }: { peerIds: PeerId[] }) {
|
|
11
|
-
* const peersInfo = useVAD({peerIds});
|
|
18
|
+
* const peersInfo = useVAD({ peerIds });
|
|
12
19
|
* const activePeers = (Object.keys(peersInfo) as PeerId[]).filter((peerId) => peersInfo[peerId]);
|
|
13
20
|
*
|
|
14
21
|
* return "Now talking: " + activePeers.join(", ");
|
|
@@ -16,7 +23,8 @@ import type { PeerId } from "../types/public";
|
|
|
16
23
|
* ```
|
|
17
24
|
* @category Connection
|
|
18
25
|
* @group Hooks
|
|
19
|
-
* @returns
|
|
26
|
+
* @returns A record where each key is a peer id and the boolean value indicates
|
|
27
|
+
* whether voice activity is currently detected for that peer.
|
|
20
28
|
*/
|
|
21
29
|
export declare const useVAD: (options: {
|
|
22
30
|
peerIds: ReadonlyArray<PeerId>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useVAD.d.ts","sourceRoot":"","sources":["../../src/hooks/useVAD.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,MAAM,EAAW,MAAM,iBAAiB,CAAC;
|
|
1
|
+
{"version":3,"file":"useVAD.d.ts","sourceRoot":"","sources":["../../src/hooks/useVAD.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,MAAM,EAAW,MAAM,iBAAiB,CAAC;AAGvD;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,eAAO,MAAM,MAAM,GAAI,SAAS;IAAE,OAAO,EAAE,aAAa,CAAC,MAAM,CAAC,CAAA;CAAE,KAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAuE1F,CAAC"}
|
package/dist/hooks/useVAD.js
CHANGED
|
@@ -1,15 +1,23 @@
|
|
|
1
1
|
import { useContext, useEffect, useMemo, useState } from "react";
|
|
2
2
|
import { FishjamClientStateContext } from "../contexts/fishjamState";
|
|
3
|
+
import { useLocalVAD } from "./useLocalVAD";
|
|
3
4
|
/**
|
|
4
|
-
* Voice activity detection. Use this hook to check if voice is detected in audio track for given peer(s).
|
|
5
|
+
* Voice activity detection. Use this hook to check if voice is detected in the audio track for given peer(s).
|
|
5
6
|
*
|
|
6
|
-
*
|
|
7
|
+
* Remote peer VAD is driven by `vadNotification` messages from the backend.
|
|
8
|
+
* If the local peer's id is included in `peerIds`, local VAD is determined client-side
|
|
9
|
+
* by polling the microphone's audio level (see `useLocalVAD`).
|
|
10
|
+
*
|
|
11
|
+
* @param options - Options object.
|
|
12
|
+
* @param options.peerIds - List of peer ids to subscribe to for VAD notifications.
|
|
13
|
+
* Include the local peer's id to also track whether the local user is speaking.
|
|
7
14
|
*
|
|
8
15
|
* Example usage:
|
|
9
16
|
* ```tsx
|
|
10
17
|
* import { useVAD, type PeerId } from "@fishjam-cloud/react-client";
|
|
18
|
+
*
|
|
11
19
|
* function WhoIsTalkingComponent({ peerIds }: { peerIds: PeerId[] }) {
|
|
12
|
-
* const peersInfo = useVAD({peerIds});
|
|
20
|
+
* const peersInfo = useVAD({ peerIds });
|
|
13
21
|
* const activePeers = (Object.keys(peersInfo) as PeerId[]).filter((peerId) => peersInfo[peerId]);
|
|
14
22
|
*
|
|
15
23
|
* return "Now talking: " + activePeers.join(", ");
|
|
@@ -17,46 +25,52 @@ import { FishjamClientStateContext } from "../contexts/fishjamState";
|
|
|
17
25
|
* ```
|
|
18
26
|
* @category Connection
|
|
19
27
|
* @group Hooks
|
|
20
|
-
* @returns
|
|
28
|
+
* @returns A record where each key is a peer id and the boolean value indicates
|
|
29
|
+
* whether voice activity is currently detected for that peer.
|
|
21
30
|
*/
|
|
22
31
|
export const useVAD = (options) => {
|
|
23
32
|
const { peerIds } = options;
|
|
24
33
|
const clientState = useContext(FishjamClientStateContext);
|
|
25
34
|
if (!clientState)
|
|
26
35
|
throw Error("useVAD must be used within FishjamProvider");
|
|
36
|
+
const showLocalPeerVAD = useMemo(() => (clientState.localPeer?.id ? peerIds.includes(clientState.localPeer?.id) : false), [clientState.localPeer?.id, peerIds]);
|
|
27
37
|
const micTracksWithSelectedPeerIds = useMemo(() => Object.values(clientState.peers)
|
|
28
38
|
.filter((peer) => peerIds.includes(peer.id))
|
|
29
39
|
.map((peer) => ({
|
|
30
40
|
peerId: peer.id,
|
|
31
|
-
|
|
41
|
+
microphoneTrack: Array.from(peer.tracks.values()).find(({ metadata }) => metadata?.type === "microphone"),
|
|
32
42
|
})), [clientState.peers, peerIds]);
|
|
33
|
-
const getDefaultVadStatuses = () => micTracksWithSelectedPeerIds.reduce((mappedTracks,
|
|
43
|
+
const getDefaultVadStatuses = () => micTracksWithSelectedPeerIds.reduce((mappedTracks, { peerId, microphoneTrack }) => ({
|
|
34
44
|
...mappedTracks,
|
|
35
|
-
[
|
|
45
|
+
[peerId]: microphoneTrack ? { [microphoneTrack.trackId]: microphoneTrack.vadStatus } : {},
|
|
36
46
|
}), {});
|
|
37
47
|
const [_vadStatuses, setVadStatuses] = useState(getDefaultVadStatuses);
|
|
38
48
|
useEffect(() => {
|
|
39
|
-
const unsubs = micTracksWithSelectedPeerIds.map(({ peerId,
|
|
49
|
+
const unsubs = micTracksWithSelectedPeerIds.map(({ peerId, microphoneTrack }) => {
|
|
40
50
|
const updateVadStatus = (track) => {
|
|
41
51
|
setVadStatuses((prev) => ({
|
|
42
52
|
...prev,
|
|
43
53
|
[peerId]: { ...prev[peerId], [track.trackId]: track.vadStatus },
|
|
44
54
|
}));
|
|
45
55
|
};
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
}
|
|
56
|
+
if (microphoneTrack) {
|
|
57
|
+
microphoneTrack.on("voiceActivityChanged", updateVadStatus);
|
|
58
|
+
}
|
|
49
59
|
return () => {
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
}
|
|
60
|
+
if (microphoneTrack) {
|
|
61
|
+
microphoneTrack.off("voiceActivityChanged", updateVadStatus);
|
|
62
|
+
}
|
|
53
63
|
};
|
|
54
64
|
});
|
|
55
65
|
return () => unsubs.forEach((unsub) => unsub());
|
|
56
66
|
}, [micTracksWithSelectedPeerIds]);
|
|
57
|
-
const
|
|
58
|
-
|
|
59
|
-
Object.
|
|
60
|
-
|
|
67
|
+
const localVAD = useLocalVAD({ disabled: !showLocalPeerVAD });
|
|
68
|
+
const vadStatuses = useMemo(() => ({
|
|
69
|
+
...Object.fromEntries(Object.entries(_vadStatuses).map(([peerId, tracks]) => [
|
|
70
|
+
peerId,
|
|
71
|
+
Object.values(tracks).some((vad) => vad === "speech"),
|
|
72
|
+
])),
|
|
73
|
+
...localVAD,
|
|
74
|
+
}), [_vadStatuses, localVAD]);
|
|
61
75
|
return vadStatuses;
|
|
62
76
|
};
|
package/dist/index.d.ts
CHANGED
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* React client SDK for building web video and audio apps with Fishjam.
|
|
3
|
+
*
|
|
4
|
+
* @packageDocumentation
|
|
5
|
+
*/
|
|
1
6
|
export { FishjamProvider, type FishjamProviderProps } from "./FishjamProvider";
|
|
2
7
|
export { useCamera } from "./hooks/devices/useCamera";
|
|
3
8
|
export { useInitializeDevices, UseInitializeDevicesParams } from "./hooks/devices/useInitializeDevices";
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,KAAK,oBAAoB,EAAE,MAAM,mBAAmB,CAAC;AAC/E,OAAO,EAAE,SAAS,EAAE,MAAM,2BAA2B,CAAC;AACtD,OAAO,EAAE,oBAAoB,EAAE,0BAA0B,EAAE,MAAM,sCAAsC,CAAC;AACxG,OAAO,EAAE,aAAa,EAAE,MAAM,+BAA+B,CAAC;AAC9D,OAAO,EAAE,yBAAyB,EAAE,MAAM,0CAA0C,CAAC;AACrF,OAAO,EAAE,KAAK,cAAc,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAC3E,OAAO,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAC;AAC1D,OAAO,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAC;AACxD,OAAO,EACL,KAAK,qBAAqB,EAC1B,KAAK,cAAc,EACnB,qBAAqB,EACrB,KAAK,2BAA2B,GACjC,MAAM,+BAA+B,CAAC;AACvC,OAAO,EACL,KAAK,mBAAmB,EACxB,mBAAmB,EACnB,KAAK,yBAAyB,GAC/B,MAAM,6BAA6B,CAAC;AACrC,OAAO,EAAE,KAAK,cAAc,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AACjE,OAAO,EAAE,KAAK,QAAQ,EAAE,UAAU,EAAE,KAAK,eAAe,EAAE,MAAM,oBAAoB,CAAC;AACrF,OAAO,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAC;AACxD,OAAO,EAAE,qBAAqB,EAAE,MAAM,+BAA+B,CAAC;AACtE,OAAO,EAAE,MAAM,EAAE,MAAM,gBAAgB,CAAC;AACxC,YAAY,EACV,eAAe,EACf,KAAK,EACL,YAAY,EACZ,WAAW,EACX,UAAU,EACV,uBAAuB,EACvB,uBAAuB,EACvB,gBAAgB,EAChB,MAAM,EACN,UAAU,EACV,yBAAyB,EACzB,wBAAwB,EACxB,YAAY,EACZ,KAAK,EACL,OAAO,EACP,eAAe,EACf,gBAAgB,EAChB,sBAAsB,EACtB,oBAAoB,GACrB,MAAM,gBAAgB,CAAC;AACxB,YAAY,EACV,eAAe,EACf,YAAY,EACZ,kBAAkB,EAClB,eAAe,EACf,QAAQ,EACR,eAAe,EACf,kBAAkB,EAClB,uBAAuB,EACvB,eAAe,EACf,mBAAmB,GACpB,MAAM,0BAA0B,CAAC;AAClC,OAAO,EAAE,OAAO,EAAE,MAAM,0BAA0B,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,EAAE,eAAe,EAAE,KAAK,oBAAoB,EAAE,MAAM,mBAAmB,CAAC;AAC/E,OAAO,EAAE,SAAS,EAAE,MAAM,2BAA2B,CAAC;AACtD,OAAO,EAAE,oBAAoB,EAAE,0BAA0B,EAAE,MAAM,sCAAsC,CAAC;AACxG,OAAO,EAAE,aAAa,EAAE,MAAM,+BAA+B,CAAC;AAC9D,OAAO,EAAE,yBAAyB,EAAE,MAAM,0CAA0C,CAAC;AACrF,OAAO,EAAE,KAAK,cAAc,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAC3E,OAAO,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAC;AAC1D,OAAO,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAC;AACxD,OAAO,EACL,KAAK,qBAAqB,EAC1B,KAAK,cAAc,EACnB,qBAAqB,EACrB,KAAK,2BAA2B,GACjC,MAAM,+BAA+B,CAAC;AACvC,OAAO,EACL,KAAK,mBAAmB,EACxB,mBAAmB,EACnB,KAAK,yBAAyB,GAC/B,MAAM,6BAA6B,CAAC;AACrC,OAAO,EAAE,KAAK,cAAc,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AACjE,OAAO,EAAE,KAAK,QAAQ,EAAE,UAAU,EAAE,KAAK,eAAe,EAAE,MAAM,oBAAoB,CAAC;AACrF,OAAO,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAC;AACxD,OAAO,EAAE,qBAAqB,EAAE,MAAM,+BAA+B,CAAC;AACtE,OAAO,EAAE,MAAM,EAAE,MAAM,gBAAgB,CAAC;AACxC,YAAY,EACV,eAAe,EACf,KAAK,EACL,YAAY,EACZ,WAAW,EACX,UAAU,EACV,uBAAuB,EACvB,uBAAuB,EACvB,gBAAgB,EAChB,MAAM,EACN,UAAU,EACV,yBAAyB,EACzB,wBAAwB,EACxB,YAAY,EACZ,KAAK,EACL,OAAO,EACP,eAAe,EACf,gBAAgB,EAChB,sBAAsB,EACtB,oBAAoB,GACrB,MAAM,gBAAgB,CAAC;AACxB,YAAY,EACV,eAAe,EACf,YAAY,EACZ,kBAAkB,EAClB,eAAe,EACf,QAAQ,EACR,eAAe,EACf,kBAAkB,EAClB,uBAAuB,EACvB,eAAe,EACf,mBAAmB,GACpB,MAAM,0BAA0B,CAAC;AAClC,OAAO,EAAE,OAAO,EAAE,MAAM,0BAA0B,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* React client SDK for building web video and audio apps with Fishjam.
|
|
3
|
+
*
|
|
4
|
+
* @packageDocumentation
|
|
5
|
+
*/
|
|
1
6
|
export { FishjamProvider } from "./FishjamProvider";
|
|
2
7
|
export { useCamera } from "./hooks/devices/useCamera";
|
|
3
8
|
export { useInitializeDevices } from "./hooks/devices/useInitializeDevices";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fishjam-cloud/react-client",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.26.0-rc.0",
|
|
4
4
|
"description": "React client library for Fishjam",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"author": "Fishjam Team",
|
|
@@ -62,7 +62,7 @@
|
|
|
62
62
|
"vitest": "^3.1.1"
|
|
63
63
|
},
|
|
64
64
|
"dependencies": {
|
|
65
|
-
"@fishjam-cloud/ts-client": "0.
|
|
65
|
+
"@fishjam-cloud/ts-client": "0.26.0-rc.0",
|
|
66
66
|
"events": "3.3.0",
|
|
67
67
|
"lodash.isequal": "4.5.0"
|
|
68
68
|
},
|