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

Commit f313d18

Browse files
Video track stats (#71)
1 parent 40de436 commit f313d18

17 files changed

+339
-115
lines changed

src/components/AudioDevicePanel.tsx

+3-2
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,12 @@ import { FaMicrophone } from "react-icons/fa";
22
import { getUserMedia } from "../utils/browser-media-utils";
33
import { DeviceInfo } from "../containers/StreamingSettingsCard";
44
import { v4 as uuidv4 } from "uuid";
5+
import { TrackSource } from "../containers/Client";
56

67
type AudioDevicePanelProps = {
78
deviceId: string;
89
label: string;
9-
addLocalAudioStream: (stream: MediaStream, id: string) => void;
10+
addLocalAudioStream: (stream: MediaStream, id: string, source: TrackSource, stop?: () => void) => void;
1011
setSelectedAudioId: (cameraId: DeviceInfo | null) => void;
1112
selected: boolean;
1213
};
@@ -23,7 +24,7 @@ export const AudioDevicePanel = ({
2324
const id = deviceId + uuidv4();
2425
getUserMedia(id, "audio").then((stream) => {
2526
setSelectedAudioId({ id: id, type: "audio", stream: stream });
26-
addLocalAudioStream(stream, id);
27+
addLocalAudioStream(stream, id, "navigator");
2728
});
2829
}}
2930
>

src/components/CameraTest.tsx

+2
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import VideoPlayer from "./VideoPlayer";
33
import { useLocalStorageState, useLocalStorageStateString } from "./LogSelector";
44
import { JsonComponent } from "./JsonComponent";
55
import { EnumerateDevices, enumerateDevices, useUserMediaById } from "../utils/browser-media-utils";
6+
import { VideoTrackInfo } from "./VideoTrackInfo";
67

78
export const CameraTest = () => {
89
const [autostartDeviceManager, setAutostartDeviceManager] = useLocalStorageState("AUTOSTART-DEVICE-MANAGER");
@@ -147,6 +148,7 @@ export const CameraTest = () => {
147148
<div className="loading"></div>
148149
</div>
149150
<VideoPlayer stream={cameraState.stream} />
151+
<VideoTrackInfo track={cameraState.stream?.getVideoTracks()[0]} />
150152
</div>
151153
</div>
152154
</div>

src/components/CanvasTile.tsx

+2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import VideoPlayer from "./VideoPlayer";
22
import { StreamInfo } from "./StreamingDeviceSelector";
3+
import { VideoTrackInfo } from "./VideoTrackInfo";
34

45
type Props = {
56
label: string;
@@ -11,5 +12,6 @@ export const CanvasTile = ({ selected, streamInfo }: Props) => (
1112
<div className="flex flex-col w-20 indicator">
1213
{selected && <span className="indicator-item badge badge-success badge-lg"></span>}
1314
<VideoPlayer stream={streamInfo.stream} />
15+
<VideoTrackInfo track={streamInfo.stream?.getVideoTracks()[0]} />
1416
</div>
1517
);

src/components/DeviceTile.tsx

+17-2
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@ import AudioVisualizer from "./AudioVisualizer";
55
import { CloseButton } from "./CloseButton";
66
import { StreamInfo } from "./StreamingDeviceSelector";
77
import VideoPlayer from "./VideoPlayer";
8+
import { VideoTrackInfo } from "./VideoTrackInfo";
9+
import { useSetAtom } from "jotai";
10+
import { activeLocalCamerasAtom } from "./VideoDevicePanel";
811

912
type Props = {
1013
selectedId: DeviceInfo | null;
@@ -20,6 +23,8 @@ export const DeviceTile = ({ selectedId, setSelectedId, streamInfo, id }: Props)
2023
const peer = state.rooms[state.selectedRoom || ""].peers[id];
2124
const api = peer.client.useSelector((state) => state.connectivity.api);
2225
const track = peer.tracks[streamInfo.id];
26+
const setActiveLocalCameras = useSetAtom(activeLocalCamerasAtom);
27+
2328
return (
2429
<div
2530
className={`flex flex-col w-40 justify-center rounded-md indicator ${
@@ -29,12 +34,17 @@ export const DeviceTile = ({ selectedId, setSelectedId, streamInfo, id }: Props)
2934
<button
3035
className={`h-fit w-fit `}
3136
onClick={() => {
32-
setSelectedId({ id: streamInfo.id, type: selectedId?.type || "unknown", stream: streamInfo.stream });
37+
setSelectedId({
38+
id: streamInfo.id,
39+
type: selectedId?.type || "unknown",
40+
stream: streamInfo.stream,
41+
});
3342
}}
3443
>
3544
{isVideo ? (
36-
<div className=" overflow-hidden h-24">
45+
<div className="overflow-hidden">
3746
<VideoPlayer stream={streamInfo.stream} size={"40"} />
47+
<VideoTrackInfo track={streamInfo.stream?.getVideoTracks()[0]} />
3848
</div>
3949
) : (
4050
<div className="w-fit items-center flex flex-col rounded-md">
@@ -63,6 +73,11 @@ export const DeviceTile = ({ selectedId, setSelectedId, streamInfo, id }: Props)
6373
if (track.serverId) {
6474
api?.removeTrack(track.serverId);
6575
}
76+
if (track.source === "navigator" && track.type === "video") {
77+
setActiveLocalCameras((prev) => prev - 1);
78+
}
79+
track.stop?.();
80+
track.track.stop();
6681
dispatch({ type: "REMOVE_TRACK", roomId: state.selectedRoom || "", trackId: track.id, peerId: id });
6782
}}
6883
/>

src/components/MockVideoPanel.tsx

+10-9
Original file line numberDiff line numberDiff line change
@@ -8,33 +8,34 @@ import { useAtom } from "jotai";
88
import { useState } from "react";
99
import { DeviceInfo } from "../containers/StreamingSettingsCard";
1010
import { v4 as uuidv4 } from "uuid";
11+
import { TrackSource } from "../containers/Client";
1112

1213
type MockVideoPanelProps = {
1314
id: string;
14-
addLocalVideoStream: (stream: MediaStream, id: string) => void;
15+
addLocalVideoStream: (stream: MediaStream, id: string, source: TrackSource, stop?: () => void) => void;
1516
selectedDeviceId: DeviceInfo | null;
1617
setSelectedDeviceId: (info: DeviceInfo | null) => void;
1718
};
1819

1920
type StreamGenerator = {
20-
create: () => { stop: () => void; stream: MediaStream };
21+
create: (quality: Quality) => { stop: () => void; stream: MediaStream };
2122
id: string;
2223
};
2324

2425
export const heartStream: StreamGenerator = {
25-
create: () => createStream("💜", "black", "high", 24),
26+
create: (quality) => createStream("💜", "black", quality, 24),
2627
id: "HEART_STREAM",
2728
};
2829
export const frogStream: StreamGenerator = {
29-
create: () => createStream("🐸", "black", "high", 24),
30+
create: (quality) => createStream("🐸", "black", quality, 24),
3031
id: "FROG_STREAM",
3132
};
3233
export const elixirStream: StreamGenerator = {
33-
create: () => createStream("🧪", "black", "high", 24),
34+
create: (quality) => createStream("🧪", "black", quality, 24),
3435
id: "ELIXIR_STREAM",
3536
};
3637
export const octopusStream: StreamGenerator = {
37-
create: () => createStream("🐙", "black", "high", 24),
38+
create: (quality) => createStream("🐙", "black", quality, 24),
3839
id: "OCTOPUS_STREAM",
3940
};
4041

@@ -64,10 +65,10 @@ export const MockVideoPanel = ({ addLocalVideoStream, setSelectedDeviceId, id }:
6465
className="btn btn-sm btn-success"
6566
onClick={() => {
6667
const uuid = uuidv4();
67-
const stream = mockStreams[index].create().stream;
68+
const { stream, stop } = mockStreams[index].create(mockQuality);
6869
const id = mockStreamNames[index] + uuid;
69-
setSelectedDeviceId({ id, type: "video", stream: stream });
70-
addLocalVideoStream(stream, id);
70+
setSelectedDeviceId({ id, type: "video", stream });
71+
addLocalVideoStream(stream, id, "mock", stop);
7172
}}
7273
>
7374
Start

src/components/ScreensharingPanel.tsx

+4-3
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,10 @@ import { useState } from "react";
33
import { showToastError } from "./Toasts";
44
import { TbScreenShare } from "react-icons/tb";
55
import { v4 as uuidv4 } from "uuid";
6+
import { TrackSource } from "../containers/Client";
67

78
type ScreensharingPanelProps = {
8-
addLocalStream: (stream: MediaStream, id: string) => void;
9+
addLocalStream: (stream: MediaStream, id: string, source: TrackSource, stop?: () => void) => void;
910
label: string;
1011
setSelectedDeviceId: (trackId: DeviceInfo | null) => void;
1112
};
@@ -35,9 +36,9 @@ export const ScreensharingPanel = ({ label, addLocalStream, setSelectedDeviceId
3536
const videoStream = new MediaStream(stream.getVideoTracks());
3637
const audioStream = new MediaStream(stream.getAudioTracks());
3738
setSelectedDeviceId({ id: videoId, type: "screenshare", stream: videoStream });
38-
addLocalStream(videoStream, videoId);
39+
addLocalStream(videoStream, videoId, "navigator");
3940
if (screenshareAudio) {
40-
addLocalStream(audioStream, audioId);
41+
addLocalStream(audioStream, audioId, "navigator");
4142
}
4243
});
4344
} else {

src/components/StreamingDeviceSelector.tsx

+102-18
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { useState } from "react";
1+
import React, { useState } from "react";
22
import { VideoDevicePanel } from "./VideoDevicePanel";
33
import { AudioDevicePanel } from "./AudioDevicePanel";
44
import { showToastError } from "./Toasts";
@@ -7,20 +7,28 @@ import { EnumerateDevices, enumerateDevices } from "../utils/browser-media-utils
77
import { MockVideoPanel } from "./MockVideoPanel";
88
import { DeviceInfo } from "../containers/StreamingSettingsCard";
99
import { ScreensharingPanel } from "./ScreensharingPanel";
10+
import { atomWithStorage } from "jotai/utils";
11+
import { useAtom } from "jotai";
12+
import { TbArrowBack } from "react-icons/tb";
13+
import { TrackSource } from "../containers/Client";
1014

1115
export type StreamInfo = {
1216
stream: MediaStream;
1317
id: string;
1418
};
15-
export type DeviceIdToStream = Record<string, StreamInfo>;
1619

1720
type StreamingDeviceSelectorProps = {
1821
id: string;
1922
selectedDeviceId: DeviceInfo | null;
2023
setSelectedDeviceId: (info: DeviceInfo | null) => void;
21-
addLocalStream: (stream: MediaStream, id: string) => void;
24+
addLocalStream: (stream: MediaStream, id: string, source: TrackSource, stop?: () => void) => void;
2225
};
2326

27+
const widthAtom = atomWithStorage("width-constraint", 1280);
28+
const heightAtom = atomWithStorage("height-constraint", 720);
29+
const frameRateAtom = atomWithStorage("frame-rate-constraint", 24);
30+
const forceConstraintsAtom = atomWithStorage("force-constraint", true);
31+
2432
export const StreamingDeviceSelector = ({
2533
id,
2634
selectedDeviceId,
@@ -29,6 +37,11 @@ export const StreamingDeviceSelector = ({
2937
}: StreamingDeviceSelectorProps) => {
3038
const [enumerateDevicesState, setEnumerateDevicesState] = useState<EnumerateDevices | null>(null);
3139

40+
const [width, setWidth] = useAtom(widthAtom);
41+
const [height, setHeight] = useAtom(heightAtom);
42+
const [frameRate, setFrameRate] = useAtom(frameRateAtom);
43+
const [forceConstraints, setForceConstraints] = useAtom(forceConstraintsAtom);
44+
3245
return (
3346
<div className="flex flex-col gap-2">
3447
{enumerateDevicesState?.video?.type !== "OK" && (
@@ -52,20 +65,93 @@ export const StreamingDeviceSelector = ({
5265
)}
5366

5467
<div className="flex place-content-center align-baseline flex-col flex-wrap w-full gap-3">
55-
{enumerateDevicesState?.video.type === "OK" &&
56-
enumerateDevicesState.video.devices.map(({ deviceId, label }) => (
57-
<div key={deviceId} className="join-item w-full">
58-
<VideoDevicePanel
59-
key={deviceId}
60-
deviceId={deviceId}
61-
label={label}
62-
addLocalVideoStream={addLocalStream}
63-
setSelectedVideoId={setSelectedDeviceId}
64-
selected={selectedDeviceId?.id === deviceId}
65-
/>
66-
</div>
67-
))}
68+
{enumerateDevicesState?.video.type === "OK" && (
69+
<div className="flex flex-col gap-2">
70+
<div className="flex flex-col">
71+
<div className="flex flex-row flex-nowrap justify-between">
72+
<div className="flex flex-row items-center gap-2">
73+
<label htmlFor="force-video-constraints">Force video constraints</label>
74+
<input
75+
className="checkbox"
76+
type="checkbox"
77+
name="force-video-constraints"
78+
checked={forceConstraints}
79+
onChange={() => setForceConstraints((prev) => !prev)}
80+
/>
81+
</div>
6882

83+
<button
84+
className="btn btn-neutral btn-sm p-1 ml-1 tooltip tooltip-info tooltip-right"
85+
data-tip="Restore default"
86+
onClick={() => {
87+
setWidth(1280);
88+
setHeight(720);
89+
setFrameRate(24);
90+
}}
91+
>
92+
<TbArrowBack size={"1.5em"} />
93+
</button>
94+
</div>
95+
<div className="flex flex-row flex-nowrap items-center">
96+
<label className="label flex-row gap-2">
97+
<span className="label-text">width</span>
98+
<input
99+
disabled={!forceConstraints}
100+
type="number"
101+
value={width}
102+
className="input w-full input-sm max-w-xs"
103+
onChange={(e) => {
104+
const parsed = Number.parseInt(e.target.value);
105+
if (isNaN(parsed)) return;
106+
setWidth(() => parsed);
107+
}}
108+
/>
109+
</label>
110+
<label className="label flex-row gap-2">
111+
<span className="label-text">height</span>
112+
<input
113+
disabled={!forceConstraints}
114+
type="number"
115+
value={height}
116+
className="input w-full input-sm max-w-xs"
117+
onChange={(e) => {
118+
const parsed = Number.parseInt(e.target.value);
119+
if (isNaN(parsed)) return;
120+
setHeight(parsed);
121+
}}
122+
/>
123+
</label>
124+
<label className="label flex-row gap-2">
125+
<span className="label-text">frame rate</span>
126+
<input
127+
disabled={!forceConstraints}
128+
type="number"
129+
value={frameRate}
130+
className="input w-full input-sm max-w-xs"
131+
onChange={(e) => {
132+
const parsed = Number.parseInt(e.target.value);
133+
if (isNaN(parsed)) return;
134+
setFrameRate(parsed);
135+
}}
136+
/>
137+
</label>
138+
</div>
139+
</div>
140+
{enumerateDevicesState.video.devices.map(({ deviceId, label }) => (
141+
<div key={deviceId} className="join-item w-full">
142+
<VideoDevicePanel
143+
key={deviceId}
144+
deviceId={deviceId}
145+
constraints={forceConstraints ? { width, height, frameRate } : undefined}
146+
label={label}
147+
addLocalVideoStream={addLocalStream}
148+
setSelectedVideoId={setSelectedDeviceId}
149+
selected={selectedDeviceId?.id === deviceId}
150+
/>
151+
</div>
152+
))}
153+
</div>
154+
)}
69155
{enumerateDevicesState?.audio?.type === "OK" &&
70156
enumerateDevicesState.audio.devices
71157
.filter(({ label }) => !label.startsWith("Default"))
@@ -81,13 +167,11 @@ export const StreamingDeviceSelector = ({
81167
/>
82168
</div>
83169
))}
84-
85170
<ScreensharingPanel
86171
addLocalStream={addLocalStream}
87172
setSelectedDeviceId={setSelectedDeviceId}
88173
label={"Screenshare"}
89174
/>
90-
91175
<MockVideoPanel
92176
id={id}
93177
addLocalVideoStream={addLocalStream}

0 commit comments

Comments
 (0)