-
Notifications
You must be signed in to change notification settings - Fork 54
/
VideoConference.tsx
180 lines (170 loc) · 6.04 KB
/
VideoConference.tsx
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
import type {
MessageDecoder,
MessageEncoder,
TrackReferenceOrPlaceholder,
WidgetState,
} from '@livekit/components-core';
import { isEqualTrackRef, isTrackReference, isWeb, log } from '@livekit/components-core';
import { RoomEvent, Track } from 'livekit-client';
import * as React from 'react';
import type { MessageFormatter } from '../components';
import {
CarouselLayout,
ConnectionStateToast,
FocusLayout,
FocusLayoutContainer,
GridLayout,
LayoutContextProvider,
ParticipantTile,
RoomAudioRenderer,
} from '../components';
import { useCreateLayoutContext } from '../context';
import { usePinnedTracks, useTracks } from '../hooks';
import { Chat } from './Chat';
import { ControlBar } from './ControlBar';
import { useWarnAboutMissingStyles } from '../hooks/useWarnAboutMissingStyles';
/**
* @public
*/
export interface VideoConferenceProps extends React.HTMLAttributes<HTMLDivElement> {
chatMessageFormatter?: MessageFormatter;
chatMessageEncoder?: MessageEncoder;
chatMessageDecoder?: MessageDecoder;
/** @alpha */
SettingsComponent?: React.ComponentType;
}
/**
* The `VideoConference` ready-made component is your drop-in solution for a classic video conferencing application.
* It provides functionality such as focusing on one participant, grid view with pagination to handle large numbers
* of participants, basic non-persistent chat, screen sharing, and more.
*
* @remarks
* The component is implemented with other LiveKit components like `FocusContextProvider`,
* `GridLayout`, `ControlBar`, `FocusLayoutContainer` and `FocusLayout`.
* You can use this components as a starting point for your own custom video conferencing application.
*
* @example
* ```tsx
* <LiveKitRoom>
* <VideoConference />
* <LiveKitRoom>
* ```
* @public
*/
export function VideoConference({
chatMessageFormatter,
chatMessageDecoder,
chatMessageEncoder,
SettingsComponent,
...props
}: VideoConferenceProps) {
const [widgetState, setWidgetState] = React.useState<WidgetState>({
showChat: false,
unreadMessages: 0,
showSettings: false,
});
const lastAutoFocusedScreenShareTrack = React.useRef<TrackReferenceOrPlaceholder | null>(null);
const tracks = useTracks(
[
{ source: Track.Source.Camera, withPlaceholder: true },
{ source: Track.Source.ScreenShare, withPlaceholder: false },
],
{ updateOnlyOn: [RoomEvent.ActiveSpeakersChanged], onlySubscribed: false },
);
const widgetUpdate = (state: WidgetState) => {
log.debug('updating widget state', state);
setWidgetState(state);
};
const layoutContext = useCreateLayoutContext();
const screenShareTracks = tracks
.filter(isTrackReference)
.filter((track) => track.publication.source === Track.Source.ScreenShare);
const focusTrack = usePinnedTracks(layoutContext)?.[0];
const carouselTracks = tracks.filter((track) => !isEqualTrackRef(track, focusTrack));
React.useEffect(() => {
// If screen share tracks are published, and no pin is set explicitly, auto set the screen share.
if (
screenShareTracks.some((track) => track.publication.isSubscribed) &&
lastAutoFocusedScreenShareTrack.current === null
) {
log.debug('Auto set screen share focus:', { newScreenShareTrack: screenShareTracks[0] });
layoutContext.pin.dispatch?.({ msg: 'set_pin', trackReference: screenShareTracks[0] });
lastAutoFocusedScreenShareTrack.current = screenShareTracks[0];
} else if (
lastAutoFocusedScreenShareTrack.current &&
!screenShareTracks.some(
(track) =>
track.publication.trackSid ===
lastAutoFocusedScreenShareTrack.current?.publication?.trackSid,
)
) {
log.debug('Auto clearing screen share focus.');
layoutContext.pin.dispatch?.({ msg: 'clear_pin' });
lastAutoFocusedScreenShareTrack.current = null;
}
if (focusTrack && !isTrackReference(focusTrack)) {
const updatedFocusTrack = tracks.find(
(tr) =>
tr.participant.identity === focusTrack.participant.identity &&
tr.source === focusTrack.source,
);
if (updatedFocusTrack !== focusTrack && isTrackReference(updatedFocusTrack)) {
layoutContext.pin.dispatch?.({ msg: 'set_pin', trackReference: updatedFocusTrack });
}
}
}, [
screenShareTracks
.map((ref) => `${ref.publication.trackSid}_${ref.publication.isSubscribed}`)
.join(),
focusTrack?.publication?.trackSid,
tracks,
]);
useWarnAboutMissingStyles();
return (
<div className="lk-video-conference" {...props}>
{isWeb() && (
<LayoutContextProvider
value={layoutContext}
// onPinChange={handleFocusStateChange}
onWidgetChange={widgetUpdate}
>
<div className="lk-video-conference-inner">
{!focusTrack ? (
<div className="lk-grid-layout-wrapper">
<GridLayout tracks={tracks}>
<ParticipantTile />
</GridLayout>
</div>
) : (
<div className="lk-focus-layout-wrapper">
<FocusLayoutContainer>
<CarouselLayout tracks={carouselTracks}>
<ParticipantTile />
</CarouselLayout>
{focusTrack && <FocusLayout trackRef={focusTrack} />}
</FocusLayoutContainer>
</div>
)}
<ControlBar controls={{ chat: true, settings: !!SettingsComponent }} />
</div>
<Chat
style={{ display: widgetState.showChat ? 'grid' : 'none' }}
messageFormatter={chatMessageFormatter}
messageEncoder={chatMessageEncoder}
messageDecoder={chatMessageDecoder}
/>
{SettingsComponent && (
<div
className="lk-settings-menu-modal"
style={{ display: widgetState.showSettings ? 'block' : 'none' }}
>
<SettingsComponent />
</div>
)}
</LayoutContextProvider>
)}
<RoomAudioRenderer />
<ConnectionStateToast />
</div>
);
}