@applicaster/quick-brick-player 12.0.0-alpha.4220424021 → 12.0.0-alpha.4241289980

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 +8 -7
  2. package/src/Player/index.tsx +1078 -144
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@applicaster/quick-brick-player",
3
- "version": "12.0.0-alpha.4220424021",
3
+ "version": "12.0.0-alpha.4241289980",
4
4
  "description": "Quick Brick Player",
5
5
  "main": "./src/index.ts",
6
6
  "types": "index.d.ts",
@@ -38,16 +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.4220424021",
42
- "@applicaster/zapp-react-native-ui-components": "12.0.0-alpha.4220424021",
43
- "@applicaster/zapp-react-native-utils": "12.0.0-alpha.4220424021",
44
- "dashjs": "^4.7.3",
45
- "hls.js": "^1.5.5",
41
+ "@applicaster/zapp-react-native-tvos-app": "12.0.0-alpha.4241289980",
42
+ "@applicaster/zapp-react-native-ui-components": "12.0.0-alpha.4241289980",
43
+ "@applicaster/zapp-react-native-utils": "12.0.0-alpha.4241289980",
46
44
  "keymirror": "^0.1.1",
47
45
  "query-string": "7.1.3",
48
46
  "react-native-web-linear-gradient": "1.1.2",
49
47
  "shaka-player": "4.3.5",
50
- "typeface-montserrat": "^0.0.54"
48
+ "typeface-montserrat": "^0.0.54",
49
+ "video.js": "7.14.3",
50
+ "videojs-contrib-eme": "^3.8.1",
51
+ "videojs-contrib-quality-levels": "3.0.0"
51
52
  },
52
53
  "peerDependencies": {
53
54
  "@applicaster/quick-brick-xray": "*",
@@ -1,152 +1,113 @@
1
- /* eslint-disable react-native/no-inline-styles */
2
1
  /* eslint-disable @typescript-eslint/no-use-before-define */
3
2
  /* eslint-disable react-native/no-color-literals */
4
3
 
5
4
  import React from "react";
6
- import HLS from "hls.js";
7
- import DASH from "dashjs";
8
-
9
- import { View } from "react-native";
10
- import { isWeb } from "@applicaster/zapp-react-native-utils/reactUtils";
11
-
12
- const EVENTS = [
13
- "abort",
14
- "error",
15
- "loadeddata",
16
- "loadedmetadata",
17
- "playing",
18
- "stalled",
19
- "suspend",
20
- ];
21
-
22
- const isHLS = (url) => {
23
- return url.includes(".m3u8");
24
- };
25
-
26
- const isDASH = (url) => {
27
- return url.includes(".mpd");
28
- };
29
-
30
- const setupEventLogging = (player, events, onEvent) => {
31
- let previous_event_time;
32
-
33
- events.map((event) =>
34
- player.addEventListener(event, () => {
35
- const event_time = Date.now();
36
-
37
- const log = `<${event}>: ${event_time - window.start_time} ms${
38
- previous_event_time ? ` (+${event_time - previous_event_time})` : ""
39
- }`;
40
-
41
- onEvent && onEvent(log);
42
- previous_event_time = event_time;
43
- })
44
- );
45
- };
46
-
47
- const DebugOverlay = ({ logs }) => (
48
- <div
49
- style={{
50
- position: "absolute",
51
- top: "10px",
52
- left: "10px",
53
- borderWidth: 1,
54
- borderColor: "white",
55
- padding: "10px",
56
- backgroundColor: "rgba(0, 0, 0, 0.5)",
57
- color: "white",
58
- fontSize: 20,
59
- zIndex: 9990,
60
- }}
61
- >
62
- {logs.map((log, index) => (
63
- <p key={`log_${index}`}>{log}</p>
64
- ))}
65
- </div>
66
- );
67
-
68
- const setupHLSPlayer = (stream, playerRef) => {
69
- if (!HLS.isSupported()) {
70
- console.error("HLS is not supported in this browser.");
71
-
72
- return false;
73
- }
5
+ import * as R from "ramda";
74
6
 
75
- try {
76
- const config = stream.license_url
77
- ? {
78
- emeEnabled: true,
79
- drmSystems: {
80
- "com.widevine.alpha": {
81
- licenseUrl: stream.license_url,
82
- },
83
- },
84
- drmSystemOptions: {
85
- audioRobustness: "SW_SECURE_CRYPTO",
86
- videoRobustness: "SW_SECURE_CRYPTO",
87
- },
88
- backBufferLength: 30, // https://github.com/video-dev/hls.js/blob/master/docs/API.md#backbufferlength
89
- }
90
- : undefined;
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";
91
10
 
92
- const hls = new HLS(config);
93
- hls.loadSource(stream.src);
94
- hls.attachMedia(playerRef);
95
-
96
- return true;
97
- } catch (err) {
98
- console.error(err);
99
- }
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";
100
19
 
101
- return false;
102
- };
20
+ import { PlayerWrapper } from "@applicaster/zapp-react-native-ui-components/Components/VideoModal";
103
21
 
104
- const setupDASHPlayer = (stream, playerRef) => {
105
- try {
106
- const player = DASH.MediaPlayer().create();
107
- player.initialize(playerRef, stream.src, true);
22
+ import { PlayNextOverlay } from "./PlayNextOverlay";
23
+ import { PlayerImageBackground } from "@applicaster/zapp-react-native-ui-components/Components/PlayerImageBackground";
108
24
 
109
- player.updateSettings({
110
- streaming: {
111
- buffer: {
112
- bufferToKeep: 30, // https://cdn.dashjs.org/latest/jsdoc/module-Settings.html#~Buffer:~:text=buffer%20in%20seconds.-,bufferToKeep,-number
113
- },
114
- },
115
- });
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";
116
41
 
117
- if (stream.license_url) {
118
- player.setProtectionData({
119
- "com.widevine.alpha": {
120
- serverURL: stream.license_url,
121
- priority: 0,
122
- audioRobustness: "SW_SECURE_CRYPTO",
123
- videoRobustness: "SW_SECURE_CRYPTO",
124
- },
125
- });
126
- }
42
+ import { RNPlayerManager } from "@applicaster/zapp-react-native-utils/appUtils/playerManager/nativePlayerManager";
127
43
 
128
- return true;
129
- } catch (error) {
130
- console.error(error);
131
- }
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
+ });
132
55
 
133
- return false;
134
- };
56
+ const logger = createLogger({
57
+ category: Categories.PLAYER,
58
+ subsystem: Subsystem,
59
+ });
135
60
 
136
61
  export default class VideoPlayer extends React.Component<
137
62
  QuickBrickPlayer.PlayerProps,
138
63
  QuickBrickPlayer.NativePlayerState
139
64
  > {
65
+ public static propTypes = {};
140
66
  _root: any;
141
67
  playerPluginId: string;
142
68
  takeParentStyle: Record<any, any>;
69
+ playbackStarted = false;
70
+ needToResumeOnForeground: boolean = null;
71
+ private onVideoLoadCalled: boolean;
72
+ playNextData: PlayNextData;
73
+
74
+ state: QuickBrickPlayer.NativePlayerState = {
75
+ audioTrackId: null,
76
+ textTrackId: null,
77
+
78
+ // TODO: Can be deleted, on controller logic
79
+ isAd: false,
80
+
81
+ // TODO: Remove paused
82
+ // TODO: Can be deleted, on controller logic
83
+ paused: false,
84
+ piped: false,
85
+ rate: 1.0,
86
+ // TODO: Hack for analytics, must br removed
87
+ resume: null,
88
+
89
+ // TODO: Replace with react method
90
+ seek: {},
91
+
92
+ // Player state
93
+ currentTime: 0,
94
+ duration: 0,
95
+ };
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
+ }
143
107
 
144
108
  constructor(props) {
145
109
  super(props);
146
-
147
- this.state = {
148
- logs: [],
149
- };
110
+ this.onVideoLoadCalled = false; // android
150
111
 
151
112
  this.takeParentStyle = isWeb()
152
113
  ? { width: "100%", flex: "1 1 auto", objectFit: "contain" }
@@ -155,27 +116,59 @@ export default class VideoPlayer extends React.Component<
155
116
  this.playerPluginId = this.props?.playerPluginIdentifier;
156
117
  }
157
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
+
158
130
  componentDidMount() {
159
- const {
160
- content: { src },
161
- extensions: { drm },
162
- } = this.props.entry;
131
+ this.playbackStarted = false;
132
+ this.onPlayerPresented();
133
+ }
163
134
 
164
- const stream = { src, license_url: drm?.widevine?.license_url };
135
+ shouldComponentUpdate(nextProps) {
136
+ const srcPath = ["entry", "content", "src"];
165
137
 
166
- window.start_time = Date.now();
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
+ }
167
142
 
168
- if (isDASH(src)) {
169
- setupDASHPlayer(stream, this._root);
170
- } else if (isHLS(src)) {
171
- setupHLSPlayer(stream, this._root);
143
+ componentDidUpdate(prevProps) {
144
+ if (
145
+ this.props.castAction?.state?.connected &&
146
+ !this.state.isCastConnected
147
+ ) {
148
+ this.stopBackgroundPlayback();
149
+ this.setState({ isCastConnected: true });
172
150
  }
173
151
 
174
- setupEventLogging(this._root, EVENTS, (log) => {
175
- this.setState(({ logs }) => ({ logs: [...logs, log] }));
176
- });
152
+ if (
153
+ !this.props.castAction?.state?.connected &&
154
+ this.state.isCastConnected
155
+ ) {
156
+ this.setState({ isCastConnected: false });
157
+ }
158
+
159
+ if (prevProps.entry?.content?.src !== this.props.entry?.content?.src) {
160
+ this.stopBackgroundPlayback();
161
+ this.playbackStarted = false;
162
+ }
177
163
  }
178
164
 
165
+ // For close button
166
+ public closeNativePlayer = () => {
167
+ this.stopBackgroundPlayback();
168
+ };
169
+
170
+ getPlayNextData = () => this.props.playNextData as PlayNextData;
171
+
179
172
  /**
180
173
  * Assigns root / ref of the player.
181
174
  */
@@ -183,12 +176,953 @@ export default class VideoPlayer extends React.Component<
183
176
  this._root = component;
184
177
  };
185
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
+
186
706
  render() {
187
- return (
188
- <View style={this.takeParentStyle} testID={"player-view"}>
189
- <video autoPlay controls ref={this._assignRoot}></video>
190
- <DebugOverlay logs={this.state.logs} />
191
- </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()
192
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();
193
1127
  }
194
1128
  }