@whereby.com/browser-sdk 0.0.0-canary-20240326095648

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.
@@ -0,0 +1,110 @@
1
+ import { ReactHTMLElement } from 'react';
2
+
3
+ interface WherebyEmbedElementAttributes extends ReactHTMLElement<HTMLElement> {
4
+ audio: string;
5
+ avatarUrl: string;
6
+ background: string;
7
+ breakout: string;
8
+ cameraAccess: string;
9
+ chat: string;
10
+ displayName: string;
11
+ emptyRoomInvitation: string;
12
+ externalId: string;
13
+ floatSelf: string;
14
+ groups: string;
15
+ help: string;
16
+ lang: string;
17
+ leaveButton: string;
18
+ locking: string;
19
+ logo: string;
20
+ lowData: string;
21
+ metadata: string;
22
+ minimal: string;
23
+ moreButton: string;
24
+ participantCount: string;
25
+ people: string;
26
+ pipButton: string;
27
+ precallReview: string;
28
+ recording: string;
29
+ room: string;
30
+ settingsButton: string;
31
+ screenshare: string;
32
+ style: {
33
+ [key: string]: string;
34
+ };
35
+ subgridLabels: string;
36
+ title: string;
37
+ video: string;
38
+ virtualBackgroundUrl: string;
39
+ toolbarDarkText: string;
40
+ }
41
+ interface WherebyEmbedElementEventMap {
42
+ ready: CustomEvent;
43
+ knock: CustomEvent;
44
+ participantupdate: CustomEvent<{
45
+ count: number;
46
+ }>;
47
+ join: CustomEvent;
48
+ leave: CustomEvent<{
49
+ removed: boolean;
50
+ }>;
51
+ participant_join: CustomEvent<{
52
+ participant: {
53
+ metadata: string;
54
+ };
55
+ }>;
56
+ participant_leave: CustomEvent<{
57
+ participant: {
58
+ metadata: string;
59
+ };
60
+ }>;
61
+ microphone_toggle: CustomEvent<{
62
+ enabled: boolean;
63
+ }>;
64
+ camera_toggle: CustomEvent<{
65
+ enabled: boolean;
66
+ }>;
67
+ chat_toggle: CustomEvent<{
68
+ open: boolean;
69
+ }>;
70
+ pip_toggle: CustomEvent<{
71
+ open: boolean;
72
+ }>;
73
+ deny_device_permission: CustomEvent<{
74
+ denied: boolean;
75
+ }>;
76
+ screenshare_toggle: CustomEvent<{
77
+ enabled: boolean;
78
+ }>;
79
+ streaming_status_change: CustomEvent<{
80
+ status: string;
81
+ }>;
82
+ connection_status_change: CustomEvent<{
83
+ status: "stable" | "unstable";
84
+ }>;
85
+ }
86
+ interface WherebyEmbedElementCommands {
87
+ startRecording: () => void;
88
+ stopRecording: () => void;
89
+ startStreaming: () => void;
90
+ stopStreaming: () => void;
91
+ toggleCamera: (enabled?: boolean) => void;
92
+ toggleMicrophone: (enabled?: boolean) => void;
93
+ toggleScreenshare: (enabled?: boolean) => void;
94
+ toogleChat: (enabled?: boolean) => void;
95
+ }
96
+ interface WherebyEmbedElement extends HTMLIFrameElement, WherebyEmbedElementCommands {
97
+ addEventListener<K extends keyof (WherebyEmbedElementEventMap & HTMLElementEventMap)>(type: K, listener: (this: HTMLIFrameElement, ev: (WherebyEmbedElementEventMap & HTMLElementEventMap)[K]) => void, options?: boolean | AddEventListenerOptions | undefined): void;
98
+ }
99
+ declare global {
100
+ namespace JSX {
101
+ interface IntrinsicElements {
102
+ ["whereby-embed"]: Partial<WherebyEmbedElementAttributes>;
103
+ }
104
+ }
105
+ }
106
+ declare const _default: {
107
+ sdkVersion: string;
108
+ };
109
+
110
+ export { type WherebyEmbedElement, _default as default };
@@ -0,0 +1,133 @@
1
+ import { define, ref } from 'heresy';
2
+ import { parseRoomUrlAndSubdomain } from '@whereby.com/core/utils';
3
+
4
+ const boolAttrs = [
5
+ "audio",
6
+ "background",
7
+ "cameraAccess",
8
+ "chat",
9
+ "people",
10
+ "embed",
11
+ "emptyRoomInvitation",
12
+ "help",
13
+ "leaveButton",
14
+ "precallReview",
15
+ "screenshare",
16
+ "video",
17
+ "floatSelf",
18
+ "recording",
19
+ "logo",
20
+ "locking",
21
+ "participantCount",
22
+ "settingsButton",
23
+ "pipButton",
24
+ "moreButton",
25
+ "personality",
26
+ "subgridLabels",
27
+ "lowData",
28
+ "breakout",
29
+ "toolbarDarkText",
30
+ ];
31
+ define("WherebyEmbed", {
32
+ oninit() {
33
+ this.iframe = ref();
34
+ },
35
+ onconnected() {
36
+ window.addEventListener("message", this.onmessage.bind(this));
37
+ },
38
+ ondisconnected() {
39
+ window.removeEventListener("message", this.onmessage.bind(this));
40
+ },
41
+ observedAttributes: [
42
+ "displayName",
43
+ "minimal",
44
+ "room",
45
+ "subdomain",
46
+ "lang",
47
+ "metadata",
48
+ "groups",
49
+ "virtualBackgroundUrl",
50
+ "avatarUrl",
51
+ "externalId",
52
+ "title",
53
+ ...boolAttrs,
54
+ ].map((a) => a.toLowerCase()),
55
+ onattributechanged({ attributeName, oldValue }) {
56
+ if (["room", "subdomain"].includes(attributeName) && oldValue == null)
57
+ return;
58
+ this.render();
59
+ },
60
+ style(self) {
61
+ return `
62
+ ${self} {
63
+ display: block;
64
+ }
65
+ ${self} iframe {
66
+ border: none;
67
+ height: 100%;
68
+ width: 100%;
69
+ }
70
+ `;
71
+ },
72
+ _postCommand(command, args = []) {
73
+ if (this.iframe.current) {
74
+ this.iframe.current.contentWindow.postMessage({ command, args }, this.roomUrl.origin);
75
+ }
76
+ },
77
+ startRecording() {
78
+ this._postCommand("start_recording");
79
+ },
80
+ stopRecording() {
81
+ this._postCommand("stop_recording");
82
+ },
83
+ startStreaming() {
84
+ this._postCommand("start_streaming");
85
+ },
86
+ stopStreaming() {
87
+ this._postCommand("stop_streaming");
88
+ },
89
+ toggleCamera(enabled) {
90
+ this._postCommand("toggle_camera", [enabled]);
91
+ },
92
+ toggleMicrophone(enabled) {
93
+ this._postCommand("toggle_microphone", [enabled]);
94
+ },
95
+ toggleScreenshare(enabled) {
96
+ this._postCommand("toggle_screenshare", [enabled]);
97
+ },
98
+ toggleChat(enabled) {
99
+ this._postCommand("toggle_chat", [enabled]);
100
+ },
101
+ onmessage({ origin, data, }) {
102
+ if (!this.roomUrl || origin !== this.roomUrl.origin)
103
+ return;
104
+ const { type, payload: detail } = data;
105
+ this.dispatchEvent(new CustomEvent(type, { detail }));
106
+ },
107
+ render() {
108
+ const { avatarurl: avatarUrl, displayname: displayName, lang, metadata, externalid: externalId, minimal, room, groups, virtualbackgroundurl: virtualBackgroundUrl, title, } = this;
109
+ let roomUrl, subdomain;
110
+ try {
111
+ ({ roomUrl, subdomain } = parseRoomUrlAndSubdomain(room, this.subdomain));
112
+ }
113
+ catch (error) {
114
+ return this.html `Whereby: ${error instanceof Error ? error.message : "unknown error"}`;
115
+ }
116
+ this.roomUrl = roomUrl;
117
+ Object.entries(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign({ jsApi: true, we: "1", iframeSource: subdomain }, (displayName && { displayName })), (lang && { lang: lang })), (metadata && { metadata: metadata })), (externalId && { externalId })), (groups && { groups: groups })), (virtualBackgroundUrl && { virtualBackgroundUrl: virtualBackgroundUrl })), (avatarUrl && { avatarUrl: avatarUrl })), (minimal != null && { embed: minimal })), boolAttrs.reduce((o, v) => (this[v.toLowerCase()] != null ? Object.assign(Object.assign({}, o), { [v]: this[v.toLowerCase()] }) : o), {}))).forEach(([k, v]) => {
118
+ if (!this.roomUrl.searchParams.has(k)) {
119
+ this.roomUrl.searchParams.set(k, v);
120
+ }
121
+ });
122
+ this.html `
123
+ <iframe
124
+ title=${title || "Video calling component"}
125
+ ref=${this.iframe}
126
+ src=${this.roomUrl}
127
+ allow="autoplay; camera; microphone; fullscreen; speaker; display-capture; media-capture" />
128
+ `;
129
+ },
130
+ });
131
+ var index = { sdkVersion: "1" };
132
+
133
+ export { index as default };
@@ -0,0 +1,121 @@
1
+ import * as React from 'react';
2
+ import { Store, LocalMediaOptions, RemoteParticipant, LocalParticipant, Screenshare, ChatMessage as ChatMessage$1 } from '@whereby.com/core';
3
+
4
+ interface VideoViewSelfProps {
5
+ stream: MediaStream;
6
+ muted?: boolean;
7
+ mirror?: boolean;
8
+ style?: React.CSSProperties;
9
+ onResize?: ({ width, height, stream }: {
10
+ width: number;
11
+ height: number;
12
+ stream: MediaStream;
13
+ }) => void;
14
+ onSetAspectRatio?: ({ aspectRatio }: {
15
+ aspectRatio: number;
16
+ }) => void;
17
+ }
18
+ type VideoViewProps = VideoViewSelfProps & React.DetailedHTMLProps<React.VideoHTMLAttributes<HTMLVideoElement>, HTMLVideoElement>;
19
+ declare const _default: ({ muted, mirror, stream, onResize, onSetAspectRatio, ...rest }: VideoViewProps) => React.JSX.Element;
20
+
21
+ interface LocalMediaState {
22
+ currentCameraDeviceId?: string;
23
+ currentMicrophoneDeviceId?: string;
24
+ cameraDeviceError: unknown;
25
+ cameraDevices: MediaDeviceInfo[];
26
+ isSettingCameraDevice: boolean;
27
+ isSettingMicrophoneDevice: boolean;
28
+ isStarting: boolean;
29
+ localStream?: MediaStream;
30
+ microphoneDeviceError: unknown;
31
+ microphoneDevices: MediaDeviceInfo[];
32
+ speakerDevices: MediaDeviceInfo[];
33
+ startError: unknown;
34
+ }
35
+ interface LocalMediaActions {
36
+ setCameraDevice: (deviceId: string) => void;
37
+ setMicrophoneDevice: (deviceId: string) => void;
38
+ toggleCameraEnabled: (enabled?: boolean) => void;
39
+ toggleMicrophoneEnabled: (enabled?: boolean) => void;
40
+ }
41
+ type UseLocalMediaResult = {
42
+ state: LocalMediaState;
43
+ actions: LocalMediaActions;
44
+ store: Store;
45
+ };
46
+ type UseLocalMediaOptions = LocalMediaOptions;
47
+
48
+ type RemoteParticipantState = Omit<RemoteParticipant, "newJoiner" | "streams">;
49
+ type LocalParticipantState = LocalParticipant;
50
+ interface WaitingParticipantState {
51
+ id: string;
52
+ displayName: string | null;
53
+ }
54
+ interface ChatMessageState {
55
+ senderId: string;
56
+ timestamp: string;
57
+ text: string;
58
+ }
59
+ type ScreenshareState = Screenshare;
60
+ type LocalScreenshareStatus = "starting" | "active";
61
+ type ChatMessage = Pick<ChatMessage$1, "senderId" | "timestamp" | "text">;
62
+ type ConnectionStatus = "initializing" | "connecting" | "connected" | "reconnect" | "room_locked" | "knocking" | "disconnecting" | "disconnected" | "knock_rejected";
63
+ type CloudRecordingState = {
64
+ error?: string;
65
+ status: "recording" | "requested" | "error";
66
+ startedAt?: number;
67
+ };
68
+ type LiveStreamState = {
69
+ status: "streaming";
70
+ startedAt?: number;
71
+ };
72
+ interface RoomConnectionState {
73
+ chatMessages: ChatMessage[];
74
+ cloudRecording?: CloudRecordingState;
75
+ localScreenshareStatus?: LocalScreenshareStatus;
76
+ localParticipant?: LocalParticipantState;
77
+ remoteParticipants: RemoteParticipantState[];
78
+ screenshares: Screenshare[];
79
+ connectionStatus: ConnectionStatus;
80
+ liveStream?: LiveStreamState;
81
+ waitingParticipants: WaitingParticipantState[];
82
+ }
83
+ interface RoomConnectionOptions {
84
+ displayName?: string;
85
+ localMediaOptions?: LocalMediaOptions;
86
+ roomKey?: string;
87
+ localMedia?: UseLocalMediaResult;
88
+ externalId?: string;
89
+ }
90
+ interface UseRoomConnectionOptions extends Omit<RoomConnectionOptions, "localMedia"> {
91
+ localMedia?: UseLocalMediaResult;
92
+ }
93
+ interface RoomConnectionActions {
94
+ sendChatMessage(text: string): void;
95
+ knock(): void;
96
+ setDisplayName(displayName: string): void;
97
+ toggleCamera(enabled?: boolean): void;
98
+ toggleMicrophone(enabled?: boolean): void;
99
+ acceptWaitingParticipant(participantId: string): void;
100
+ rejectWaitingParticipant(participantId: string): void;
101
+ startCloudRecording(): void;
102
+ startScreenshare(): void;
103
+ stopCloudRecording(): void;
104
+ stopScreenshare(): void;
105
+ }
106
+
107
+ type VideoViewComponentProps = Omit<React.ComponentProps<typeof _default>, "onResize">;
108
+ interface RoomConnectionComponents {
109
+ VideoView: (props: VideoViewComponentProps) => ReturnType<typeof _default>;
110
+ }
111
+ type RoomConnectionRef = {
112
+ state: RoomConnectionState;
113
+ actions: RoomConnectionActions;
114
+ components: RoomConnectionComponents;
115
+ _ref: Store;
116
+ };
117
+ declare function useRoomConnection(roomUrl: string, roomConnectionOptions?: UseRoomConnectionOptions): RoomConnectionRef;
118
+
119
+ declare function useLocalMedia(optionsOrStream?: UseLocalMediaOptions | MediaStream): UseLocalMediaResult;
120
+
121
+ export { type ChatMessageState as ChatMessage, type LocalParticipantState as LocalParticipant, type RemoteParticipantState as RemoteParticipant, type RoomConnectionState as RoomConnection, type ScreenshareState as Screenshare, type UseLocalMediaResult, _default as VideoView, type WaitingParticipantState as WaitingParticipant, useLocalMedia, useRoomConnection };
@@ -0,0 +1,253 @@
1
+ import * as React from 'react';
2
+ import { useState, useEffect, useCallback } from 'react';
3
+ import { debounce } from '@whereby.com/core/utils';
4
+ import { selectChatMessages, selectCloudRecordingRaw, selectLocalParticipantRaw, selectLocalMediaStream, selectRemoteParticipants, selectScreenshares, selectRoomConnectionStatus, selectStreamingRaw, selectWaitingParticipants, createServices, createStore, observeStore, doAppJoin, sdkVersion, appLeft, doRtcReportStreamResolution, doSendChatMessage, doKnockRoom, doSetDisplayName, toggleCameraEnabled, toggleMicrophoneEnabled, doAcceptWaitingParticipant, doRejectWaitingParticipant, doStartCloudRecording, doStartScreenshare, doStopCloudRecording, doStopScreenshare, selectCameraDeviceError, selectCameraDevices, selectCurrentCameraDeviceId, selectCurrentMicrophoneDeviceId, selectIsSettingCameraDevice, selectIsSettingMicrophoneDevice, selectIsLocalMediaStarting, selectMicrophoneDeviceError, selectMicrophoneDevices, selectSpeakerDevices, selectLocalMediaStartError, doStartLocalMedia, doStopLocalMedia, setCurrentCameraDeviceId, setCurrentMicrophoneDeviceId } from '@whereby.com/core';
5
+ import { createSelector } from '@reduxjs/toolkit';
6
+
7
+ /******************************************************************************
8
+ Copyright (c) Microsoft Corporation.
9
+
10
+ Permission to use, copy, modify, and/or distribute this software for any
11
+ purpose with or without fee is hereby granted.
12
+
13
+ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
14
+ REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
15
+ AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
16
+ INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
17
+ LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
18
+ OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
19
+ PERFORMANCE OF THIS SOFTWARE.
20
+ ***************************************************************************** */
21
+ /* global Reflect, Promise, SuppressedError, Symbol */
22
+
23
+
24
+ function __rest(s, e) {
25
+ var t = {};
26
+ for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
27
+ t[p] = s[p];
28
+ if (s != null && typeof Object.getOwnPropertySymbols === "function")
29
+ for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
30
+ if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
31
+ t[p[i]] = s[p[i]];
32
+ }
33
+ return t;
34
+ }
35
+
36
+ typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) {
37
+ var e = new Error(message);
38
+ return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
39
+ };
40
+
41
+ var VideoView = (_a) => {
42
+ var { muted, mirror = false, stream, onResize, onSetAspectRatio } = _a, rest = __rest(_a, ["muted", "mirror", "stream", "onResize", "onSetAspectRatio"]);
43
+ const videoEl = React.useRef(null);
44
+ React.useEffect(() => {
45
+ if (!videoEl.current) {
46
+ return;
47
+ }
48
+ const resizeObserver = new ResizeObserver(debounce(() => {
49
+ if (videoEl.current && (stream === null || stream === void 0 ? void 0 : stream.id)) {
50
+ if (onResize) {
51
+ onResize({
52
+ width: videoEl.current.clientWidth,
53
+ height: videoEl.current.clientHeight,
54
+ stream,
55
+ });
56
+ }
57
+ const h = videoEl.current.videoHeight;
58
+ const w = videoEl.current.videoWidth;
59
+ if (onSetAspectRatio && w && h && w + h > 20) {
60
+ onSetAspectRatio({ aspectRatio: w / h });
61
+ }
62
+ }
63
+ }, { delay: 1000, edges: true }));
64
+ resizeObserver.observe(videoEl.current);
65
+ return () => {
66
+ resizeObserver.disconnect();
67
+ };
68
+ }, [stream]);
69
+ React.useEffect(() => {
70
+ if (!videoEl.current) {
71
+ return;
72
+ }
73
+ if (videoEl.current.srcObject !== stream) {
74
+ videoEl.current.srcObject = stream;
75
+ }
76
+ if (videoEl.current.muted !== muted) {
77
+ videoEl.current.muted = Boolean(muted);
78
+ }
79
+ }, [muted, stream, videoEl]);
80
+ return (React.createElement("video", Object.assign({ ref: videoEl, autoPlay: true, playsInline: true }, rest, { style: Object.assign({ transform: mirror ? "scaleX(-1)" : "none", width: "100%", height: "100%" }, rest.style) })));
81
+ };
82
+
83
+ const selectRoomConnectionState = createSelector(selectChatMessages, selectCloudRecordingRaw, selectLocalParticipantRaw, selectLocalMediaStream, selectRemoteParticipants, selectScreenshares, selectRoomConnectionStatus, selectStreamingRaw, selectWaitingParticipants, (chatMessages, cloudRecording, localParticipant, localMediaStream, remoteParticipants, screenshares, connectionStatus, streaming, waitingParticipants) => {
84
+ const state = {
85
+ chatMessages,
86
+ cloudRecording: cloudRecording.isRecording ? { status: "recording" } : undefined,
87
+ localScreenshareStatus: localParticipant.isScreenSharing ? "active" : undefined,
88
+ localParticipant: Object.assign(Object.assign({}, localParticipant), { stream: localMediaStream }),
89
+ remoteParticipants,
90
+ screenshares,
91
+ connectionStatus,
92
+ liveStream: streaming.isStreaming
93
+ ? {
94
+ status: "streaming",
95
+ startedAt: streaming.startedAt,
96
+ }
97
+ : undefined,
98
+ waitingParticipants,
99
+ };
100
+ return state;
101
+ });
102
+
103
+ const initialState$1 = {
104
+ chatMessages: [],
105
+ remoteParticipants: [],
106
+ connectionStatus: "initializing",
107
+ screenshares: [],
108
+ waitingParticipants: [],
109
+ };
110
+ const defaultRoomConnectionOptions = {
111
+ localMediaOptions: {
112
+ audio: true,
113
+ video: true,
114
+ },
115
+ };
116
+ function useRoomConnection(roomUrl, roomConnectionOptions = defaultRoomConnectionOptions) {
117
+ const [store] = React.useState(() => {
118
+ if (roomConnectionOptions.localMedia) {
119
+ return roomConnectionOptions.localMedia.store;
120
+ }
121
+ const services = createServices();
122
+ return createStore({ injectServices: services });
123
+ });
124
+ const [boundVideoView, setBoundVideoView] = React.useState();
125
+ const [roomConnectionState, setRoomConnectionState] = React.useState(initialState$1);
126
+ React.useEffect(() => {
127
+ const unsubscribe = observeStore(store, selectRoomConnectionState, setRoomConnectionState);
128
+ const url = new URL(roomUrl);
129
+ const searchParams = new URLSearchParams(url.search);
130
+ const roomKey = searchParams.get("roomKey");
131
+ store.dispatch(doAppJoin({
132
+ displayName: roomConnectionOptions.displayName || "Guest",
133
+ localMediaOptions: roomConnectionOptions.localMedia
134
+ ? undefined
135
+ : roomConnectionOptions.localMediaOptions,
136
+ roomKey,
137
+ roomUrl,
138
+ sdkVersion: sdkVersion,
139
+ externalId: roomConnectionOptions.externalId || null,
140
+ }));
141
+ return () => {
142
+ unsubscribe();
143
+ store.dispatch(appLeft());
144
+ };
145
+ }, []);
146
+ React.useEffect(() => {
147
+ if (store && !boundVideoView) {
148
+ setBoundVideoView(() => (props) => {
149
+ return React.createElement(VideoView, Object.assign({}, props, {
150
+ onResize: ({ stream, width, height, }) => {
151
+ store.dispatch(doRtcReportStreamResolution({
152
+ streamId: stream.id,
153
+ width,
154
+ height,
155
+ }));
156
+ },
157
+ }));
158
+ });
159
+ }
160
+ }, [store, boundVideoView]);
161
+ const sendChatMessage = React.useCallback((text) => store.dispatch(doSendChatMessage({ text })), [store]);
162
+ const knock = React.useCallback(() => store.dispatch(doKnockRoom()), [store]);
163
+ const setDisplayName = React.useCallback((displayName) => store.dispatch(doSetDisplayName({ displayName })), [store]);
164
+ const toggleCamera = React.useCallback((enabled) => store.dispatch(toggleCameraEnabled({ enabled })), [store]);
165
+ const toggleMicrophone = React.useCallback((enabled) => store.dispatch(toggleMicrophoneEnabled({ enabled })), [store]);
166
+ const acceptWaitingParticipant = React.useCallback((participantId) => store.dispatch(doAcceptWaitingParticipant({ participantId })), [store]);
167
+ const rejectWaitingParticipant = React.useCallback((participantId) => store.dispatch(doRejectWaitingParticipant({ participantId })), [store]);
168
+ const startCloudRecording = React.useCallback(() => store.dispatch(doStartCloudRecording()), [store]);
169
+ const startScreenshare = React.useCallback(() => store.dispatch(doStartScreenshare()), [store]);
170
+ const stopCloudRecording = React.useCallback(() => store.dispatch(doStopCloudRecording()), [store]);
171
+ const stopScreenshare = React.useCallback(() => store.dispatch(doStopScreenshare()), [store]);
172
+ return {
173
+ state: roomConnectionState,
174
+ actions: {
175
+ sendChatMessage,
176
+ knock,
177
+ setDisplayName,
178
+ toggleCamera,
179
+ toggleMicrophone,
180
+ acceptWaitingParticipant,
181
+ rejectWaitingParticipant,
182
+ startCloudRecording,
183
+ startScreenshare,
184
+ stopCloudRecording,
185
+ stopScreenshare,
186
+ },
187
+ components: {
188
+ VideoView: boundVideoView || VideoView,
189
+ },
190
+ _ref: store,
191
+ };
192
+ }
193
+
194
+ const selectLocalMediaState = createSelector(selectCameraDeviceError, selectCameraDevices, selectCurrentCameraDeviceId, selectCurrentMicrophoneDeviceId, selectIsSettingCameraDevice, selectIsSettingMicrophoneDevice, selectIsLocalMediaStarting, selectLocalMediaStream, selectMicrophoneDeviceError, selectMicrophoneDevices, selectSpeakerDevices, selectLocalMediaStartError, (cameraDeviceError, cameraDevices, currentCameraDeviceId, currentMicrophoneDeviceId, isSettingCameraDevice, isSettingMicrophoneDevice, isStarting, localStream, microphoneDeviceError, microphoneDevices, speakerDevices, startError) => {
195
+ const state = {
196
+ cameraDeviceError,
197
+ cameraDevices,
198
+ currentCameraDeviceId,
199
+ currentMicrophoneDeviceId,
200
+ isSettingCameraDevice,
201
+ isSettingMicrophoneDevice,
202
+ isStarting,
203
+ localStream,
204
+ microphoneDeviceError,
205
+ microphoneDevices,
206
+ speakerDevices,
207
+ startError,
208
+ };
209
+ return state;
210
+ });
211
+
212
+ const initialState = {
213
+ cameraDeviceError: null,
214
+ cameraDevices: [],
215
+ isSettingCameraDevice: false,
216
+ isSettingMicrophoneDevice: false,
217
+ isStarting: false,
218
+ microphoneDeviceError: null,
219
+ microphoneDevices: [],
220
+ speakerDevices: [],
221
+ startError: null,
222
+ };
223
+ function useLocalMedia(optionsOrStream = { audio: true, video: true }) {
224
+ const [store] = useState(() => {
225
+ const services = createServices();
226
+ return createStore({ injectServices: services });
227
+ });
228
+ const [localMediaState, setLocalMediaState] = useState(initialState);
229
+ useEffect(() => {
230
+ const unsubscribe = observeStore(store, selectLocalMediaState, setLocalMediaState);
231
+ store.dispatch(doStartLocalMedia(optionsOrStream));
232
+ return () => {
233
+ unsubscribe();
234
+ store.dispatch(doStopLocalMedia());
235
+ };
236
+ }, []);
237
+ const setCameraDevice = useCallback((deviceId) => store.dispatch(setCurrentCameraDeviceId({ deviceId })), [store]);
238
+ const setMicrophoneDevice = useCallback((deviceId) => store.dispatch(setCurrentMicrophoneDeviceId({ deviceId })), [store]);
239
+ const toggleCamera = useCallback((enabled) => store.dispatch(toggleCameraEnabled({ enabled })), [store]);
240
+ const toggleMicrophone = useCallback((enabled) => store.dispatch(toggleMicrophoneEnabled({ enabled })), [store]);
241
+ return {
242
+ state: localMediaState,
243
+ actions: {
244
+ setCameraDevice,
245
+ setMicrophoneDevice,
246
+ toggleCameraEnabled: toggleCamera,
247
+ toggleMicrophoneEnabled: toggleMicrophone,
248
+ },
249
+ store,
250
+ };
251
+ }
252
+
253
+ export { VideoView, useLocalMedia, useRoomConnection };