@@ -14,7 +14,7 @@ import {
14
14
type RoomOptions ,
15
15
Track ,
16
16
} from "livekit-client" ;
17
- import { useEffect , useMemo , useRef } from "react" ;
17
+ import { useCallback , useEffect , useMemo , useRef , useState } from "react" ;
18
18
import E2EEWorker from "livekit-client/e2ee-worker?worker" ;
19
19
import { logger } from "matrix-js-sdk/lib/logger" ;
20
20
import { type MatrixRTCSession } from "matrix-js-sdk/lib/matrixrtc" ;
@@ -40,7 +40,6 @@ import {
40
40
useTrackProcessor ,
41
41
useTrackProcessorSync ,
42
42
} from "./TrackProcessorContext" ;
43
- import { useInitial } from "../useInitial" ;
44
43
import { observeTrackReference$ } from "../state/MediaViewModel" ;
45
44
import { useUrlParams } from "../UrlParams" ;
46
45
@@ -57,87 +56,127 @@ export function useLivekit(
57
56
) : UseLivekitResult {
58
57
const { controlledAudioDevices } = useUrlParams ( ) ;
59
58
60
- const e2eeOptions = useMemo ( ( ) : E2EEManagerOptions | undefined => {
61
- if ( e2eeSystem . kind === E2eeType . NONE ) return undefined ;
59
+ const initialMuteStates = useRef < MuteStates > ( muteStates ) ;
60
+ const devices = useMediaDevices ( ) ;
61
+ const initialDevices = useRef < MediaDevices > ( devices ) ;
62
62
63
- if ( e2eeSystem . kind === E2eeType . PER_PARTICIPANT ) {
63
+ // Store if audio/video are currently updating. If to prohibit unnecessary calls
64
+ // to setMicrophoneEnabled/setCameraEnabled
65
+ const audioMuteUpdating = useRef ( false ) ;
66
+ const videoMuteUpdating = useRef ( false ) ;
67
+ // Store the current button mute state that gets passed to this hook via props.
68
+ // We need to store it for awaited code that relies on the current value.
69
+ const buttonEnabled = useRef ( {
70
+ audio : initialMuteStates . current . audio . enabled ,
71
+ video : initialMuteStates . current . video . enabled ,
72
+ } ) ;
73
+
74
+ const { processor } = useTrackProcessor ( ) ;
75
+
76
+ const createRoom = ( opt : RoomOptions , e2ee : EncryptionSystem ) : Room => {
77
+ logger . info ( "[LivekitRoom] Create LiveKit room with options" , opt ) ;
78
+ // We have to create the room manually here due to a bug inside
79
+ // @livekit /components-react. JSON.stringify() is used in deps of a
80
+ // useEffect() with an argument that references itself, if E2EE is enabled
81
+ let newE2eeOptions : E2EEManagerOptions | undefined ;
82
+ if ( e2ee . kind === E2eeType . PER_PARTICIPANT ) {
64
83
logger . info ( "Created MatrixKeyProvider (per participant)" ) ;
65
- return {
84
+ newE2eeOptions = {
66
85
keyProvider : new MatrixKeyProvider ( ) ,
67
86
worker : new E2EEWorker ( ) ,
68
87
} ;
69
- } else if ( e2eeSystem . kind === E2eeType . SHARED_KEY && e2eeSystem . secret ) {
88
+ } else if ( e2ee . kind === E2eeType . SHARED_KEY && e2ee . secret ) {
70
89
logger . info ( "Created ExternalE2EEKeyProvider (shared key)" ) ;
71
-
72
- return {
90
+ newE2eeOptions = {
73
91
keyProvider : new ExternalE2EEKeyProvider ( ) ,
74
92
worker : new E2EEWorker ( ) ,
75
93
} ;
76
94
}
77
- } , [ e2eeSystem ] ) ;
78
-
79
- useEffect ( ( ) => {
80
- if ( e2eeSystem . kind === E2eeType . NONE || ! e2eeOptions ) return ;
81
-
82
- if ( e2eeSystem . kind === E2eeType . PER_PARTICIPANT ) {
83
- ( e2eeOptions . keyProvider as MatrixKeyProvider ) . setRTCSession ( rtcSession ) ;
84
- } else if ( e2eeSystem . kind === E2eeType . SHARED_KEY && e2eeSystem . secret ) {
85
- ( e2eeOptions . keyProvider as ExternalE2EEKeyProvider )
86
- . setKey ( e2eeSystem . secret )
87
- . catch ( ( e ) => {
88
- logger . error ( "Failed to set shared key for E2EE" , e ) ;
89
- } ) ;
90
- }
91
- } , [ e2eeOptions , e2eeSystem , rtcSession ] ) ;
95
+ const r = new Room ( { ...opt , e2ee : newE2eeOptions } ) ;
96
+ r . setE2EEEnabled ( e2ee . kind !== E2eeType . NONE ) . catch ( ( e ) => {
97
+ logger . error ( "Failed to set E2EE enabled on room" , e ) ;
98
+ } ) ;
92
99
93
- const initialMuteStates = useRef < MuteStates > ( muteStates ) ;
94
- const devices = useMediaDevices ( ) ;
95
- const initialDevices = useRef < MediaDevices > ( devices ) ;
100
+ return r ;
101
+ } ;
96
102
97
- const { processor } = useTrackProcessor ( ) ;
98
- const initialProcessor = useInitial ( ( ) => processor ) ;
103
+ // Track the current room options in case we need to recreate the room if the encryption system changes
104
+ // Only needed because we allow swapping the room in case the e2ee system changes.
105
+ // otherwise this could become part of: `createRoom`
99
106
const roomOptions = useMemo (
100
107
( ) : RoomOptions => ( {
101
108
...defaultLiveKitOptions ,
102
109
videoCaptureDefaults : {
103
110
...defaultLiveKitOptions . videoCaptureDefaults ,
104
- deviceId : initialDevices . current . videoInput . selectedId ,
105
- processor : initialProcessor ,
111
+ deviceId : devices . videoInput . selectedId ,
112
+ processor : processor ,
106
113
} ,
107
114
audioCaptureDefaults : {
108
115
...defaultLiveKitOptions . audioCaptureDefaults ,
109
- deviceId : initialDevices . current . audioInput . selectedId ,
116
+ deviceId : devices . audioInput . selectedId ,
110
117
} ,
111
118
audioOutput : {
112
- deviceId : initialDevices . current . audioOutput . selectedId ,
119
+ deviceId : devices . audioOutput . selectedId ,
113
120
} ,
114
- e2ee : e2eeOptions ,
115
121
} ) ,
116
- [ e2eeOptions , initialProcessor ] ,
122
+ [ processor , devices ] ,
117
123
) ;
124
+ const [ room , setRoom ] = useState ( ( ) => createRoom ( roomOptions , e2eeSystem ) ) ;
118
125
119
- // Store if audio/video are currently updating. If to prohibit unnecessary calls
120
- // to setMicrophoneEnabled/setCameraEnabled
121
- const audioMuteUpdating = useRef ( false ) ;
122
- const videoMuteUpdating = useRef ( false ) ;
123
- // Store the current button mute state that gets passed to this hook via props.
124
- // We need to store it for awaited code that relies on the current value.
125
- const buttonEnabled = useRef ( {
126
- audio : initialMuteStates . current . audio . enabled ,
127
- video : initialMuteStates . current . video . enabled ,
128
- } ) ;
126
+ // Setup and update the already existing keyProvider
127
+ useEffect ( ( ) => {
128
+ const e2eeOptions = room . options . e2ee ;
129
+ if (
130
+ e2eeSystem . kind === E2eeType . NONE ||
131
+ ! ( e2eeOptions && "keyProvider" in e2eeOptions )
132
+ )
133
+ return ;
129
134
130
- // We have to create the room manually here due to a bug inside
131
- // @livekit /components-react. JSON.stringify() is used in deps of a
132
- // useEffect() with an argument that references itself, if E2EE is enabled
133
- const room = useMemo ( ( ) => {
134
- logger . info ( "[LivekitRooms] Create LiveKit room with options" , roomOptions ) ;
135
- const r = new Room ( roomOptions ) ;
136
- r . setE2EEEnabled ( e2eeSystem . kind !== E2eeType . NONE ) . catch ( ( e ) => {
137
- logger . error ( "Failed to set E2EE enabled on room" , e ) ;
138
- } ) ;
139
- return r ;
140
- } , [ roomOptions , e2eeSystem ] ) ;
135
+ if ( e2eeSystem . kind === E2eeType . PER_PARTICIPANT ) {
136
+ ( e2eeOptions . keyProvider as MatrixKeyProvider ) . setRTCSession ( rtcSession ) ;
137
+ } else if ( e2eeSystem . kind === E2eeType . SHARED_KEY && e2eeSystem . secret ) {
138
+ ( e2eeOptions . keyProvider as ExternalE2EEKeyProvider )
139
+ . setKey ( e2eeSystem . secret )
140
+ . catch ( ( e ) => {
141
+ logger . error ( "Failed to set shared key for E2EE" , e ) ;
142
+ } ) ;
143
+ }
144
+ } , [ room . options . e2ee , e2eeSystem , rtcSession ] ) ;
145
+
146
+ // Do we really allow hot swapping the e2ee system?
147
+ // Will we ever reach this code?
148
+ useEffect ( ( ) => {
149
+ const e2eeOptions = room . options . e2ee ;
150
+ // Only do sth if our e2eeSystem has changed.
151
+ if (
152
+ // from non to sth else.
153
+ ( e2eeSystem . kind === E2eeType . NONE && e2eeOptions !== undefined ) ||
154
+ // from MatrixKeyProvider to sth else
155
+ ( e2eeSystem . kind === E2eeType . PER_PARTICIPANT &&
156
+ ! (
157
+ e2eeOptions &&
158
+ "keyProvider" in e2eeOptions &&
159
+ e2eeOptions . keyProvider instanceof MatrixKeyProvider
160
+ ) ) ||
161
+ // from ExternalE2EEKeyProvider to sth else
162
+ ( e2eeSystem . kind === E2eeType . SHARED_KEY &&
163
+ ! (
164
+ e2eeOptions &&
165
+ "keyProvider" in e2eeOptions &&
166
+ e2eeOptions . keyProvider instanceof ExternalE2EEKeyProvider
167
+ ) )
168
+ ) {
169
+ logger . warn (
170
+ "[LivekitRoom] we cannot change the key provider after the room has been created, disconnecting and creating a new room" ,
171
+ ) ;
172
+ const resetRoom = async ( ) : Promise < void > => {
173
+ await room . disconnect ( ) ;
174
+ const newRoom = createRoom ( roomOptions , e2eeSystem ) ;
175
+ setRoom ( newRoom ) ;
176
+ } ;
177
+ void resetRoom ( ) ;
178
+ }
179
+ } , [ room , e2eeSystem , roomOptions , createRoom ] ) ;
141
180
142
181
// Sync the requested track processors with LiveKit
143
182
useTrackProcessorSync (
0 commit comments