Skip to content
This repository was archived by the owner on Jun 28, 2024. It is now read-only.

Commit cb4eeed

Browse files
Mute track (#79)
## Description This PR introduces changes from: fishjam-dev/ts-client-sdk#49 and also: ### useCamera, useMicrophone, useScreenshare Hooks for managing a single track of a given type in the global state (similarly to the React Native API). #### onDeviceStop `onDeviceStop?: "remove" | "mute"; This PR introduces a new `onDeviceStop` config parameter that allows the user to decide what to do if a track stops. Without it, the track has been removed from `RTCPeerConnection`. Now, with the option `mute`, that track can be reused later without renegotiation. It is a companion event to `onDeviceChange`. Added `onDeviceStop`, `onDeviceChange`, and the ability to stop a device to `use-camera-and-microphone-example`. #### onDeviceChange `onDeviceChange?: "replace" | "stop";` Determines whether a track should be replaced when the user requests a device change. Previously, `broadcastOnDeviceChange`. #### Other changes - Names of types like `UseMicrophoneResult` changed to something simpler, like `MicrophoneAPI`. ## Motivation and Context Fishjam now offers the ability to reuse tracks. ## Types of changes - [ ] Bug fix (a non-breaking change that fixes an issue) - [ ] New feature (a non-breaking change that adds functionality) - [x] Breaking change (a fix or feature that would cause existing functionality to not work as expected) - The `replaceTrack` method can handle a nullable track parameter. - The `stream` parameter was removed from the `addTrack` method.
1 parent e7b883e commit cb4eeed

15 files changed

Lines changed: 344 additions & 244 deletions

File tree

examples/minimal-react/package-lock.json

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

examples/minimal-react/src/components/App.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ export const App = () => {
5959
// Get screen sharing MediaStream
6060
navigator.mediaDevices.getDisplayMedia(SCREEN_SHARING_MEDIA_CONSTRAINTS).then((screenStream) => {
6161
// Add local MediaStream to webrtc
62-
screenStream.getTracks().forEach((track) => client.addTrack(track, screenStream, { type: "screen" }));
62+
screenStream.getTracks().forEach((track) => client.addTrack(track, { type: "screen" }));
6363
});
6464
}}
6565
>

examples/use-camera-and-microphone-example/package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

examples/use-camera-and-microphone-example/src/DeviceControls.tsx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,20 @@
1-
import type { PeerStatus, UseMicrophoneResult, UseCameraResult, UseScreenShareResult } from "@fishjam-dev/react-client";
1+
import type { PeerStatus, CameraAPI, MicrophoneAPI, ScreenShareAPI } from "@fishjam-dev/react-client";
22
import type { TrackMetadata } from "./fishjamSetup";
33

44
type DeviceControlsProps = {
55
status: PeerStatus;
66
metadata: TrackMetadata;
77
} & (
88
| {
9-
device: UseMicrophoneResult<TrackMetadata>;
9+
device: MicrophoneAPI<TrackMetadata>;
1010
type: "audio";
1111
}
1212
| {
13-
device: UseCameraResult<TrackMetadata>;
13+
device: CameraAPI<TrackMetadata>;
1414
type: "video";
1515
}
1616
| {
17-
device: UseScreenShareResult<TrackMetadata>;
17+
device: ScreenShareAPI<TrackMetadata>;
1818
type: "screenshare";
1919
}
2020
);

examples/use-camera-and-microphone-example/src/DeviceSelector.tsx

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,12 @@ type Props = {
55
name: string;
66
defaultOptionText: string;
77
devices: MediaDeviceInfo[] | null;
8+
stop: () => void;
89
setInput: (value: string | null) => void;
910
activeDevice: string | null;
1011
};
1112

12-
export const DeviceSelector = ({ name, devices, setInput, defaultOptionText, activeDevice }: Props) => {
13+
export const DeviceSelector = ({ name, devices, setInput, defaultOptionText, activeDevice, stop }: Props) => {
1314
const [selectedDevice, setSelectedDevice] = useState<string | null>(null);
1415

1516
const onOptionChangeHandler = (event: ChangeEvent<HTMLSelectElement>) => {
@@ -32,13 +33,22 @@ export const DeviceSelector = ({ name, devices, setInput, defaultOptionText, act
3233
))}
3334
</select>
3435
<button
35-
className="btn btn-error btn-sm"
36+
className="btn btn-success btn-sm"
3637
disabled={!selectedDevice}
3738
onClick={() => {
3839
setInput(selectedDevice);
3940
}}
4041
>
41-
Change device!
42+
start / change
43+
</button>
44+
<button
45+
className="btn btn-error btn-sm"
46+
disabled={!selectedDevice}
47+
onClick={() => {
48+
stop();
49+
}}
50+
>
51+
stop
4252
</button>
4353
</div>
4454
</div>

examples/use-camera-and-microphone-example/src/MainControls.tsx

Lines changed: 64 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -27,21 +27,26 @@ import { Badge } from "./Badge";
2727
import { DeviceControls } from "./DeviceControls";
2828
import { Radio } from "./Radio";
2929

30-
type RestartChange = "stop" | "replace" | undefined;
30+
type OnDeviceChange = "remove" | "replace" | undefined;
31+
type OnDeviceStop = "remove" | "mute" | undefined;
3132

32-
const isRestartChange = (e: string | undefined): e is RestartChange => {
33-
return e === undefined || e === "stop" || e === "replace";
34-
};
33+
const isDeviceChangeValue = (e: string | undefined): e is OnDeviceChange =>
34+
e === undefined || e === "remove" || e === "replace";
35+
36+
const isDeviceStopValue = (e: string | undefined): e is OnDeviceStop =>
37+
e === undefined || e === "remove" || e === "mute";
3538

3639
const tokenAtom = atomWithStorage("token", "");
3740

3841
const broadcastVideoOnConnectAtom = atomWithStorage<boolean | undefined>("broadcastVideoOnConnect", undefined);
3942
const broadcastVideoOnDeviceStartAtom = atomWithStorage<boolean | undefined>("broadcastVideoOnDeviceStart", undefined);
40-
const broadcastVideoOnDeviceChangeAtom = atomWithStorage<RestartChange>("broadcastVideoOnDeviceChange", undefined);
43+
const videoOnDeviceChangeAtom = atomWithStorage<OnDeviceChange>("videoOnDeviceChange", undefined);
44+
const videoOnDeviceStopAtom = atomWithStorage<OnDeviceStop>("videoOnDeviceStop", undefined);
4145

4246
const broadcastAudioOnConnectAtom = atomWithStorage<boolean | undefined>("broadcastAudioOnConnect", undefined);
4347
const broadcastAudioOnDeviceStartAtom = atomWithStorage<boolean | undefined>("broadcastAudioOnDeviceStart", undefined);
44-
const broadcastAudioOnDeviceChangeAtom = atomWithStorage<RestartChange>("broadcastAudioOnDeviceChange", undefined);
48+
const audioOnDeviceChangeAtom = atomWithStorage<OnDeviceChange>("audioOnDeviceChange", undefined);
49+
const audioOnDeviceStopAtom = atomWithStorage<OnDeviceStop>("audioOnDeviceStop", undefined);
4550

4651
const broadcastScreenShareOnConnectAtom = atomWithStorage<boolean | undefined>(
4752
"broadcastScreenShareOnConnect",
@@ -67,11 +72,13 @@ export const MainControls = () => {
6772

6873
const [broadcastVideoOnConnect, setBroadcastVideoOnConnect] = useAtom(broadcastVideoOnConnectAtom);
6974
const [broadcastVideoOnDeviceStart, setBroadcastVideoOnDeviceStart] = useAtom(broadcastVideoOnDeviceStartAtom);
70-
const [broadcastVideoOnDeviceChange, setBroadcastVideoOnDeviceChange] = useAtom(broadcastVideoOnDeviceChangeAtom);
75+
const [broadcastVideoOnDeviceChange, setBroadcastVideoOnDeviceChange] = useAtom(videoOnDeviceChangeAtom);
76+
const [broadcastVideoOnDeviceStop, setBroadcastVideoOnDeviceStop] = useAtom(videoOnDeviceStopAtom);
7177

7278
const [broadcastAudioOnConnect, setBroadcastAudioOnConnect] = useAtom(broadcastAudioOnConnectAtom);
7379
const [broadcastAudioOnDeviceStart, setBroadcastAudioOnDeviceStart] = useAtom(broadcastAudioOnDeviceStartAtom);
74-
const [broadcastAudioOnDeviceChange, setBroadcastAudioOnDeviceChange] = useAtom(broadcastAudioOnDeviceChangeAtom);
80+
const [broadcastAudioOnDeviceChange, setBroadcastAudioOnDeviceChange] = useAtom(audioOnDeviceChangeAtom);
81+
const [broadcastAudioOnDeviceStop, setBroadcastAudioOnDeviceStop] = useAtom(audioOnDeviceStopAtom);
7582

7683
const [broadcastScreenShareOnConnect, setBroadcastScreenShareOnConnect] = useAtom(broadcastScreenShareOnConnectAtom);
7784
const [broadcastScreenShareOnDeviceStart, setBroadcastScreenShareOnDeviceStart] = useAtom(
@@ -85,7 +92,8 @@ export const MainControls = () => {
8592
trackConstraints: VIDEO_TRACK_CONSTRAINTS,
8693
broadcastOnConnect: broadcastVideoOnConnect,
8794
broadcastOnDeviceStart: broadcastVideoOnDeviceStart,
88-
broadcastOnDeviceChange: broadcastVideoOnDeviceChange,
95+
onDeviceChange: broadcastVideoOnDeviceChange,
96+
onDeviceStop: broadcastVideoOnDeviceStop,
8997
defaultTrackMetadata: DEFAULT_VIDEO_TRACK_METADATA,
9098
defaultSimulcastConfig: {
9199
enabled: true,
@@ -97,7 +105,8 @@ export const MainControls = () => {
97105
trackConstraints: AUDIO_TRACK_CONSTRAINTS,
98106
broadcastOnConnect: broadcastAudioOnConnect,
99107
broadcastOnDeviceStart: broadcastAudioOnDeviceStart,
100-
broadcastOnDeviceChange: broadcastAudioOnDeviceChange,
108+
onDeviceChange: broadcastAudioOnDeviceChange,
109+
onDeviceStop: broadcastAudioOnDeviceStop,
101110
defaultTrackMetadata: DEFAULT_AUDIO_TRACK_METADATA,
102111
},
103112
screenShare: {
@@ -221,15 +230,28 @@ export const MainControls = () => {
221230
name='Broadcast video on device change (default "replace")'
222231
value={broadcastVideoOnDeviceChange}
223232
set={(value) => {
224-
if (isRestartChange(value)) setBroadcastVideoOnDeviceChange(value);
233+
if (isDeviceChangeValue(value)) setBroadcastVideoOnDeviceChange(value);
225234
}}
226235
radioClass="radio-primary"
227236
options={[
228237
{ value: undefined, key: "undefined" },
229-
{ value: "stop", key: "stop" },
238+
{ value: "remove", key: "remove" },
230239
{ value: "replace", key: "replace" },
231240
]}
232241
/>
242+
<Radio
243+
name='Broadcast video on device stop (default "mute")'
244+
value={broadcastVideoOnDeviceStop}
245+
set={(value) => {
246+
if (isDeviceStopValue(value)) setBroadcastVideoOnDeviceStop(value);
247+
}}
248+
radioClass="radio-primary"
249+
options={[
250+
{ value: undefined, key: "undefined" },
251+
{ value: "remove", key: "remove" },
252+
{ value: "mute", key: "mute" },
253+
]}
254+
/>
233255

234256
<ThreeStateRadio
235257
name="Broadcast audio on connect (default false)"
@@ -247,15 +269,28 @@ export const MainControls = () => {
247269
name='Broadcast audio on device change (default "replace")'
248270
value={broadcastAudioOnDeviceChange}
249271
set={(value) => {
250-
if (isRestartChange(value)) setBroadcastAudioOnDeviceChange(value);
272+
if (isDeviceChangeValue(value)) setBroadcastAudioOnDeviceChange(value);
251273
}}
252274
radioClass="radio-secondary"
253275
options={[
254276
{ value: undefined, key: "undefined" },
255-
{ value: "stop", key: "stop" },
277+
{ value: "remove", key: "remove" },
256278
{ value: "replace", key: "replace" },
257279
]}
258280
/>
281+
<Radio
282+
name='Broadcast audio on device stop (default "mute")'
283+
value={broadcastAudioOnDeviceStop}
284+
set={(value) => {
285+
if (isDeviceStopValue(value)) setBroadcastAudioOnDeviceStop(value);
286+
}}
287+
radioClass="radio-secondary"
288+
options={[
289+
{ value: undefined, key: "undefined" },
290+
{ value: "remove", key: "remove" },
291+
{ value: "mute", key: "mute" },
292+
]}
293+
/>
259294

260295
<ThreeStateRadio
261296
name="Broadcast screen share on connect (default false)"
@@ -279,6 +314,9 @@ export const MainControls = () => {
279314
video.start(id);
280315
}}
281316
defaultOptionText="Select video device"
317+
stop={() => {
318+
video.stop();
319+
}}
282320
/>
283321

284322
<DeviceSelector
@@ -290,6 +328,9 @@ export const MainControls = () => {
290328
audio.start(id);
291329
}}
292330
defaultOptionText="Select audio device"
331+
stop={() => {
332+
audio.stop();
333+
}}
293334
/>
294335

295336
<div className="grid grid-cols-3 gap-2">
@@ -318,12 +359,15 @@ export const MainControls = () => {
318359

319360
<div>
320361
<h3>Streaming:</h3>
321-
{local.map(({ trackId, stream, track }) => (
322-
<div key={trackId} className="max-w-[500px]">
323-
{track?.kind === "video" && <VideoPlayer key={trackId} stream={stream} />}
324-
{track?.kind === "audio" && <AudioVisualizer trackId={track.id} stream={stream} />}
325-
</div>
326-
))}
362+
<div className="flex max-w-[500px] flex-col gap-2">
363+
{local.map(({ trackId, stream, track }) => (
364+
<div key={trackId} className="max-w-[500px] border">
365+
<span>trackId: {trackId}</span>
366+
{track?.kind === "audio" && <AudioVisualizer trackId={track.id} stream={stream} />}
367+
{track?.kind === "video" && <VideoPlayer key={trackId} stream={stream} />}
368+
</div>
369+
))}
370+
</div>
327371
</div>
328372
</div>
329373
</div>

0 commit comments

Comments
 (0)