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

Commit

Permalink
Mute track (#79)
Browse files Browse the repository at this point in the history
## 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.
  • Loading branch information
kamil-stasiak authored Jun 13, 2024
1 parent e7b883e commit cb4eeed
Show file tree
Hide file tree
Showing 15 changed files with 344 additions and 244 deletions.
2 changes: 1 addition & 1 deletion examples/minimal-react/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion examples/minimal-react/src/components/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ export const App = () => {
// Get screen sharing MediaStream
navigator.mediaDevices.getDisplayMedia(SCREEN_SHARING_MEDIA_CONSTRAINTS).then((screenStream) => {
// Add local MediaStream to webrtc
screenStream.getTracks().forEach((track) => client.addTrack(track, screenStream, { type: "screen" }));
screenStream.getTracks().forEach((track) => client.addTrack(track, { type: "screen" }));
});
}}
>
Expand Down
4 changes: 2 additions & 2 deletions examples/use-camera-and-microphone-example/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
@@ -1,20 +1,20 @@
import type { PeerStatus, UseMicrophoneResult, UseCameraResult, UseScreenShareResult } from "@fishjam-dev/react-client";
import type { PeerStatus, CameraAPI, MicrophoneAPI, ScreenShareAPI } from "@fishjam-dev/react-client";
import type { TrackMetadata } from "./fishjamSetup";

type DeviceControlsProps = {
status: PeerStatus;
metadata: TrackMetadata;
} & (
| {
device: UseMicrophoneResult<TrackMetadata>;
device: MicrophoneAPI<TrackMetadata>;
type: "audio";
}
| {
device: UseCameraResult<TrackMetadata>;
device: CameraAPI<TrackMetadata>;
type: "video";
}
| {
device: UseScreenShareResult<TrackMetadata>;
device: ScreenShareAPI<TrackMetadata>;
type: "screenshare";
}
);
Expand Down
16 changes: 13 additions & 3 deletions examples/use-camera-and-microphone-example/src/DeviceSelector.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,12 @@ type Props = {
name: string;
defaultOptionText: string;
devices: MediaDeviceInfo[] | null;
stop: () => void;
setInput: (value: string | null) => void;
activeDevice: string | null;
};

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

const onOptionChangeHandler = (event: ChangeEvent<HTMLSelectElement>) => {
Expand All @@ -32,13 +33,22 @@ export const DeviceSelector = ({ name, devices, setInput, defaultOptionText, act
))}
</select>
<button
className="btn btn-error btn-sm"
className="btn btn-success btn-sm"
disabled={!selectedDevice}
onClick={() => {
setInput(selectedDevice);
}}
>
Change device!
start / change
</button>
<button
className="btn btn-error btn-sm"
disabled={!selectedDevice}
onClick={() => {
stop();
}}
>
stop
</button>
</div>
</div>
Expand Down
84 changes: 64 additions & 20 deletions examples/use-camera-and-microphone-example/src/MainControls.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,21 +27,26 @@ import { Badge } from "./Badge";
import { DeviceControls } from "./DeviceControls";
import { Radio } from "./Radio";

type RestartChange = "stop" | "replace" | undefined;
type OnDeviceChange = "remove" | "replace" | undefined;
type OnDeviceStop = "remove" | "mute" | undefined;

const isRestartChange = (e: string | undefined): e is RestartChange => {
return e === undefined || e === "stop" || e === "replace";
};
const isDeviceChangeValue = (e: string | undefined): e is OnDeviceChange =>
e === undefined || e === "remove" || e === "replace";

const isDeviceStopValue = (e: string | undefined): e is OnDeviceStop =>
e === undefined || e === "remove" || e === "mute";

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

const broadcastVideoOnConnectAtom = atomWithStorage<boolean | undefined>("broadcastVideoOnConnect", undefined);
const broadcastVideoOnDeviceStartAtom = atomWithStorage<boolean | undefined>("broadcastVideoOnDeviceStart", undefined);
const broadcastVideoOnDeviceChangeAtom = atomWithStorage<RestartChange>("broadcastVideoOnDeviceChange", undefined);
const videoOnDeviceChangeAtom = atomWithStorage<OnDeviceChange>("videoOnDeviceChange", undefined);
const videoOnDeviceStopAtom = atomWithStorage<OnDeviceStop>("videoOnDeviceStop", undefined);

const broadcastAudioOnConnectAtom = atomWithStorage<boolean | undefined>("broadcastAudioOnConnect", undefined);
const broadcastAudioOnDeviceStartAtom = atomWithStorage<boolean | undefined>("broadcastAudioOnDeviceStart", undefined);
const broadcastAudioOnDeviceChangeAtom = atomWithStorage<RestartChange>("broadcastAudioOnDeviceChange", undefined);
const audioOnDeviceChangeAtom = atomWithStorage<OnDeviceChange>("audioOnDeviceChange", undefined);
const audioOnDeviceStopAtom = atomWithStorage<OnDeviceStop>("audioOnDeviceStop", undefined);

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

const [broadcastVideoOnConnect, setBroadcastVideoOnConnect] = useAtom(broadcastVideoOnConnectAtom);
const [broadcastVideoOnDeviceStart, setBroadcastVideoOnDeviceStart] = useAtom(broadcastVideoOnDeviceStartAtom);
const [broadcastVideoOnDeviceChange, setBroadcastVideoOnDeviceChange] = useAtom(broadcastVideoOnDeviceChangeAtom);
const [broadcastVideoOnDeviceChange, setBroadcastVideoOnDeviceChange] = useAtom(videoOnDeviceChangeAtom);
const [broadcastVideoOnDeviceStop, setBroadcastVideoOnDeviceStop] = useAtom(videoOnDeviceStopAtom);

const [broadcastAudioOnConnect, setBroadcastAudioOnConnect] = useAtom(broadcastAudioOnConnectAtom);
const [broadcastAudioOnDeviceStart, setBroadcastAudioOnDeviceStart] = useAtom(broadcastAudioOnDeviceStartAtom);
const [broadcastAudioOnDeviceChange, setBroadcastAudioOnDeviceChange] = useAtom(broadcastAudioOnDeviceChangeAtom);
const [broadcastAudioOnDeviceChange, setBroadcastAudioOnDeviceChange] = useAtom(audioOnDeviceChangeAtom);
const [broadcastAudioOnDeviceStop, setBroadcastAudioOnDeviceStop] = useAtom(audioOnDeviceStopAtom);

const [broadcastScreenShareOnConnect, setBroadcastScreenShareOnConnect] = useAtom(broadcastScreenShareOnConnectAtom);
const [broadcastScreenShareOnDeviceStart, setBroadcastScreenShareOnDeviceStart] = useAtom(
Expand All @@ -85,7 +92,8 @@ export const MainControls = () => {
trackConstraints: VIDEO_TRACK_CONSTRAINTS,
broadcastOnConnect: broadcastVideoOnConnect,
broadcastOnDeviceStart: broadcastVideoOnDeviceStart,
broadcastOnDeviceChange: broadcastVideoOnDeviceChange,
onDeviceChange: broadcastVideoOnDeviceChange,
onDeviceStop: broadcastVideoOnDeviceStop,
defaultTrackMetadata: DEFAULT_VIDEO_TRACK_METADATA,
defaultSimulcastConfig: {
enabled: true,
Expand All @@ -97,7 +105,8 @@ export const MainControls = () => {
trackConstraints: AUDIO_TRACK_CONSTRAINTS,
broadcastOnConnect: broadcastAudioOnConnect,
broadcastOnDeviceStart: broadcastAudioOnDeviceStart,
broadcastOnDeviceChange: broadcastAudioOnDeviceChange,
onDeviceChange: broadcastAudioOnDeviceChange,
onDeviceStop: broadcastAudioOnDeviceStop,
defaultTrackMetadata: DEFAULT_AUDIO_TRACK_METADATA,
},
screenShare: {
Expand Down Expand Up @@ -221,15 +230,28 @@ export const MainControls = () => {
name='Broadcast video on device change (default "replace")'
value={broadcastVideoOnDeviceChange}
set={(value) => {
if (isRestartChange(value)) setBroadcastVideoOnDeviceChange(value);
if (isDeviceChangeValue(value)) setBroadcastVideoOnDeviceChange(value);
}}
radioClass="radio-primary"
options={[
{ value: undefined, key: "undefined" },
{ value: "stop", key: "stop" },
{ value: "remove", key: "remove" },
{ value: "replace", key: "replace" },
]}
/>
<Radio
name='Broadcast video on device stop (default "mute")'
value={broadcastVideoOnDeviceStop}
set={(value) => {
if (isDeviceStopValue(value)) setBroadcastVideoOnDeviceStop(value);
}}
radioClass="radio-primary"
options={[
{ value: undefined, key: "undefined" },
{ value: "remove", key: "remove" },
{ value: "mute", key: "mute" },
]}
/>

<ThreeStateRadio
name="Broadcast audio on connect (default false)"
Expand All @@ -247,15 +269,28 @@ export const MainControls = () => {
name='Broadcast audio on device change (default "replace")'
value={broadcastAudioOnDeviceChange}
set={(value) => {
if (isRestartChange(value)) setBroadcastAudioOnDeviceChange(value);
if (isDeviceChangeValue(value)) setBroadcastAudioOnDeviceChange(value);
}}
radioClass="radio-secondary"
options={[
{ value: undefined, key: "undefined" },
{ value: "stop", key: "stop" },
{ value: "remove", key: "remove" },
{ value: "replace", key: "replace" },
]}
/>
<Radio
name='Broadcast audio on device stop (default "mute")'
value={broadcastAudioOnDeviceStop}
set={(value) => {
if (isDeviceStopValue(value)) setBroadcastAudioOnDeviceStop(value);
}}
radioClass="radio-secondary"
options={[
{ value: undefined, key: "undefined" },
{ value: "remove", key: "remove" },
{ value: "mute", key: "mute" },
]}
/>

<ThreeStateRadio
name="Broadcast screen share on connect (default false)"
Expand All @@ -279,6 +314,9 @@ export const MainControls = () => {
video.start(id);
}}
defaultOptionText="Select video device"
stop={() => {
video.stop();
}}
/>

<DeviceSelector
Expand All @@ -290,6 +328,9 @@ export const MainControls = () => {
audio.start(id);
}}
defaultOptionText="Select audio device"
stop={() => {
audio.stop();
}}
/>

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

<div>
<h3>Streaming:</h3>
{local.map(({ trackId, stream, track }) => (
<div key={trackId} className="max-w-[500px]">
{track?.kind === "video" && <VideoPlayer key={trackId} stream={stream} />}
{track?.kind === "audio" && <AudioVisualizer trackId={track.id} stream={stream} />}
</div>
))}
<div className="flex max-w-[500px] flex-col gap-2">
{local.map(({ trackId, stream, track }) => (
<div key={trackId} className="max-w-[500px] border">
<span>trackId: {trackId}</span>
{track?.kind === "audio" && <AudioVisualizer trackId={track.id} stream={stream} />}
{track?.kind === "video" && <VideoPlayer key={trackId} stream={stream} />}
</div>
))}
</div>
</div>
</div>
</div>
Expand Down
Loading

0 comments on commit cb4eeed

Please sign in to comment.