|
1 | 1 | import type { Participant, TrackPublication } from 'livekit-client'; |
2 | 2 | import { LocalParticipant, Room, RoomEvent, Track } from 'livekit-client'; |
3 | 3 | import type { Subscriber, Subscription } from 'rxjs'; |
4 | | -import { concat, filter, finalize, map, Observable, startWith, Subject } from 'rxjs'; |
| 4 | +import { concat, filter, map, Observable, startWith } from 'rxjs'; |
5 | 5 | // @ts-ignore some module resolutions (other than 'node') choke on this |
6 | 6 | import type { RoomEventCallbacks } from 'livekit-client/dist/src/room/Room'; |
7 | 7 | import { log } from '../logger'; |
@@ -182,38 +182,48 @@ export function createMediaDeviceObserver( |
182 | 182 | onError?: (e: Error) => void, |
183 | 183 | requestPermissions = true, |
184 | 184 | ) { |
185 | | - const onDeviceChange = async () => { |
186 | | - try { |
187 | | - const newDevices = await Room.getLocalDevices(kind, requestPermissions); |
188 | | - deviceSubject.next(newDevices); |
189 | | - } catch (e: any) { |
190 | | - onError?.(e); |
| 185 | + // Initial devices fetch observable |
| 186 | + const initialDevices$ = new Observable<MediaDeviceInfo[]>((subscriber) => { |
| 187 | + Room.getLocalDevices(kind, requestPermissions) |
| 188 | + .then((devices) => { |
| 189 | + subscriber.next(devices); |
| 190 | + subscriber.complete(); |
| 191 | + }) |
| 192 | + .catch((e) => { |
| 193 | + onError?.(e); |
| 194 | + subscriber.next([] as MediaDeviceInfo[]); |
| 195 | + subscriber.complete(); |
| 196 | + }); |
| 197 | + }); |
| 198 | + |
| 199 | + // Device change observable |
| 200 | + const deviceChanges$ = new Observable<MediaDeviceInfo[]>((subscriber) => { |
| 201 | + const onDeviceChange = async () => { |
| 202 | + try { |
| 203 | + const newDevices = await Room.getLocalDevices(kind, requestPermissions); |
| 204 | + subscriber.next(newDevices); |
| 205 | + } catch (e: any) { |
| 206 | + onError?.(e); |
| 207 | + } |
| 208 | + }; |
| 209 | + |
| 210 | + if (typeof window !== 'undefined') { |
| 211 | + if (!window.isSecureContext) { |
| 212 | + throw new Error( |
| 213 | + `Accessing media devices is available only in secure contexts (HTTPS and localhost), in some or all supporting browsers. See: https://developer.mozilla.org/en-US/docs/Web/API/Navigator/mediaDevices`, |
| 214 | + ); |
| 215 | + } |
| 216 | + navigator?.mediaDevices?.addEventListener('devicechange', onDeviceChange); |
191 | 217 | } |
192 | | - }; |
193 | | - const deviceSubject = new Subject<MediaDeviceInfo[]>(); |
194 | 218 |
|
195 | | - const observable = deviceSubject.pipe( |
196 | | - finalize(() => { |
| 219 | + // Return unsubscribe function that cleans up when the observable is unsubscribed |
| 220 | + return () => { |
197 | 221 | navigator?.mediaDevices?.removeEventListener('devicechange', onDeviceChange); |
198 | | - }), |
199 | | - ); |
| 222 | + }; |
| 223 | + }); |
200 | 224 |
|
201 | | - if (typeof window !== 'undefined') { |
202 | | - if (!window.isSecureContext) { |
203 | | - throw new Error( |
204 | | - `Accessing media devices is available only in secure contexts (HTTPS and localhost), in some or all supporting browsers. See: https://developer.mozilla.org/en-US/docs/Web/API/Navigator/mediaDevices`, |
205 | | - ); |
206 | | - } |
207 | | - navigator?.mediaDevices?.addEventListener('devicechange', onDeviceChange); |
208 | | - } |
209 | | - // because we rely on an async function, concat the promise to retrieve the initial values with the observable |
210 | | - return concat( |
211 | | - Room.getLocalDevices(kind, requestPermissions).catch((e) => { |
212 | | - onError?.(e); |
213 | | - return [] as MediaDeviceInfo[]; |
214 | | - }), |
215 | | - observable, |
216 | | - ); |
| 225 | + // Combine both observables |
| 226 | + return concat(initialDevices$, deviceChanges$); |
217 | 227 | } |
218 | 228 |
|
219 | 229 | export function createDataObserver(room: Room) { |
|
0 commit comments