@plusscommunities/pluss-core-app 1.1.5 → 1.2.3

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@plusscommunities/pluss-core-app",
3
- "version": "1.1.5",
3
+ "version": "1.2.3",
4
4
  "description": "Core extension package for Pluss Communities platform",
5
5
  "main": "src/index.js",
6
6
  "scripts": {
@@ -35,7 +35,9 @@
35
35
  "react-native-elements": "^0.17.0",
36
36
  "react-native-image-zoom-viewer": "^3.0.1",
37
37
  "react-native-iphone-x-helper": "^1.3.1",
38
+ "react-native-vimeo-iframe": "^1.0.4",
38
39
  "react-native-webview": "11.2.3",
40
+ "react-native-youtube-iframe": "^2.2.1",
39
41
  "react-redux": "^5.0.5"
40
42
  },
41
43
  "peerDependencies": {
@@ -110,9 +110,7 @@ class ImagePopup extends Component {
110
110
  const { showFullscreenVideo, currentVideoUrl } = this.state;
111
111
  if (!currentVideoUrl) return;
112
112
 
113
- return (
114
- <VideoPopup uri={currentVideoUrl} visible={showFullscreenVideo} showFullscreenButton={false} onClose={this.toggleFullscreenVideo} />
115
- );
113
+ return <VideoPopup uri={currentVideoUrl} visible={showFullscreenVideo} onClose={this.toggleFullscreenVideo} />;
116
114
  }
117
115
 
118
116
  render() {
@@ -0,0 +1,397 @@
1
+ import React, { Component } from 'react';
2
+ import { Platform, View, StyleSheet, Dimensions } from 'react-native';
3
+ import * as ScreenOrientation from 'expo-screen-orientation';
4
+ import { DeviceMotion } from 'expo-sensors';
5
+ import YoutubePlayer, { getYoutubeMeta } from 'react-native-youtube-iframe';
6
+ import { Vimeo } from 'react-native-vimeo-iframe';
7
+ import WebView from 'react-native-webview';
8
+ import { Video } from 'expo-av';
9
+ import VideoPlayer from 'expo-video-player';
10
+ import { Spinner } from './Spinner';
11
+
12
+ const SCREEN_HEIGHT = Dimensions.get('window').height;
13
+ const SCREEN_WIDTH = Dimensions.get('window').width;
14
+ const EXPO_VIDEO_PROPS = { rate: 1, isMuted: false, volume: 1 };
15
+ const RETRY_RECOVER = 30000;
16
+ const RETRY_LOADING = 10000;
17
+
18
+ class MediaPlayer extends Component {
19
+ constructor(props) {
20
+ super(props);
21
+ this.state = {
22
+ deviceOrientation: null,
23
+ isLandscape: false,
24
+ isUrlLink: true,
25
+ isYoutube: false,
26
+ isVimeo: false,
27
+ forceWebview: false,
28
+ heightFactor: 0,
29
+ playbackLoaded: false,
30
+ playbackBuffering: false,
31
+ playbackPlaying: false,
32
+ readyToRender: false,
33
+ };
34
+ this.youtubePlayer = null;
35
+ this.videoPlayer = null;
36
+ this.checkStreamLoaded = null;
37
+ this.retryLoading = false;
38
+ this.orientationQueue = [];
39
+ }
40
+
41
+ componentDidMount = async () => {
42
+ this.setupForPlaying();
43
+
44
+ ScreenOrientation.lockAsync(Platform.OS === 'ios' ? ScreenOrientation.OrientationLock.DEFAULT : ScreenOrientation.OrientationLock.ALL);
45
+ const motionAvailalble = await DeviceMotion.isAvailableAsync();
46
+ // console.log('motionAvailalble', motionAvailalble);
47
+ if (motionAvailalble) {
48
+ DeviceMotion.setUpdateInterval(500);
49
+ DeviceMotion.addListener(this.onMotionChange);
50
+ }
51
+ };
52
+
53
+ componentWillUnmount = () => {
54
+ if (this.checkStreamLoaded) clearInterval(this.checkStreamLoaded);
55
+ DeviceMotion.removeAllListeners();
56
+ ScreenOrientation.lockAsync(ScreenOrientation.OrientationLock.PORTRAIT_UP);
57
+ };
58
+
59
+ getYoutubeVideoId = url => {
60
+ url = url.replace(/(>|<)/gi, '').split(/(vi\/|v=|\/v\/|youtu\.be\/|\/embed\/)/);
61
+ if (url[2] !== undefined) {
62
+ const ids = url[2].split(/[^0-9a-z_\-]/i);
63
+ return ids[0];
64
+ } else {
65
+ return '';
66
+ }
67
+ };
68
+
69
+ getVimeoId = url => {
70
+ const result = url.match(
71
+ /(?:www\.|player\.)?vimeo.com\/(?:channels\/(?:\w+\/)?|groups\/(?:[^\/]*)\/videos\/|album\/(?:\d+)\/video\/|video\/|)(\d+)(?:[a-zA-Z0-9_\-]+)?/i,
72
+ );
73
+ return result[1];
74
+ };
75
+
76
+ setupForPlaying = async () => {
77
+ // Extract live stream info
78
+ const { source } = this.props;
79
+ const isUrlLink = source.startsWith('http');
80
+ const isYoutube = source.includes('youtube.com/watch?v=') || source.includes('youtu.be/');
81
+ const isVimeo = source.includes('vimeo.com/');
82
+ this.setState({ isUrlLink, isYoutube, isVimeo, playbackLoaded: isYoutube || isVimeo });
83
+
84
+ if (isYoutube) {
85
+ const metadata = await getYoutubeMeta(this.getYoutubeVideoId(source));
86
+ this.setState({ heightFactor: metadata.height / metadata.width, readyToRender: true });
87
+ // console.log('youtube metadata', metadata);
88
+ } else if (isVimeo) {
89
+ this.setState({ heightFactor: 0.565, readyToRender: true });
90
+ } else {
91
+ this.setState({ readyToRender: true });
92
+ }
93
+ };
94
+
95
+ getStreamPlaybackUrl = () => {
96
+ // return 'https://stream.mux.com/nflNhyFQMlrSt00II01Hjh2BLSW009PH1tl023pdpJ01s1vc.m3u8';
97
+ return `https://stream.mux.com/${this.props.playbackId}.m3u8`;
98
+ };
99
+
100
+ checkReloadStream = (wait = RETRY_LOADING, force = false) => {
101
+ if (!force && this.retryLoading) return;
102
+
103
+ console.log(`Check loading in ${wait} milliseconds...`);
104
+ this.retryLoading = true;
105
+ setTimeout(async () => {
106
+ const { playbackLoaded, playbackBuffering } = this.state;
107
+ try {
108
+ if (playbackLoaded === false || playbackBuffering === true) {
109
+ console.log('Reloading started');
110
+ await this.videoPlayer.loadAsync(
111
+ { uri: this.getStreamPlaybackUrl(), overrideFileExtensionAndroid: 'm3u8' },
112
+ EXPO_VIDEO_PROPS,
113
+ false,
114
+ );
115
+ } else {
116
+ console.log('Already loaded - not reloading');
117
+ }
118
+ this.retryLoading = false;
119
+ } catch {}
120
+ }, wait);
121
+ };
122
+
123
+ isLandscape = deviceRotation => {
124
+ const { beta, gamma } = deviceRotation;
125
+ const absGamma = Math.abs(gamma);
126
+ const absBeta = Math.abs(beta);
127
+ const isGammaNegative = Math.sign(gamma) === -1;
128
+
129
+ if (absGamma <= 0.04 && absBeta <= 0.24) {
130
+ //Portrait mode, on a flat surface.
131
+ return false;
132
+ } else if ((absGamma <= 1.0 || absGamma >= 2.3) && absBeta >= 0.5) {
133
+ //General Portrait mode, accounting for forward and back tilt on the top of the phone.
134
+ return false;
135
+ } else {
136
+ if (isGammaNegative) {
137
+ //Landscape mode with the top of the phone to the left.
138
+ return true;
139
+ } else {
140
+ //Landscape mode with the top of the phone to the right.
141
+ return true;
142
+ }
143
+ }
144
+ };
145
+
146
+ onMotionChange = async changeEvent => {
147
+ const deviceRotation = changeEvent.rotation;
148
+ const deviceOrientation = changeEvent.orientation;
149
+ const isLandscape = this.isLandscape(deviceRotation);
150
+ this.orientationQueue.push(isLandscape);
151
+ if (this.orientationQueue.length > 2) this.orientationQueue = this.orientationQueue.slice(-2);
152
+ // console.log('onMotionChange - orientationQueue', this.orientationQueue);
153
+ if (
154
+ isLandscape !== this.state.isLandscape &&
155
+ isLandscape === (Math.abs(deviceOrientation) === 90) &&
156
+ this.orientationQueue.every(i => i === isLandscape)
157
+ ) {
158
+ this.setState({ isLandscape }, () => {
159
+ // console.log('isLandscape', this.state.isLandscape);
160
+ if (this.props.orientationChanged) this.props.orientationChanged(this.state.isLandscape);
161
+ });
162
+ }
163
+ };
164
+
165
+ onStreamLoadStart = () => {
166
+ console.log('Stream load started');
167
+ this.checkStreamLoaded = setInterval(() => {
168
+ const { playbackLoaded } = this.state;
169
+ if (playbackLoaded === false) {
170
+ console.log('Stream loading failed unexpectedly');
171
+ this.checkReloadStream(1000, true);
172
+ } else {
173
+ clearInterval(this.checkStreamLoaded);
174
+ }
175
+ }, RETRY_RECOVER);
176
+ };
177
+
178
+ onStreamLoaded = status => {
179
+ console.log(`Stream loaded - isLoaded:${status.isLoaded}, isBuffering:${status.isBuffering}, isPlaying:${status.isPlaying}`);
180
+ };
181
+
182
+ onStreamError = error => {
183
+ console.log('Stream error', error);
184
+ };
185
+
186
+ onStreamStatusUpdate = status => {
187
+ this.setState(
188
+ {
189
+ playbackLoaded: status.isLoaded,
190
+ playbackBuffering: status.isBuffering,
191
+ playbackPlaying: status.isPlaying,
192
+ },
193
+ () => {
194
+ const { playbackLoaded, playbackBuffering, playbackPlaying, shouldPlay } = this.state;
195
+ // console.log(`Status updated - isLoaded:${playbackLoaded}, isBuffering:${playbackBuffering}, isPlaying:${playbackPlaying}`);
196
+ if (playbackLoaded === false && playbackBuffering === undefined && playbackPlaying === undefined) {
197
+ this.checkReloadStream();
198
+ } else if (playbackLoaded === true && playbackBuffering === true) {
199
+ this.checkReloadStream(RETRY_RECOVER);
200
+ }
201
+ },
202
+ );
203
+ };
204
+
205
+ onYoutubeError = e => {
206
+ // console.log('onYoutubeError', e);
207
+ this.setState({ forceWebview: true });
208
+ };
209
+
210
+ onYoutubeReady = () => {
211
+ // console.log('onYoutubeReady');
212
+ };
213
+
214
+ onVideoPlayStatusUpdate = status => {
215
+ // console.log('onVideoPlayStatusUpdate', status);
216
+ };
217
+
218
+ onVideoPlayError = error => {
219
+ // console.log('onVideoPlayError', error);
220
+ this.setState({ forceWebview: true });
221
+ };
222
+
223
+ getYoutubePlayer = (youtubeId, width, height, autoPlay) => (
224
+ <YoutubePlayer
225
+ ref={ref => (this.youtubePlayer = ref)}
226
+ height={height}
227
+ width={width}
228
+ videoId={youtubeId}
229
+ play={autoPlay}
230
+ // onChangeState={event => console.log('onChangeState', event)}
231
+ onReady={this.onYoutubeReady}
232
+ onError={this.onYoutubeError}
233
+ // onPlaybackQualityChange={q => console.log('onPlaybackQualityChange', q)}
234
+ volume={50}
235
+ playbackRate={1}
236
+ playerParams={{
237
+ cc_lang_pref: 'us',
238
+ showClosedCaptions: true,
239
+ }}
240
+ />
241
+ );
242
+
243
+ getVimeoPlayer = (vimeoId, width, height, autoPlay) => (
244
+ <View style={{ width, height }}>
245
+ <Vimeo
246
+ style={{ marginLeft: -8, marginRight: -5 }}
247
+ videoId={vimeoId}
248
+ // onReady={() => console.log('Video is ready')}
249
+ // onPlay={() => console.log('Video is playing')}
250
+ // onPlayProgress={data => console.log('Video progress data:', data)}
251
+ // onFinish={() => console.log('Video is finished')}
252
+ loop={false}
253
+ autoPlay={autoPlay}
254
+ controls={true}
255
+ speed={true}
256
+ time={'0m0s'}
257
+ />
258
+ </View>
259
+ );
260
+
261
+ getWebviewPlayer = embedUrl => (
262
+ <WebView
263
+ startInLoadingState
264
+ javaScriptEnabled
265
+ scrollEnabled={false}
266
+ automaticallyAdjustContentInsets={false}
267
+ mediaPlaybackRequiresUserAction
268
+ style={styles.webView}
269
+ source={{ uri: embedUrl }}
270
+ />
271
+ );
272
+
273
+ getStreamPlayer = (embedUrl, width, height, autoPlay) => (
274
+ <Video
275
+ ref={vp => {
276
+ this.videoPlayer = vp;
277
+ }}
278
+ source={{ uri: embedUrl }}
279
+ {...EXPO_VIDEO_PROPS}
280
+ shouldPlay={autoPlay}
281
+ resizeMode={Video.RESIZE_MODE_CONTAIN}
282
+ useNativeControls
283
+ style={{ width, height }}
284
+ onLoadStart={this.onStreamLoadStart}
285
+ onLoad={this.onStreamLoaded}
286
+ onError={this.onStreamError}
287
+ onPlaybackStatusUpdate={this.onStreamStatusUpdate}
288
+ />
289
+ );
290
+
291
+ getVideoPlayer = (embedUrl, width, height, autoPlay) => (
292
+ <VideoPlayer
293
+ videoProps={{
294
+ shouldPlay: autoPlay,
295
+ resizeMode: Video.RESIZE_MODE_CONTAIN,
296
+ source: { uri: embedUrl },
297
+ }}
298
+ showFullscreenButton={false}
299
+ errorCallback={this.onVideoPlayError}
300
+ playbackCallback={this.onVideoPlayStatusUpdate}
301
+ height={height}
302
+ width={width}
303
+ />
304
+ );
305
+
306
+ render() {
307
+ let width, height;
308
+ const { playbackId, source, autoPlay, useVideoPlayer } = this.props;
309
+ const {
310
+ readyToRender,
311
+ isLandscape,
312
+ heightFactor,
313
+ isUrlLink,
314
+ isYoutube,
315
+ isVimeo,
316
+ forceWebview,
317
+ playbackLoaded,
318
+ playbackBuffering,
319
+ } = this.state;
320
+ if (!readyToRender) return null;
321
+
322
+ if (isLandscape) {
323
+ width = SCREEN_HEIGHT;
324
+ height = SCREEN_WIDTH;
325
+ } else {
326
+ width = SCREEN_WIDTH;
327
+ height = heightFactor ? SCREEN_WIDTH * heightFactor : SCREEN_HEIGHT;
328
+ }
329
+
330
+ let embedUrl, player, showLoading;
331
+ if (!forceWebview) {
332
+ if (isYoutube) {
333
+ const youtubeId = this.getYoutubeVideoId(source);
334
+ embedUrl = youtubeId ? `https://www.youtube.com/embed/${youtubeId}` : source;
335
+ player = this.getYoutubePlayer(youtubeId, width, height, autoPlay);
336
+ } else if (isVimeo) {
337
+ const vimeoId = this.getVimeoId(source);
338
+ embedUrl = source;
339
+ player = this.getVimeoPlayer(vimeoId, width, height, autoPlay);
340
+ } else if (playbackId) {
341
+ embedUrl = this.getStreamPlaybackUrl();
342
+ player = this.getStreamPlayer(embedUrl, width, height, autoPlay);
343
+ showLoading = true;
344
+ } else if (useVideoPlayer) {
345
+ embedUrl = source;
346
+ player = this.getVideoPlayer(embedUrl, width, height, autoPlay);
347
+ }
348
+ }
349
+ if (!player && isUrlLink) {
350
+ embedUrl = source;
351
+ player = this.getWebviewPlayer(embedUrl);
352
+ }
353
+
354
+ // console.log({
355
+ // Streaming: embedUrl,
356
+ // Landscape: isLandscape,
357
+ // Loaded: playbackLoaded,
358
+ // Buffering: playbackBuffering,
359
+ // LoadingIndicator: showLoading,
360
+ // Width: width,
361
+ // Height: height,
362
+ // HeightFactor: heightFactor,
363
+ // });
364
+ return (
365
+ <View style={styles.container}>
366
+ {player}
367
+ {showLoading && (!playbackLoaded || playbackBuffering) && (
368
+ <View style={styles.loadingContainer}>
369
+ <Spinner color={'#fff'} />
370
+ </View>
371
+ )}
372
+ </View>
373
+ );
374
+ }
375
+ }
376
+
377
+ const styles = StyleSheet.create({
378
+ container: {
379
+ backgroundColor: '#000',
380
+ alignItems: 'center',
381
+ justifyContent: 'center',
382
+ },
383
+ webView: {
384
+ flex: 1,
385
+ },
386
+ loadingContainer: {
387
+ position: 'absolute',
388
+ top: 50,
389
+ left: 0,
390
+ right: 0,
391
+ bottom: 50,
392
+ justifyContent: 'center',
393
+ alignItems: 'center',
394
+ },
395
+ });
396
+
397
+ export default MediaPlayer;
@@ -713,9 +713,7 @@ class PlussChat extends Component {
713
713
  const { showFullscreenVideo, currentVideoUrl } = this.state;
714
714
  if (!currentVideoUrl) return;
715
715
 
716
- return (
717
- <VideoPopup uri={currentVideoUrl} visible={showFullscreenVideo} showFullscreenButton={false} onClose={this.toggleFullscreenVideo} />
718
- );
716
+ return <VideoPopup uri={currentVideoUrl} visible={showFullscreenVideo} onClose={this.toggleFullscreenVideo} />;
719
717
  }
720
718
 
721
719
  renderPDF() {
@@ -1,12 +1,9 @@
1
1
  import React, { Component } from 'react';
2
- import { Dimensions, Modal, TouchableOpacity, StyleSheet } from 'react-native';
3
- import { Video } from 'expo-av';
4
- import VideoPlayer from 'expo-video-player';
2
+ import { Platform, View, Modal, TouchableOpacity, StyleSheet } from 'react-native';
3
+ import * as ScreenOrientation from 'expo-screen-orientation';
5
4
  import { StatusBarHeight, getCompressed, imageExists } from '../helper';
6
5
  import { Pl60Icon } from '../fonts';
7
- import { SharingTools } from './SharingTools';
8
-
9
- const SCREEN_HEIGHT = Dimensions.get('window').height;
6
+ import { SharingTools, MediaPlayer } from './';
10
7
 
11
8
  class VideoPopup extends Component {
12
9
  constructor(props) {
@@ -14,18 +11,26 @@ class VideoPopup extends Component {
14
11
 
15
12
  this.state = {
16
13
  uri: '',
14
+ isFullScreen: false,
17
15
  };
18
16
  }
19
17
 
20
- UNSAFE_componentWillMount() {
18
+ UNSAFE_componentWillMount = async () => {
21
19
  this.checkCompressed(this.props.uri);
22
- }
20
+ await ScreenOrientation.lockAsync(
21
+ Platform.OS === 'ios' ? ScreenOrientation.OrientationLock.DEFAULT : ScreenOrientation.OrientationLock.ALL,
22
+ );
23
+ };
23
24
 
24
25
  UNSAFE_componentWillReceiveProps(nextProps) {
25
26
  if (this.props.uri === nextProps.uri) return;
26
27
  this.checkCompressed(nextProps.uri);
27
28
  }
28
29
 
30
+ componentWillUnmount = async () => {
31
+ await ScreenOrientation.lockAsync(ScreenOrientation.OrientationLock.PORTRAIT_UP);
32
+ };
33
+
29
34
  checkCompressed = url => {
30
35
  const compressedUri = getCompressed(url);
31
36
  imageExists(compressedUri).then(compressedExists => {
@@ -33,11 +38,6 @@ class VideoPopup extends Component {
33
38
  });
34
39
  };
35
40
 
36
- playbackCallback = status => {
37
- const { playbackCallback } = this.props;
38
- if (playbackCallback) playbackCallback(status);
39
- };
40
-
41
41
  renderClose() {
42
42
  return (
43
43
  <TouchableOpacity style={styles.menuIconContainer} onPress={this.props.onClose} activeOpacity={0.6}>
@@ -47,35 +47,36 @@ class VideoPopup extends Component {
47
47
  }
48
48
 
49
49
  renderPlayer() {
50
- const { shouldPlay, showFullscreenButton, switchToPortrait, switchToLandscape } = this.props;
51
50
  const { uri } = this.state;
52
51
  if (!uri) return null;
53
- console.log(`playing video\ncurrent: ${uri}\noriginal: ${this.props.uri}`);
54
52
 
55
53
  return (
56
- <VideoPlayer
57
- videoProps={{
58
- shouldPlay: shouldPlay || true,
59
- resizeMode: Video.RESIZE_MODE_CONTAIN,
60
- source: { uri },
61
- }}
62
- showFullscreenButton={showFullscreenButton}
63
- switchToPortrait={switchToPortrait}
64
- switchToLandscape={switchToLandscape}
65
- playbackCallback={this.playbackCallback}
66
- height={SCREEN_HEIGHT}
67
- />
54
+ <View style={styles.playerContainer}>
55
+ <MediaPlayer
56
+ source={uri}
57
+ useVideoPlayer={true}
58
+ autoPlay={true}
59
+ orientationChanged={isFullScreen => this.setState({ isFullScreen })}
60
+ />
61
+ </View>
68
62
  );
69
63
  }
70
64
 
71
65
  render() {
72
- const { visible, onClose, style } = this.props;
66
+ const { isFullScreen } = this.state;
67
+ const { visible, onClose, animationType, style } = this.props;
73
68
 
74
69
  return (
75
- <Modal visible={visible} animationType="fade" onRequestClose={onClose} style={[styles.container, style]}>
70
+ <Modal
71
+ visible={visible}
72
+ animationType={animationType || 'fade'}
73
+ onRequestClose={onClose}
74
+ style={[styles.container, style]}
75
+ supportedOrientations={['portrait', 'landscape']}
76
+ >
76
77
  {this.renderPlayer()}
77
- {this.renderClose()}
78
- <SharingTools uri={this.props.uri} />
78
+ {!isFullScreen && this.renderClose()}
79
+ {!isFullScreen && <SharingTools uri={this.props.uri} />}
79
80
  </Modal>
80
81
  );
81
82
  }
@@ -101,6 +102,12 @@ const styles = StyleSheet.create({
101
102
  color: '#fff',
102
103
  zIndex: 3,
103
104
  },
105
+ playerContainer: {
106
+ flex: 1,
107
+ backgroundColor: '#000',
108
+ alignItems: 'center',
109
+ justifyContent: 'center',
110
+ },
104
111
  });
105
112
 
106
113
  export { VideoPopup };
@@ -47,3 +47,4 @@ export { default as UserListing } from './UserListing';
47
47
  export { default as PlussChat } from './PlussChat';
48
48
  export { default as PositionedImage } from './PositionedImage';
49
49
  export { default as FormattedText } from './FormattedText';
50
+ export { default as MediaPlayer } from './MediaPlayer';