@applicaster/quick-brick-player 12.0.0-alpha.4739493702 → 12.0.0-alpha.4796013211

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 (2) hide show
  1. package/package.json +7 -7
  2. package/src/Player/index.tsx +1057 -67
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@applicaster/quick-brick-player",
3
- "version": "12.0.0-alpha.4739493702",
3
+ "version": "12.0.0-alpha.4796013211",
4
4
  "description": "Quick Brick Player",
5
5
  "main": "./src/index.ts",
6
6
  "types": "index.d.ts",
@@ -38,17 +38,17 @@
38
38
  "dependencies": {
39
39
  "@applicaster/quick-brick-mobile-transport-controls": "12.0.0-rc.118",
40
40
  "@applicaster/quick-brick-tv-transport-controls": "12.0.0-rc.112",
41
- "@applicaster/zapp-react-native-tvos-app": "12.0.0-alpha.4739493702",
42
- "@applicaster/zapp-react-native-ui-components": "12.0.0-alpha.4739493702",
43
- "@applicaster/zapp-react-native-utils": "12.0.0-alpha.4739493702",
41
+ "@applicaster/zapp-react-native-tvos-app": "12.0.0-alpha.4796013211",
42
+ "@applicaster/zapp-react-native-ui-components": "12.0.0-alpha.4796013211",
43
+ "@applicaster/zapp-react-native-utils": "12.0.0-alpha.4796013211",
44
44
  "keymirror": "^0.1.1",
45
45
  "query-string": "7.1.3",
46
46
  "react-native-web-linear-gradient": "1.1.2",
47
47
  "shaka-player": "4.3.5",
48
48
  "typeface-montserrat": "^0.0.54",
49
- "video.js": "8.21.0",
50
- "videojs-contrib-eme": "^5.5.1",
51
- "videojs-contrib-quality-levels": "^4.1.0"
49
+ "video.js": "7.14.3",
50
+ "videojs-contrib-eme": "^3.8.1",
51
+ "videojs-contrib-quality-levels": "3.0.0"
52
52
  },
53
53
  "peerDependencies": {
54
54
  "@applicaster/quick-brick-xray": "*",
@@ -2,32 +2,74 @@
2
2
  /* eslint-disable react-native/no-color-literals */
3
3
 
4
4
  import React from "react";
5
- import videojs from "video.js";
5
+ import * as R from "ramda";
6
6
 
7
- import "video.js/dist/video-js.css";
8
- import "videojs-contrib-eme";
7
+ import { findNodeHandle, StyleSheet, View } from "react-native";
8
+ import TransportControlsMobile from "@applicaster/quick-brick-mobile-transport-controls";
9
+ import TransportControlsTV from "@applicaster/quick-brick-tv-transport-controls";
9
10
 
10
- import { View } from "react-native";
11
- import { isWeb } from "@applicaster/zapp-react-native-utils/reactUtils";
11
+ import { AudioPlayer } from "@applicaster/zapp-react-native-ui-components/Components/AudioPlayer";
12
+ import {
13
+ isAndroidPlatform,
14
+ isApplePlatform,
15
+ isTV,
16
+ isTvOSPlatform,
17
+ isWeb,
18
+ } from "@applicaster/zapp-react-native-utils/reactUtils";
12
19
 
13
- const STREAM_TYPES = {
14
- m3u8: "application/x-mpegURL",
15
- mpd: "application/dash+xml",
16
- };
20
+ import { PlayerWrapper } from "@applicaster/zapp-react-native-ui-components/Components/VideoModal";
17
21
 
18
- const getStreamType = (url) => {
19
- const extension = url.split(".").pop();
22
+ import { PlayNextOverlay } from "./PlayNextOverlay";
23
+ import { PlayerImageBackground } from "@applicaster/zapp-react-native-ui-components/Components/PlayerImageBackground";
20
24
 
21
- return STREAM_TYPES[extension];
22
- };
25
+ import { GENERAL_EVENT } from "@applicaster/zapp-react-native-utils/analyticsUtils/events";
26
+ import { Categories, createLogger, Subsystem } from "../logger";
27
+ import { parseLanguageTracks } from "@applicaster/zapp-react-native-utils/playerUtils/configurationUtils";
28
+ import { DEFAULT_COLORS, SEEK_TIME } from "../utils/const";
29
+ import { AudioPlayerWrapper } from "./AudioLayer/AudioPlayerWrapper";
30
+ import { playerManager } from "@applicaster/zapp-react-native-utils/appUtils";
31
+ import { getAllAccessibilityProps } from "@applicaster/zapp-react-native-utils/configurationUtils";
32
+ import { PlayNextData } from "@applicaster/zapp-react-native-ui-components/Components/PlayerContainer/PlayerContainer";
33
+ import { getTargetScreenDataFromEntry } from "@applicaster/zapp-react-native-utils/reactHooks/screen/useTargetScreenData";
34
+ import { isAppleTV } from "@applicaster/zapp-react-native-ui-components/Helpers/Platform";
35
+ import {
36
+ AnimationComponent,
37
+ ComponentAnimationType,
38
+ } from "@applicaster/zapp-react-native-ui-components/Components/VideoModal/ModalAnimation";
39
+ import { PlayerAnalyticsManager } from "@applicaster/zapp-react-native-utils/analyticsUtils/PlayerAnalyticsManager";
40
+ import { PlayerAnalyticsTrackerI } from "@applicaster/zapp-react-native-utils/analyticsUtils/playerAnalyticsTracker";
41
+
42
+ import { RNPlayerManager } from "@applicaster/zapp-react-native-utils/appUtils/playerManager/nativePlayerManager";
43
+
44
+ const styles = StyleSheet.create({
45
+ container: { flex: 1 },
46
+ playerContainerBlack: { backgroundColor: DEFAULT_COLORS.black },
47
+ playerContainerTransparent: { backgroundColor: DEFAULT_COLORS.transparent }, // android
48
+ audio: {
49
+ overflow: "hidden",
50
+ position: "absolute",
51
+ zIndex: 1,
52
+ },
53
+ playerSize: { width: "100%", height: "100%" },
54
+ });
55
+
56
+ const logger = createLogger({
57
+ category: Categories.PLAYER,
58
+ subsystem: Subsystem,
59
+ });
23
60
 
24
61
  export default class VideoPlayer extends React.Component<
25
62
  QuickBrickPlayer.PlayerProps,
26
63
  QuickBrickPlayer.NativePlayerState
27
64
  > {
65
+ public static propTypes = {};
28
66
  _root: any;
29
67
  playerPluginId: string;
30
68
  takeParentStyle: Record<any, any>;
69
+ playbackStarted = false;
70
+ needToResumeOnForeground: boolean = null;
71
+ private onVideoLoadCalled: boolean;
72
+ playNextData: PlayNextData;
31
73
 
32
74
  state: QuickBrickPlayer.NativePlayerState = {
33
75
  audioTrackId: null,
@@ -52,8 +94,20 @@ export default class VideoPlayer extends React.Component<
52
94
  duration: 0,
53
95
  };
54
96
 
97
+ private _tracker: PlayerAnalyticsTrackerI;
98
+ get analyticsTracker() {
99
+ if (!this._tracker) {
100
+ this._tracker = PlayerAnalyticsManager.getInstance().getTracker(
101
+ this.props.playerId
102
+ );
103
+ }
104
+
105
+ return this._tracker;
106
+ }
107
+
55
108
  constructor(props) {
56
109
  super(props);
110
+ this.onVideoLoadCalled = false; // android
57
111
 
58
112
  this.takeParentStyle = isWeb()
59
113
  ? { width: "100%", flex: "1 1 auto", objectFit: "contain" }
@@ -62,58 +116,59 @@ export default class VideoPlayer extends React.Component<
62
116
  this.playerPluginId = this.props?.playerPluginIdentifier;
63
117
  }
64
118
 
119
+ componentWillUnmount() {
120
+ this.playbackStarted = false;
121
+
122
+ this.analyticsTracker.handleAnalyticEvent(GENERAL_EVENT.session_end);
123
+ this.onPlayerClosed();
124
+
125
+ if (isAndroidPlatform()) {
126
+ this.disablePiP();
127
+ }
128
+ }
129
+
65
130
  componentDidMount() {
66
- const {
67
- content: { src, type },
68
- extensions: { drm },
69
- } = this.props.entry;
70
-
71
- const options = {
72
- height: 1080,
73
- width: 1920,
74
- playsinline: undefined,
75
- autoplay: true,
76
- retryOnError: true,
77
- defaultVolume: 1,
78
- preload: "metadata",
79
- html5: {
80
- nativeTextTracks: false,
81
- nativeVideoTracks: false,
82
- nativeAudioTracks: false,
83
- vhs: {
84
- limitRenditionByPlayerDimensions: true,
85
- useDevicePixelRatio: true,
86
- overrideNative: true,
87
- enableLowInitialPlaylist: true,
88
- },
89
- },
90
- };
131
+ this.playbackStarted = false;
132
+ this.onPlayerPresented();
133
+ }
91
134
 
92
- const player = videojs(this._root, options);
135
+ shouldComponentUpdate(nextProps) {
136
+ const srcPath = ["entry", "content", "src"];
93
137
 
94
- let keySystems;
138
+ // when using vods from applicaster2, `entry.content.src` might be
139
+ // null when trying to exit the player. This prevents a crash in that scenario
140
+ return !(R.path(srcPath, this.props) && !R.path(srcPath, nextProps));
141
+ }
95
142
 
96
- if (drm) {
97
- player.eme();
143
+ componentDidUpdate(prevProps) {
144
+ if (
145
+ this.props.castAction?.state?.connected &&
146
+ !this.state.isCastConnected
147
+ ) {
148
+ this.stopBackgroundPlayback();
149
+ this.setState({ isCastConnected: true });
150
+ }
98
151
 
99
- keySystems = {
100
- "com.widevine.alpha": {
101
- audioRobustness: "SW_SECURE_CRYPTO",
102
- videoRobustness: "SW_SECURE_CRYPTO",
103
- url: drm?.widevine?.license_url,
104
- },
105
- };
152
+ if (
153
+ !this.props.castAction?.state?.connected &&
154
+ this.state.isCastConnected
155
+ ) {
156
+ this.setState({ isCastConnected: false });
106
157
  }
107
158
 
108
- player.src([
109
- {
110
- src: src,
111
- type: getStreamType(type),
112
- keySystems,
113
- },
114
- ]);
159
+ if (prevProps.entry?.content?.src !== this.props.entry?.content?.src) {
160
+ this.stopBackgroundPlayback();
161
+ this.playbackStarted = false;
162
+ }
115
163
  }
116
164
 
165
+ // For close button
166
+ public closeNativePlayer = () => {
167
+ this.stopBackgroundPlayback();
168
+ };
169
+
170
+ getPlayNextData = () => this.props.playNextData as PlayNextData;
171
+
117
172
  /**
118
173
  * Assigns root / ref of the player.
119
174
  */
@@ -121,18 +176,953 @@ export default class VideoPlayer extends React.Component<
121
176
  this._root = component;
122
177
  };
123
178
 
179
+ getPluginConfiguration = () => {
180
+ return this.props.pluginConfiguration;
181
+ };
182
+
183
+ /**
184
+ * This method is for the events that have no implementation in react but are being invoked by native
185
+ * When we get the synthetic event we need to pull the data in the nativeEvent object
186
+ * nativeEvent contains the return value from native
187
+ * Value could be null if nothing return
188
+ */
189
+ unHandledEvent = (eventName) => {
190
+ return function dispatchUnhandledEvent({ nativeEvent = null }) {
191
+ logger.warning({
192
+ message:
193
+ "You have invoked an event that is being deprecated or unhandled",
194
+ data: { eventName, nativeEvent },
195
+ });
196
+ };
197
+ };
198
+
199
+ isReactNativeMethodsSupported = () =>
200
+ this.props.PlayerComponent === "QuickBrickDefaultPlayerView" ||
201
+ this.props.PlayerComponent === "PlayerModule";
202
+
203
+ pause = () => {
204
+ // TODO: Call player instance controller methods directly
205
+ // this._root was added here because chromecast,
206
+ // is checking player state for the pause
207
+ if (
208
+ this.isReactNativeMethodsSupported() &&
209
+ RNPlayerManager?.[this.playerPluginId]?.pause &&
210
+ this._root
211
+ ) {
212
+ if (!this.state.paused) {
213
+ const node = findNodeHandle(this._root);
214
+
215
+ if (!node) {
216
+ return;
217
+ }
218
+
219
+ RNPlayerManager?.[this.playerPluginId]?.pause(node);
220
+ }
221
+ } else {
222
+ this.setState({ paused: true });
223
+ }
224
+
225
+ // User invoked player pause action
226
+ this.needToResumeOnForeground = null;
227
+ };
228
+
229
+ play = () => {
230
+ // TODO: Call player instance controller methods directly
231
+ // this._root was added here because chromecast,
232
+ // is checking player state for the pause
233
+ if (
234
+ this.isReactNativeMethodsSupported() &&
235
+ RNPlayerManager?.[this.playerPluginId]?.play &&
236
+ this._root
237
+ ) {
238
+ if (this.state.paused) {
239
+ const node = findNodeHandle(this._root);
240
+
241
+ if (!node) {
242
+ return;
243
+ }
244
+
245
+ RNPlayerManager?.[this.playerPluginId]?.play(node);
246
+ }
247
+ } else {
248
+ this.setState({ paused: false });
249
+ }
250
+
251
+ // User invoked player pause action
252
+ this.needToResumeOnForeground = null;
253
+ };
254
+
255
+ stopBackgroundPlayback = () => {
256
+ if (
257
+ this.isReactNativeMethodsSupported() &&
258
+ RNPlayerManager?.[this.playerPluginId]?.stopBackgroundPlayback
259
+ ) {
260
+ RNPlayerManager?.[this.playerPluginId]?.stopBackgroundPlayback();
261
+ }
262
+ };
263
+
264
+ forward = (seekTime = SEEK_TIME) => {
265
+ const currentTime = this.state.currentTime;
266
+
267
+ const _duration = this.state.isLive
268
+ ? this.state?.seekableDuration
269
+ : this.getDuration();
270
+
271
+ const _time = this.state.isLive
272
+ ? R.clamp(0, _duration, currentTime - seekTime)
273
+ : R.clamp(0, _duration, currentTime + seekTime);
274
+
275
+ const seekTo = {
276
+ time: _time,
277
+ tolerance: 1,
278
+ };
279
+
280
+ this.setState({ seek: seekTo });
281
+ };
282
+
283
+ rewind = (seekTime = SEEK_TIME) => {
284
+ const currentTime = this.state.currentTime;
285
+
286
+ const _duration = this.state.isLive
287
+ ? this.state?.seekableDuration
288
+ : this.getDuration();
289
+
290
+ const _time = this.state.isLive
291
+ ? R.clamp(0, _duration, currentTime + seekTime)
292
+ : R.clamp(0, _duration, currentTime - seekTime);
293
+
294
+ const seekTo = {
295
+ time: _time,
296
+ tolerance: 1,
297
+ };
298
+
299
+ this.setState({ seek: seekTo });
300
+ };
301
+
302
+ seeking = (time, tolerance = 1) => {
303
+ if (isNaN(time)) {
304
+ logger.error({
305
+ message: `Specified time is not a number time: ${time}`,
306
+ });
307
+
308
+ return;
309
+ }
310
+
311
+ // This hack was added, if send same value to seek two times like 0,
312
+ // It will not property change callback to native.
313
+ // TODO: This func must be refactored to View React method.
314
+ if (time === this.state.seek.time) {
315
+ this.setState({ seek: {} }, () => {
316
+ this.setState({ seek: { time, tolerance } });
317
+ });
318
+ } else {
319
+ this.setState({ seek: { time, tolerance } });
320
+ }
321
+ };
322
+
323
+ selectTrack = (selected) => {
324
+ const { trackType, id, language } = selected || {};
325
+
326
+ const isOff = language === "Off";
327
+
328
+ if (!id) {
329
+ logger.error({
330
+ message:
331
+ "Failed to set Audio or Text track. Language track is missing required parameter ID",
332
+ data: { selected },
333
+ });
334
+
335
+ return;
336
+ }
337
+
338
+ switch (trackType) {
339
+ case "text":
340
+ this.setState({
341
+ textTrackId: isOff ? null : id,
342
+ });
343
+
344
+ break;
345
+ case "audio":
346
+ this.setState({
347
+ audioTrackId: id,
348
+ });
349
+
350
+ break;
351
+ }
352
+ };
353
+
354
+ setPlaybackRate = (rate = 1.0) => {
355
+ this.setState({ rate });
356
+ };
357
+
358
+ getCurrentTime = () => {
359
+ return this.state.currentTime;
360
+ };
361
+
362
+ getDuration = () => {
363
+ return this.state.duration;
364
+ };
365
+
366
+ getState = () => {
367
+ return { ...this.state, fullscreen: Boolean(this.props?.fullscreen) };
368
+ };
369
+
370
+ /**
371
+ * Handles key events transmitted by the native
372
+ * Adds the key event to the playerState so that it can be consumed by interaction manager
373
+ * Needs for Android
374
+ */
375
+ onKeyDown = (event) => {
376
+ this.setState({
377
+ keyEvent: event,
378
+ });
379
+ };
380
+
381
+ onPlayerPresented = () => {
382
+ this.analyticsTracker?.handleAnalyticEvent(GENERAL_EVENT.player_presented);
383
+ };
384
+
385
+ onPlayerClosed = () => {
386
+ this.analyticsTracker?.handleAnalyticEvent(GENERAL_EVENT.player_closed);
387
+ };
388
+
389
+ onVideoEnd = () => {
390
+ this.setState({ displayOverlay: !this.state.displayOverlay }, () => {
391
+ if (this.state?.displayOverlay) {
392
+ this.props?.listener?.onVideoEnd({});
393
+ }
394
+ });
395
+ };
396
+
397
+ // TODO: Should be unified on both platform
398
+ onVideoError = ({ nativeEvent }) => {
399
+ const { message } = nativeEvent;
400
+
401
+ return this.props?.listener?.onError(new Error(message));
402
+ };
403
+
404
+ onPlayerPause = ({ nativeEvent = null }) => {
405
+ this.setState({ paused: true });
406
+ this.props?.listener?.onPlayerPause(nativeEvent);
407
+ };
408
+
409
+ onPlayerResume = ({ nativeEvent = null }) => {
410
+ const { isAd, resume } = this.state;
411
+ const isNotAnAd = !isAd;
412
+ this.setState({ paused: false });
413
+ this.props?.listener?.onPlayerResume?.({ nativeEvent });
414
+
415
+ if (isNotAnAd && !resume) {
416
+ this.setState({ resume: true });
417
+ }
418
+ };
419
+
420
+ onPlayerSeekComplete = ({ nativeEvent }) => {
421
+ this.props?.listener?.onPlayerSeekComplete(nativeEvent);
422
+ };
423
+
424
+ onPlayerSeekStart = ({ nativeEvent }) => {
425
+ this.props?.listener?.onPlayerSeekStart(nativeEvent);
426
+ };
427
+
428
+ onBufferStart = ({ nativeEvent }) => {
429
+ this.props?.listener?.onBufferStart(nativeEvent);
430
+ };
431
+
432
+ onBufferComplete = ({ nativeEvent }) => {
433
+ this.props?.listener?.onBufferComplete(nativeEvent);
434
+ };
435
+
436
+ onAdBegin = ({ nativeEvent }) => {
437
+ this.setState({ isAd: true });
438
+ this.props.listener?.onAdBegin?.(nativeEvent);
439
+ };
440
+
441
+ onAdBreakBegin = ({ nativeEvent }) => {
442
+ this.setState({ isAd: true });
443
+ this.props.listener?.onAdBreakBegin?.(nativeEvent);
444
+ };
445
+
446
+ onAdBreakEnd = ({ nativeEvent }) => {
447
+ this.setState({ isAd: false });
448
+ this.props.listener?.onAdBreakEnd?.(nativeEvent);
449
+ };
450
+
451
+ onAdEnd = ({ nativeEvent }) => {
452
+ this.setState({ isAd: false });
453
+ this.props.listener?.onAdEnd?.(nativeEvent);
454
+ };
455
+
456
+ onAdError = ({ nativeEvent }) => {
457
+ this.setState({ isAd: false });
458
+ this.props.listener?.onAdError?.(nativeEvent);
459
+ };
460
+
461
+ onAdRequest = ({ nativeEvent }) => {
462
+ this.props.listener?.onAdRequest?.(nativeEvent);
463
+ };
464
+
465
+ onAdClicked = ({ nativeEvent }) => {
466
+ // Broadcast click event
467
+ this.props.playerEvent("ad-clicked", nativeEvent);
468
+ this.props.listener?.onAdClicked?.(nativeEvent);
469
+ };
470
+
471
+ onAdTapped = ({ nativeEvent }) => {
472
+ // Broadcast click event
473
+ this.props.playerEvent("ad-tapped", nativeEvent);
474
+ this.props.listener?.onAdTapped?.(nativeEvent);
475
+ };
476
+
477
+ onPlaybackStalled = () => {
478
+ return this.props?.updatePlayerState?.("stalled");
479
+ };
480
+
481
+ onPlayerAttached = ({ nativeEvent }) => {
482
+ this.props.listener?.onPlayerAttached?.(nativeEvent);
483
+ };
484
+
485
+ onPlayerDetached = ({ nativeEvent }) => {
486
+ this.props.listener?.onPlayerDetached?.(nativeEvent);
487
+ };
488
+
489
+ onVideoFullscreenPlayerDidDismiss = () => {
490
+ this.props.listener?.onVideoFullscreenPlayerDidDismiss?.();
491
+ };
492
+
493
+ onVideoFullscreenPlayerDidPresent = () => {
494
+ this.props.listener?.onVideoFullscreenPlayerDidPresent?.();
495
+ };
496
+
497
+ onVideoFullscreenPlayerWillDismiss = () => {
498
+ this.props.listener?.onVideoFullscreenPlayerWillDismiss?.();
499
+ };
500
+
501
+ onVideoFullscreenPlayerWillPresent = () => {
502
+ this.props.listener?.onVideoFullscreenPlayerWillPresent?.();
503
+ };
504
+
505
+ onPlayerClose = () => {
506
+ this.props.listener?.onPlayerClose?.();
507
+ };
508
+
509
+ onPiPEnter = () => {
510
+ this.setState({ piped: true });
511
+ this.props.openPiP();
512
+ };
513
+
514
+ onPiPExit = () => {
515
+ this.setState({ piped: false });
516
+ this.props.closePiP();
517
+ };
518
+
519
+ onPiPCloseByUser = () => {
520
+ if (this.state.piped) {
521
+ const node = findNodeHandle(this._root);
522
+
523
+ if (!node) {
524
+ return;
525
+ }
526
+
527
+ playerManager.close();
528
+ }
529
+ };
530
+
531
+ disablePiP = () => {
532
+ const node = findNodeHandle(this._root);
533
+
534
+ if (!node) {
535
+ return;
536
+ }
537
+
538
+ RNPlayerManager?.[this.playerPluginId]?.disablePiP?.(node);
539
+ // TODO: Remove after implementing play next for PiP
540
+ RNPlayerManager?.[this.playerPluginId]?.closePiP?.(node);
541
+ };
542
+
543
+ onVideoBuffer = ({ nativeEvent }) => {
544
+ // video buffer event is invoked from native side when asking the player to pause
545
+ // this causes the buffer animation to trigger and incorrectly request to update state
546
+ if (this.state.paused) {
547
+ return;
548
+ }
549
+
550
+ // TODO: This events must be called from native directly
551
+ if (nativeEvent?.isBuffering) {
552
+ this.onBufferStart({ nativeEvent });
553
+ } else {
554
+ this.onBufferComplete({ nativeEvent });
555
+ }
556
+
557
+ return this.props?.updatePlayerState?.("loadstart");
558
+ };
559
+
560
+ onVideoLoad = ({ nativeEvent }) => {
561
+ const duration = nativeEvent.duration;
562
+ const currentTime = nativeEvent.currentTime;
563
+
564
+ // check if content is loaded
565
+ if (duration < 0) {
566
+ return;
567
+ }
568
+
569
+ this.setState(
570
+ {
571
+ currentTime,
572
+ duration,
573
+ },
574
+ () => {
575
+ this.props?.listener?.onVideoLoad(nativeEvent);
576
+
577
+ if (!this.playbackStarted) {
578
+ this.playbackStarted = true;
579
+ // TODO: Remove not needed anymore, implemented on player.tsx
580
+ this.props?.listener?.onLoad(nativeEvent);
581
+ }
582
+
583
+ if (!this.state.isAd && !this.onVideoLoadCalled) {
584
+ // TODO: Hack, needs to remove
585
+ this.onVideoLoadCalled = true;
586
+ }
587
+ }
588
+ );
589
+ };
590
+
591
+ onTracksChanged = ({ nativeEvent }) => {
592
+ const { textTracks, audioTracks, audioTrackId, textTrackId } =
593
+ parseLanguageTracks(nativeEvent);
594
+
595
+ this.setState(
596
+ {
597
+ audioTrackId,
598
+ textTrackId,
599
+ },
600
+ () => {
601
+ this.props?.listener?.onTracksChanged?.({
602
+ textTracks,
603
+ audioTracks,
604
+ audioTrackId,
605
+ textTrackId,
606
+ });
607
+ }
608
+ );
609
+ };
610
+
611
+ onVideoProgress = ({ nativeEvent }) => {
612
+ const duration = nativeEvent.duration;
613
+ const currentTime = nativeEvent.currentTime;
614
+ const seekableDuration = nativeEvent.seekableDuration;
615
+ const isLive = nativeEvent.isLive;
616
+
617
+ // TODO: Delete states on view
618
+ this.setState({
619
+ currentTime,
620
+ duration,
621
+ resume: false,
622
+ seekableDuration,
623
+ isLive,
624
+ });
625
+
626
+ this.props?.listener?.onVideoProgress({
627
+ currentTime,
628
+ duration,
629
+ item: this.props?.entry,
630
+ seekableDuration,
631
+ isLive,
632
+ });
633
+ };
634
+
635
+ onPlaybackRateChange = ({ nativeEvent }) => {
636
+ if (nativeEvent.playbackRate !== this.state.rate) {
637
+ this.setState({ rate: nativeEvent.playbackRate });
638
+
639
+ if (this.props?.listener?.onPlaybackRateChange) {
640
+ this.props?.listener?.onPlaybackRateChange(nativeEvent);
641
+ }
642
+
643
+ if (nativeEvent?.playbackRate > 0) {
644
+ if (this.props?.listener?.onPlayerResume) {
645
+ this.props?.listener?.onPlayerResume(nativeEvent);
646
+ }
647
+ }
648
+ }
649
+ };
650
+
651
+ appStateChange = (appState, previousAppState) => {
652
+ const { pluginConfiguration } = this.props;
653
+ const shouldPauseOnExit = pluginConfiguration?.nowPlayingEnabled !== true;
654
+
655
+ // TODO: This const must be imported from upstate change of TS
656
+ const ACTIVE = "active";
657
+ const BACKGROUND = "background";
658
+ const INACTIVE = "inactive";
659
+
660
+ if (
661
+ previousAppState === ACTIVE &&
662
+ appState &&
663
+ (appState === BACKGROUND || appState === INACTIVE)
664
+ ) {
665
+ if ((isTV() || shouldPauseOnExit) && this.props.mode !== "PIP") {
666
+ const wasPlaying = !this.state.paused;
667
+ this.pause();
668
+ this.needToResumeOnForeground = wasPlaying;
669
+ }
670
+ }
671
+
672
+ if (appState && appState === ACTIVE && previousAppState !== ACTIVE) {
673
+ if (this.needToResumeOnForeground) {
674
+ this.play();
675
+ this.needToResumeOnForeground = null;
676
+ }
677
+ }
678
+ };
679
+
680
+ childrenWithPlayNext = () => {
681
+ if (!isTvOSPlatform() || !this.getPlayNextData()) {
682
+ return this.props.children;
683
+ }
684
+
685
+ // tvos only
686
+ const { docked, entry } = this.props;
687
+
688
+ const children = R.pathOr([], "children", this.props);
689
+
690
+ return [
691
+ ...children,
692
+ <PlayNextOverlay
693
+ key="play-next-overlay"
694
+ item={entry}
695
+ playerIsDocked={docked}
696
+ pluginConfiguration={this.getPluginConfiguration()}
697
+ playerId={this.props.playerId}
698
+ playNextData={this.getPlayNextData()}
699
+ setNextVideoPreloadThresholdPercentage={
700
+ this.props.setNextVideoPreloadThresholdPercentage
701
+ }
702
+ />,
703
+ ];
704
+ };
705
+
124
706
  render() {
125
- return (
126
- <View style={this.takeParentStyle} testID={"player-view"}>
127
- <div data-vjs-player>
128
- <video
129
- className="video-js vjs-big-play-centered fullscreen"
130
- autoPlay
131
- controls
132
- ref={this._assignRoot}
133
- ></video>
134
- </div>
135
- </View>
707
+ const {
708
+ docked,
709
+ entry,
710
+ fullscreen,
711
+ hideTransportControls,
712
+ inline,
713
+ isModal,
714
+ isTabletPortrait,
715
+ muted,
716
+ style,
717
+ castAction,
718
+ controller,
719
+ startComponentsAnimation,
720
+ } = this.props;
721
+
722
+ const supportNativeCast = controller.supportNativeCast();
723
+
724
+ const { audioTrackId, keyEvent, paused, seek, textTrackId, rate } =
725
+ this.state;
726
+
727
+ const isAudioItem =
728
+ entry?.content?.type?.includes?.("audio") ||
729
+ entry?.type?.value === "audio";
730
+
731
+ const getStyle = (dimensions) => {
732
+ const generalStyles = isAudioItem ? styles.audio : styles.video;
733
+
734
+ if (isAndroidPlatform() && isAudioItem) {
735
+ return generalStyles;
736
+ }
737
+
738
+ return { ...generalStyles, ...dimensions };
739
+ };
740
+
741
+ const getBackgroundImageStyle = (dimensions): any =>
742
+ Object.assign(
743
+ {},
744
+ isAudioItem ? styles.audio : { position: "absolute" },
745
+ dimensions
746
+ );
747
+
748
+ const nativeResizeMode = "ScaleAspectFit";
749
+
750
+ const nowPlayingEnabled =
751
+ this.props?.pluginConfiguration?.nowPlayingEnabled;
752
+
753
+ const enableAutoAudioTrackSelection =
754
+ this.props.pluginConfiguration?.enable_auto_audio_track_selection;
755
+
756
+ const minimumAllowedSeekableDurationInSeconds =
757
+ this.props?.pluginConfiguration?.minimumAllowedSeekableDurationInSeconds;
758
+ // In case no seeking duration or it less than 10 min we do not want to enable live seeking feature
759
+
760
+ const liveSeekingEnabled =
761
+ (this.props.pluginConfiguration?.liveSeekingEnabled &&
762
+ this.state?.seekableDuration >
763
+ minimumAllowedSeekableDurationInSeconds) ||
764
+ false;
765
+
766
+ // keep undefined if not present, since not all native players have the property
767
+ const liveCatchUpEnabled =
768
+ this.props.pluginConfiguration?.liveCatchUpEnabled;
769
+
770
+ // TODO: Get screen data more reliable way useCurrentScreenData
771
+ const screenData = getTargetScreenDataFromEntry(this.props.playableItem);
772
+
773
+ const isInlineTV = screenData?.ui_components?.length > 0 && isTV();
774
+
775
+ let platformSpecificProps: any = isApplePlatform()
776
+ ? {
777
+ resizeMode: nativeResizeMode,
778
+ }
779
+ : {};
780
+
781
+ const supportsNativeControls =
782
+ !!this.props.controller?.supportsNativeControls?.();
783
+
784
+ const controls = isAppleTV()
785
+ ? (supportsNativeControls && !isInlineTV) || !!this.getPlayNextData()
786
+ : supportsNativeControls && !this.getPlayNextData();
787
+
788
+ const shouldUseNativeControls =
789
+ controls &&
790
+ !startComponentsAnimation &&
791
+ !docked &&
792
+ !castAction?.state?.connected;
793
+
794
+ platformSpecificProps = {
795
+ ...platformSpecificProps,
796
+ controls: shouldUseNativeControls,
797
+ };
798
+
799
+ const pictureInPictureEnabled =
800
+ this.props.pluginConfiguration?.pictureInPictureEnabled;
801
+
802
+ const nativeProps = {
803
+ entry, // Must be passed first in list
804
+ ...platformSpecificProps,
805
+ audioTrackId,
806
+ docked,
807
+ enableAutoAudioTrackSelection,
808
+ fullscreen, // TODO: Check fullscreen params not crash
809
+ inline,
810
+ muted,
811
+ nowPlayingEnabled,
812
+ paused,
813
+ seek,
814
+ rate,
815
+ textTrackId,
816
+ liveSeekingEnabled,
817
+ liveCatchUpEnabled,
818
+ playerId: this.props.playerId,
819
+ pictureInPictureEnabled,
820
+ seekStep:
821
+ (this.props.pluginConfiguration?.seek_duration || SEEK_TIME) * 1000, // Only for Android
822
+ accessibilityProps: getAllAccessibilityProps(
823
+ this.getPluginConfiguration()
824
+ ),
825
+ }; // rate temporary removed, because of bug with subtitles
826
+
827
+ if (
828
+ this.isReactNativeMethodsSupported() &&
829
+ RNPlayerManager?.[this.playerPluginId]?.play
830
+ ) {
831
+ delete nativeProps["paused"];
832
+ }
833
+
834
+ const adEvents = {
835
+ onAdBegin: this.onAdBegin, // ad_start
836
+ onAdBreakBegin: this.onAdBreakBegin, // ad_break_start
837
+ onAdBreakEnd: this.onAdBreakEnd, // ad_break_completed
838
+ onAdEnd: this.onAdEnd, // ad_complete
839
+ onAdError: this.onAdError, // ad_error
840
+ onAdRequest: this.onAdRequest, // ad_request
841
+ onAdClicked: this.onAdClicked, // ad_clicked
842
+ onAdTapped: this.onAdTapped, // ad_tapped
843
+ };
844
+
845
+ const playbackEvents = {
846
+ onPlaybackRateChange: this.onPlaybackRateChange,
847
+ onPlayerPause: this.onPlayerPause, // pause
848
+ onPlayerResume: this.onPlayerResume, // play
849
+ onPlayerSeekComplete: this.onPlayerSeekComplete, // seek_complete
850
+ onPlayerSeekStart: this.onPlayerSeekStart, // seek_start
851
+ };
852
+
853
+ const lifecycleEvents = {
854
+ onReadyForDisplay: this.unHandledEvent("onReadyForDisplay"),
855
+ onVideoEnd: this.onVideoEnd, // complete
856
+ onVideoError: this.onVideoError, // error
857
+ onVideoProgress: this.onVideoProgress,
858
+ onVideoFullscreenPlayerDidDismiss: this.onVideoFullscreenPlayerDidDismiss,
859
+ onVideoFullscreenPlayerDidPresent: this.onVideoFullscreenPlayerDidPresent,
860
+ onVideoFullscreenPlayerWillDismiss:
861
+ this.onVideoFullscreenPlayerWillDismiss,
862
+ onVideoFullscreenPlayerWillPresent:
863
+ this.onVideoFullscreenPlayerWillPresent,
864
+ onVideoLoad: this.onVideoLoad,
865
+ onVideoLoadStart: this.unHandledEvent("onVideoLoadStart"),
866
+ onVideoBuffer: this.onVideoBuffer,
867
+ onPlaybackStalled: this.onPlaybackStalled,
868
+ onPlayerClose: this.onPlayerClose,
869
+ onPlayerAttached: this.onPlayerAttached,
870
+ onPlayerDetached: this.onPlayerDetached,
871
+ };
872
+
873
+ const androidLifecyclePiPEvents = isAndroidPlatform() && {
874
+ onPiPEnter: this.onPiPEnter,
875
+ onPiPExit: this.onPiPExit,
876
+ onPiPCloseByUser: this.onPiPCloseByUser,
877
+ };
878
+
879
+ const playerMetadataChangedEvents = {
880
+ onTimedMetadata: this.unHandledEvent("onTimedMetadata"),
881
+ onTracksChanged: this.onTracksChanged,
882
+ eventSubtitlesChange: this.unHandledEvent("eventSubtitlesChange"),
883
+ };
884
+
885
+ const playerSystemEvents = {
886
+ onAudioFocusChanged: this.unHandledEvent("onAudioFocusChanged"),
887
+ onVideoExternalPlaybackChange: this.unHandledEvent(
888
+ "onVideoExternalPlaybackChange"
889
+ ),
890
+ onVideoAudioBecomingNoisy: this.unHandledEvent(
891
+ "onVideoAudioBecomingNoisy"
892
+ ),
893
+ };
894
+
895
+ const androidPlayerEvents = {
896
+ onKeyChanged: keyEvent,
897
+ };
898
+
899
+ const unsupportedEvents = {
900
+ onBufferComplete: this.onBufferComplete, // buffer_complete
901
+ onBufferStart: this.onBufferStart, // buffer_start
902
+ };
903
+
904
+ const nativeEvents = {
905
+ ...adEvents,
906
+ ...androidPlayerEvents,
907
+ ...lifecycleEvents,
908
+ ...playerMetadataChangedEvents,
909
+ ...playerSystemEvents,
910
+ ...unsupportedEvents,
911
+ ...playbackEvents,
912
+ ...androidLifecyclePiPEvents,
913
+ };
914
+
915
+ const needToShowPlayNextOverlay =
916
+ isTV() && this.getPlayNextData() && !docked;
917
+
918
+ const needToShowDefaultControls = !(
919
+ (controls && !castAction?.state?.connected) ||
920
+ this.getPlayNextData()
136
921
  );
922
+
923
+ const playerContainerStyles = isApplePlatform()
924
+ ? styles.playerContainerBlack
925
+ : isTV() && isAudioItem
926
+ ? styles.playerContainerTransparent
927
+ : styles.playerContainerBlack;
928
+
929
+ const isInline = () => {
930
+ if (isTV()) {
931
+ return inline;
932
+ }
933
+
934
+ // HACK: on mobile overwrite inline value if player is fullScreen
935
+ return fullscreen ? false : inline;
936
+ };
937
+
938
+ const renderTransportControls = (playerDimensions) => {
939
+ const needsToRender =
940
+ !hideTransportControls &&
941
+ !needToShowPlayNextOverlay &&
942
+ this.props.mode !== "PIP";
943
+
944
+ if (shouldUseNativeControls) {
945
+ return null;
946
+ }
947
+
948
+ const TransportControls = isTV()
949
+ ? TransportControlsTV
950
+ : TransportControlsMobile;
951
+
952
+ return needsToRender ? (
953
+ <TransportControls
954
+ playerContent={entry}
955
+ configuration={this.getPluginConfiguration()}
956
+ playerDimensions={playerDimensions}
957
+ inline={isInline()}
958
+ docked={fullscreen ? false : docked}
959
+ isModal={isModal}
960
+ extraInfoText={castAction?.state?.friendlyStatus}
961
+ controlsAlwaysVisible={castAction?.state?.connected}
962
+ playNextData={this.getPlayNextData()}
963
+ needToShowDefaultControls={needToShowDefaultControls}
964
+ />
965
+ ) : null;
966
+ };
967
+
968
+ const isFullScreenAudioPlayer =
969
+ this.getPluginConfiguration()?.["full_screen_audio_player"] || false;
970
+
971
+ const sharedWrapperViewProps = {
972
+ style,
973
+ entry,
974
+ inline,
975
+ docked,
976
+ isModal,
977
+ pip: this.state.piped,
978
+ layoutState: {
979
+ inline,
980
+ docked,
981
+ isModal,
982
+ pip: this.state.piped,
983
+ },
984
+ configuration: this.getPluginConfiguration(),
985
+ fullscreen: fullscreen,
986
+ isTabletPortrait: isTabletPortrait,
987
+ playNextData: this.getPlayNextData(),
988
+ };
989
+
990
+ const containerStyle = { flex: 1 };
991
+
992
+ const backgroundImageKey =
993
+ this.getPluginConfiguration()?.player_preview_image_key;
994
+
995
+ const renderMobilePlayer = () => {
996
+ return isFullScreenAudioPlayer && isAudioItem ? (
997
+ <View style={containerStyle}>
998
+ {(!castAction?.state?.connected || supportNativeCast) && (
999
+ <this.props.PlayerComponent
1000
+ ref={this._assignRoot}
1001
+ {...nativeEvents}
1002
+ {...nativeProps}
1003
+ />
1004
+ )}
1005
+ <AudioPlayerWrapper {...sharedWrapperViewProps} />
1006
+ </View>
1007
+ ) : (
1008
+ <PlayerWrapper {...sharedWrapperViewProps}>
1009
+ {(playerDimensions) => {
1010
+ const shouldRenderBackground =
1011
+ !R.isNil(backgroundImageKey) &&
1012
+ backgroundImageKey.length > 0 &&
1013
+ !this.onVideoLoadCalled &&
1014
+ !this.state.isAd;
1015
+
1016
+ return (
1017
+ <View style={styles.container}>
1018
+ <View style={[playerContainerStyles, playerDimensions]}>
1019
+ {isAudioItem && (
1020
+ <AnimationComponent
1021
+ animationType={ComponentAnimationType.player}
1022
+ additionalData={{
1023
+ ...playerDimensions,
1024
+ inlineAudioPlayer: true,
1025
+ }}
1026
+ style={getStyle(playerDimensions)}
1027
+ >
1028
+ <AudioPlayer
1029
+ style={
1030
+ isTV()
1031
+ ? getStyle(playerDimensions)
1032
+ : styles.playerSize
1033
+ }
1034
+ audio_item={entry}
1035
+ plugin_configuration={this.getPluginConfiguration()}
1036
+ />
1037
+ </AnimationComponent>
1038
+ )}
1039
+
1040
+ {shouldRenderBackground && (
1041
+ <PlayerImageBackground
1042
+ imageKey={backgroundImageKey}
1043
+ configuration={this.getPluginConfiguration()}
1044
+ entry={entry}
1045
+ style={getBackgroundImageStyle(playerDimensions)}
1046
+ />
1047
+ )}
1048
+
1049
+ {(!castAction?.state?.connected || supportNativeCast) && (
1050
+ <AnimationComponent
1051
+ animationType={ComponentAnimationType.player}
1052
+ additionalData={playerDimensions}
1053
+ style={getStyle(playerDimensions)}
1054
+ >
1055
+ <this.props.PlayerComponent
1056
+ ref={this._assignRoot}
1057
+ {...nativeEvents}
1058
+ {...nativeProps}
1059
+ listener={this.props.listener}
1060
+ style={
1061
+ isTV()
1062
+ ? getStyle(playerDimensions)
1063
+ : styles.playerSize
1064
+ }
1065
+ pluginConfiguration={this.getPluginConfiguration()}
1066
+ controller={this.props.controller}
1067
+ >
1068
+ {this.childrenWithPlayNext()}
1069
+ </this.props.PlayerComponent>
1070
+ </AnimationComponent>
1071
+ )}
1072
+ </View>
1073
+
1074
+ {renderTransportControls(playerDimensions)}
1075
+ {!this.state.piped && (
1076
+ <PlayNextOverlay
1077
+ item={entry}
1078
+ playerIsDocked={docked}
1079
+ pluginConfiguration={this.getPluginConfiguration()}
1080
+ playerId={this.props.playerId}
1081
+ playNextData={this.getPlayNextData()}
1082
+ setNextVideoPreloadThresholdPercentage={
1083
+ this.props.setNextVideoPreloadThresholdPercentage
1084
+ }
1085
+ />
1086
+ )}
1087
+ </View>
1088
+ );
1089
+ }}
1090
+ </PlayerWrapper>
1091
+ );
1092
+ };
1093
+
1094
+ const renderTVPlayer = () => {
1095
+ return (
1096
+ <View style={this.takeParentStyle} testID={"player-view"}>
1097
+ <this.props.PlayerComponent
1098
+ ref={this._assignRoot}
1099
+ {...nativeEvents}
1100
+ {...nativeProps}
1101
+ listener={this.props.listener}
1102
+ style={this.takeParentStyle}
1103
+ pluginConfiguration={this.getPluginConfiguration()}
1104
+ controller={this.props.controller}
1105
+ >
1106
+ {this.childrenWithPlayNext()}
1107
+ </this.props.PlayerComponent>
1108
+
1109
+ {renderTransportControls(this.takeParentStyle)}
1110
+ {!isTvOSPlatform() && (
1111
+ <PlayNextOverlay
1112
+ item={entry}
1113
+ playerIsDocked={docked}
1114
+ pluginConfiguration={this.getPluginConfiguration()}
1115
+ playerId={this.props.playerId}
1116
+ playNextData={this.getPlayNextData()}
1117
+ setNextVideoPreloadThresholdPercentage={
1118
+ this.props.setNextVideoPreloadThresholdPercentage
1119
+ }
1120
+ />
1121
+ )}
1122
+ </View>
1123
+ );
1124
+ };
1125
+
1126
+ return isTV() ? renderTVPlayer() : renderMobilePlayer();
137
1127
  }
138
1128
  }