@livekit/react-native 1.4.3 → 2.0.2
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.
- package/README.md +35 -23
- package/android/src/main/java/com/livekit/reactnative/LiveKitReactNative.kt +4 -4
- package/android/src/main/java/com/livekit/reactnative/video/CustomVideoDecoderFactory.kt +67 -0
- package/android/src/main/java/com/livekit/reactnative/video/CustomVideoEncoderFactory.kt +74 -0
- package/lib/commonjs/audio/AudioManager.js +15 -6
- package/lib/commonjs/audio/AudioManager.js.map +1 -1
- package/lib/commonjs/audio/AudioSession.js +2 -0
- package/lib/commonjs/audio/AudioSession.js.map +1 -1
- package/lib/commonjs/components/VideoView.js +12 -11
- package/lib/commonjs/components/VideoView.js.map +1 -1
- package/lib/commonjs/components/ViewPortDetector.js +134 -39
- package/lib/commonjs/components/ViewPortDetector.js.map +1 -1
- package/lib/commonjs/useParticipant.js +9 -9
- package/lib/commonjs/useParticipant.js.map +1 -1
- package/lib/commonjs/useRoom.js +5 -5
- package/lib/commonjs/useRoom.js.map +1 -1
- package/lib/module/audio/AudioManager.js +15 -6
- package/lib/module/audio/AudioManager.js.map +1 -1
- package/lib/module/audio/AudioSession.js +2 -0
- package/lib/module/audio/AudioSession.js.map +1 -1
- package/lib/module/components/VideoView.js +13 -12
- package/lib/module/components/VideoView.js.map +1 -1
- package/lib/module/components/ViewPortDetector.js +134 -40
- package/lib/module/components/ViewPortDetector.js.map +1 -1
- package/lib/module/useParticipant.js +9 -9
- package/lib/module/useParticipant.js.map +1 -1
- package/lib/module/useRoom.js +5 -5
- package/lib/module/useRoom.js.map +1 -1
- package/lib/typescript/audio/AudioManager.d.ts +1 -1
- package/lib/typescript/audio/AudioSession.d.ts +2 -0
- package/lib/typescript/components/ViewPortDetector.d.ts +11 -4
- package/package.json +2 -2
- package/src/audio/AudioManager.ts +18 -8
- package/src/audio/AudioSession.ts +2 -0
- package/src/components/VideoView.tsx +20 -13
- package/src/components/ViewPortDetector.tsx +112 -21
- package/src/useParticipant.ts +15 -9
- package/src/useRoom.ts +5 -5
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { useState, useEffect, useMemo } from 'react';
|
|
2
2
|
import { Platform } from 'react-native';
|
|
3
|
-
import { RoomEvent,
|
|
3
|
+
import { RoomEvent, Room } from 'livekit-client';
|
|
4
4
|
import AudioSession, {
|
|
5
5
|
getDefaultAppleAudioConfigurationForMode,
|
|
6
6
|
type AppleAudioConfiguration,
|
|
@@ -31,14 +31,25 @@ export function useIOSAudioManagement(
|
|
|
31
31
|
[localTrackCount, remoteTrackCount]
|
|
32
32
|
);
|
|
33
33
|
|
|
34
|
+
useEffect(() => {
|
|
35
|
+
let recalculateTrackCounts = () => {
|
|
36
|
+
setLocalTrackCount(getLocalAudioTrackCount(room));
|
|
37
|
+
setRemoteTrackCount(getRemoteAudioTrackCount(room));
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
recalculateTrackCounts();
|
|
41
|
+
|
|
42
|
+
room.on(RoomEvent.Connected, recalculateTrackCounts);
|
|
43
|
+
|
|
44
|
+
return () => {
|
|
45
|
+
room.off(RoomEvent.Connected, recalculateTrackCounts);
|
|
46
|
+
};
|
|
47
|
+
}, [room]);
|
|
34
48
|
useEffect(() => {
|
|
35
49
|
if (Platform.OS !== 'ios') {
|
|
36
50
|
return () => {};
|
|
37
51
|
}
|
|
38
52
|
|
|
39
|
-
setLocalTrackCount(getLocalAudioTrackCount(room));
|
|
40
|
-
setRemoteTrackCount(getRemoteAudioTrackCount(room));
|
|
41
|
-
|
|
42
53
|
let onLocalPublished = () => {
|
|
43
54
|
setLocalTrackCount(localTrackCount + 1);
|
|
44
55
|
};
|
|
@@ -85,7 +96,6 @@ export function useIOSAudioManagement(
|
|
|
85
96
|
let configFunc =
|
|
86
97
|
onConfigureNativeAudio ?? getDefaultAppleAudioConfigurationForMode;
|
|
87
98
|
let audioConfig = configFunc(trackState, preferSpeakerOutput);
|
|
88
|
-
|
|
89
99
|
AudioSession.setAppleAudioConfiguration(audioConfig);
|
|
90
100
|
}, [trackState, onConfigureNativeAudio, preferSpeakerOutput]);
|
|
91
101
|
}
|
|
@@ -106,13 +116,13 @@ function computeAudioTrackState(
|
|
|
106
116
|
}
|
|
107
117
|
|
|
108
118
|
function getLocalAudioTrackCount(room: Room): number {
|
|
109
|
-
return room.localParticipant.
|
|
119
|
+
return room.localParticipant.audioTrackPublications.size;
|
|
110
120
|
}
|
|
111
121
|
|
|
112
122
|
function getRemoteAudioTrackCount(room: Room): number {
|
|
113
123
|
var audioTracks = 0;
|
|
114
|
-
room.
|
|
115
|
-
audioTracks += participant.
|
|
124
|
+
room.remoteParticipants.forEach((participant) => {
|
|
125
|
+
audioTracks += participant.audioTrackPublications.size;
|
|
116
126
|
});
|
|
117
127
|
|
|
118
128
|
return audioTracks;
|
|
@@ -36,6 +36,8 @@ const LivekitReactNative = NativeModules.LivekitReactNative
|
|
|
36
36
|
*
|
|
37
37
|
* See {@link AndroidAudioTypePresets} for pre-configured values.
|
|
38
38
|
*
|
|
39
|
+
* NOTE: If `audioTypeOptions` is set, this must also be reflected in your android MainApplication setup.
|
|
40
|
+
*
|
|
39
41
|
* ----
|
|
40
42
|
* iOS
|
|
41
43
|
*
|
|
@@ -9,7 +9,7 @@ import {
|
|
|
9
9
|
VideoTrack,
|
|
10
10
|
} from 'livekit-client';
|
|
11
11
|
import { RTCView } from '@livekit/react-native-webrtc';
|
|
12
|
-
import { useEffect, useState } from 'react';
|
|
12
|
+
import { useCallback, useEffect, useMemo, useState } from 'react';
|
|
13
13
|
import { RemoteVideoTrack } from 'livekit-client';
|
|
14
14
|
import ViewPortDetector from './ViewPortDetector';
|
|
15
15
|
|
|
@@ -35,6 +35,20 @@ export const VideoView = ({
|
|
|
35
35
|
return info;
|
|
36
36
|
});
|
|
37
37
|
|
|
38
|
+
const layoutOnChange = useCallback(
|
|
39
|
+
(event: LayoutChangeEvent) => elementInfo.onLayout(event),
|
|
40
|
+
[elementInfo]
|
|
41
|
+
);
|
|
42
|
+
const visibilityOnChange = useCallback(
|
|
43
|
+
(isVisible: boolean) => elementInfo.onVisibility(isVisible),
|
|
44
|
+
[elementInfo]
|
|
45
|
+
);
|
|
46
|
+
const shouldObserveVisibility = useMemo(() => {
|
|
47
|
+
return (
|
|
48
|
+
videoTrack instanceof RemoteVideoTrack && videoTrack.isAdaptiveStream
|
|
49
|
+
);
|
|
50
|
+
}, [videoTrack]);
|
|
51
|
+
|
|
38
52
|
const [mediaStream, setMediaStream] = useState(videoTrack?.mediaStream);
|
|
39
53
|
useEffect(() => {
|
|
40
54
|
setMediaStream(videoTrack?.mediaStream);
|
|
@@ -64,22 +78,15 @@ export const VideoView = ({
|
|
|
64
78
|
}, [videoTrack, elementInfo]);
|
|
65
79
|
|
|
66
80
|
return (
|
|
67
|
-
<View
|
|
68
|
-
style={{ ...style, ...styles.container }}
|
|
69
|
-
onLayout={(event) => {
|
|
70
|
-
elementInfo.onLayout(event);
|
|
71
|
-
}}
|
|
72
|
-
>
|
|
81
|
+
<View style={{ ...style, ...styles.container }} onLayout={layoutOnChange}>
|
|
73
82
|
<ViewPortDetector
|
|
74
|
-
onChange={
|
|
83
|
+
onChange={visibilityOnChange}
|
|
75
84
|
style={styles.videoView}
|
|
85
|
+
disabled={!shouldObserveVisibility}
|
|
86
|
+
propKey={videoTrack}
|
|
76
87
|
>
|
|
77
88
|
<RTCView
|
|
78
|
-
|
|
79
|
-
style={{
|
|
80
|
-
flex: 1,
|
|
81
|
-
width: '100%',
|
|
82
|
-
}}
|
|
89
|
+
style={styles.videoView}
|
|
83
90
|
streamURL={mediaStream?.toURL() ?? ''}
|
|
84
91
|
objectFit={objectFit}
|
|
85
92
|
zOrder={zOrder}
|
|
@@ -1,15 +1,61 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
import React, { Component, PropsWithChildren } from 'react';
|
|
4
|
-
import {
|
|
4
|
+
import {
|
|
5
|
+
AppState,
|
|
6
|
+
AppStateStatus,
|
|
7
|
+
NativeEventSubscription,
|
|
8
|
+
View,
|
|
9
|
+
ViewStyle,
|
|
10
|
+
} from 'react-native';
|
|
11
|
+
|
|
12
|
+
const DEFAULT_DELAY = 1000;
|
|
5
13
|
|
|
6
14
|
export type Props = {
|
|
7
15
|
disabled?: boolean;
|
|
8
16
|
style?: ViewStyle;
|
|
9
17
|
onChange?: (isVisible: boolean) => void;
|
|
10
18
|
delay?: number;
|
|
19
|
+
propKey?: any;
|
|
11
20
|
};
|
|
12
21
|
|
|
22
|
+
class TimeoutHandler {
|
|
23
|
+
private handlerRef: { id: any } = { id: -1 };
|
|
24
|
+
|
|
25
|
+
get handler(): any {
|
|
26
|
+
return this.handlerRef.id;
|
|
27
|
+
}
|
|
28
|
+
set handler(n: any) {
|
|
29
|
+
this.handlerRef.id = n;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
clear() {
|
|
33
|
+
clearTimeout(this.handlerRef.id as any);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function setIntervalWithTimeout(
|
|
38
|
+
callback: (clear: () => void) => any,
|
|
39
|
+
intervalMs: number,
|
|
40
|
+
handleWrapper = new TimeoutHandler()
|
|
41
|
+
): TimeoutHandler {
|
|
42
|
+
let cleared = false;
|
|
43
|
+
|
|
44
|
+
const timeout = () => {
|
|
45
|
+
handleWrapper.handler = setTimeout(() => {
|
|
46
|
+
callback(() => {
|
|
47
|
+
cleared = true;
|
|
48
|
+
handleWrapper.clear();
|
|
49
|
+
});
|
|
50
|
+
if (!cleared) {
|
|
51
|
+
timeout();
|
|
52
|
+
}
|
|
53
|
+
}, intervalMs);
|
|
54
|
+
};
|
|
55
|
+
timeout();
|
|
56
|
+
return handleWrapper;
|
|
57
|
+
}
|
|
58
|
+
|
|
13
59
|
/**
|
|
14
60
|
* Detects when this is in the viewport and visible.
|
|
15
61
|
*
|
|
@@ -19,8 +65,10 @@ export default class ViewPortDetector extends Component<
|
|
|
19
65
|
PropsWithChildren<Props>
|
|
20
66
|
> {
|
|
21
67
|
private lastValue: boolean | null = null;
|
|
22
|
-
private interval:
|
|
68
|
+
private interval: TimeoutHandler | null = null;
|
|
23
69
|
private view: View | null = null;
|
|
70
|
+
private lastAppStateActive = false;
|
|
71
|
+
private appStateSubscription: NativeEventSubscription | null = null;
|
|
24
72
|
|
|
25
73
|
constructor(props: Props) {
|
|
26
74
|
super(props);
|
|
@@ -28,43 +76,84 @@ export default class ViewPortDetector extends Component<
|
|
|
28
76
|
}
|
|
29
77
|
|
|
30
78
|
componentDidMount() {
|
|
31
|
-
|
|
79
|
+
this.lastAppStateActive = AppState.currentState === 'active';
|
|
80
|
+
this.appStateSubscription = AppState.addEventListener(
|
|
81
|
+
'change',
|
|
82
|
+
this.handleAppStateChange
|
|
83
|
+
);
|
|
84
|
+
if (this.hasValidTimeout(this.props.disabled, this.props.delay)) {
|
|
32
85
|
this.startWatching();
|
|
33
86
|
}
|
|
34
87
|
}
|
|
35
88
|
|
|
36
89
|
componentWillUnmount() {
|
|
90
|
+
this.appStateSubscription?.remove();
|
|
91
|
+
this.appStateSubscription = null;
|
|
37
92
|
this.stopWatching();
|
|
38
93
|
}
|
|
39
94
|
|
|
95
|
+
hasValidTimeout = (disabled?: boolean, delay?: number): boolean => {
|
|
96
|
+
let disabledValue = disabled ?? false;
|
|
97
|
+
let delayValue = delay ?? DEFAULT_DELAY;
|
|
98
|
+
return (
|
|
99
|
+
AppState.currentState === 'active' && !disabledValue && delayValue > 0
|
|
100
|
+
);
|
|
101
|
+
};
|
|
102
|
+
|
|
40
103
|
UNSAFE_componentWillReceiveProps(nextProps: Props) {
|
|
41
|
-
if (nextProps.disabled) {
|
|
104
|
+
if (!this.hasValidTimeout(nextProps.disabled, nextProps.delay)) {
|
|
42
105
|
this.stopWatching();
|
|
43
106
|
} else {
|
|
44
|
-
this.
|
|
107
|
+
if (this.props.propKey !== nextProps.propKey) {
|
|
108
|
+
this.lastValue = null;
|
|
109
|
+
}
|
|
45
110
|
this.startWatching();
|
|
46
111
|
}
|
|
47
112
|
}
|
|
113
|
+
handleAppStateChange = (nextAppState: AppStateStatus) => {
|
|
114
|
+
let nextAppStateActive = nextAppState === 'active';
|
|
115
|
+
if (this.lastAppStateActive !== nextAppStateActive) {
|
|
116
|
+
this.checkVisibility();
|
|
117
|
+
}
|
|
118
|
+
this.lastAppStateActive = nextAppStateActive;
|
|
48
119
|
|
|
49
|
-
|
|
120
|
+
if (!this.hasValidTimeout(this.props.disabled, this.props.delay)) {
|
|
121
|
+
this.stopWatching();
|
|
122
|
+
} else {
|
|
123
|
+
this.startWatching();
|
|
124
|
+
}
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
startWatching = () => {
|
|
50
128
|
if (this.interval) {
|
|
51
129
|
return;
|
|
52
130
|
}
|
|
53
|
-
this.interval =
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
this.checkInViewPort(width, height);
|
|
59
|
-
});
|
|
60
|
-
}, this.props.delay || 100);
|
|
61
|
-
}
|
|
131
|
+
this.interval = setIntervalWithTimeout(
|
|
132
|
+
this.checkVisibility,
|
|
133
|
+
this.props.delay || DEFAULT_DELAY
|
|
134
|
+
);
|
|
135
|
+
};
|
|
62
136
|
|
|
63
|
-
|
|
64
|
-
this.interval
|
|
65
|
-
|
|
137
|
+
stopWatching = () => {
|
|
138
|
+
this.interval?.clear();
|
|
139
|
+
this.interval = null;
|
|
140
|
+
};
|
|
66
141
|
|
|
67
|
-
|
|
142
|
+
checkVisibility = () => {
|
|
143
|
+
if (!this.view) {
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
if (AppState.currentState !== 'active') {
|
|
148
|
+
this.updateVisibility(false);
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
this.view.measure((_x, _y, width, height, _pageX, _pageY) => {
|
|
153
|
+
this.checkInViewPort(width, height);
|
|
154
|
+
});
|
|
155
|
+
};
|
|
156
|
+
checkInViewPort = (width?: number, height?: number) => {
|
|
68
157
|
let isVisible: boolean;
|
|
69
158
|
// Not visible if any of these are missing.
|
|
70
159
|
if (!width || !height) {
|
|
@@ -72,13 +161,15 @@ export default class ViewPortDetector extends Component<
|
|
|
72
161
|
} else {
|
|
73
162
|
isVisible = true;
|
|
74
163
|
}
|
|
164
|
+
this.updateVisibility(isVisible);
|
|
165
|
+
};
|
|
75
166
|
|
|
167
|
+
updateVisibility = (isVisible: boolean) => {
|
|
76
168
|
if (this.lastValue !== isVisible) {
|
|
77
169
|
this.lastValue = isVisible;
|
|
78
170
|
this.props.onChange?.(isVisible);
|
|
79
171
|
}
|
|
80
|
-
}
|
|
81
|
-
|
|
172
|
+
};
|
|
82
173
|
render() {
|
|
83
174
|
return (
|
|
84
175
|
<View
|
package/src/useParticipant.ts
CHANGED
|
@@ -34,22 +34,28 @@ export function useParticipant(participant: Participant): ParticipantState {
|
|
|
34
34
|
);
|
|
35
35
|
|
|
36
36
|
const [cameraPublication, setCameraPublication] = useState(
|
|
37
|
-
participant.
|
|
37
|
+
participant.getTrackPublication(Track.Source.Camera)
|
|
38
38
|
);
|
|
39
39
|
const [microphonePublication, setMicrophonePublication] = useState(
|
|
40
|
-
participant.
|
|
40
|
+
participant.getTrackPublication(Track.Source.Microphone)
|
|
41
41
|
);
|
|
42
42
|
const [screenSharePublication, setScreenSharePublication] = useState(
|
|
43
|
-
participant.
|
|
43
|
+
participant.getTrackPublication(Track.Source.ScreenShare)
|
|
44
44
|
);
|
|
45
45
|
useEffect(() => {
|
|
46
46
|
const onPublicationsChanged = () => {
|
|
47
|
-
setPublications(Array.from(participant.
|
|
48
|
-
setCameraPublication(
|
|
49
|
-
|
|
50
|
-
|
|
47
|
+
setPublications(Array.from(participant.trackPublications.values()));
|
|
48
|
+
setCameraPublication(
|
|
49
|
+
participant.getTrackPublication(Track.Source.Camera)
|
|
50
|
+
);
|
|
51
|
+
setMicrophonePublication(
|
|
52
|
+
participant.getTrackPublication(Track.Source.Microphone)
|
|
53
|
+
);
|
|
54
|
+
setScreenSharePublication(
|
|
55
|
+
participant.getTrackPublication(Track.Source.ScreenShare)
|
|
56
|
+
);
|
|
51
57
|
setSubscribedTracks(
|
|
52
|
-
Array.from(participant.
|
|
58
|
+
Array.from(participant.trackPublications.values()).filter((pub) => {
|
|
53
59
|
return pub.isSubscribed && pub.track !== undefined;
|
|
54
60
|
})
|
|
55
61
|
);
|
|
@@ -120,7 +126,7 @@ export function useParticipant(participant: Participant): ParticipantState {
|
|
|
120
126
|
}, [participant]);
|
|
121
127
|
|
|
122
128
|
let muted: boolean | undefined;
|
|
123
|
-
participant.
|
|
129
|
+
participant.audioTrackPublications.forEach((pub) => {
|
|
124
130
|
muted = pub.isMuted;
|
|
125
131
|
});
|
|
126
132
|
if (muted === undefined) {
|
package/src/useRoom.ts
CHANGED
|
@@ -32,7 +32,7 @@ export function useRoom(room: Room, options?: RoomOptions): RoomState {
|
|
|
32
32
|
|
|
33
33
|
useEffect(() => {
|
|
34
34
|
const onParticipantsChanged = () => {
|
|
35
|
-
const remotes = Array.from(room.
|
|
35
|
+
const remotes = Array.from(room.remoteParticipants.values());
|
|
36
36
|
const newParticipants: Participant[] = [room.localParticipant];
|
|
37
37
|
newParticipants.push(...remotes);
|
|
38
38
|
sortFunc(newParticipants, room.localParticipant);
|
|
@@ -45,8 +45,8 @@ export function useRoom(room: Room, options?: RoomOptions): RoomState {
|
|
|
45
45
|
return;
|
|
46
46
|
}
|
|
47
47
|
const tracks: AudioTrack[] = [];
|
|
48
|
-
room.
|
|
49
|
-
p.
|
|
48
|
+
room.remoteParticipants.forEach((p) => {
|
|
49
|
+
p.audioTrackPublications.forEach((pub) => {
|
|
50
50
|
if (pub.audioTrack) {
|
|
51
51
|
tracks.push(pub.audioTrack);
|
|
52
52
|
}
|
|
@@ -135,8 +135,8 @@ export function sortParticipants(
|
|
|
135
135
|
}
|
|
136
136
|
|
|
137
137
|
// video on
|
|
138
|
-
const aVideo = a.
|
|
139
|
-
const bVideo = b.
|
|
138
|
+
const aVideo = a.videoTrackPublications.size > 0;
|
|
139
|
+
const bVideo = b.videoTrackPublications.size > 0;
|
|
140
140
|
if (aVideo !== bVideo) {
|
|
141
141
|
if (aVideo) {
|
|
142
142
|
return -1;
|