@livekit/react-native 2.0.1 → 2.1.0-0

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.
Files changed (59) hide show
  1. package/README.md +4 -0
  2. package/android/src/main/java/com/livekit/reactnative/LiveKitReactNative.kt +4 -4
  3. package/android/src/main/java/com/livekit/reactnative/video/CustomVideoDecoderFactory.kt +67 -0
  4. package/android/src/main/java/com/livekit/reactnative/video/CustomVideoEncoderFactory.kt +74 -0
  5. package/lib/commonjs/components/LiveKitRoom.js +43 -0
  6. package/lib/commonjs/components/LiveKitRoom.js.map +1 -0
  7. package/lib/commonjs/components/VideoTrack.js +171 -0
  8. package/lib/commonjs/components/VideoTrack.js.map +1 -0
  9. package/lib/commonjs/components/VideoView.js +15 -11
  10. package/lib/commonjs/components/VideoView.js.map +1 -1
  11. package/lib/commonjs/components/ViewPortDetector.js +134 -39
  12. package/lib/commonjs/components/ViewPortDetector.js.map +1 -1
  13. package/lib/commonjs/hooks.js +232 -0
  14. package/lib/commonjs/hooks.js.map +1 -0
  15. package/lib/commonjs/index.js +42 -0
  16. package/lib/commonjs/index.js.map +1 -1
  17. package/lib/commonjs/useParticipant.js +1 -0
  18. package/lib/commonjs/useParticipant.js.map +1 -1
  19. package/lib/commonjs/useRoom.js +1 -0
  20. package/lib/commonjs/useRoom.js.map +1 -1
  21. package/lib/module/components/LiveKitRoom.js +32 -0
  22. package/lib/module/components/LiveKitRoom.js.map +1 -0
  23. package/lib/module/components/VideoTrack.js +152 -0
  24. package/lib/module/components/VideoTrack.js.map +1 -0
  25. package/lib/module/components/VideoView.js +20 -12
  26. package/lib/module/components/VideoView.js.map +1 -1
  27. package/lib/module/components/ViewPortDetector.js +134 -40
  28. package/lib/module/components/ViewPortDetector.js.map +1 -1
  29. package/lib/module/hooks.js +3 -0
  30. package/lib/module/hooks.js.map +1 -0
  31. package/lib/module/index.js +9 -3
  32. package/lib/module/index.js.map +1 -1
  33. package/lib/module/useParticipant.js +3 -0
  34. package/lib/module/useParticipant.js.map +1 -1
  35. package/lib/module/useRoom.js +2 -0
  36. package/lib/module/useRoom.js.map +1 -1
  37. package/lib/typescript/components/LiveKitRoom.d.ts +90 -0
  38. package/lib/typescript/components/VideoTrack.d.ts +11 -0
  39. package/lib/typescript/components/VideoView.d.ts +6 -0
  40. package/lib/typescript/components/ViewPortDetector.d.ts +11 -4
  41. package/lib/typescript/hooks.d.ts +2 -0
  42. package/lib/typescript/index.d.ts +3 -0
  43. package/lib/typescript/useParticipant.d.ts +2 -0
  44. package/lib/typescript/useRoom.d.ts +1 -0
  45. package/livekit-react-native.podspec +5 -0
  46. package/package.json +2 -1
  47. package/src/components/LiveKitRoom.tsx +118 -0
  48. package/src/components/VideoTrack.tsx +150 -0
  49. package/src/components/VideoView.tsx +26 -13
  50. package/src/components/ViewPortDetector.tsx +112 -21
  51. package/src/hooks.ts +40 -0
  52. package/src/index.tsx +6 -4
  53. package/src/useParticipant.ts +2 -1
  54. package/src/useRoom.ts +1 -0
  55. package/android/local.properties +0 -8
  56. package/ios/LivekitReactNative.xcodeproj/project.xcworkspace/contents.xcworkspacedata +0 -7
  57. package/ios/LivekitReactNative.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +0 -8
  58. package/ios/LivekitReactNative.xcodeproj/project.xcworkspace/xcuserdata/davidliu.xcuserdatad/UserInterfaceState.xcuserstate +0 -0
  59. package/ios/LivekitReactNative.xcodeproj/xcuserdata/davidliu.xcuserdatad/xcschemes/xcschememanagement.plist +0 -14
@@ -0,0 +1,150 @@
1
+ import * as React from 'react';
2
+
3
+ import { LayoutChangeEvent, StyleSheet, View, ViewStyle } from 'react-native';
4
+ import {
5
+ ElementInfo,
6
+ LocalVideoTrack,
7
+ Track,
8
+ TrackEvent,
9
+ } from 'livekit-client';
10
+ import { RTCView } from '@livekit/react-native-webrtc';
11
+ import { useCallback, useEffect, useMemo, useState } from 'react';
12
+ import { RemoteVideoTrack } from 'livekit-client';
13
+ import ViewPortDetector from './ViewPortDetector';
14
+ import type { TrackReference } from '@livekit/components-react';
15
+
16
+ export type VideoTrackProps = {
17
+ trackRef: TrackReference | undefined;
18
+ style?: ViewStyle;
19
+ objectFit?: 'cover' | 'contain' | undefined;
20
+ mirror?: boolean;
21
+ zOrder?: number;
22
+ };
23
+
24
+ export const VideoTrack = ({
25
+ style = {},
26
+ trackRef,
27
+ objectFit = 'cover',
28
+ zOrder,
29
+ mirror,
30
+ }: VideoTrackProps) => {
31
+ const [elementInfo] = useState(() => {
32
+ let info = new VideoTrackElementInfo();
33
+ info.id = trackRef?.publication?.trackSid;
34
+ return info;
35
+ });
36
+
37
+ const layoutOnChange = useCallback(
38
+ (event: LayoutChangeEvent) => elementInfo.onLayout(event),
39
+ [elementInfo]
40
+ );
41
+ const visibilityOnChange = useCallback(
42
+ (isVisible: boolean) => elementInfo.onVisibility(isVisible),
43
+ [elementInfo]
44
+ );
45
+
46
+ const videoTrack = trackRef?.publication.track;
47
+
48
+ const shouldObserveVisibility = useMemo(() => {
49
+ return (
50
+ videoTrack instanceof RemoteVideoTrack && videoTrack.isAdaptiveStream
51
+ );
52
+ }, [videoTrack]);
53
+
54
+ const [mediaStream, setMediaStream] = useState(videoTrack?.mediaStream);
55
+ useEffect(() => {
56
+ setMediaStream(videoTrack?.mediaStream);
57
+ if (videoTrack instanceof LocalVideoTrack) {
58
+ const onRestarted = (track: Track | null) => {
59
+ setMediaStream(track?.mediaStream);
60
+ };
61
+ videoTrack.on(TrackEvent.Restarted, onRestarted);
62
+
63
+ return () => {
64
+ videoTrack.off(TrackEvent.Restarted, onRestarted);
65
+ };
66
+ } else {
67
+ return () => {};
68
+ }
69
+ }, [videoTrack]);
70
+
71
+ useEffect(() => {
72
+ if (videoTrack instanceof RemoteVideoTrack && videoTrack.isAdaptiveStream) {
73
+ videoTrack?.observeElementInfo(elementInfo);
74
+ return () => {
75
+ videoTrack?.stopObservingElementInfo(elementInfo);
76
+ };
77
+ } else {
78
+ return () => {};
79
+ }
80
+ }, [videoTrack, elementInfo]);
81
+
82
+ return (
83
+ <View style={{ ...style, ...styles.container }} onLayout={layoutOnChange}>
84
+ <ViewPortDetector
85
+ onChange={visibilityOnChange}
86
+ style={styles.videoTrack}
87
+ disabled={!shouldObserveVisibility}
88
+ propKey={videoTrack}
89
+ >
90
+ <RTCView
91
+ style={styles.videoTrack}
92
+ streamURL={mediaStream?.toURL() ?? ''}
93
+ objectFit={objectFit}
94
+ zOrder={zOrder}
95
+ mirror={mirror}
96
+ />
97
+ </ViewPortDetector>
98
+ </View>
99
+ );
100
+ };
101
+
102
+ const styles = StyleSheet.create({
103
+ container: {},
104
+ videoTrack: {
105
+ flex: 1,
106
+ width: '100%',
107
+ },
108
+ });
109
+
110
+ class VideoTrackElementInfo implements ElementInfo {
111
+ element: object = {};
112
+ something?: any;
113
+ id?: string;
114
+ _width = 0;
115
+ _height = 0;
116
+ _observing = false;
117
+ visible: boolean = true;
118
+ visibilityChangedAt: number | undefined;
119
+ pictureInPicture = false;
120
+ handleResize?: (() => void) | undefined;
121
+ handleVisibilityChanged?: (() => void) | undefined;
122
+ width = () => this._width;
123
+ height = () => this._height;
124
+
125
+ observe(): void {
126
+ this._observing = true;
127
+ }
128
+ stopObserving(): void {
129
+ this._observing = false;
130
+ }
131
+
132
+ onLayout(event: LayoutChangeEvent) {
133
+ let { width, height } = event.nativeEvent.layout;
134
+ this._width = width;
135
+ this._height = height;
136
+
137
+ if (this._observing) {
138
+ this.handleResize?.();
139
+ }
140
+ }
141
+ onVisibility(isVisible: boolean) {
142
+ if (this.visible !== isVisible) {
143
+ this.visible = isVisible;
144
+ this.visibilityChangedAt = Date.now();
145
+ if (this._observing) {
146
+ this.handleVisibilityChanged?.();
147
+ }
148
+ }
149
+ }
150
+ }
@@ -9,10 +9,13 @@ 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
 
16
+ /**
17
+ * @deprecated use `VideoTrack` and `VideoTrackProps` instead.
18
+ */
16
19
  export type Props = {
17
20
  videoTrack?: VideoTrack | undefined;
18
21
  style?: ViewStyle;
@@ -21,6 +24,9 @@ export type Props = {
21
24
  zOrder?: number;
22
25
  };
23
26
 
27
+ /**
28
+ * @deprecated use `VideoTrack` and `VideoTrackProps` instead.
29
+ */
24
30
  export const VideoView = ({
25
31
  style = {},
26
32
  videoTrack,
@@ -35,6 +41,20 @@ export const VideoView = ({
35
41
  return info;
36
42
  });
37
43
 
44
+ const layoutOnChange = useCallback(
45
+ (event: LayoutChangeEvent) => elementInfo.onLayout(event),
46
+ [elementInfo]
47
+ );
48
+ const visibilityOnChange = useCallback(
49
+ (isVisible: boolean) => elementInfo.onVisibility(isVisible),
50
+ [elementInfo]
51
+ );
52
+ const shouldObserveVisibility = useMemo(() => {
53
+ return (
54
+ videoTrack instanceof RemoteVideoTrack && videoTrack.isAdaptiveStream
55
+ );
56
+ }, [videoTrack]);
57
+
38
58
  const [mediaStream, setMediaStream] = useState(videoTrack?.mediaStream);
39
59
  useEffect(() => {
40
60
  setMediaStream(videoTrack?.mediaStream);
@@ -64,22 +84,15 @@ export const VideoView = ({
64
84
  }, [videoTrack, elementInfo]);
65
85
 
66
86
  return (
67
- <View
68
- style={{ ...style, ...styles.container }}
69
- onLayout={(event) => {
70
- elementInfo.onLayout(event);
71
- }}
72
- >
87
+ <View style={{ ...style, ...styles.container }} onLayout={layoutOnChange}>
73
88
  <ViewPortDetector
74
- onChange={(isVisible: boolean) => elementInfo.onVisibility(isVisible)}
89
+ onChange={visibilityOnChange}
75
90
  style={styles.videoView}
91
+ disabled={!shouldObserveVisibility}
92
+ propKey={videoTrack}
76
93
  >
77
94
  <RTCView
78
- // eslint-disable-next-line react-native/no-inline-styles
79
- style={{
80
- flex: 1,
81
- width: '100%',
82
- }}
95
+ style={styles.videoView}
83
96
  streamURL={mediaStream?.toURL() ?? ''}
84
97
  objectFit={objectFit}
85
98
  zOrder={zOrder}
@@ -1,15 +1,61 @@
1
1
  'use strict';
2
2
 
3
3
  import React, { Component, PropsWithChildren } from 'react';
4
- import { View, ViewStyle } from 'react-native';
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: any | null = null;
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
- if (!this.props.disabled) {
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.lastValue = null;
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
- private startWatching() {
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 = setInterval(() => {
54
- if (!this.view) {
55
- return;
56
- }
57
- this.view.measure((_x, _y, width, height, _pageX, _pageY) => {
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
- private stopWatching() {
64
- this.interval = clearInterval(this.interval);
65
- }
137
+ stopWatching = () => {
138
+ this.interval?.clear();
139
+ this.interval = null;
140
+ };
66
141
 
67
- private checkInViewPort(width?: number, height?: number) {
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/hooks.ts ADDED
@@ -0,0 +1,40 @@
1
+ export {
2
+ useConnectionState,
3
+ useDataChannel,
4
+ useIsSpeaking,
5
+ useLocalParticipant,
6
+ UseLocalParticipantOptions,
7
+ useLocalParticipantPermissions,
8
+ useParticipantInfo,
9
+ UseParticipantInfoOptions,
10
+ useParticipants,
11
+ UseParticipantsOptions,
12
+ useRemoteParticipants,
13
+ UseRemoteParticipantOptions,
14
+ useRemoteParticipant,
15
+ UseRemoteParticipantsOptions,
16
+ useSpeakingParticipants,
17
+ useSortedParticipants,
18
+ useChat,
19
+ useIsEncrypted,
20
+ useIsMuted,
21
+ useParticipantTracks,
22
+ useLiveKitRoom,
23
+ RoomContext,
24
+ useRoomContext,
25
+ ParticipantContext,
26
+ useParticipantContext,
27
+ TrackRefContext,
28
+ useTrackRefContext,
29
+ useTracks,
30
+ UseTracksOptions,
31
+ TrackReference,
32
+ TrackReferenceOrPlaceholder,
33
+ isTrackReference,
34
+ useEnsureTrackRef,
35
+ useTrackMutedIndicator,
36
+ useVisualStableUpdate,
37
+ UseVisualStableUpdateOptions,
38
+ } from '@livekit/components-react';
39
+
40
+ export { ReceivedDataMessage } from '@livekit/components-core';
package/src/index.tsx CHANGED
@@ -74,10 +74,12 @@ function shimIterator() {
74
74
  var shim = require('well-known-symbols/Symbol.iterator/shim');
75
75
  shim();
76
76
  }
77
-
78
- export * from './components/VideoView';
79
- export * from './useParticipant';
80
- export * from './useRoom';
77
+ export * from './hooks';
78
+ export * from './components/LiveKitRoom';
79
+ export * from './components/VideoTrack';
80
+ export * from './components/VideoView'; // deprecated
81
+ export * from './useParticipant'; // deprecated
82
+ export * from './useRoom'; // deprecated
81
83
  export * from './logger';
82
84
  export * from './audio/AudioManager';
83
85
 
@@ -8,6 +8,7 @@ import {
8
8
  } from 'livekit-client';
9
9
  import { useEffect, useState } from 'react';
10
10
 
11
+ /** @deprecated use `useRemoteParticipant` or `useLocalParticipant` instead */
11
12
  export interface ParticipantState {
12
13
  isSpeaking: boolean;
13
14
  connectionQuality: ConnectionQuality;
@@ -19,7 +20,7 @@ export interface ParticipantState {
19
20
  microphonePublication?: TrackPublication;
20
21
  screenSharePublication?: TrackPublication;
21
22
  }
22
-
23
+ /** @deprecated use `useRemoteParticipant` or `useLocalParticipant` instead */
23
24
  export function useParticipant(participant: Participant): ParticipantState {
24
25
  const [isAudioMuted, setAudioMuted] = useState(false);
25
26
  const [, setVideoMuted] = useState(false);
package/src/useRoom.ts CHANGED
@@ -23,6 +23,7 @@ export interface RoomOptions {
23
23
  sortParticipants?: (participants: Participant[]) => void;
24
24
  }
25
25
 
26
+ /** @deprecated wrap your components in a <LiveKitRoom> component instead and use more granular hooks to track state you're interested in */
26
27
  export function useRoom(room: Room, options?: RoomOptions): RoomState {
27
28
  const [error] = useState<Error>();
28
29
  const [participants, setParticipants] = useState<Participant[]>([]);
@@ -1,8 +0,0 @@
1
- ## This file must *NOT* be checked into Version Control Systems,
2
- # as it contains information specific to your local configuration.
3
- #
4
- # Location of the SDK. This is only used by Gradle.
5
- # For customization when using a Version Control System, please read the
6
- # header note.
7
- #Mon Jan 23 20:31:14 JST 2023
8
- sdk.dir=/Users/davidliu/Library/Android/sdk
@@ -1,7 +0,0 @@
1
- <?xml version="1.0" encoding="UTF-8"?>
2
- <Workspace
3
- version = "1.0">
4
- <FileRef
5
- location = "self:">
6
- </FileRef>
7
- </Workspace>
@@ -1,8 +0,0 @@
1
- <?xml version="1.0" encoding="UTF-8"?>
2
- <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
3
- <plist version="1.0">
4
- <dict>
5
- <key>IDEDidComputeMac32BitWarning</key>
6
- <true/>
7
- </dict>
8
- </plist>
@@ -1,14 +0,0 @@
1
- <?xml version="1.0" encoding="UTF-8"?>
2
- <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
3
- <plist version="1.0">
4
- <dict>
5
- <key>SchemeUserState</key>
6
- <dict>
7
- <key>LivekitReactNative.xcscheme_^#shared#^_</key>
8
- <dict>
9
- <key>orderHint</key>
10
- <integer>0</integer>
11
- </dict>
12
- </dict>
13
- </dict>
14
- </plist>