@livekit/react-native 2.0.1 → 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 CHANGED
@@ -14,6 +14,10 @@
14
14
  Use this SDK to add real-time video, audio and data features to your React Native app. By connecting to a self- or cloud-hosted <a href="https://livekit.io/">LiveKit</a> server, you can quickly build applications like interactive live streaming or video calls with just a few lines of code.
15
15
  <!--END_DESCRIPTION-->
16
16
 
17
+ > [!NOTE]
18
+ > This is v2 of the React-Native SDK. When migrating from v1.x to v2.x you might encounter a small set of breaking changes.
19
+ > Read the [migration guide](https://docs.livekit.io/guides/migrate-from-v1/) for a detailed overview of what has changed.
20
+
17
21
  ## Installation
18
22
 
19
23
  ### NPM
@@ -4,8 +4,8 @@ import android.app.Application
4
4
  import android.content.Context
5
5
  import android.os.Build
6
6
  import com.livekit.reactnative.audio.AudioType
7
- import com.livekit.reactnative.video.SimulcastVideoEncoderFactoryWrapper
8
- import com.livekit.reactnative.video.WrappedVideoDecoderFactoryProxy
7
+ import com.livekit.reactnative.video.CustomVideoEncoderFactory
8
+ import com.livekit.reactnative.video.CustomVideoDecoderFactory
9
9
  import com.oney.WebRTCModule.WebRTCModuleOptions
10
10
  import org.webrtc.audio.JavaAudioDeviceModule
11
11
 
@@ -21,8 +21,8 @@ object LiveKitReactNative {
21
21
  @JvmOverloads
22
22
  fun setup(context: Context, audioType: AudioType = AudioType.CommunicationAudioType()) {
23
23
  val options = WebRTCModuleOptions.getInstance()
24
- options.videoEncoderFactory = SimulcastVideoEncoderFactoryWrapper(null, true, true)
25
- options.videoDecoderFactory = WrappedVideoDecoderFactoryProxy()
24
+ options.videoEncoderFactory = CustomVideoEncoderFactory(null, true, true)
25
+ options.videoDecoderFactory = CustomVideoDecoderFactory()
26
26
 
27
27
  val useHardwareAudioProcessing = Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q
28
28
 
@@ -0,0 +1,67 @@
1
+ /*
2
+ * Copyright 2023-2024 LiveKit, Inc.
3
+ *
4
+ * Licensed under the Apache License, Version 2.0 (the "License");
5
+ * you may not use this file except in compliance with the License.
6
+ * You may obtain a copy of the License at
7
+ *
8
+ * http://www.apache.org/licenses/LICENSE-2.0
9
+ *
10
+ * Unless required by applicable law or agreed to in writing, software
11
+ * distributed under the License is distributed on an "AS IS" BASIS,
12
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ * See the License for the specific language governing permissions and
14
+ * limitations under the License.
15
+ */
16
+
17
+ package com.livekit.reactnative.video
18
+
19
+ import com.oney.WebRTCModule.webrtcutils.SoftwareVideoDecoderFactoryProxy
20
+ import org.webrtc.EglBase
21
+ import org.webrtc.SoftwareVideoDecoderFactory
22
+ import org.webrtc.VideoCodecInfo
23
+ import org.webrtc.VideoDecoder
24
+ import org.webrtc.VideoDecoderFactory
25
+ import org.webrtc.WrappedVideoDecoderFactory
26
+
27
+ open class CustomVideoDecoderFactory(
28
+ private var forceSWCodec: Boolean = false,
29
+ private var forceSWCodecs: List<String> = listOf("VP9"),
30
+ ) : VideoDecoderFactory {
31
+ private val softwareVideoDecoderFactory = SoftwareVideoDecoderFactoryProxy()
32
+ private val wrappedVideoDecoderFactory = WrappedVideoDecoderFactoryProxy()
33
+
34
+ /**
35
+ * Set to true to force software codecs.
36
+ */
37
+ fun setForceSWCodec(forceSWCodec: Boolean) {
38
+ this.forceSWCodec = forceSWCodec
39
+ }
40
+
41
+ /**
42
+ * Set a list of codecs for which to use software codecs.
43
+ */
44
+ fun setForceSWCodecList(forceSWCodecs: List<String>) {
45
+ this.forceSWCodecs = forceSWCodecs
46
+ }
47
+
48
+ override fun createDecoder(videoCodecInfo: VideoCodecInfo): VideoDecoder? {
49
+ if (forceSWCodec) {
50
+ return softwareVideoDecoderFactory.createDecoder(videoCodecInfo)
51
+ }
52
+ if (forceSWCodecs.isNotEmpty()) {
53
+ if (forceSWCodecs.contains(videoCodecInfo.name)) {
54
+ return softwareVideoDecoderFactory.createDecoder(videoCodecInfo)
55
+ }
56
+ }
57
+ return wrappedVideoDecoderFactory.createDecoder(videoCodecInfo)
58
+ }
59
+
60
+ override fun getSupportedCodecs(): Array<VideoCodecInfo> {
61
+ return if (forceSWCodec && forceSWCodecs.isEmpty()) {
62
+ softwareVideoDecoderFactory.supportedCodecs
63
+ } else {
64
+ wrappedVideoDecoderFactory.supportedCodecs
65
+ }
66
+ }
67
+ }
@@ -0,0 +1,74 @@
1
+ /*
2
+ * Copyright 2023-2024 LiveKit, Inc.
3
+ *
4
+ * Licensed under the Apache License, Version 2.0 (the "License");
5
+ * you may not use this file except in compliance with the License.
6
+ * You may obtain a copy of the License at
7
+ *
8
+ * http://www.apache.org/licenses/LICENSE-2.0
9
+ *
10
+ * Unless required by applicable law or agreed to in writing, software
11
+ * distributed under the License is distributed on an "AS IS" BASIS,
12
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ * See the License for the specific language governing permissions and
14
+ * limitations under the License.
15
+ */
16
+
17
+ package com.livekit.reactnative.video
18
+
19
+ import com.oney.WebRTCModule.webrtcutils.SoftwareVideoEncoderFactoryProxy
20
+ import org.webrtc.EglBase
21
+ import org.webrtc.SoftwareVideoEncoderFactory
22
+ import org.webrtc.VideoCodecInfo
23
+ import org.webrtc.VideoEncoder
24
+ import org.webrtc.VideoEncoderFactory
25
+
26
+ open class CustomVideoEncoderFactory(
27
+ sharedContext: EglBase.Context?,
28
+ enableIntelVp8Encoder: Boolean,
29
+ enableH264HighProfile: Boolean,
30
+ private var forceSWCodec: Boolean = false,
31
+ private var forceSWCodecs: List<String> = listOf("VP9"),
32
+ ) : VideoEncoderFactory {
33
+ private val softwareVideoEncoderFactory = SoftwareVideoEncoderFactoryProxy()
34
+ private val simulcastVideoEncoderFactoryWrapper: SimulcastVideoEncoderFactoryWrapper
35
+
36
+ init {
37
+ simulcastVideoEncoderFactoryWrapper =
38
+ SimulcastVideoEncoderFactoryWrapper(sharedContext, enableIntelVp8Encoder, enableH264HighProfile)
39
+ }
40
+
41
+ /**
42
+ * Set to true to force software codecs.
43
+ */
44
+ fun setForceSWCodec(forceSWCodec: Boolean) {
45
+ this.forceSWCodec = forceSWCodec
46
+ }
47
+
48
+ /**
49
+ * Set a list of codecs for which to use software codecs.
50
+ */
51
+ fun setForceSWCodecList(forceSWCodecs: List<String>) {
52
+ this.forceSWCodecs = forceSWCodecs
53
+ }
54
+
55
+ override fun createEncoder(videoCodecInfo: VideoCodecInfo): VideoEncoder? {
56
+ if (forceSWCodec) {
57
+ return softwareVideoEncoderFactory.createEncoder(videoCodecInfo)
58
+ }
59
+ if (forceSWCodecs.isNotEmpty()) {
60
+ if (forceSWCodecs.contains(videoCodecInfo.name)) {
61
+ return softwareVideoEncoderFactory.createEncoder(videoCodecInfo)
62
+ }
63
+ }
64
+ return simulcastVideoEncoderFactoryWrapper.createEncoder(videoCodecInfo)
65
+ }
66
+
67
+ override fun getSupportedCodecs(): Array<VideoCodecInfo> {
68
+ return if (forceSWCodec && forceSWCodecs.isEmpty()) {
69
+ softwareVideoEncoderFactory.supportedCodecs
70
+ } else {
71
+ simulcastVideoEncoderFactoryWrapper.supportedCodecs
72
+ }
73
+ }
74
+ }
@@ -39,6 +39,11 @@ const VideoView = _ref => {
39
39
  info.something = videoTrack;
40
40
  return info;
41
41
  });
42
+ const layoutOnChange = (0, React.useCallback)(event => elementInfo.onLayout(event), [elementInfo]);
43
+ const visibilityOnChange = (0, React.useCallback)(isVisible => elementInfo.onVisibility(isVisible), [elementInfo]);
44
+ const shouldObserveVisibility = (0, React.useMemo)(() => {
45
+ return videoTrack instanceof _livekitClient.RemoteVideoTrack && videoTrack.isAdaptiveStream;
46
+ }, [videoTrack]);
42
47
  const [mediaStream, setMediaStream] = (0, React.useState)(videoTrack === null || videoTrack === void 0 ? void 0 : videoTrack.mediaStream);
43
48
  (0, React.useEffect)(() => {
44
49
  setMediaStream(videoTrack === null || videoTrack === void 0 ? void 0 : videoTrack.mediaStream);
@@ -70,18 +75,14 @@ const VideoView = _ref => {
70
75
  style: { ...style,
71
76
  ...styles.container
72
77
  },
73
- onLayout: event => {
74
- elementInfo.onLayout(event);
75
- }
78
+ onLayout: layoutOnChange
76
79
  }, /*#__PURE__*/React.createElement(_ViewPortDetector.default, {
77
- onChange: isVisible => elementInfo.onVisibility(isVisible),
78
- style: styles.videoView
79
- }, /*#__PURE__*/React.createElement(_reactNativeWebrtc.RTCView // eslint-disable-next-line react-native/no-inline-styles
80
- , {
81
- style: {
82
- flex: 1,
83
- width: '100%'
84
- },
80
+ onChange: visibilityOnChange,
81
+ style: styles.videoView,
82
+ disabled: !shouldObserveVisibility,
83
+ propKey: videoTrack
84
+ }, /*#__PURE__*/React.createElement(_reactNativeWebrtc.RTCView, {
85
+ style: styles.videoView,
85
86
  streamURL: (_mediaStream$toURL = mediaStream === null || mediaStream === void 0 ? void 0 : mediaStream.toURL()) !== null && _mediaStream$toURL !== void 0 ? _mediaStream$toURL : '',
86
87
  objectFit: objectFit,
87
88
  zOrder: zOrder,
@@ -1 +1 @@
1
- {"version":3,"sources":["VideoView.tsx"],"names":["VideoView","style","videoTrack","objectFit","zOrder","mirror","elementInfo","info","VideoViewElementInfo","id","sid","something","mediaStream","setMediaStream","LocalVideoTrack","onRestarted","track","on","TrackEvent","Restarted","off","RemoteVideoTrack","isAdaptiveStream","observeElementInfo","stopObservingElementInfo","styles","container","event","onLayout","isVisible","onVisibility","videoView","flex","width","toURL","StyleSheet","create","_width","_height","observe","_observing","stopObserving","height","nativeEvent","layout","handleResize","visible","visibilityChangedAt","Date","now","handleVisibilityChanged"],"mappings":"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","sourcesContent":["import * as React from 'react';\n\nimport { LayoutChangeEvent, StyleSheet, View, ViewStyle } from 'react-native';\nimport {\n ElementInfo,\n LocalVideoTrack,\n Track,\n TrackEvent,\n VideoTrack,\n} from 'livekit-client';\nimport { RTCView } from '@livekit/react-native-webrtc';\nimport { useEffect, useState } from 'react';\nimport { RemoteVideoTrack } from 'livekit-client';\nimport ViewPortDetector from './ViewPortDetector';\n\nexport type Props = {\n videoTrack?: VideoTrack | undefined;\n style?: ViewStyle;\n objectFit?: 'cover' | 'contain' | undefined;\n mirror?: boolean;\n zOrder?: number;\n};\n\nexport const VideoView = ({\n style = {},\n videoTrack,\n objectFit = 'cover',\n zOrder,\n mirror,\n}: Props) => {\n const [elementInfo] = useState(() => {\n let info = new VideoViewElementInfo();\n info.id = videoTrack?.sid;\n info.something = videoTrack;\n return info;\n });\n\n const [mediaStream, setMediaStream] = useState(videoTrack?.mediaStream);\n useEffect(() => {\n setMediaStream(videoTrack?.mediaStream);\n if (videoTrack instanceof LocalVideoTrack) {\n const onRestarted = (track: Track | null) => {\n setMediaStream(track?.mediaStream);\n };\n videoTrack.on(TrackEvent.Restarted, onRestarted);\n\n return () => {\n videoTrack.off(TrackEvent.Restarted, onRestarted);\n };\n } else {\n return () => {};\n }\n }, [videoTrack]);\n\n useEffect(() => {\n if (videoTrack instanceof RemoteVideoTrack && videoTrack.isAdaptiveStream) {\n videoTrack?.observeElementInfo(elementInfo);\n return () => {\n videoTrack?.stopObservingElementInfo(elementInfo);\n };\n } else {\n return () => {};\n }\n }, [videoTrack, elementInfo]);\n\n return (\n <View\n style={{ ...style, ...styles.container }}\n onLayout={(event) => {\n elementInfo.onLayout(event);\n }}\n >\n <ViewPortDetector\n onChange={(isVisible: boolean) => elementInfo.onVisibility(isVisible)}\n style={styles.videoView}\n >\n <RTCView\n // eslint-disable-next-line react-native/no-inline-styles\n style={{\n flex: 1,\n width: '100%',\n }}\n streamURL={mediaStream?.toURL() ?? ''}\n objectFit={objectFit}\n zOrder={zOrder}\n mirror={mirror}\n />\n </ViewPortDetector>\n </View>\n );\n};\n\nconst styles = StyleSheet.create({\n container: {},\n videoView: {\n flex: 1,\n width: '100%',\n },\n});\n\nclass VideoViewElementInfo implements ElementInfo {\n element: object = {};\n something?: any;\n id?: string;\n _width = 0;\n _height = 0;\n _observing = false;\n visible: boolean = true;\n visibilityChangedAt: number | undefined;\n pictureInPicture = false;\n handleResize?: (() => void) | undefined;\n handleVisibilityChanged?: (() => void) | undefined;\n width = () => this._width;\n height = () => this._height;\n\n observe(): void {\n this._observing = true;\n }\n stopObserving(): void {\n this._observing = false;\n }\n\n onLayout(event: LayoutChangeEvent) {\n let { width, height } = event.nativeEvent.layout;\n this._width = width;\n this._height = height;\n\n if (this._observing) {\n this.handleResize?.();\n }\n }\n onVisibility(isVisible: boolean) {\n if (this.visible !== isVisible) {\n this.visible = isVisible;\n this.visibilityChangedAt = Date.now();\n if (this._observing) {\n this.handleVisibilityChanged?.();\n }\n }\n }\n}\n"]}
1
+ {"version":3,"sources":["VideoView.tsx"],"names":["VideoView","style","videoTrack","objectFit","zOrder","mirror","elementInfo","info","VideoViewElementInfo","id","sid","something","layoutOnChange","event","onLayout","visibilityOnChange","isVisible","onVisibility","shouldObserveVisibility","RemoteVideoTrack","isAdaptiveStream","mediaStream","setMediaStream","LocalVideoTrack","onRestarted","track","on","TrackEvent","Restarted","off","observeElementInfo","stopObservingElementInfo","styles","container","videoView","toURL","StyleSheet","create","flex","width","_width","_height","observe","_observing","stopObserving","height","nativeEvent","layout","handleResize","visible","visibilityChangedAt","Date","now","handleVisibilityChanged"],"mappings":"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","sourcesContent":["import * as React from 'react';\n\nimport { LayoutChangeEvent, StyleSheet, View, ViewStyle } from 'react-native';\nimport {\n ElementInfo,\n LocalVideoTrack,\n Track,\n TrackEvent,\n VideoTrack,\n} from 'livekit-client';\nimport { RTCView } from '@livekit/react-native-webrtc';\nimport { useCallback, useEffect, useMemo, useState } from 'react';\nimport { RemoteVideoTrack } from 'livekit-client';\nimport ViewPortDetector from './ViewPortDetector';\n\nexport type Props = {\n videoTrack?: VideoTrack | undefined;\n style?: ViewStyle;\n objectFit?: 'cover' | 'contain' | undefined;\n mirror?: boolean;\n zOrder?: number;\n};\n\nexport const VideoView = ({\n style = {},\n videoTrack,\n objectFit = 'cover',\n zOrder,\n mirror,\n}: Props) => {\n const [elementInfo] = useState(() => {\n let info = new VideoViewElementInfo();\n info.id = videoTrack?.sid;\n info.something = videoTrack;\n return info;\n });\n\n const layoutOnChange = useCallback(\n (event: LayoutChangeEvent) => elementInfo.onLayout(event),\n [elementInfo]\n );\n const visibilityOnChange = useCallback(\n (isVisible: boolean) => elementInfo.onVisibility(isVisible),\n [elementInfo]\n );\n const shouldObserveVisibility = useMemo(() => {\n return (\n videoTrack instanceof RemoteVideoTrack && videoTrack.isAdaptiveStream\n );\n }, [videoTrack]);\n\n const [mediaStream, setMediaStream] = useState(videoTrack?.mediaStream);\n useEffect(() => {\n setMediaStream(videoTrack?.mediaStream);\n if (videoTrack instanceof LocalVideoTrack) {\n const onRestarted = (track: Track | null) => {\n setMediaStream(track?.mediaStream);\n };\n videoTrack.on(TrackEvent.Restarted, onRestarted);\n\n return () => {\n videoTrack.off(TrackEvent.Restarted, onRestarted);\n };\n } else {\n return () => {};\n }\n }, [videoTrack]);\n\n useEffect(() => {\n if (videoTrack instanceof RemoteVideoTrack && videoTrack.isAdaptiveStream) {\n videoTrack?.observeElementInfo(elementInfo);\n return () => {\n videoTrack?.stopObservingElementInfo(elementInfo);\n };\n } else {\n return () => {};\n }\n }, [videoTrack, elementInfo]);\n\n return (\n <View style={{ ...style, ...styles.container }} onLayout={layoutOnChange}>\n <ViewPortDetector\n onChange={visibilityOnChange}\n style={styles.videoView}\n disabled={!shouldObserveVisibility}\n propKey={videoTrack}\n >\n <RTCView\n style={styles.videoView}\n streamURL={mediaStream?.toURL() ?? ''}\n objectFit={objectFit}\n zOrder={zOrder}\n mirror={mirror}\n />\n </ViewPortDetector>\n </View>\n );\n};\n\nconst styles = StyleSheet.create({\n container: {},\n videoView: {\n flex: 1,\n width: '100%',\n },\n});\n\nclass VideoViewElementInfo implements ElementInfo {\n element: object = {};\n something?: any;\n id?: string;\n _width = 0;\n _height = 0;\n _observing = false;\n visible: boolean = true;\n visibilityChangedAt: number | undefined;\n pictureInPicture = false;\n handleResize?: (() => void) | undefined;\n handleVisibilityChanged?: (() => void) | undefined;\n width = () => this._width;\n height = () => this._height;\n\n observe(): void {\n this._observing = true;\n }\n stopObserving(): void {\n this._observing = false;\n }\n\n onLayout(event: LayoutChangeEvent) {\n let { width, height } = event.nativeEvent.layout;\n this._width = width;\n this._height = height;\n\n if (this._observing) {\n this.handleResize?.();\n }\n }\n onVisibility(isVisible: boolean) {\n if (this.visible !== isVisible) {\n this.visible = isVisible;\n this.visibilityChangedAt = Date.now();\n if (this._observing) {\n this.handleVisibilityChanged?.();\n }\n }\n }\n}\n"]}
@@ -17,11 +17,56 @@ function _extends() { _extends = Object.assign || function (target) { for (var i
17
17
 
18
18
  function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
19
19
 
20
+ const DEFAULT_DELAY = 1000;
21
+
22
+ class TimeoutHandler {
23
+ constructor() {
24
+ _defineProperty(this, "handlerRef", {
25
+ id: -1
26
+ });
27
+ }
28
+
29
+ get handler() {
30
+ return this.handlerRef.id;
31
+ }
32
+
33
+ set handler(n) {
34
+ this.handlerRef.id = n;
35
+ }
36
+
37
+ clear() {
38
+ clearTimeout(this.handlerRef.id);
39
+ }
40
+
41
+ }
42
+
43
+ function setIntervalWithTimeout(callback, intervalMs) {
44
+ let handleWrapper = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : new TimeoutHandler();
45
+ let cleared = false;
46
+
47
+ const timeout = () => {
48
+ handleWrapper.handler = setTimeout(() => {
49
+ callback(() => {
50
+ cleared = true;
51
+ handleWrapper.clear();
52
+ });
53
+
54
+ if (!cleared) {
55
+ timeout();
56
+ }
57
+ }, intervalMs);
58
+ };
59
+
60
+ timeout();
61
+ return handleWrapper;
62
+ }
20
63
  /**
21
64
  * Detects when this is in the viewport and visible.
22
65
  *
23
66
  * Will not fire visibility changes for zero width/height components.
24
67
  */
68
+
69
+
25
70
  class ViewPortDetector extends _react.Component {
26
71
  constructor(props) {
27
72
  super(props);
@@ -32,6 +77,83 @@ class ViewPortDetector extends _react.Component {
32
77
 
33
78
  _defineProperty(this, "view", null);
34
79
 
80
+ _defineProperty(this, "lastAppStateActive", false);
81
+
82
+ _defineProperty(this, "appStateSubscription", null);
83
+
84
+ _defineProperty(this, "hasValidTimeout", (disabled, delay) => {
85
+ let disabledValue = disabled !== null && disabled !== void 0 ? disabled : false;
86
+ let delayValue = delay !== null && delay !== void 0 ? delay : DEFAULT_DELAY;
87
+ return _reactNative.AppState.currentState === 'active' && !disabledValue && delayValue > 0;
88
+ });
89
+
90
+ _defineProperty(this, "handleAppStateChange", nextAppState => {
91
+ let nextAppStateActive = nextAppState === 'active';
92
+
93
+ if (this.lastAppStateActive !== nextAppStateActive) {
94
+ this.checkVisibility();
95
+ }
96
+
97
+ this.lastAppStateActive = nextAppStateActive;
98
+
99
+ if (!this.hasValidTimeout(this.props.disabled, this.props.delay)) {
100
+ this.stopWatching();
101
+ } else {
102
+ this.startWatching();
103
+ }
104
+ });
105
+
106
+ _defineProperty(this, "startWatching", () => {
107
+ if (this.interval) {
108
+ return;
109
+ }
110
+
111
+ this.interval = setIntervalWithTimeout(this.checkVisibility, this.props.delay || DEFAULT_DELAY);
112
+ });
113
+
114
+ _defineProperty(this, "stopWatching", () => {
115
+ var _this$interval;
116
+
117
+ (_this$interval = this.interval) === null || _this$interval === void 0 ? void 0 : _this$interval.clear();
118
+ this.interval = null;
119
+ });
120
+
121
+ _defineProperty(this, "checkVisibility", () => {
122
+ if (!this.view) {
123
+ return;
124
+ }
125
+
126
+ if (_reactNative.AppState.currentState !== 'active') {
127
+ this.updateVisibility(false);
128
+ return;
129
+ }
130
+
131
+ this.view.measure((_x, _y, width, height, _pageX, _pageY) => {
132
+ this.checkInViewPort(width, height);
133
+ });
134
+ });
135
+
136
+ _defineProperty(this, "checkInViewPort", (width, height) => {
137
+ let isVisible; // Not visible if any of these are missing.
138
+
139
+ if (!width || !height) {
140
+ isVisible = false;
141
+ } else {
142
+ isVisible = true;
143
+ }
144
+
145
+ this.updateVisibility(isVisible);
146
+ });
147
+
148
+ _defineProperty(this, "updateVisibility", isVisible => {
149
+ if (this.lastValue !== isVisible) {
150
+ var _this$props$onChange, _this$props;
151
+
152
+ this.lastValue = isVisible;
153
+ (_this$props$onChange = (_this$props = this.props).onChange) === null || _this$props$onChange === void 0 ? void 0 : _this$props$onChange.call(_this$props, isVisible);
154
+ }
155
+ });
156
+
35
157
  this.state = {
36
158
  rectTop: 0,
37
159
  rectBottom: 0
@@ -39,58 +161,31 @@ class ViewPortDetector extends _react.Component {
39
161
  }
40
162
 
41
163
  componentDidMount() {
42
- if (!this.props.disabled) {
164
+ this.lastAppStateActive = _reactNative.AppState.currentState === 'active';
165
+ this.appStateSubscription = _reactNative.AppState.addEventListener('change', this.handleAppStateChange);
166
+
167
+ if (this.hasValidTimeout(this.props.disabled, this.props.delay)) {
43
168
  this.startWatching();
44
169
  }
45
170
  }
46
171
 
47
172
  componentWillUnmount() {
173
+ var _this$appStateSubscri;
174
+
175
+ (_this$appStateSubscri = this.appStateSubscription) === null || _this$appStateSubscri === void 0 ? void 0 : _this$appStateSubscri.remove();
176
+ this.appStateSubscription = null;
48
177
  this.stopWatching();
49
178
  }
50
179
 
51
180
  UNSAFE_componentWillReceiveProps(nextProps) {
52
- if (nextProps.disabled) {
181
+ if (!this.hasValidTimeout(nextProps.disabled, nextProps.delay)) {
53
182
  this.stopWatching();
54
183
  } else {
55
- this.lastValue = null;
56
- this.startWatching();
57
- }
58
- }
59
-
60
- startWatching() {
61
- if (this.interval) {
62
- return;
63
- }
64
-
65
- this.interval = setInterval(() => {
66
- if (!this.view) {
67
- return;
184
+ if (this.props.propKey !== nextProps.propKey) {
185
+ this.lastValue = null;
68
186
  }
69
187
 
70
- this.view.measure((_x, _y, width, height, _pageX, _pageY) => {
71
- this.checkInViewPort(width, height);
72
- });
73
- }, this.props.delay || 100);
74
- }
75
-
76
- stopWatching() {
77
- this.interval = clearInterval(this.interval);
78
- }
79
-
80
- checkInViewPort(width, height) {
81
- let isVisible; // Not visible if any of these are missing.
82
-
83
- if (!width || !height) {
84
- isVisible = false;
85
- } else {
86
- isVisible = true;
87
- }
88
-
89
- if (this.lastValue !== isVisible) {
90
- var _this$props$onChange, _this$props;
91
-
92
- this.lastValue = isVisible;
93
- (_this$props$onChange = (_this$props = this.props).onChange) === null || _this$props$onChange === void 0 ? void 0 : _this$props$onChange.call(_this$props, isVisible);
188
+ this.startWatching();
94
189
  }
95
190
  }
96
191
 
@@ -1 +1 @@
1
- {"version":3,"sources":["ViewPortDetector.tsx"],"names":["ViewPortDetector","Component","constructor","props","state","rectTop","rectBottom","componentDidMount","disabled","startWatching","componentWillUnmount","stopWatching","UNSAFE_componentWillReceiveProps","nextProps","lastValue","interval","setInterval","view","measure","_x","_y","width","height","_pageX","_pageY","checkInViewPort","delay","clearInterval","isVisible","onChange","render","component","children"],"mappings":"AAAA;;;;;;;AAEA;;AACA;;;;;;;;;;AASA;AACA;AACA;AACA;AACA;AACe,MAAMA,gBAAN,SAA+BC,gBAA/B,CAEb;AAKAC,EAAAA,WAAW,CAACC,KAAD,EAAe;AACxB,UAAMA,KAAN;;AADwB,uCAJU,IAIV;;AAAA,sCAHK,IAGL;;AAAA,kCAFE,IAEF;;AAExB,SAAKC,KAAL,GAAa;AAAEC,MAAAA,OAAO,EAAE,CAAX;AAAcC,MAAAA,UAAU,EAAE;AAA1B,KAAb;AACD;;AAEDC,EAAAA,iBAAiB,GAAG;AAClB,QAAI,CAAC,KAAKJ,KAAL,CAAWK,QAAhB,EAA0B;AACxB,WAAKC,aAAL;AACD;AACF;;AAEDC,EAAAA,oBAAoB,GAAG;AACrB,SAAKC,YAAL;AACD;;AAEDC,EAAAA,gCAAgC,CAACC,SAAD,EAAmB;AACjD,QAAIA,SAAS,CAACL,QAAd,EAAwB;AACtB,WAAKG,YAAL;AACD,KAFD,MAEO;AACL,WAAKG,SAAL,GAAiB,IAAjB;AACA,WAAKL,aAAL;AACD;AACF;;AAEOA,EAAAA,aAAa,GAAG;AACtB,QAAI,KAAKM,QAAT,EAAmB;AACjB;AACD;;AACD,SAAKA,QAAL,GAAgBC,WAAW,CAAC,MAAM;AAChC,UAAI,CAAC,KAAKC,IAAV,EAAgB;AACd;AACD;;AACD,WAAKA,IAAL,CAAUC,OAAV,CAAkB,CAACC,EAAD,EAAKC,EAAL,EAASC,KAAT,EAAgBC,MAAhB,EAAwBC,MAAxB,EAAgCC,MAAhC,KAA2C;AAC3D,aAAKC,eAAL,CAAqBJ,KAArB,EAA4BC,MAA5B;AACD,OAFD;AAGD,KAP0B,EAOxB,KAAKnB,KAAL,CAAWuB,KAAX,IAAoB,GAPI,CAA3B;AAQD;;AAEOf,EAAAA,YAAY,GAAG;AACrB,SAAKI,QAAL,GAAgBY,aAAa,CAAC,KAAKZ,QAAN,CAA7B;AACD;;AAEOU,EAAAA,eAAe,CAACJ,KAAD,EAAiBC,MAAjB,EAAkC;AACvD,QAAIM,SAAJ,CADuD,CAEvD;;AACA,QAAI,CAACP,KAAD,IAAU,CAACC,MAAf,EAAuB;AACrBM,MAAAA,SAAS,GAAG,KAAZ;AACD,KAFD,MAEO;AACLA,MAAAA,SAAS,GAAG,IAAZ;AACD;;AAED,QAAI,KAAKd,SAAL,KAAmBc,SAAvB,EAAkC;AAAA;;AAChC,WAAKd,SAAL,GAAiBc,SAAjB;AACA,kDAAKzB,KAAL,EAAW0B,QAAX,gGAAsBD,SAAtB;AACD;AACF;;AAEDE,EAAAA,MAAM,GAAG;AACP,wBACE,6BAAC,iBAAD;AACE,MAAA,WAAW,EAAE,KADf;AAEE,MAAA,GAAG,EAAGC,SAAD,IAAe;AAClB,aAAKd,IAAL,GAAYc,SAAZ;AACD;AAJH,OAKM,KAAK5B,KALX,GAOG,KAAKA,KAAL,CAAW6B,QAPd,CADF;AAWD;;AA1ED","sourcesContent":["'use strict';\n\nimport React, { Component, PropsWithChildren } from 'react';\nimport { View, ViewStyle } from 'react-native';\n\nexport type Props = {\n disabled?: boolean;\n style?: ViewStyle;\n onChange?: (isVisible: boolean) => void;\n delay?: number;\n};\n\n/**\n * Detects when this is in the viewport and visible.\n *\n * Will not fire visibility changes for zero width/height components.\n */\nexport default class ViewPortDetector extends Component<\n PropsWithChildren<Props>\n> {\n private lastValue: boolean | null = null;\n private interval: any | null = null;\n private view: View | null = null;\n\n constructor(props: Props) {\n super(props);\n this.state = { rectTop: 0, rectBottom: 0 };\n }\n\n componentDidMount() {\n if (!this.props.disabled) {\n this.startWatching();\n }\n }\n\n componentWillUnmount() {\n this.stopWatching();\n }\n\n UNSAFE_componentWillReceiveProps(nextProps: Props) {\n if (nextProps.disabled) {\n this.stopWatching();\n } else {\n this.lastValue = null;\n this.startWatching();\n }\n }\n\n private startWatching() {\n if (this.interval) {\n return;\n }\n this.interval = setInterval(() => {\n if (!this.view) {\n return;\n }\n this.view.measure((_x, _y, width, height, _pageX, _pageY) => {\n this.checkInViewPort(width, height);\n });\n }, this.props.delay || 100);\n }\n\n private stopWatching() {\n this.interval = clearInterval(this.interval);\n }\n\n private checkInViewPort(width?: number, height?: number) {\n let isVisible: boolean;\n // Not visible if any of these are missing.\n if (!width || !height) {\n isVisible = false;\n } else {\n isVisible = true;\n }\n\n if (this.lastValue !== isVisible) {\n this.lastValue = isVisible;\n this.props.onChange?.(isVisible);\n }\n }\n\n render() {\n return (\n <View\n collapsable={false}\n ref={(component) => {\n this.view = component;\n }}\n {...this.props}\n >\n {this.props.children}\n </View>\n );\n }\n}\n"]}
1
+ {"version":3,"sources":["ViewPortDetector.tsx"],"names":["DEFAULT_DELAY","TimeoutHandler","id","handler","handlerRef","n","clear","clearTimeout","setIntervalWithTimeout","callback","intervalMs","handleWrapper","cleared","timeout","setTimeout","ViewPortDetector","Component","constructor","props","disabled","delay","disabledValue","delayValue","AppState","currentState","nextAppState","nextAppStateActive","lastAppStateActive","checkVisibility","hasValidTimeout","stopWatching","startWatching","interval","view","updateVisibility","measure","_x","_y","width","height","_pageX","_pageY","checkInViewPort","isVisible","lastValue","onChange","state","rectTop","rectBottom","componentDidMount","appStateSubscription","addEventListener","handleAppStateChange","componentWillUnmount","remove","UNSAFE_componentWillReceiveProps","nextProps","propKey","render","component","children"],"mappings":"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","sourcesContent":["'use strict';\n\nimport React, { Component, PropsWithChildren } from 'react';\nimport {\n AppState,\n AppStateStatus,\n NativeEventSubscription,\n View,\n ViewStyle,\n} from 'react-native';\n\nconst DEFAULT_DELAY = 1000;\n\nexport type Props = {\n disabled?: boolean;\n style?: ViewStyle;\n onChange?: (isVisible: boolean) => void;\n delay?: number;\n propKey?: any;\n};\n\nclass TimeoutHandler {\n private handlerRef: { id: any } = { id: -1 };\n\n get handler(): any {\n return this.handlerRef.id;\n }\n set handler(n: any) {\n this.handlerRef.id = n;\n }\n\n clear() {\n clearTimeout(this.handlerRef.id as any);\n }\n}\n\nfunction setIntervalWithTimeout(\n callback: (clear: () => void) => any,\n intervalMs: number,\n handleWrapper = new TimeoutHandler()\n): TimeoutHandler {\n let cleared = false;\n\n const timeout = () => {\n handleWrapper.handler = setTimeout(() => {\n callback(() => {\n cleared = true;\n handleWrapper.clear();\n });\n if (!cleared) {\n timeout();\n }\n }, intervalMs);\n };\n timeout();\n return handleWrapper;\n}\n\n/**\n * Detects when this is in the viewport and visible.\n *\n * Will not fire visibility changes for zero width/height components.\n */\nexport default class ViewPortDetector extends Component<\n PropsWithChildren<Props>\n> {\n private lastValue: boolean | null = null;\n private interval: TimeoutHandler | null = null;\n private view: View | null = null;\n private lastAppStateActive = false;\n private appStateSubscription: NativeEventSubscription | null = null;\n\n constructor(props: Props) {\n super(props);\n this.state = { rectTop: 0, rectBottom: 0 };\n }\n\n componentDidMount() {\n this.lastAppStateActive = AppState.currentState === 'active';\n this.appStateSubscription = AppState.addEventListener(\n 'change',\n this.handleAppStateChange\n );\n if (this.hasValidTimeout(this.props.disabled, this.props.delay)) {\n this.startWatching();\n }\n }\n\n componentWillUnmount() {\n this.appStateSubscription?.remove();\n this.appStateSubscription = null;\n this.stopWatching();\n }\n\n hasValidTimeout = (disabled?: boolean, delay?: number): boolean => {\n let disabledValue = disabled ?? false;\n let delayValue = delay ?? DEFAULT_DELAY;\n return (\n AppState.currentState === 'active' && !disabledValue && delayValue > 0\n );\n };\n\n UNSAFE_componentWillReceiveProps(nextProps: Props) {\n if (!this.hasValidTimeout(nextProps.disabled, nextProps.delay)) {\n this.stopWatching();\n } else {\n if (this.props.propKey !== nextProps.propKey) {\n this.lastValue = null;\n }\n this.startWatching();\n }\n }\n handleAppStateChange = (nextAppState: AppStateStatus) => {\n let nextAppStateActive = nextAppState === 'active';\n if (this.lastAppStateActive !== nextAppStateActive) {\n this.checkVisibility();\n }\n this.lastAppStateActive = nextAppStateActive;\n\n if (!this.hasValidTimeout(this.props.disabled, this.props.delay)) {\n this.stopWatching();\n } else {\n this.startWatching();\n }\n };\n\n startWatching = () => {\n if (this.interval) {\n return;\n }\n this.interval = setIntervalWithTimeout(\n this.checkVisibility,\n this.props.delay || DEFAULT_DELAY\n );\n };\n\n stopWatching = () => {\n this.interval?.clear();\n this.interval = null;\n };\n\n checkVisibility = () => {\n if (!this.view) {\n return;\n }\n\n if (AppState.currentState !== 'active') {\n this.updateVisibility(false);\n return;\n }\n\n this.view.measure((_x, _y, width, height, _pageX, _pageY) => {\n this.checkInViewPort(width, height);\n });\n };\n checkInViewPort = (width?: number, height?: number) => {\n let isVisible: boolean;\n // Not visible if any of these are missing.\n if (!width || !height) {\n isVisible = false;\n } else {\n isVisible = true;\n }\n this.updateVisibility(isVisible);\n };\n\n updateVisibility = (isVisible: boolean) => {\n if (this.lastValue !== isVisible) {\n this.lastValue = isVisible;\n this.props.onChange?.(isVisible);\n }\n };\n render() {\n return (\n <View\n collapsable={false}\n ref={(component) => {\n this.view = component;\n }}\n {...this.props}\n >\n {this.props.children}\n </View>\n );\n }\n}\n"]}
@@ -4,7 +4,7 @@ import * as React from 'react';
4
4
  import { StyleSheet, View } from 'react-native';
5
5
  import { LocalVideoTrack, TrackEvent } from 'livekit-client';
6
6
  import { RTCView } from '@livekit/react-native-webrtc';
7
- import { useEffect, useState } from 'react';
7
+ import { useCallback, useEffect, useMemo, useState } from 'react';
8
8
  import { RemoteVideoTrack } from 'livekit-client';
9
9
  import ViewPortDetector from './ViewPortDetector';
10
10
  export const VideoView = _ref => {
@@ -23,6 +23,11 @@ export const VideoView = _ref => {
23
23
  info.something = videoTrack;
24
24
  return info;
25
25
  });
26
+ const layoutOnChange = useCallback(event => elementInfo.onLayout(event), [elementInfo]);
27
+ const visibilityOnChange = useCallback(isVisible => elementInfo.onVisibility(isVisible), [elementInfo]);
28
+ const shouldObserveVisibility = useMemo(() => {
29
+ return videoTrack instanceof RemoteVideoTrack && videoTrack.isAdaptiveStream;
30
+ }, [videoTrack]);
26
31
  const [mediaStream, setMediaStream] = useState(videoTrack === null || videoTrack === void 0 ? void 0 : videoTrack.mediaStream);
27
32
  useEffect(() => {
28
33
  setMediaStream(videoTrack === null || videoTrack === void 0 ? void 0 : videoTrack.mediaStream);
@@ -54,18 +59,14 @@ export const VideoView = _ref => {
54
59
  style: { ...style,
55
60
  ...styles.container
56
61
  },
57
- onLayout: event => {
58
- elementInfo.onLayout(event);
59
- }
62
+ onLayout: layoutOnChange
60
63
  }, /*#__PURE__*/React.createElement(ViewPortDetector, {
61
- onChange: isVisible => elementInfo.onVisibility(isVisible),
62
- style: styles.videoView
63
- }, /*#__PURE__*/React.createElement(RTCView // eslint-disable-next-line react-native/no-inline-styles
64
- , {
65
- style: {
66
- flex: 1,
67
- width: '100%'
68
- },
64
+ onChange: visibilityOnChange,
65
+ style: styles.videoView,
66
+ disabled: !shouldObserveVisibility,
67
+ propKey: videoTrack
68
+ }, /*#__PURE__*/React.createElement(RTCView, {
69
+ style: styles.videoView,
69
70
  streamURL: (_mediaStream$toURL = mediaStream === null || mediaStream === void 0 ? void 0 : mediaStream.toURL()) !== null && _mediaStream$toURL !== void 0 ? _mediaStream$toURL : '',
70
71
  objectFit: objectFit,
71
72
  zOrder: zOrder,
@@ -1 +1 @@
1
- {"version":3,"sources":["VideoView.tsx"],"names":["React","StyleSheet","View","LocalVideoTrack","TrackEvent","RTCView","useEffect","useState","RemoteVideoTrack","ViewPortDetector","VideoView","style","videoTrack","objectFit","zOrder","mirror","elementInfo","info","VideoViewElementInfo","id","sid","something","mediaStream","setMediaStream","onRestarted","track","on","Restarted","off","isAdaptiveStream","observeElementInfo","stopObservingElementInfo","styles","container","event","onLayout","isVisible","onVisibility","videoView","flex","width","toURL","create","_width","_height","observe","_observing","stopObserving","height","nativeEvent","layout","handleResize","visible","visibilityChangedAt","Date","now","handleVisibilityChanged"],"mappings":"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","sourcesContent":["import * as React from 'react';\n\nimport { LayoutChangeEvent, StyleSheet, View, ViewStyle } from 'react-native';\nimport {\n ElementInfo,\n LocalVideoTrack,\n Track,\n TrackEvent,\n VideoTrack,\n} from 'livekit-client';\nimport { RTCView } from '@livekit/react-native-webrtc';\nimport { useEffect, useState } from 'react';\nimport { RemoteVideoTrack } from 'livekit-client';\nimport ViewPortDetector from './ViewPortDetector';\n\nexport type Props = {\n videoTrack?: VideoTrack | undefined;\n style?: ViewStyle;\n objectFit?: 'cover' | 'contain' | undefined;\n mirror?: boolean;\n zOrder?: number;\n};\n\nexport const VideoView = ({\n style = {},\n videoTrack,\n objectFit = 'cover',\n zOrder,\n mirror,\n}: Props) => {\n const [elementInfo] = useState(() => {\n let info = new VideoViewElementInfo();\n info.id = videoTrack?.sid;\n info.something = videoTrack;\n return info;\n });\n\n const [mediaStream, setMediaStream] = useState(videoTrack?.mediaStream);\n useEffect(() => {\n setMediaStream(videoTrack?.mediaStream);\n if (videoTrack instanceof LocalVideoTrack) {\n const onRestarted = (track: Track | null) => {\n setMediaStream(track?.mediaStream);\n };\n videoTrack.on(TrackEvent.Restarted, onRestarted);\n\n return () => {\n videoTrack.off(TrackEvent.Restarted, onRestarted);\n };\n } else {\n return () => {};\n }\n }, [videoTrack]);\n\n useEffect(() => {\n if (videoTrack instanceof RemoteVideoTrack && videoTrack.isAdaptiveStream) {\n videoTrack?.observeElementInfo(elementInfo);\n return () => {\n videoTrack?.stopObservingElementInfo(elementInfo);\n };\n } else {\n return () => {};\n }\n }, [videoTrack, elementInfo]);\n\n return (\n <View\n style={{ ...style, ...styles.container }}\n onLayout={(event) => {\n elementInfo.onLayout(event);\n }}\n >\n <ViewPortDetector\n onChange={(isVisible: boolean) => elementInfo.onVisibility(isVisible)}\n style={styles.videoView}\n >\n <RTCView\n // eslint-disable-next-line react-native/no-inline-styles\n style={{\n flex: 1,\n width: '100%',\n }}\n streamURL={mediaStream?.toURL() ?? ''}\n objectFit={objectFit}\n zOrder={zOrder}\n mirror={mirror}\n />\n </ViewPortDetector>\n </View>\n );\n};\n\nconst styles = StyleSheet.create({\n container: {},\n videoView: {\n flex: 1,\n width: '100%',\n },\n});\n\nclass VideoViewElementInfo implements ElementInfo {\n element: object = {};\n something?: any;\n id?: string;\n _width = 0;\n _height = 0;\n _observing = false;\n visible: boolean = true;\n visibilityChangedAt: number | undefined;\n pictureInPicture = false;\n handleResize?: (() => void) | undefined;\n handleVisibilityChanged?: (() => void) | undefined;\n width = () => this._width;\n height = () => this._height;\n\n observe(): void {\n this._observing = true;\n }\n stopObserving(): void {\n this._observing = false;\n }\n\n onLayout(event: LayoutChangeEvent) {\n let { width, height } = event.nativeEvent.layout;\n this._width = width;\n this._height = height;\n\n if (this._observing) {\n this.handleResize?.();\n }\n }\n onVisibility(isVisible: boolean) {\n if (this.visible !== isVisible) {\n this.visible = isVisible;\n this.visibilityChangedAt = Date.now();\n if (this._observing) {\n this.handleVisibilityChanged?.();\n }\n }\n }\n}\n"]}
1
+ {"version":3,"sources":["VideoView.tsx"],"names":["React","StyleSheet","View","LocalVideoTrack","TrackEvent","RTCView","useCallback","useEffect","useMemo","useState","RemoteVideoTrack","ViewPortDetector","VideoView","style","videoTrack","objectFit","zOrder","mirror","elementInfo","info","VideoViewElementInfo","id","sid","something","layoutOnChange","event","onLayout","visibilityOnChange","isVisible","onVisibility","shouldObserveVisibility","isAdaptiveStream","mediaStream","setMediaStream","onRestarted","track","on","Restarted","off","observeElementInfo","stopObservingElementInfo","styles","container","videoView","toURL","create","flex","width","_width","_height","observe","_observing","stopObserving","height","nativeEvent","layout","handleResize","visible","visibilityChangedAt","Date","now","handleVisibilityChanged"],"mappings":"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","sourcesContent":["import * as React from 'react';\n\nimport { LayoutChangeEvent, StyleSheet, View, ViewStyle } from 'react-native';\nimport {\n ElementInfo,\n LocalVideoTrack,\n Track,\n TrackEvent,\n VideoTrack,\n} from 'livekit-client';\nimport { RTCView } from '@livekit/react-native-webrtc';\nimport { useCallback, useEffect, useMemo, useState } from 'react';\nimport { RemoteVideoTrack } from 'livekit-client';\nimport ViewPortDetector from './ViewPortDetector';\n\nexport type Props = {\n videoTrack?: VideoTrack | undefined;\n style?: ViewStyle;\n objectFit?: 'cover' | 'contain' | undefined;\n mirror?: boolean;\n zOrder?: number;\n};\n\nexport const VideoView = ({\n style = {},\n videoTrack,\n objectFit = 'cover',\n zOrder,\n mirror,\n}: Props) => {\n const [elementInfo] = useState(() => {\n let info = new VideoViewElementInfo();\n info.id = videoTrack?.sid;\n info.something = videoTrack;\n return info;\n });\n\n const layoutOnChange = useCallback(\n (event: LayoutChangeEvent) => elementInfo.onLayout(event),\n [elementInfo]\n );\n const visibilityOnChange = useCallback(\n (isVisible: boolean) => elementInfo.onVisibility(isVisible),\n [elementInfo]\n );\n const shouldObserveVisibility = useMemo(() => {\n return (\n videoTrack instanceof RemoteVideoTrack && videoTrack.isAdaptiveStream\n );\n }, [videoTrack]);\n\n const [mediaStream, setMediaStream] = useState(videoTrack?.mediaStream);\n useEffect(() => {\n setMediaStream(videoTrack?.mediaStream);\n if (videoTrack instanceof LocalVideoTrack) {\n const onRestarted = (track: Track | null) => {\n setMediaStream(track?.mediaStream);\n };\n videoTrack.on(TrackEvent.Restarted, onRestarted);\n\n return () => {\n videoTrack.off(TrackEvent.Restarted, onRestarted);\n };\n } else {\n return () => {};\n }\n }, [videoTrack]);\n\n useEffect(() => {\n if (videoTrack instanceof RemoteVideoTrack && videoTrack.isAdaptiveStream) {\n videoTrack?.observeElementInfo(elementInfo);\n return () => {\n videoTrack?.stopObservingElementInfo(elementInfo);\n };\n } else {\n return () => {};\n }\n }, [videoTrack, elementInfo]);\n\n return (\n <View style={{ ...style, ...styles.container }} onLayout={layoutOnChange}>\n <ViewPortDetector\n onChange={visibilityOnChange}\n style={styles.videoView}\n disabled={!shouldObserveVisibility}\n propKey={videoTrack}\n >\n <RTCView\n style={styles.videoView}\n streamURL={mediaStream?.toURL() ?? ''}\n objectFit={objectFit}\n zOrder={zOrder}\n mirror={mirror}\n />\n </ViewPortDetector>\n </View>\n );\n};\n\nconst styles = StyleSheet.create({\n container: {},\n videoView: {\n flex: 1,\n width: '100%',\n },\n});\n\nclass VideoViewElementInfo implements ElementInfo {\n element: object = {};\n something?: any;\n id?: string;\n _width = 0;\n _height = 0;\n _observing = false;\n visible: boolean = true;\n visibilityChangedAt: number | undefined;\n pictureInPicture = false;\n handleResize?: (() => void) | undefined;\n handleVisibilityChanged?: (() => void) | undefined;\n width = () => this._width;\n height = () => this._height;\n\n observe(): void {\n this._observing = true;\n }\n stopObserving(): void {\n this._observing = false;\n }\n\n onLayout(event: LayoutChangeEvent) {\n let { width, height } = event.nativeEvent.layout;\n this._width = width;\n this._height = height;\n\n if (this._observing) {\n this.handleResize?.();\n }\n }\n onVisibility(isVisible: boolean) {\n if (this.visible !== isVisible) {\n this.visible = isVisible;\n this.visibilityChangedAt = Date.now();\n if (this._observing) {\n this.handleVisibilityChanged?.();\n }\n }\n }\n}\n"]}
@@ -5,13 +5,57 @@ function _extends() { _extends = Object.assign || function (target) { for (var i
5
5
  function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
6
6
 
7
7
  import React, { Component } from 'react';
8
- import { View } from 'react-native';
8
+ import { AppState, View } from 'react-native';
9
+ const DEFAULT_DELAY = 1000;
10
+
11
+ class TimeoutHandler {
12
+ constructor() {
13
+ _defineProperty(this, "handlerRef", {
14
+ id: -1
15
+ });
16
+ }
17
+
18
+ get handler() {
19
+ return this.handlerRef.id;
20
+ }
21
+
22
+ set handler(n) {
23
+ this.handlerRef.id = n;
24
+ }
25
+
26
+ clear() {
27
+ clearTimeout(this.handlerRef.id);
28
+ }
29
+
30
+ }
31
+
32
+ function setIntervalWithTimeout(callback, intervalMs) {
33
+ let handleWrapper = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : new TimeoutHandler();
34
+ let cleared = false;
9
35
 
36
+ const timeout = () => {
37
+ handleWrapper.handler = setTimeout(() => {
38
+ callback(() => {
39
+ cleared = true;
40
+ handleWrapper.clear();
41
+ });
42
+
43
+ if (!cleared) {
44
+ timeout();
45
+ }
46
+ }, intervalMs);
47
+ };
48
+
49
+ timeout();
50
+ return handleWrapper;
51
+ }
10
52
  /**
11
53
  * Detects when this is in the viewport and visible.
12
54
  *
13
55
  * Will not fire visibility changes for zero width/height components.
14
56
  */
57
+
58
+
15
59
  export default class ViewPortDetector extends Component {
16
60
  constructor(props) {
17
61
  super(props);
@@ -22,6 +66,83 @@ export default class ViewPortDetector extends Component {
22
66
 
23
67
  _defineProperty(this, "view", null);
24
68
 
69
+ _defineProperty(this, "lastAppStateActive", false);
70
+
71
+ _defineProperty(this, "appStateSubscription", null);
72
+
73
+ _defineProperty(this, "hasValidTimeout", (disabled, delay) => {
74
+ let disabledValue = disabled !== null && disabled !== void 0 ? disabled : false;
75
+ let delayValue = delay !== null && delay !== void 0 ? delay : DEFAULT_DELAY;
76
+ return AppState.currentState === 'active' && !disabledValue && delayValue > 0;
77
+ });
78
+
79
+ _defineProperty(this, "handleAppStateChange", nextAppState => {
80
+ let nextAppStateActive = nextAppState === 'active';
81
+
82
+ if (this.lastAppStateActive !== nextAppStateActive) {
83
+ this.checkVisibility();
84
+ }
85
+
86
+ this.lastAppStateActive = nextAppStateActive;
87
+
88
+ if (!this.hasValidTimeout(this.props.disabled, this.props.delay)) {
89
+ this.stopWatching();
90
+ } else {
91
+ this.startWatching();
92
+ }
93
+ });
94
+
95
+ _defineProperty(this, "startWatching", () => {
96
+ if (this.interval) {
97
+ return;
98
+ }
99
+
100
+ this.interval = setIntervalWithTimeout(this.checkVisibility, this.props.delay || DEFAULT_DELAY);
101
+ });
102
+
103
+ _defineProperty(this, "stopWatching", () => {
104
+ var _this$interval;
105
+
106
+ (_this$interval = this.interval) === null || _this$interval === void 0 ? void 0 : _this$interval.clear();
107
+ this.interval = null;
108
+ });
109
+
110
+ _defineProperty(this, "checkVisibility", () => {
111
+ if (!this.view) {
112
+ return;
113
+ }
114
+
115
+ if (AppState.currentState !== 'active') {
116
+ this.updateVisibility(false);
117
+ return;
118
+ }
119
+
120
+ this.view.measure((_x, _y, width, height, _pageX, _pageY) => {
121
+ this.checkInViewPort(width, height);
122
+ });
123
+ });
124
+
125
+ _defineProperty(this, "checkInViewPort", (width, height) => {
126
+ let isVisible; // Not visible if any of these are missing.
127
+
128
+ if (!width || !height) {
129
+ isVisible = false;
130
+ } else {
131
+ isVisible = true;
132
+ }
133
+
134
+ this.updateVisibility(isVisible);
135
+ });
136
+
137
+ _defineProperty(this, "updateVisibility", isVisible => {
138
+ if (this.lastValue !== isVisible) {
139
+ var _this$props$onChange, _this$props;
140
+
141
+ this.lastValue = isVisible;
142
+ (_this$props$onChange = (_this$props = this.props).onChange) === null || _this$props$onChange === void 0 ? void 0 : _this$props$onChange.call(_this$props, isVisible);
143
+ }
144
+ });
145
+
25
146
  this.state = {
26
147
  rectTop: 0,
27
148
  rectBottom: 0
@@ -29,58 +150,31 @@ export default class ViewPortDetector extends Component {
29
150
  }
30
151
 
31
152
  componentDidMount() {
32
- if (!this.props.disabled) {
153
+ this.lastAppStateActive = AppState.currentState === 'active';
154
+ this.appStateSubscription = AppState.addEventListener('change', this.handleAppStateChange);
155
+
156
+ if (this.hasValidTimeout(this.props.disabled, this.props.delay)) {
33
157
  this.startWatching();
34
158
  }
35
159
  }
36
160
 
37
161
  componentWillUnmount() {
162
+ var _this$appStateSubscri;
163
+
164
+ (_this$appStateSubscri = this.appStateSubscription) === null || _this$appStateSubscri === void 0 ? void 0 : _this$appStateSubscri.remove();
165
+ this.appStateSubscription = null;
38
166
  this.stopWatching();
39
167
  }
40
168
 
41
169
  UNSAFE_componentWillReceiveProps(nextProps) {
42
- if (nextProps.disabled) {
170
+ if (!this.hasValidTimeout(nextProps.disabled, nextProps.delay)) {
43
171
  this.stopWatching();
44
172
  } else {
45
- this.lastValue = null;
46
- this.startWatching();
47
- }
48
- }
49
-
50
- startWatching() {
51
- if (this.interval) {
52
- return;
53
- }
54
-
55
- this.interval = setInterval(() => {
56
- if (!this.view) {
57
- return;
173
+ if (this.props.propKey !== nextProps.propKey) {
174
+ this.lastValue = null;
58
175
  }
59
176
 
60
- this.view.measure((_x, _y, width, height, _pageX, _pageY) => {
61
- this.checkInViewPort(width, height);
62
- });
63
- }, this.props.delay || 100);
64
- }
65
-
66
- stopWatching() {
67
- this.interval = clearInterval(this.interval);
68
- }
69
-
70
- checkInViewPort(width, height) {
71
- let isVisible; // Not visible if any of these are missing.
72
-
73
- if (!width || !height) {
74
- isVisible = false;
75
- } else {
76
- isVisible = true;
77
- }
78
-
79
- if (this.lastValue !== isVisible) {
80
- var _this$props$onChange, _this$props;
81
-
82
- this.lastValue = isVisible;
83
- (_this$props$onChange = (_this$props = this.props).onChange) === null || _this$props$onChange === void 0 ? void 0 : _this$props$onChange.call(_this$props, isVisible);
177
+ this.startWatching();
84
178
  }
85
179
  }
86
180
 
@@ -1 +1 @@
1
- {"version":3,"sources":["ViewPortDetector.tsx"],"names":["React","Component","View","ViewPortDetector","constructor","props","state","rectTop","rectBottom","componentDidMount","disabled","startWatching","componentWillUnmount","stopWatching","UNSAFE_componentWillReceiveProps","nextProps","lastValue","interval","setInterval","view","measure","_x","_y","width","height","_pageX","_pageY","checkInViewPort","delay","clearInterval","isVisible","onChange","render","component","children"],"mappings":"AAAA;;;;;;AAEA,OAAOA,KAAP,IAAgBC,SAAhB,QAAoD,OAApD;AACA,SAASC,IAAT,QAAgC,cAAhC;;AASA;AACA;AACA;AACA;AACA;AACA,eAAe,MAAMC,gBAAN,SAA+BF,SAA/B,CAEb;AAKAG,EAAAA,WAAW,CAACC,KAAD,EAAe;AACxB,UAAMA,KAAN;;AADwB,uCAJU,IAIV;;AAAA,sCAHK,IAGL;;AAAA,kCAFE,IAEF;;AAExB,SAAKC,KAAL,GAAa;AAAEC,MAAAA,OAAO,EAAE,CAAX;AAAcC,MAAAA,UAAU,EAAE;AAA1B,KAAb;AACD;;AAEDC,EAAAA,iBAAiB,GAAG;AAClB,QAAI,CAAC,KAAKJ,KAAL,CAAWK,QAAhB,EAA0B;AACxB,WAAKC,aAAL;AACD;AACF;;AAEDC,EAAAA,oBAAoB,GAAG;AACrB,SAAKC,YAAL;AACD;;AAEDC,EAAAA,gCAAgC,CAACC,SAAD,EAAmB;AACjD,QAAIA,SAAS,CAACL,QAAd,EAAwB;AACtB,WAAKG,YAAL;AACD,KAFD,MAEO;AACL,WAAKG,SAAL,GAAiB,IAAjB;AACA,WAAKL,aAAL;AACD;AACF;;AAEOA,EAAAA,aAAa,GAAG;AACtB,QAAI,KAAKM,QAAT,EAAmB;AACjB;AACD;;AACD,SAAKA,QAAL,GAAgBC,WAAW,CAAC,MAAM;AAChC,UAAI,CAAC,KAAKC,IAAV,EAAgB;AACd;AACD;;AACD,WAAKA,IAAL,CAAUC,OAAV,CAAkB,CAACC,EAAD,EAAKC,EAAL,EAASC,KAAT,EAAgBC,MAAhB,EAAwBC,MAAxB,EAAgCC,MAAhC,KAA2C;AAC3D,aAAKC,eAAL,CAAqBJ,KAArB,EAA4BC,MAA5B;AACD,OAFD;AAGD,KAP0B,EAOxB,KAAKnB,KAAL,CAAWuB,KAAX,IAAoB,GAPI,CAA3B;AAQD;;AAEOf,EAAAA,YAAY,GAAG;AACrB,SAAKI,QAAL,GAAgBY,aAAa,CAAC,KAAKZ,QAAN,CAA7B;AACD;;AAEOU,EAAAA,eAAe,CAACJ,KAAD,EAAiBC,MAAjB,EAAkC;AACvD,QAAIM,SAAJ,CADuD,CAEvD;;AACA,QAAI,CAACP,KAAD,IAAU,CAACC,MAAf,EAAuB;AACrBM,MAAAA,SAAS,GAAG,KAAZ;AACD,KAFD,MAEO;AACLA,MAAAA,SAAS,GAAG,IAAZ;AACD;;AAED,QAAI,KAAKd,SAAL,KAAmBc,SAAvB,EAAkC;AAAA;;AAChC,WAAKd,SAAL,GAAiBc,SAAjB;AACA,kDAAKzB,KAAL,EAAW0B,QAAX,gGAAsBD,SAAtB;AACD;AACF;;AAEDE,EAAAA,MAAM,GAAG;AACP,wBACE,oBAAC,IAAD;AACE,MAAA,WAAW,EAAE,KADf;AAEE,MAAA,GAAG,EAAGC,SAAD,IAAe;AAClB,aAAKd,IAAL,GAAYc,SAAZ;AACD;AAJH,OAKM,KAAK5B,KALX,GAOG,KAAKA,KAAL,CAAW6B,QAPd,CADF;AAWD;;AA1ED","sourcesContent":["'use strict';\n\nimport React, { Component, PropsWithChildren } from 'react';\nimport { View, ViewStyle } from 'react-native';\n\nexport type Props = {\n disabled?: boolean;\n style?: ViewStyle;\n onChange?: (isVisible: boolean) => void;\n delay?: number;\n};\n\n/**\n * Detects when this is in the viewport and visible.\n *\n * Will not fire visibility changes for zero width/height components.\n */\nexport default class ViewPortDetector extends Component<\n PropsWithChildren<Props>\n> {\n private lastValue: boolean | null = null;\n private interval: any | null = null;\n private view: View | null = null;\n\n constructor(props: Props) {\n super(props);\n this.state = { rectTop: 0, rectBottom: 0 };\n }\n\n componentDidMount() {\n if (!this.props.disabled) {\n this.startWatching();\n }\n }\n\n componentWillUnmount() {\n this.stopWatching();\n }\n\n UNSAFE_componentWillReceiveProps(nextProps: Props) {\n if (nextProps.disabled) {\n this.stopWatching();\n } else {\n this.lastValue = null;\n this.startWatching();\n }\n }\n\n private startWatching() {\n if (this.interval) {\n return;\n }\n this.interval = setInterval(() => {\n if (!this.view) {\n return;\n }\n this.view.measure((_x, _y, width, height, _pageX, _pageY) => {\n this.checkInViewPort(width, height);\n });\n }, this.props.delay || 100);\n }\n\n private stopWatching() {\n this.interval = clearInterval(this.interval);\n }\n\n private checkInViewPort(width?: number, height?: number) {\n let isVisible: boolean;\n // Not visible if any of these are missing.\n if (!width || !height) {\n isVisible = false;\n } else {\n isVisible = true;\n }\n\n if (this.lastValue !== isVisible) {\n this.lastValue = isVisible;\n this.props.onChange?.(isVisible);\n }\n }\n\n render() {\n return (\n <View\n collapsable={false}\n ref={(component) => {\n this.view = component;\n }}\n {...this.props}\n >\n {this.props.children}\n </View>\n );\n }\n}\n"]}
1
+ {"version":3,"sources":["ViewPortDetector.tsx"],"names":["React","Component","AppState","View","DEFAULT_DELAY","TimeoutHandler","id","handler","handlerRef","n","clear","clearTimeout","setIntervalWithTimeout","callback","intervalMs","handleWrapper","cleared","timeout","setTimeout","ViewPortDetector","constructor","props","disabled","delay","disabledValue","delayValue","currentState","nextAppState","nextAppStateActive","lastAppStateActive","checkVisibility","hasValidTimeout","stopWatching","startWatching","interval","view","updateVisibility","measure","_x","_y","width","height","_pageX","_pageY","checkInViewPort","isVisible","lastValue","onChange","state","rectTop","rectBottom","componentDidMount","appStateSubscription","addEventListener","handleAppStateChange","componentWillUnmount","remove","UNSAFE_componentWillReceiveProps","nextProps","propKey","render","component","children"],"mappings":"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","sourcesContent":["'use strict';\n\nimport React, { Component, PropsWithChildren } from 'react';\nimport {\n AppState,\n AppStateStatus,\n NativeEventSubscription,\n View,\n ViewStyle,\n} from 'react-native';\n\nconst DEFAULT_DELAY = 1000;\n\nexport type Props = {\n disabled?: boolean;\n style?: ViewStyle;\n onChange?: (isVisible: boolean) => void;\n delay?: number;\n propKey?: any;\n};\n\nclass TimeoutHandler {\n private handlerRef: { id: any } = { id: -1 };\n\n get handler(): any {\n return this.handlerRef.id;\n }\n set handler(n: any) {\n this.handlerRef.id = n;\n }\n\n clear() {\n clearTimeout(this.handlerRef.id as any);\n }\n}\n\nfunction setIntervalWithTimeout(\n callback: (clear: () => void) => any,\n intervalMs: number,\n handleWrapper = new TimeoutHandler()\n): TimeoutHandler {\n let cleared = false;\n\n const timeout = () => {\n handleWrapper.handler = setTimeout(() => {\n callback(() => {\n cleared = true;\n handleWrapper.clear();\n });\n if (!cleared) {\n timeout();\n }\n }, intervalMs);\n };\n timeout();\n return handleWrapper;\n}\n\n/**\n * Detects when this is in the viewport and visible.\n *\n * Will not fire visibility changes for zero width/height components.\n */\nexport default class ViewPortDetector extends Component<\n PropsWithChildren<Props>\n> {\n private lastValue: boolean | null = null;\n private interval: TimeoutHandler | null = null;\n private view: View | null = null;\n private lastAppStateActive = false;\n private appStateSubscription: NativeEventSubscription | null = null;\n\n constructor(props: Props) {\n super(props);\n this.state = { rectTop: 0, rectBottom: 0 };\n }\n\n componentDidMount() {\n this.lastAppStateActive = AppState.currentState === 'active';\n this.appStateSubscription = AppState.addEventListener(\n 'change',\n this.handleAppStateChange\n );\n if (this.hasValidTimeout(this.props.disabled, this.props.delay)) {\n this.startWatching();\n }\n }\n\n componentWillUnmount() {\n this.appStateSubscription?.remove();\n this.appStateSubscription = null;\n this.stopWatching();\n }\n\n hasValidTimeout = (disabled?: boolean, delay?: number): boolean => {\n let disabledValue = disabled ?? false;\n let delayValue = delay ?? DEFAULT_DELAY;\n return (\n AppState.currentState === 'active' && !disabledValue && delayValue > 0\n );\n };\n\n UNSAFE_componentWillReceiveProps(nextProps: Props) {\n if (!this.hasValidTimeout(nextProps.disabled, nextProps.delay)) {\n this.stopWatching();\n } else {\n if (this.props.propKey !== nextProps.propKey) {\n this.lastValue = null;\n }\n this.startWatching();\n }\n }\n handleAppStateChange = (nextAppState: AppStateStatus) => {\n let nextAppStateActive = nextAppState === 'active';\n if (this.lastAppStateActive !== nextAppStateActive) {\n this.checkVisibility();\n }\n this.lastAppStateActive = nextAppStateActive;\n\n if (!this.hasValidTimeout(this.props.disabled, this.props.delay)) {\n this.stopWatching();\n } else {\n this.startWatching();\n }\n };\n\n startWatching = () => {\n if (this.interval) {\n return;\n }\n this.interval = setIntervalWithTimeout(\n this.checkVisibility,\n this.props.delay || DEFAULT_DELAY\n );\n };\n\n stopWatching = () => {\n this.interval?.clear();\n this.interval = null;\n };\n\n checkVisibility = () => {\n if (!this.view) {\n return;\n }\n\n if (AppState.currentState !== 'active') {\n this.updateVisibility(false);\n return;\n }\n\n this.view.measure((_x, _y, width, height, _pageX, _pageY) => {\n this.checkInViewPort(width, height);\n });\n };\n checkInViewPort = (width?: number, height?: number) => {\n let isVisible: boolean;\n // Not visible if any of these are missing.\n if (!width || !height) {\n isVisible = false;\n } else {\n isVisible = true;\n }\n this.updateVisibility(isVisible);\n };\n\n updateVisibility = (isVisible: boolean) => {\n if (this.lastValue !== isVisible) {\n this.lastValue = isVisible;\n this.props.onChange?.(isVisible);\n }\n };\n render() {\n return (\n <View\n collapsable={false}\n ref={(component) => {\n this.view = component;\n }}\n {...this.props}\n >\n {this.props.children}\n </View>\n );\n }\n}\n"]}
@@ -1,10 +1,11 @@
1
1
  import React, { Component, PropsWithChildren } from 'react';
2
- import { ViewStyle } from 'react-native';
2
+ import { AppStateStatus, ViewStyle } from 'react-native';
3
3
  export declare type Props = {
4
4
  disabled?: boolean;
5
5
  style?: ViewStyle;
6
6
  onChange?: (isVisible: boolean) => void;
7
7
  delay?: number;
8
+ propKey?: any;
8
9
  };
9
10
  /**
10
11
  * Detects when this is in the viewport and visible.
@@ -15,12 +16,18 @@ export default class ViewPortDetector extends Component<PropsWithChildren<Props>
15
16
  private lastValue;
16
17
  private interval;
17
18
  private view;
19
+ private lastAppStateActive;
20
+ private appStateSubscription;
18
21
  constructor(props: Props);
19
22
  componentDidMount(): void;
20
23
  componentWillUnmount(): void;
24
+ hasValidTimeout: (disabled?: boolean, delay?: number) => boolean;
21
25
  UNSAFE_componentWillReceiveProps(nextProps: Props): void;
22
- private startWatching;
23
- private stopWatching;
24
- private checkInViewPort;
26
+ handleAppStateChange: (nextAppState: AppStateStatus) => void;
27
+ startWatching: () => void;
28
+ stopWatching: () => void;
29
+ checkVisibility: () => void;
30
+ checkInViewPort: (width?: number, height?: number) => void;
31
+ updateVisibility: (isVisible: boolean) => void;
25
32
  render(): React.JSX.Element;
26
33
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@livekit/react-native",
3
- "version": "2.0.1",
3
+ "version": "2.0.2",
4
4
  "description": "LiveKit for React Native",
5
5
  "main": "lib/commonjs/index",
6
6
  "module": "lib/module/index",
@@ -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={(isVisible: boolean) => elementInfo.onVisibility(isVisible)}
83
+ onChange={visibilityOnChange}
75
84
  style={styles.videoView}
85
+ disabled={!shouldObserveVisibility}
86
+ propKey={videoTrack}
76
87
  >
77
88
  <RTCView
78
- // eslint-disable-next-line react-native/no-inline-styles
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 { 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