@applicaster/quick-brick-player 14.0.0-rc.8 → 14.0.0-rc.81

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 (34) hide show
  1. package/package.json +6 -11
  2. package/src/Player/AudioLayer/AudioPlayerWrapper.tsx +28 -88
  3. package/src/Player/AudioLayer/Layout/Controls/index.tsx +8 -18
  4. package/src/Player/AudioLayer/Layout/DockedControls/index.tsx +119 -125
  5. package/src/Player/AudioLayer/Layout/MobileLayout.tsx +5 -38
  6. package/src/Player/AudioLayer/Layout/PlayerImage/AnimatedImage.tsx +3 -8
  7. package/src/Player/AudioLayer/Layout/PlayerImage/FlexImage.tsx +5 -9
  8. package/src/Player/AudioLayer/Layout/PlayerImage/index.tsx +22 -72
  9. package/src/Player/AudioLayer/Layout/TabletLandscapeLayout.tsx +1 -58
  10. package/src/Player/AudioLayer/Layout/TabletPortraitLayout.tsx +0 -33
  11. package/src/Player/AudioLayer/Layout/TransportInfo/index.tsx +6 -18
  12. package/src/Player/AudioLayer/components/SleepTimerModalHeader/SleepTimerModalHeader.tsx +1 -1
  13. package/src/Player/PlayerModal/GestureAnimatedScrollView.ts +7 -0
  14. package/src/Player/PlayerModal/PlayerModal.tsx +115 -0
  15. package/src/Player/PlayerModal/VideoPlayerModal.tsx +271 -0
  16. package/src/Player/PlayerModal/consts/index.ts +17 -0
  17. package/src/Player/PlayerModal/context/index.ts +6 -0
  18. package/src/Player/PlayerModal/hooks/index.ts +201 -0
  19. package/src/Player/PlayerModal/index.ts +3 -0
  20. package/src/Player/PlayerModal/index.web.ts +5 -0
  21. package/src/Player/PlayerModal/styles.ts +104 -0
  22. package/src/Player/PlayerModal/utils/index.ts +29 -0
  23. package/src/Player/PlayerView/GenericTVPlayerView.tsx +42 -0
  24. package/src/Player/PlayerView/TVPlayerView.tsx +26 -0
  25. package/src/Player/PlayerView/TvOSPlayerView.tsx +51 -0
  26. package/src/Player/PlayerView/types.ts +41 -0
  27. package/src/Player/index.tsx +574 -380
  28. package/src/QuickBrickPlayer.tsx +16 -10
  29. package/src/utils/dimensions.ts +29 -0
  30. package/src/utils/index.ts +12 -0
  31. package/src/utils/logger.ts +6 -0
  32. package/src/utils/playerHelpers.ts +11 -0
  33. package/src/utils/playerStyles.ts +50 -0
  34. /package/src/Player/AudioLayer/Layout/{Debug → DebugComponent}/index.tsx +0 -0
@@ -1,12 +1,12 @@
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
6
  import * as R from "ramda";
6
7
 
7
- import { flatten } from "@applicaster/zapp-react-native-utils/utils";
8
+ import { findNodeHandle, StyleSheet, View, ViewStyle } from "react-native";
8
9
 
9
- import { findNodeHandle, StyleSheet, View } from "react-native";
10
10
  import TransportControlsMobile from "@applicaster/quick-brick-mobile-transport-controls";
11
11
  import TransportControlsTV from "@applicaster/quick-brick-tv-transport-controls";
12
12
 
@@ -19,33 +19,41 @@ import {
19
19
  isWeb,
20
20
  } from "@applicaster/zapp-react-native-utils/reactUtils";
21
21
 
22
- import { PlayerWrapper } from "@applicaster/zapp-react-native-ui-components/Components/VideoModal";
22
+ import {
23
+ PlayerDetails,
24
+ PlayerWrapper,
25
+ } from "@applicaster/zapp-react-native-ui-components/Components/VideoModal";
23
26
 
24
27
  import { PlayNextOverlay } from "./PlayNextOverlay";
25
28
  import { PlayerImageBackground } from "@applicaster/zapp-react-native-ui-components/Components/PlayerImageBackground";
26
29
 
27
30
  import { GENERAL_EVENT } from "@applicaster/zapp-react-native-utils/analyticsUtils/events";
28
- import { Categories, createLogger, Subsystem } from "../logger";
29
31
  import { parseLanguageTracks } from "@applicaster/zapp-react-native-utils/playerUtils/configurationUtils";
30
32
  import { DEFAULT_COLORS, SEEK_TIME } from "../utils/const";
33
+ import { logger, getScreenAspectRatio } from "../utils";
31
34
  import { AudioPlayerWrapper } from "./AudioLayer/AudioPlayerWrapper";
32
35
  import { playerManager } from "@applicaster/zapp-react-native-utils/appUtils";
33
36
  import { getAllAccessibilityProps } from "@applicaster/zapp-react-native-utils/configurationUtils";
34
37
  import { PlayNextData } from "@applicaster/zapp-react-native-ui-components/Components/PlayerContainer/PlayerContainer";
35
38
  import { getTargetScreenDataFromEntry } from "@applicaster/zapp-react-native-utils/reactHooks/screen/useTargetScreenData";
36
- import {
37
- AnimationComponent,
38
- ComponentAnimationType,
39
- } from "@applicaster/zapp-react-native-ui-components/Components/VideoModal/ModalAnimation";
40
39
  import { PlayerAnalyticsManager } from "@applicaster/zapp-react-native-utils/analyticsUtils/PlayerAnalyticsManager";
41
40
  import { PlayerAnalyticsTrackerI } from "@applicaster/zapp-react-native-utils/analyticsUtils/playerAnalyticsTracker";
42
41
 
43
42
  import { RNPlayerManager } from "@applicaster/zapp-react-native-utils/appUtils/playerManager/nativePlayerManager";
44
43
  import {
44
+ getTabletWidth,
45
45
  isAudioItem as isAudioItemUtil,
46
46
  isInlineTV as isInlineTVUtil,
47
47
  } from "@applicaster/zapp-react-native-utils/playerUtils";
48
48
 
49
+ import { TVPlayerView } from "./PlayerView/TVPlayerView";
50
+ import PlayerModal, { VideoPlayerModal } from "./PlayerModal";
51
+ import { DockedControls } from "./AudioLayer/Layout/DockedControls";
52
+ import { FlexImage } from "./AudioLayer/Layout/PlayerImage/FlexImage";
53
+ import { PlayerError } from "@applicaster/zapp-react-native-utils/appUtils/playerManager/conts";
54
+
55
+ const isTvOS = isTvOSPlatform();
56
+
49
57
  const styles = StyleSheet.create({
50
58
  container: { flex: 1 },
51
59
  playerContainerBlack: { backgroundColor: DEFAULT_COLORS.black },
@@ -55,22 +63,47 @@ const styles = StyleSheet.create({
55
63
  position: "absolute",
56
64
  zIndex: 1,
57
65
  },
58
- audioPlayerSize: { width: "100%", height: "100%" },
59
66
  playerSize: { width: "100%", height: "100%" },
67
+ flexImage: { height: "100%", aspectRatio: 1, flex: -1 },
60
68
  });
61
69
 
62
- const logger = createLogger({
63
- category: Categories.PLAYER,
64
- subsystem: Subsystem,
65
- });
66
-
67
- const isTvOS = isTvOSPlatform();
68
-
69
- const AUDIO_PLAYER_NATIVE_ID = "audio-player-view";
70
+ type VideoPlayerProps = QuickBrickPlayer.PlayerProps & {
71
+ isCasting?: boolean;
72
+ playNextData?: PlayNextData;
73
+ castAction?: {
74
+ state?: {
75
+ connected?: boolean;
76
+ friendlyStatus?: string;
77
+ };
78
+ };
79
+ openPiP?: () => void;
80
+ closePiP?: () => void;
81
+ playerEvent?: (eventName: string, eventData?: any) => void;
82
+ mode?: string;
83
+ updatePlayerState?: (state: string) => void;
84
+ setNextVideoPreloadThresholdPercentage?: (percentage: number) => void;
85
+ hideTransportControls?: boolean;
86
+ startComponentsAnimation?: boolean;
87
+ playableItem?: any;
88
+ playerPluginIdentifier?: string;
89
+ controller?: {
90
+ supportNativeCast?: () => boolean;
91
+ supportsNativeControls?: () => boolean;
92
+ };
93
+ children?: React.ReactNode;
94
+ };
70
95
 
71
96
  export default class VideoPlayer extends React.Component<
72
- QuickBrickPlayer.PlayerProps,
73
- QuickBrickPlayer.NativePlayerState
97
+ VideoPlayerProps,
98
+ QuickBrickPlayer.NativePlayerState & {
99
+ isCastConnected?: boolean;
100
+ audioTrackId: string | null;
101
+ textTrackId: string | null;
102
+ keyEvent?: any;
103
+ isLive?: boolean;
104
+ seekableDuration?: number;
105
+ displayOverlay?: boolean;
106
+ }
74
107
  > {
75
108
  public static propTypes = {};
76
109
  _root: any;
@@ -81,7 +114,26 @@ export default class VideoPlayer extends React.Component<
81
114
  private onVideoLoadCalled: boolean;
82
115
  playNextData: PlayNextData;
83
116
 
84
- state: QuickBrickPlayer.NativePlayerState = {
117
+ state: QuickBrickPlayer.NativePlayerState & {
118
+ isCastConnected?: boolean;
119
+ audioTrackId: string | null;
120
+ textTrackId: string | null;
121
+ keyEvent?: any;
122
+ isLive?: boolean;
123
+ seekableDuration?: number;
124
+ displayOverlay?: boolean;
125
+ } = {
126
+ // Base required properties from NativePlayerState
127
+ buffering: false,
128
+ docked: false,
129
+ fullscreen: false,
130
+ inline: false,
131
+ isModal: false,
132
+ isTabletPortrait: false,
133
+ muted: false,
134
+ showPoster: false,
135
+
136
+ // Extended properties
85
137
  audioTrackId: null,
86
138
  textTrackId: null,
87
139
  isCasting: this.props.isCasting,
@@ -127,6 +179,8 @@ export default class VideoPlayer extends React.Component<
127
179
  : { flexBasis: "auto", flexGrow: 1, flexShrink: 1 };
128
180
 
129
181
  this.playerPluginId = this.props?.playerPluginIdentifier;
182
+
183
+ this.renderPlayerContent = this.renderPlayerContent.bind(this);
130
184
  }
131
185
 
132
186
  componentWillUnmount() {
@@ -404,9 +458,12 @@ export default class VideoPlayer extends React.Component<
404
458
 
405
459
  // TODO: Should be unified on both platform
406
460
  onVideoError = ({ nativeEvent }) => {
407
- const { message } = nativeEvent;
461
+ const description = nativeEvent.message;
462
+ const error = nativeEvent.error || nativeEvent.exception || "Unknown error"; // Android only fallback
463
+
464
+ const newError = new PlayerError(error, description);
408
465
 
409
- return this.props?.listener?.onError(new Error(message));
466
+ return this.props?.listener?.onError(newError);
410
467
  };
411
468
 
412
469
  onPlayerPause = ({ nativeEvent = null }) => {
@@ -688,214 +745,223 @@ export default class VideoPlayer extends React.Component<
688
745
  }
689
746
  };
690
747
 
691
- childrenWithExtensions = () => {
692
- const { docked, entry, children } = this.props;
693
-
694
- if (!isTvOS) {
695
- return children;
696
- }
697
-
698
- const output = [];
699
-
700
- // audio-player
701
- if (isAudioItemUtil(entry)) {
702
- const { nativePlayerIsReady } = this.state;
703
-
704
- // we have to wait ready status of native player view, before showing audio_player
705
- if (nativePlayerIsReady) {
706
- output.push(
707
- <View nativeID={AUDIO_PLAYER_NATIVE_ID} key={AUDIO_PLAYER_NATIVE_ID}>
708
- {children}
709
- </View>
710
- );
711
- }
712
- } else {
713
- output.push(children);
714
- }
715
- // audio-player
716
-
717
- // play-next
718
- if (this.getPlayNextData()) {
719
- output.push(
720
- <PlayNextOverlay
721
- key="play-next-overlay"
722
- item={entry}
723
- playerIsDocked={docked}
724
- pluginConfiguration={this.getPluginConfiguration()}
725
- playerId={this.props.playerId}
726
- playNextData={this.getPlayNextData()}
727
- setNextVideoPreloadThresholdPercentage={
728
- this.props.setNextVideoPreloadThresholdPercentage
729
- }
730
- />
731
- );
732
- }
733
- // play-next
734
-
735
- return flatten(output); // prevent possible case with nested array
736
- };
737
-
738
748
  get supportNativeCast() {
739
749
  return this.props.controller.supportNativeCast();
740
750
  }
741
751
 
742
- render() {
743
- const {
744
- docked,
745
- entry,
746
- fullscreen,
747
- hideTransportControls,
748
- inline,
749
- isModal,
750
- isTabletPortrait,
751
- muted,
752
- style,
753
- castAction,
754
- startComponentsAnimation,
755
- } = this.props;
756
-
757
- const { audioTrackId, keyEvent, paused, seek, textTrackId, rate } =
758
- this.state;
752
+ renderPlayerContent(playerDimensions: ViewStyle) {
753
+ const { entry, docked } = this.props;
754
+ const { isAd, piped } = this.state;
759
755
 
756
+ const pluginConfiguration = this.getPluginConfiguration();
760
757
  const isAudioItem = isAudioItemUtil(entry);
761
758
 
762
- const getStyle = (dimensions) => {
763
- const generalStyles = isAudioItem ? styles.audio : styles.video;
759
+ const backgroundImageKey = pluginConfiguration?.player_preview_image_key;
764
760
 
765
- if (isAndroidPlatform() && isAudioItem) {
766
- return generalStyles;
767
- }
761
+ const shouldRenderBackground =
762
+ !R.isNil(backgroundImageKey) &&
763
+ backgroundImageKey.length > 0 &&
764
+ !this.onVideoLoadCalled &&
765
+ !isAd;
768
766
 
769
- return { ...generalStyles, ...dimensions };
770
- };
767
+ const shouldRenderNativePlayer =
768
+ !this.props.isCasting || this.supportNativeCast;
771
769
 
772
- const getBackgroundImageStyle = (dimensions): any =>
773
- Object.assign(
774
- {},
775
- isAudioItem ? styles.audio : { position: "absolute" },
776
- dimensions
777
- );
770
+ const playerContainerStyles = this.getPlayerContainerStyles(isAudioItem);
778
771
 
779
- const nativeResizeMode = "ScaleAspectFit";
772
+ const backgroundImageStyle = this.getBackgroundImageStyle(
773
+ playerDimensions,
774
+ isAudioItem
775
+ );
780
776
 
781
- const nowPlayingEnabled =
782
- this.props?.pluginConfiguration?.nowPlayingEnabled;
777
+ const playerViewStyle = this.getPlayerViewStyle(
778
+ playerDimensions,
779
+ isAudioItem
780
+ );
783
781
 
784
- const enableAutoAudioTrackSelection =
785
- this.props.pluginConfiguration?.enable_auto_audio_track_selection;
782
+ const nativeEvents = this.getNativeEvents();
783
+ const nativeProps = this.getNativeProps();
786
784
 
787
- const minimumAllowedSeekableDurationInSeconds =
788
- this.props?.pluginConfiguration?.minimumAllowedSeekableDurationInSeconds;
789
- // In case no seeking duration or it less than 10 min we do not want to enable live seeking feature
785
+ const renderedNativePlayer = this.renderNativePlayer(
786
+ shouldRenderNativePlayer,
787
+ playerViewStyle,
788
+ nativeEvents,
789
+ nativeProps,
790
+ pluginConfiguration
791
+ );
790
792
 
791
- const liveSeekingEnabled =
792
- (this.props.pluginConfiguration?.liveSeekingEnabled &&
793
- this.state?.seekableDuration >
794
- minimumAllowedSeekableDurationInSeconds) ||
795
- false;
793
+ const renderedBackgroundImage = this.renderBackgroundImage(
794
+ shouldRenderBackground,
795
+ backgroundImageKey,
796
+ entry,
797
+ backgroundImageStyle
798
+ );
796
799
 
797
- // keep undefined if not present, since not all native players have the property
798
- const liveCatchUpEnabled =
799
- this.props.pluginConfiguration?.liveCatchUpEnabled;
800
+ const renderedAudioPlayer = this.renderAudioPlayer(
801
+ isAudioItem,
802
+ entry,
803
+ pluginConfiguration
804
+ );
800
805
 
801
- // TODO: Get screen data more reliable way useCurrentScreenData
802
- const screenData = getTargetScreenDataFromEntry(this.props.playableItem);
806
+ const renderedTransportControls =
807
+ this.renderTransportControls(playerDimensions);
803
808
 
804
- const isInlineTV = isInlineTVUtil(screenData);
809
+ const renderedPlayNextOverlay = this.renderPlayNextOverlay(
810
+ piped,
811
+ entry,
812
+ docked,
813
+ pluginConfiguration
814
+ );
805
815
 
806
- let platformSpecificProps: any = isApplePlatform()
807
- ? {
808
- resizeMode: nativeResizeMode,
809
- }
810
- : {};
816
+ return (
817
+ <View style={styles.container}>
818
+ <View style={[playerContainerStyles, playerDimensions]}>
819
+ {renderedAudioPlayer}
820
+ {renderedBackgroundImage}
821
+ {renderedNativePlayer}
822
+ </View>
811
823
 
812
- const supportsNativeControls =
813
- !!this.props.controller?.supportsNativeControls?.();
824
+ {renderedTransportControls}
825
+ {renderedPlayNextOverlay}
826
+ </View>
827
+ );
828
+ }
814
829
 
815
- const controls = isTvOS
816
- ? (supportsNativeControls && !isInlineTV) || !!this.getPlayNextData()
817
- : supportsNativeControls && !this.getPlayNextData();
830
+ /* Returns player container style based on platform and content type (audio/video) */
831
+ private getPlayerContainerStyles = (isAudioItem: boolean) =>
832
+ isApplePlatform()
833
+ ? styles.playerContainerBlack
834
+ : isTV() && isAudioItem
835
+ ? styles.playerContainerTransparent
836
+ : styles.playerContainerBlack;
818
837
 
819
- const shouldUseNativeControls =
820
- controls &&
821
- !startComponentsAnimation &&
822
- !docked &&
823
- !castAction?.state?.connected;
838
+ private getBackgroundImageStyle = (
839
+ dimensions: any,
840
+ isAudioItem: boolean
841
+ ): ViewStyle => ({
842
+ ...(isAudioItem ? styles.audio : { position: "absolute" }),
843
+ ...dimensions,
844
+ });
845
+
846
+ /* Returns player view style based on platform and content type (audio/video) */
847
+ private getPlayerViewStyle = (
848
+ dimensions: ViewStyle,
849
+ isAudioItem: boolean
850
+ ): ViewStyle => {
851
+ const generalStyles = isAudioItem ? styles.audio : {};
852
+
853
+ if (isAndroidPlatform() && isAudioItem) {
854
+ return generalStyles;
855
+ }
824
856
 
825
- platformSpecificProps = {
826
- ...platformSpecificProps,
827
- controls: shouldUseNativeControls,
828
- };
857
+ return { ...generalStyles, ...dimensions };
858
+ };
829
859
 
830
- const isFullScreenAudioPlayer =
831
- this.getPluginConfiguration()?.["full_screen_audio_player"] || false;
860
+ private renderAudioPlayer = (
861
+ isAudioItem: boolean,
862
+ entry: ZappEntry,
863
+ pluginConfiguration: Record<string, any>
864
+ ) => {
865
+ if (!isAudioItem) return null;
866
+
867
+ return (
868
+ <AudioPlayer
869
+ style={styles.playerSize}
870
+ audio_item={entry}
871
+ plugin_configuration={pluginConfiguration}
872
+ />
873
+ );
874
+ };
832
875
 
833
- const isAudioPlayer = isFullScreenAudioPlayer && isAudioItem;
876
+ private renderBackgroundImage = (
877
+ shouldRender: boolean,
878
+ imageKey: string,
879
+ entry: ZappEntry,
880
+ style: ViewStyle
881
+ ) => {
882
+ if (!shouldRender) return null;
834
883
 
835
- const pictureInPictureEnabled =
836
- this.props.pluginConfiguration?.pictureInPictureEnabled && !isAudioPlayer;
884
+ return (
885
+ <PlayerImageBackground imageKey={imageKey} entry={entry} style={style} />
886
+ );
887
+ };
837
888
 
838
- const nativeProps = {
839
- entry, // Must be passed first in list
840
- ...platformSpecificProps,
841
- audioTrackId,
842
- docked,
843
- enableAutoAudioTrackSelection,
844
- fullscreen, // TODO: Check fullscreen params not crash
845
- inline,
846
- muted,
847
- nowPlayingEnabled,
848
- paused,
849
- seek,
850
- textTrackId,
851
- liveSeekingEnabled,
852
- liveCatchUpEnabled,
853
- playerId: this.props.playerId,
854
- pictureInPictureEnabled,
855
- seekStep:
856
- (this.props.pluginConfiguration?.seek_duration || SEEK_TIME) * 1000, // Only for Android
857
- accessibilityProps: getAllAccessibilityProps(
858
- this.getPluginConfiguration()
859
- ),
860
- };
889
+ private renderNativePlayer = (
890
+ shouldRender: boolean,
891
+ style: ViewStyle,
892
+ nativeEvents: ReturnType<typeof this.getNativeEvents>,
893
+ nativeProps: ReturnType<typeof this.getNativeProps>,
894
+ pluginConfiguration: Record<string, any>
895
+ ) => {
896
+ if (!shouldRender) return null;
897
+ const PlayerComponent = this.props.PlayerComponent;
898
+
899
+ return (
900
+ <View style={style}>
901
+ <PlayerComponent
902
+ ref={this._assignRoot}
903
+ {...nativeEvents}
904
+ {...nativeProps}
905
+ listener={this.props.listener}
906
+ style={styles.playerSize}
907
+ pluginConfiguration={pluginConfiguration}
908
+ controller={this.props.controller}
909
+ >
910
+ {this.props.children}
911
+ </PlayerComponent>
912
+ </View>
913
+ );
914
+ };
861
915
 
862
- if (!isTvOS) {
863
- // TODO: Move to RN method and remove from props.
864
- // Causes tvOS bug: native player passes rate on fast seek,
865
- // RN returns it and it breaks playback.
866
- nativeProps.rate = rate;
867
- }
916
+ private renderPlayNextOverlay = (
917
+ piped: boolean,
918
+ entry: ZappEntry,
919
+ docked: boolean,
920
+ pluginConfiguration: Record<string, any>
921
+ ) => {
922
+ if (piped) return null;
923
+
924
+ return (
925
+ <PlayNextOverlay
926
+ item={entry}
927
+ playerIsDocked={docked}
928
+ pluginConfiguration={pluginConfiguration}
929
+ playerId={this.props.playerId}
930
+ playNextData={this.getPlayNextData()}
931
+ setNextVideoPreloadThresholdPercentage={
932
+ this.props.setNextVideoPreloadThresholdPercentage
933
+ }
934
+ />
935
+ );
936
+ };
868
937
 
869
- if (
870
- this.isReactNativeMethodsSupported() &&
871
- RNPlayerManager?.[this.playerPluginId]?.play
872
- ) {
873
- delete nativeProps["paused"];
874
- }
938
+ // Extract native events to a separate method for reusability
939
+ getNativeEvents = () => {
940
+ const { keyEvent } = this.state;
875
941
 
876
942
  const adEvents = {
877
- onAdBegin: this.onAdBegin, // ad_start
878
- onAdBreakBegin: this.onAdBreakBegin, // ad_break_start
879
- onAdBreakEnd: this.onAdBreakEnd, // ad_break_completed
880
- onAdEnd: this.onAdEnd, // ad_complete
881
- onAdError: this.onAdError, // ad_error
882
- onAdRequest: this.onAdRequest, // ad_request
883
- onAdClicked: this.onAdClicked, // ad_clicked
884
- onAdTapped: this.onAdTapped, // ad_tapped
943
+ onAdBegin: this.onAdBegin,
944
+ onAdBreakBegin: this.onAdBreakBegin,
945
+ onAdBreakEnd: this.onAdBreakEnd,
946
+ onAdEnd: this.onAdEnd,
947
+ onAdError: this.onAdError,
948
+ onAdRequest: this.onAdRequest,
949
+ onAdClicked: this.onAdClicked,
950
+ onAdTapped: this.onAdTapped,
885
951
  };
886
952
 
887
953
  const playbackEvents = {
888
954
  onPlaybackRateChange: this.onPlaybackRateChange,
889
- onPlayerPause: this.onPlayerPause, // pause
890
- onPlayerResume: this.onPlayerResume, // play
891
- onPlayerSeekComplete: this.onPlayerSeekComplete, // seek_complete
892
- onPlayerSeekStart: this.onPlayerSeekStart, // seek_start
955
+ onPlayerPause: this.onPlayerPause,
956
+ onPlayerResume: this.onPlayerResume,
957
+ onPlayerSeekComplete: this.onPlayerSeekComplete,
958
+ onPlayerSeekStart: this.onPlayerSeekStart,
893
959
  };
894
960
 
895
961
  const lifecycleEvents = {
896
962
  onReadyForDisplay: this.unHandledEvent("onReadyForDisplay"),
897
- onVideoEnd: this.onVideoEnd, // complete
898
- onVideoError: this.onVideoError, // error
963
+ onVideoEnd: this.onVideoEnd,
964
+ onVideoError: this.onVideoError,
899
965
  onVideoProgress: this.onVideoProgress,
900
966
  onVideoFullscreenPlayerDidDismiss: this.onVideoFullscreenPlayerDidDismiss,
901
967
  onVideoFullscreenPlayerDidPresent: this.onVideoFullscreenPlayerDidPresent,
@@ -939,11 +1005,11 @@ export default class VideoPlayer extends React.Component<
939
1005
  };
940
1006
 
941
1007
  const unsupportedEvents = {
942
- onBufferComplete: this.onBufferComplete, // buffer_complete
943
- onBufferStart: this.onBufferStart, // buffer_start
1008
+ onBufferComplete: this.onBufferComplete,
1009
+ onBufferStart: this.onBufferStart,
944
1010
  };
945
1011
 
946
- const nativeEvents = {
1012
+ return {
947
1013
  ...adEvents,
948
1014
  ...androidPlayerEvents,
949
1015
  ...lifecycleEvents,
@@ -952,60 +1018,204 @@ export default class VideoPlayer extends React.Component<
952
1018
  ...unsupportedEvents,
953
1019
  ...playbackEvents,
954
1020
  ...androidLifecyclePiPEvents,
1021
+ } as const;
1022
+ };
1023
+
1024
+ // Extract native props to a separate method for reusability
1025
+ getNativeProps = () => {
1026
+ const { entry, docked, fullscreen, inline, muted, castAction } = this.props;
1027
+ const { audioTrackId, textTrackId, paused, seek, rate } = this.state;
1028
+
1029
+ const nativeResizeMode = "ScaleAspectFit";
1030
+
1031
+ const isFullScreenAudioPlayer =
1032
+ this.getPluginConfiguration()?.full_screen_audio_player || false;
1033
+
1034
+ const isAudioItem = isAudioItemUtil(entry);
1035
+ const isAudioPlayer = isFullScreenAudioPlayer && isAudioItem;
1036
+
1037
+ let platformSpecificProps: Record<string, any> = isApplePlatform()
1038
+ ? { resizeMode: nativeResizeMode }
1039
+ : {};
1040
+
1041
+ const supportsNativeControls =
1042
+ !!this.props.controller?.supportsNativeControls?.();
1043
+
1044
+ const controls = isTvOS
1045
+ ? (supportsNativeControls && !this.isInlineTV()) ||
1046
+ !!this.getPlayNextData()
1047
+ : supportsNativeControls && !this.getPlayNextData();
1048
+
1049
+ const shouldUseNativeControls =
1050
+ controls &&
1051
+ !this.props.startComponentsAnimation &&
1052
+ !docked &&
1053
+ !castAction?.state?.connected;
1054
+
1055
+ platformSpecificProps = {
1056
+ ...platformSpecificProps,
1057
+ controls: shouldUseNativeControls,
955
1058
  };
956
1059
 
1060
+ const pictureInPictureEnabled =
1061
+ this.props.pluginConfiguration?.pictureInPictureEnabled && !isAudioPlayer;
1062
+
1063
+ const nowPlayingEnabled =
1064
+ this.props?.pluginConfiguration?.nowPlayingEnabled;
1065
+
1066
+ const enableAutoAudioTrackSelection =
1067
+ this.props.pluginConfiguration?.enable_auto_audio_track_selection;
1068
+
1069
+ const minimumAllowedSeekableDurationInSeconds =
1070
+ this.props?.pluginConfiguration?.minimumAllowedSeekableDurationInSeconds;
1071
+
1072
+ const liveSeekingEnabled =
1073
+ (this.props.pluginConfiguration?.liveSeekingEnabled &&
1074
+ this.state?.seekableDuration >
1075
+ minimumAllowedSeekableDurationInSeconds) ||
1076
+ false;
1077
+
1078
+ const liveCatchUpEnabled =
1079
+ this.props.pluginConfiguration?.liveCatchUpEnabled;
1080
+
1081
+ const nativeProps = {
1082
+ rate: undefined,
1083
+ entry,
1084
+ ...platformSpecificProps,
1085
+ audioTrackId,
1086
+ docked,
1087
+ enableAutoAudioTrackSelection,
1088
+ fullscreen,
1089
+ inline,
1090
+ muted,
1091
+ nowPlayingEnabled,
1092
+ paused,
1093
+ seek,
1094
+ textTrackId,
1095
+ liveSeekingEnabled,
1096
+ liveCatchUpEnabled,
1097
+ playerId: this.props.playerId,
1098
+ pictureInPictureEnabled,
1099
+ seekStep:
1100
+ (this.props.pluginConfiguration?.seek_duration || SEEK_TIME) * 1000,
1101
+ accessibilityProps: getAllAccessibilityProps(
1102
+ this.getPluginConfiguration()
1103
+ ),
1104
+ };
1105
+
1106
+ if (!isTvOS) {
1107
+ nativeProps.rate = rate;
1108
+ }
1109
+
1110
+ if (
1111
+ this.isReactNativeMethodsSupported() &&
1112
+ RNPlayerManager?.[this.playerPluginId]?.play
1113
+ ) {
1114
+ delete nativeProps.paused;
1115
+ }
1116
+
1117
+ return nativeProps;
1118
+ };
1119
+
1120
+ // Extract transport controls rendering to a separate method
1121
+ renderTransportControls = (playerDimensions) => {
1122
+ const {
1123
+ hideTransportControls,
1124
+ castAction,
1125
+ docked,
1126
+ fullscreen,
1127
+ isModal,
1128
+ entry,
1129
+ } = this.props;
1130
+
957
1131
  const needToShowPlayNextOverlay =
958
1132
  isTV() && this.getPlayNextData() && !docked;
959
1133
 
1134
+ const needsToRender =
1135
+ !hideTransportControls &&
1136
+ !needToShowPlayNextOverlay &&
1137
+ this.props.mode !== "PIP";
1138
+
1139
+ const supportsNativeControls =
1140
+ !!this.props.controller?.supportsNativeControls?.();
1141
+
1142
+ const controls = isTvOS
1143
+ ? (supportsNativeControls && !this.isInlineTV()) ||
1144
+ !!this.getPlayNextData()
1145
+ : supportsNativeControls && !this.getPlayNextData();
1146
+
1147
+ const shouldUseNativeControls =
1148
+ controls &&
1149
+ !this.props.startComponentsAnimation &&
1150
+ !docked &&
1151
+ !castAction?.state?.connected;
1152
+
1153
+ if (shouldUseNativeControls) {
1154
+ return null;
1155
+ }
1156
+
1157
+ const TransportControls = isTV()
1158
+ ? TransportControlsTV
1159
+ : TransportControlsMobile;
1160
+
960
1161
  const needToShowDefaultControls = !(
961
1162
  (controls && !castAction?.state?.connected) ||
962
1163
  this.getPlayNextData()
963
1164
  );
964
1165
 
965
- const playerContainerStyles = isApplePlatform()
966
- ? styles.playerContainerBlack
967
- : isTV() && isAudioItem
968
- ? styles.playerContainerTransparent
969
- : styles.playerContainerBlack;
970
-
971
1166
  const isInline = () => {
972
1167
  if (isTV()) {
973
- return inline;
1168
+ return this.props.inline;
974
1169
  }
975
1170
 
976
- // HACK: on mobile overwrite inline value if player is fullScreen
977
- return fullscreen ? false : inline;
1171
+ return fullscreen ? false : this.props.inline;
978
1172
  };
979
1173
 
980
- const renderTransportControls = (playerDimensions) => {
981
- const needsToRender =
982
- !hideTransportControls &&
983
- !needToShowPlayNextOverlay &&
984
- this.props.mode !== "PIP";
1174
+ return needsToRender ? (
1175
+ <TransportControls
1176
+ playerContent={entry}
1177
+ configuration={this.getPluginConfiguration()}
1178
+ playerDimensions={playerDimensions}
1179
+ inline={isInline()}
1180
+ docked={fullscreen ? false : docked}
1181
+ isModal={isModal}
1182
+ extraInfoText={castAction?.state?.friendlyStatus}
1183
+ controlsAlwaysVisible={castAction?.state?.connected}
1184
+ playNextData={this.getPlayNextData()}
1185
+ needToShowDefaultControls={needToShowDefaultControls}
1186
+ />
1187
+ ) : null;
1188
+ };
985
1189
 
986
- if (shouldUseNativeControls) {
987
- return null;
988
- }
1190
+ // Helper method for inline TV check
1191
+ isInlineTV = () => {
1192
+ const screenData = getTargetScreenDataFromEntry(this.props.playableItem);
989
1193
 
990
- const TransportControls = isTV()
991
- ? TransportControlsTV
992
- : TransportControlsMobile;
993
-
994
- return needsToRender ? (
995
- <TransportControls
996
- playerContent={entry}
997
- configuration={this.getPluginConfiguration()}
998
- playerDimensions={playerDimensions}
999
- inline={isInline()}
1000
- docked={fullscreen ? false : docked}
1001
- isModal={isModal}
1002
- extraInfoText={castAction?.state?.friendlyStatus}
1003
- controlsAlwaysVisible={castAction?.state?.connected}
1004
- playNextData={this.getPlayNextData()}
1005
- needToShowDefaultControls={needToShowDefaultControls}
1006
- />
1007
- ) : null;
1008
- };
1194
+ return isInlineTVUtil(screenData);
1195
+ };
1196
+
1197
+ renderMobile = () => {
1198
+ const {
1199
+ docked,
1200
+ entry,
1201
+ fullscreen,
1202
+ inline,
1203
+ isModal,
1204
+ isTabletPortrait,
1205
+ style,
1206
+ isCasting,
1207
+ PlayerComponent,
1208
+ listener,
1209
+ } = this.props;
1210
+
1211
+ const { piped } = this.state;
1212
+
1213
+ const pluginConfiguration = this.getPluginConfiguration();
1214
+
1215
+ const isAudioItem = isAudioItemUtil(entry);
1216
+
1217
+ const isFullScreenAudioPlayer =
1218
+ pluginConfiguration?.full_screen_audio_player || false;
1009
1219
 
1010
1220
  const sharedWrapperViewProps = {
1011
1221
  style,
@@ -1013,171 +1223,155 @@ export default class VideoPlayer extends React.Component<
1013
1223
  inline,
1014
1224
  docked,
1015
1225
  isModal,
1016
- pip: this.state.piped,
1226
+ pip: piped,
1017
1227
  layoutState: {
1018
1228
  inline,
1019
1229
  docked,
1020
1230
  isModal,
1021
- pip: this.state.piped,
1231
+ pip: piped,
1022
1232
  },
1023
- configuration: this.getPluginConfiguration(),
1024
- fullscreen: fullscreen,
1025
- isTabletPortrait: isTabletPortrait,
1233
+ configuration: pluginConfiguration,
1234
+ isTabletPortrait,
1026
1235
  playNextData: this.getPlayNextData(),
1027
1236
  };
1028
1237
 
1029
- const containerStyle = { flex: 1 };
1238
+ const shouldRenderNativePlayer = !isCasting || this.supportNativeCast;
1030
1239
 
1031
- const backgroundImageKey =
1032
- this.getPluginConfiguration()?.player_preview_image_key;
1240
+ const aspectRatio =
1241
+ !inline && isModal && !piped ? getScreenAspectRatio() : 16 / 9;
1033
1242
 
1034
- const shouldRenderNativePlayer =
1035
- !this.props.isCasting || this.supportNativeCast;
1036
-
1037
- const renderMobilePlayer = () => {
1038
- return isFullScreenAudioPlayer && isAudioItem ? (
1039
- // Ensure the View remains in the native hierarchy for proper rendering and interaction
1040
- <View collapsable={false} style={containerStyle}>
1041
- {shouldRenderNativePlayer ? (
1042
- <this.props.PlayerComponent
1043
- ref={this._assignRoot}
1044
- {...nativeEvents}
1045
- {...nativeProps}
1046
- listener={this.props.listener}
1047
- />
1048
- ) : null}
1049
- <AudioPlayerWrapper {...sharedWrapperViewProps} />
1050
- </View>
1051
- ) : (
1052
- <PlayerWrapper {...sharedWrapperViewProps}>
1053
- {(playerDimensions) => {
1054
- const shouldRenderBackground =
1055
- !R.isNil(backgroundImageKey) &&
1056
- backgroundImageKey.length > 0 &&
1057
- !this.onVideoLoadCalled &&
1058
- !this.state.isAd;
1059
-
1060
- return (
1061
- <View style={styles.container}>
1062
- <View style={[playerContainerStyles, playerDimensions]}>
1063
- {isAudioItem ? (
1064
- <AnimationComponent
1065
- animationType={ComponentAnimationType.player}
1066
- additionalData={{
1067
- ...playerDimensions,
1068
- inlineAudioPlayer: true,
1069
- }}
1070
- style={getStyle(playerDimensions)}
1071
- >
1072
- <AudioPlayer
1073
- style={
1074
- isTV()
1075
- ? getStyle(playerDimensions)
1076
- : styles.playerSize
1077
- }
1078
- audio_item={entry}
1079
- plugin_configuration={this.getPluginConfiguration()}
1080
- />
1081
- </AnimationComponent>
1082
- ) : null}
1083
-
1084
- {shouldRenderBackground ? (
1085
- <PlayerImageBackground
1086
- imageKey={backgroundImageKey}
1087
- configuration={this.getPluginConfiguration()}
1088
- entry={entry}
1089
- style={getBackgroundImageStyle(playerDimensions)}
1090
- />
1091
- ) : null}
1092
-
1093
- {shouldRenderNativePlayer ? (
1094
- <AnimationComponent
1095
- animationType={ComponentAnimationType.player}
1096
- additionalData={{
1097
- ...playerDimensions,
1098
- disableAnimatedComponent: isAudioItem,
1099
- }}
1100
- style={getStyle(playerDimensions)}
1101
- >
1102
- <this.props.PlayerComponent
1103
- ref={this._assignRoot}
1104
- {...nativeEvents}
1105
- {...nativeProps}
1106
- listener={this.props.listener}
1107
- style={
1108
- isTV()
1109
- ? getStyle(playerDimensions)
1110
- : styles.playerSize
1111
- }
1112
- pluginConfiguration={this.getPluginConfiguration()}
1113
- controller={this.props.controller}
1114
- >
1115
- {this.props.children}
1116
- </this.props.PlayerComponent>
1117
- </AnimationComponent>
1118
- ) : null}
1119
- </View>
1120
-
1121
- {renderTransportControls(playerDimensions)}
1122
- {!this.state.piped && (
1123
- <PlayNextOverlay
1124
- item={entry}
1125
- playerIsDocked={docked}
1126
- pluginConfiguration={this.getPluginConfiguration()}
1127
- playerId={this.props.playerId}
1128
- playNextData={this.getPlayNextData()}
1129
- setNextVideoPreloadThresholdPercentage={
1130
- this.props.setNextVideoPreloadThresholdPercentage
1131
- }
1132
- />
1133
- )}
1134
- </View>
1135
- );
1136
- }}
1137
- </PlayerWrapper>
1138
- );
1243
+ const audioPlayerProps = {
1244
+ ref: this._assignRoot,
1245
+ ...this.getNativeEvents(),
1246
+ ...this.getNativeProps(),
1139
1247
  };
1140
1248
 
1141
- const renderTVPlayer = () => {
1142
- return (
1143
- <View style={this.takeParentStyle} testID={"player-view"}>
1144
- {isAudioItem && (isInlineTV || !isTvOS) ? (
1145
- <AudioPlayer
1146
- style={getStyle(styles.audioPlayerSize)}
1147
- audio_item={entry}
1148
- plugin_configuration={this.getPluginConfiguration()}
1149
- />
1150
- ) : null}
1151
-
1152
- <this.props.PlayerComponent
1153
- ref={this._assignRoot}
1154
- {...nativeEvents}
1155
- {...nativeProps}
1156
- listener={this.props.listener}
1157
- style={this.takeParentStyle}
1158
- pluginConfiguration={this.getPluginConfiguration()}
1159
- controller={this.props.controller}
1160
- >
1161
- {!isInlineTV && this.childrenWithExtensions()}
1162
- </this.props.PlayerComponent>
1163
-
1164
- {renderTransportControls(this.takeParentStyle)}
1165
- {!isTvOS && (
1166
- <PlayNextOverlay
1167
- item={entry}
1168
- playerIsDocked={docked}
1169
- pluginConfiguration={this.getPluginConfiguration()}
1170
- playerId={this.props.playerId}
1171
- playNextData={this.getPlayNextData()}
1172
- setNextVideoPreloadThresholdPercentage={
1173
- this.props.setNextVideoPreloadThresholdPercentage
1249
+ return isFullScreenAudioPlayer && isAudioItem ? (
1250
+ // Ensure the View remains in the native hierarchy for proper rendering and interaction
1251
+ <PlayerModal
1252
+ content={
1253
+ <View collapsable={false} style={styles.container}>
1254
+ {shouldRenderNativePlayer ? (
1255
+ <PlayerComponent {...audioPlayerProps} listener={listener} />
1256
+ ) : null}
1257
+ <AudioPlayerWrapper {...sharedWrapperViewProps} />
1258
+ </View>
1259
+ }
1260
+ collapsedContent={
1261
+ <View style={styles.container}>
1262
+ <DockedControls
1263
+ entry={entry}
1264
+ aspectRatio={1}
1265
+ layoutState={sharedWrapperViewProps.layoutState}
1266
+ playNextData={sharedWrapperViewProps.playNextData}
1267
+ ImageComponent={
1268
+ <FlexImage entry={entry} style={styles.flexImage} />
1174
1269
  }
1175
1270
  />
1176
- )}
1177
- </View>
1178
- );
1271
+ </View>
1272
+ }
1273
+ />
1274
+ ) : (
1275
+ <VideoPlayerModal
1276
+ isTabletPortrait={isTabletPortrait}
1277
+ aspectRatio={aspectRatio}
1278
+ pip={piped}
1279
+ fullscreen={fullscreen}
1280
+ modal={isModal}
1281
+ style={{
1282
+ tabletLandscapeWidth: getTabletWidth(
1283
+ this.getPluginConfiguration().tablet_landscape_sidebar_width,
1284
+ style
1285
+ ),
1286
+ width: style.width,
1287
+ height: style.height,
1288
+ }}
1289
+ content={
1290
+ <PlayerWrapper
1291
+ {...sharedWrapperViewProps}
1292
+ playerContent={this.renderPlayerContent}
1293
+ />
1294
+ }
1295
+ extraContent={
1296
+ <PlayerDetails
1297
+ configuration={this.getPluginConfiguration()}
1298
+ style={{ flex: 1 }}
1299
+ entry={entry}
1300
+ isTabletLandscape={!isTabletPortrait}
1301
+ inline={inline}
1302
+ docked={docked}
1303
+ isModal={isModal}
1304
+ pip={piped}
1305
+ />
1306
+ }
1307
+ collapsedContent={
1308
+ <View style={styles.container}>
1309
+ <DockedControls
1310
+ entry={entry}
1311
+ aspectRatio={aspectRatio}
1312
+ layoutState={sharedWrapperViewProps.layoutState}
1313
+ playNextData={sharedWrapperViewProps.playNextData}
1314
+ />
1315
+ </View>
1316
+ }
1317
+ />
1318
+ );
1319
+ };
1320
+
1321
+ renderTV = () => {
1322
+ const {
1323
+ docked,
1324
+ entry,
1325
+ listener,
1326
+ controller,
1327
+ PlayerComponent,
1328
+ playerId,
1329
+ setNextVideoPreloadThresholdPercentage,
1330
+ children,
1331
+ } = this.props;
1332
+
1333
+ const isAudioItem = isAudioItemUtil(entry);
1334
+
1335
+ const playerProps = {
1336
+ ref: this._assignRoot,
1337
+ ...this.getNativeEvents(),
1338
+ ...this.getNativeProps(),
1339
+ listener,
1340
+ style: this.takeParentStyle,
1341
+ controller,
1342
+ };
1343
+
1344
+ const audioPlayerProps = {
1345
+ style: { ...styles.audio, ...this.takeParentStyle },
1179
1346
  };
1180
1347
 
1181
- return isTV() ? renderTVPlayer() : renderMobilePlayer();
1348
+ return (
1349
+ <View style={this.takeParentStyle} testID={"player-view"}>
1350
+ <TVPlayerView
1351
+ type={isAudioItem ? "audio" : "video"}
1352
+ entry={entry}
1353
+ inline={this.isInlineTV()}
1354
+ transportControls={this.renderTransportControls(this.takeParentStyle)}
1355
+ PlayerComponent={PlayerComponent}
1356
+ nativePlayerIsReady={this.state.nativePlayerIsReady}
1357
+ pluginConfiguration={this.getPluginConfiguration()}
1358
+ playerProps={playerProps}
1359
+ audioPlayerProps={audioPlayerProps}
1360
+ playNextProps={{
1361
+ playerIsDocked: docked,
1362
+ playerId: playerId,
1363
+ playNextData: this.getPlayNextData(),
1364
+ setNextVideoPreloadThresholdPercentage:
1365
+ setNextVideoPreloadThresholdPercentage,
1366
+ }}
1367
+ >
1368
+ {children}
1369
+ </TVPlayerView>
1370
+ </View>
1371
+ );
1372
+ };
1373
+
1374
+ render() {
1375
+ return isTV() ? this.renderTV() : this.renderMobile();
1182
1376
  }
1183
1377
  }