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

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