@stream-io/video-react-sdk 0.4.7 → 0.4.9

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.
@@ -4,6 +4,7 @@ export * from './CallControls';
4
4
  export * from './CallParticipantsList';
5
5
  export * from './CallPreview';
6
6
  export * from './CallRecordingList';
7
+ export * from './CallStats';
7
8
  export * from './DeviceSettings';
8
9
  export * from './Icon';
9
10
  export * from './LoadingIndicator';
@@ -1,8 +1,14 @@
1
- import type { UseFloatingProps } from '@floating-ui/react';
2
- export declare const useFloatingUIPreset: ({ placement, strategy, }: Pick<UseFloatingProps, 'placement' | 'strategy'>) => {
3
- refs: import("@floating-ui/react").ExtendedRefs<import("@floating-ui/react").ReferenceType>;
4
- x: number | null;
5
- y: number | null;
1
+ /// <reference types="react" />
2
+ import type { UseFloatingData } from '@floating-ui/react';
3
+ export declare const useFloatingUIPreset: ({ placement, strategy, }: Pick<UseFloatingData, 'placement' | 'strategy'>) => {
4
+ refs: {
5
+ reference: import("react").MutableRefObject<import("@floating-ui/react-dom").ReferenceType | null>;
6
+ floating: import("react").MutableRefObject<HTMLElement | null>;
7
+ setReference: (node: import("@floating-ui/react-dom").ReferenceType | null) => void;
8
+ setFloating: (node: HTMLElement | null) => void;
9
+ } & import("@floating-ui/react").ExtendedRefs<import("@floating-ui/react").ReferenceType>;
10
+ x: number;
11
+ y: number;
6
12
  domReference: Element | null;
7
13
  floating: HTMLElement | null;
8
14
  strategy: import("@floating-ui/utils").Strategy;
@@ -67,6 +67,8 @@ export declare const translations: {
67
67
  Leave: string;
68
68
  "{{ direction }} fullscreen": string;
69
69
  "{{ direction }} picture-in-picture": string;
70
+ "Dominant Speaker": string;
71
+ "Poor connection quality": string;
70
72
  Participants: string;
71
73
  Anonymous: string;
72
74
  "No participants found": string;
package/index.ts CHANGED
@@ -6,7 +6,6 @@ export * from '@stream-io/video-react-bindings';
6
6
  export * from './src/core';
7
7
 
8
8
  export * from './src/components';
9
- export * from './src/types';
10
9
  export * from './src/translations';
11
10
  export {
12
11
  useHorizontalScrollPosition,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stream-io/video-react-sdk",
3
- "version": "0.4.7",
3
+ "version": "0.4.9",
4
4
  "packageManager": "yarn@3.2.4",
5
5
  "main": "./dist/index.cjs.js",
6
6
  "module": "./dist/index.es.js",
@@ -28,11 +28,11 @@
28
28
  "CHANGELOG.md"
29
29
  ],
30
30
  "dependencies": {
31
- "@floating-ui/react": "^0.22.0",
31
+ "@floating-ui/react": "^0.26.1",
32
32
  "@nivo/core": "^0.80.0",
33
33
  "@nivo/line": "^0.80.0",
34
- "@stream-io/video-client": "^0.4.4",
35
- "@stream-io/video-react-bindings": "^0.3.4",
34
+ "@stream-io/video-client": "^0.4.6",
35
+ "@stream-io/video-react-bindings": "^0.3.6",
36
36
  "clsx": "^2.0.0",
37
37
  "prop-types": "^15.8.1"
38
38
  },
@@ -44,7 +44,7 @@
44
44
  "@rollup/plugin-json": "^6.0.1",
45
45
  "@rollup/plugin-replace": "^5.0.4",
46
46
  "@rollup/plugin-typescript": "^11.1.5",
47
- "@stream-io/video-styling": "^0.1.13",
47
+ "@stream-io/video-styling": "^0.1.14",
48
48
  "@types/prop-types": "^15.7.8",
49
49
  "react": "^18.2.0",
50
50
  "react-dom": "^18.2.0",
@@ -49,6 +49,7 @@ export const PermissionRequests = () => {
49
49
  OwnCapability.UPDATE_CALL_PERMISSIONS,
50
50
  );
51
51
 
52
+ const localUserId = localParticipant?.userId;
52
53
  useEffect(() => {
53
54
  if (!call || !canUpdateCallPermissions) return;
54
55
 
@@ -56,8 +57,7 @@ export const PermissionRequests = () => {
56
57
  'call.permission_request',
57
58
  (event: StreamVideoEvent) => {
58
59
  if (event.type !== 'call.permission_request') return;
59
-
60
- if (event.user.id !== localParticipant?.userId) {
60
+ if (event.user.id !== localUserId) {
61
61
  setPermissionRequests((requests) =>
62
62
  [...requests, event as PermissionRequestEvent].sort((a, b) =>
63
63
  byNameOrId(a.user, b.user),
@@ -69,7 +69,7 @@ export const PermissionRequests = () => {
69
69
  return () => {
70
70
  unsubscribe();
71
71
  };
72
- }, [call, canUpdateCallPermissions, localParticipant]);
72
+ }, [call, canUpdateCallPermissions, localUserId]);
73
73
 
74
74
  const handleUpdatePermission: HandleUpdatePermission = (request, type) => {
75
75
  return async () => {
@@ -4,6 +4,7 @@ export * from './CallControls';
4
4
  export * from './CallParticipantsList';
5
5
  export * from './CallPreview';
6
6
  export * from './CallRecordingList';
7
+ export * from './CallStats';
7
8
  export * from './DeviceSettings';
8
9
  export * from './Icon';
9
10
  export * from './LoadingIndicator';
@@ -13,10 +13,6 @@ import {
13
13
  ToggleMenuButtonProps,
14
14
  } from '../../../components';
15
15
  import { Reaction } from '../../../components/Reaction';
16
-
17
- import { DebugParticipantPublishQuality } from '../../../components/Debug/DebugParticipantPublishQuality';
18
- import { DebugStatsView } from '../../../components/Debug/DebugStatsView';
19
- import { useIsDebugMode } from '../../../components/Debug/useIsDebugMode';
20
16
  import { useParticipantViewContext } from './ParticipantViewContext';
21
17
 
22
18
  export type DefaultParticipantViewUIProps = {
@@ -124,10 +120,10 @@ export const ParticipantDetails = ({
124
120
  sessionId,
125
121
  name,
126
122
  userId,
127
- videoStream,
128
123
  } = participant;
129
- const call = useCall()!;
124
+ const call = useCall();
130
125
 
126
+ const { t } = useI18n();
131
127
  const connectionQualityAsString =
132
128
  !!connectionQuality &&
133
129
  SfuModels.ConnectionQuality[connectionQuality].toLowerCase();
@@ -136,8 +132,6 @@ export const ParticipantDetails = ({
136
132
  const hasVideo = publishedTracks.includes(SfuModels.TrackType.VIDEO);
137
133
  const canUnpin = !!pin && pin.isLocalPin;
138
134
 
139
- const isDebugMode = useIsDebugMode();
140
-
141
135
  return (
142
136
  <div className="str-video__participant-details">
143
137
  <span className="str-video__participant-details__name">
@@ -145,7 +139,7 @@ export const ParticipantDetails = ({
145
139
  {indicatorsVisible && isDominantSpeaker && (
146
140
  <span
147
141
  className="str-video__participant-details__name--dominant_speaker"
148
- title="Dominant speaker"
142
+ title={t('Dominant speaker')}
149
143
  />
150
144
  )}
151
145
  {indicatorsVisible && (
@@ -154,7 +148,7 @@ export const ParticipantDetails = ({
154
148
  isLocalParticipant &&
155
149
  connectionQuality === SfuModels.ConnectionQuality.POOR
156
150
  }
157
- message="Poor connection quality. Please check your internet connection."
151
+ message={t('Poor connection quality')}
158
152
  >
159
153
  {connectionQualityAsString && (
160
154
  <span
@@ -176,27 +170,13 @@ export const ParticipantDetails = ({
176
170
  {indicatorsVisible && canUnpin && (
177
171
  // TODO: remove this monstrosity once we have a proper design
178
172
  <span
179
- title="Unpin"
173
+ title={t('Unpin')}
180
174
  onClick={() => call?.unpin(sessionId)}
181
175
  style={{ cursor: 'pointer' }}
182
176
  className="str-video__participant-details__name--pinned"
183
177
  />
184
178
  )}
185
179
  </span>
186
- {isDebugMode && (
187
- <>
188
- <DebugParticipantPublishQuality
189
- participant={participant}
190
- call={call}
191
- />
192
- <DebugStatsView
193
- call={call}
194
- sessionId={sessionId}
195
- userId={userId}
196
- mediaStream={videoStream}
197
- />
198
- </>
199
- )}
200
180
  </div>
201
181
  );
202
182
  };
@@ -1,18 +1,18 @@
1
1
  import { useEffect } from 'react';
2
+ import type { UseFloatingData } from '@floating-ui/react';
2
3
  import {
3
- offset,
4
4
  autoUpdate,
5
+ flip,
6
+ offset,
7
+ shift,
5
8
  size,
6
9
  useFloating,
7
- shift,
8
- flip,
9
10
  } from '@floating-ui/react';
10
- import type { UseFloatingProps } from '@floating-ui/react';
11
11
 
12
12
  export const useFloatingUIPreset = ({
13
13
  placement,
14
14
  strategy,
15
- }: Pick<UseFloatingProps, 'placement' | 'strategy'>) => {
15
+ }: Pick<UseFloatingData, 'placement' | 'strategy'>) => {
16
16
  const {
17
17
  refs,
18
18
  x,
@@ -72,6 +72,9 @@
72
72
  "{{ direction }} fullscreen": "{{ direction }} fullscreen",
73
73
  "{{ direction }} picture-in-picture": "{{ direction }} picture-in-picture",
74
74
 
75
+ "Dominant Speaker": "Dominant Speaker",
76
+ "Poor connection quality": "Poor connection quality. Please check your internet connection.",
77
+
75
78
  "Participants": "Participants",
76
79
  "Anonymous": ", and ({{ count }}) anonymous",
77
80
  "No participants found": "No participants found",
@@ -1,5 +0,0 @@
1
- import { Call, StreamVideoParticipant } from '@stream-io/video-client';
2
- export declare const DebugParticipantPublishQuality: (props: {
3
- participant: StreamVideoParticipant;
4
- call: Call;
5
- }) => import("react/jsx-runtime").JSX.Element;
@@ -1,7 +0,0 @@
1
- import { Call } from '@stream-io/video-client';
2
- export declare const DebugStatsView: (props: {
3
- call: Call;
4
- mediaStream?: MediaStream;
5
- sessionId: string;
6
- userId: string;
7
- }) => import("react/jsx-runtime").JSX.Element;
@@ -1,5 +0,0 @@
1
- /**
2
- * Internal purpose hook. Enables certain development mode tools.
3
- */
4
- export declare const useIsDebugMode: () => boolean;
5
- export declare const useDebugPreferredVideoCodec: () => string | null | undefined;
@@ -1,7 +0,0 @@
1
- import { ReactNode } from 'react';
2
- export type ChildrenOnly = {
3
- children: ReactNode;
4
- };
5
- export type Readable<T> = {
6
- [k in keyof T]: T[k];
7
- } & {};
@@ -1 +0,0 @@
1
- export * from './components';
@@ -1,62 +0,0 @@
1
- import { useEffect, useState } from 'react';
2
- import { Call, StreamVideoParticipant } from '@stream-io/video-client';
3
-
4
- export const DebugParticipantPublishQuality = (props: {
5
- participant: StreamVideoParticipant;
6
- call: Call;
7
- }) => {
8
- const { call, participant } = props;
9
- const [quality, setQuality] = useState<string>();
10
- const [publishStats, setPublishStats] = useState(() => ({
11
- f: true,
12
- h: true,
13
- q: true,
14
- }));
15
-
16
- useEffect(() => {
17
- return call.on('changePublishQuality', (event) => {
18
- if (event.eventPayload.oneofKind !== 'changePublishQuality') return;
19
- const { videoSenders } = event.eventPayload.changePublishQuality;
20
- // FIXME OL: support additional layers (like screenshare)
21
- const [videoLayer] = videoSenders.map(({ layers }) => {
22
- return layers.map((l) => ({ [l.name]: l.active }));
23
- });
24
- setPublishStats((s) => ({
25
- ...s,
26
- ...videoLayer,
27
- }));
28
- });
29
- }, [call]);
30
-
31
- return (
32
- <select
33
- title={`Published tracks: ${JSON.stringify(publishStats)}`}
34
- value={quality}
35
- onChange={(e) => {
36
- const value = e.target.value;
37
- setQuality(value);
38
- let w = 960;
39
- let h = 540;
40
- if (value === 'h') {
41
- w = w / 2; // 480
42
- h = h / 2; // 270
43
- } else if (value === 'q') {
44
- w = w / 4; // 240
45
- h = h / 4; // 135
46
- }
47
- call.updateSubscriptionsPartial('video', {
48
- [participant.sessionId]: {
49
- dimension: {
50
- width: w,
51
- height: h,
52
- },
53
- },
54
- });
55
- }}
56
- >
57
- <option value="f">High (f)</option>
58
- <option value="h">Medium (h)</option>
59
- <option value="q">Low (q)</option>
60
- </select>
61
- );
62
- };
@@ -1,126 +0,0 @@
1
- import { useEffect, useRef, useState } from 'react';
2
- import { Call } from '@stream-io/video-client';
3
- import { useFloatingUIPreset } from '../../hooks';
4
- import { StatCard } from '../CallStats';
5
- import { useCallStateHooks } from '@stream-io/video-react-bindings';
6
-
7
- export const DebugStatsView = (props: {
8
- call: Call;
9
- mediaStream?: MediaStream;
10
- sessionId: string;
11
- userId: string;
12
- }) => {
13
- const { call, mediaStream, sessionId, userId } = props;
14
- const { useCallStatsReport } = useCallStateHooks();
15
- const callStatsReport = useCallStatsReport();
16
-
17
- useEffect(() => {
18
- call.startReportingStatsFor(sessionId);
19
- return () => {
20
- call.stopReportingStatsFor(sessionId);
21
- };
22
- }, [call, sessionId]);
23
-
24
- const reportForTracks = callStatsReport?.participants[sessionId];
25
- const trackStats = reportForTracks?.flatMap((report) => report.streams);
26
-
27
- const previousWidth = useRef<Record<string, number>>({ f: 0, h: 0, q: 0 });
28
- const previousHeight = useRef<Record<string, number>>({ f: 0, h: 0, q: 0 });
29
- trackStats?.forEach((track) => {
30
- if (track.kind !== 'video') return;
31
- const { frameWidth = 0, frameHeight = 0, rid = '' } = track;
32
- if (
33
- frameWidth !== previousWidth.current[rid] ||
34
- frameHeight !== previousHeight.current[rid]
35
- ) {
36
- const trackSize = `${frameWidth}x${frameHeight}`;
37
- console.log(`Track stats (${userId}/${sessionId}): ${rid}(${trackSize})`);
38
- previousWidth.current[rid] = frameWidth;
39
- previousHeight.current[rid] = frameHeight;
40
- }
41
- });
42
-
43
- const { refs, strategy, y, x } = useFloatingUIPreset({
44
- placement: 'top',
45
- strategy: 'absolute',
46
- });
47
-
48
- const [isPopperOpen, setIsPopperOpen] = useState(false);
49
-
50
- const [videoTrack] = mediaStream?.getVideoTracks() ?? [];
51
- const settings = videoTrack?.getSettings();
52
- return (
53
- <>
54
- <span
55
- className="str-video__debug__track-stats-icon"
56
- tabIndex={0}
57
- ref={refs.setReference}
58
- title={
59
- settings &&
60
- `${settings.width}x${settings.height}@${Math.round(
61
- settings.frameRate || 0,
62
- )}`
63
- }
64
- onClick={() => {
65
- setIsPopperOpen((v) => !v);
66
- }}
67
- />
68
- {isPopperOpen && (
69
- <div
70
- className="str-video__debug__track-stats str-video__call-stats"
71
- ref={refs.setFloating}
72
- style={{
73
- position: strategy,
74
- top: y ?? 0,
75
- left: x ?? 0,
76
- overflowY: 'auto',
77
- }}
78
- >
79
- <h3>Participant stats</h3>
80
- <div className="str-video__call-stats__card-container">
81
- {trackStats
82
- ?.map((track) => {
83
- if (track.kind === 'video') {
84
- return (
85
- <StatCard
86
- key={`${track.rid}/${track.ssrc}/${track.codec}/${track.kind}`}
87
- label={
88
- `${track.kind}: ${track.codec} ` +
89
- (track.rid ? ` (${track.rid})` : '')
90
- }
91
- value={`${track.frameWidth || 0}x${
92
- track.frameHeight || 0
93
- }@${track.framesPerSecond || 0}fps`}
94
- />
95
- );
96
- } else if (track.kind === 'audio') {
97
- return (
98
- <StatCard
99
- key={`${track.ssrc}/${track.codec}/${track.kind}`}
100
- label={track.codec || 'N/A'}
101
- value={`Jitter: ${track.jitter || 0}ms`}
102
- />
103
- );
104
- }
105
- return null;
106
- })
107
- .filter(Boolean)}
108
- </div>
109
- {reportForTracks?.map((report, index) => (
110
- <pre key={index}>
111
- {JSON.stringify(unwrapStats(report.rawStats), null, 2)}
112
- </pre>
113
- ))}
114
- </div>
115
- )}
116
- </>
117
- );
118
- };
119
-
120
- const unwrapStats = (rawStats?: RTCStatsReport) => {
121
- const decodedStats: Record<string, string> = {};
122
- rawStats?.forEach((s) => {
123
- decodedStats[s.id] = s;
124
- });
125
- return decodedStats;
126
- };
@@ -1,24 +0,0 @@
1
- import { useMemo } from 'react';
2
-
3
- const useQueryParams = () => {
4
- return useMemo(
5
- () =>
6
- typeof window === 'undefined'
7
- ? null
8
- : new URLSearchParams(window.location.search),
9
- [],
10
- );
11
- };
12
-
13
- /**
14
- * Internal purpose hook. Enables certain development mode tools.
15
- */
16
- export const useIsDebugMode = () => {
17
- const params = useQueryParams();
18
- return !!params?.get('debug');
19
- };
20
-
21
- export const useDebugPreferredVideoCodec = () => {
22
- const params = useQueryParams();
23
- return params?.get('video_codec');
24
- };
@@ -1,7 +0,0 @@
1
- import { ReactNode } from 'react';
2
-
3
- export type ChildrenOnly = { children: ReactNode };
4
-
5
- export type Readable<T> = {
6
- [k in keyof T]: T[k];
7
- } & {};
@@ -1 +0,0 @@
1
- export * from './components';