@applicaster/quick-brick-player 14.0.0-rc.78 → 14.0.0-rc.79

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.
@@ -5,7 +5,7 @@
5
5
  import React from "react";
6
6
  import * as R from "ramda";
7
7
 
8
- import { findNodeHandle, StyleSheet, View, Dimensions } from "react-native";
8
+ import { findNodeHandle, StyleSheet, View, ViewStyle } from "react-native";
9
9
 
10
10
  import TransportControlsMobile from "@applicaster/quick-brick-mobile-transport-controls";
11
11
  import TransportControlsTV from "@applicaster/quick-brick-tv-transport-controls";
@@ -28,9 +28,9 @@ import { PlayNextOverlay } from "./PlayNextOverlay";
28
28
  import { PlayerImageBackground } from "@applicaster/zapp-react-native-ui-components/Components/PlayerImageBackground";
29
29
 
30
30
  import { GENERAL_EVENT } from "@applicaster/zapp-react-native-utils/analyticsUtils/events";
31
- import { Categories, createLogger, Subsystem } from "../logger";
32
31
  import { parseLanguageTracks } from "@applicaster/zapp-react-native-utils/playerUtils/configurationUtils";
33
32
  import { DEFAULT_COLORS, SEEK_TIME } from "../utils/const";
33
+ import { logger, getScreenAspectRatio } from "../utils";
34
34
  import { AudioPlayerWrapper } from "./AudioLayer/AudioPlayerWrapper";
35
35
  import { playerManager } from "@applicaster/zapp-react-native-utils/appUtils";
36
36
  import { getAllAccessibilityProps } from "@applicaster/zapp-react-native-utils/configurationUtils";
@@ -52,6 +52,8 @@ import { DockedControls } from "./AudioLayer/Layout/DockedControls";
52
52
  import { FlexImage } from "./AudioLayer/Layout/PlayerImage/FlexImage";
53
53
  import { PlayerError } from "@applicaster/zapp-react-native-utils/appUtils/playerManager/conts";
54
54
 
55
+ const isTvOS = isTvOSPlatform();
56
+
55
57
  const styles = StyleSheet.create({
56
58
  container: { flex: 1 },
57
59
  playerContainerBlack: { backgroundColor: DEFAULT_COLORS.black },
@@ -65,25 +67,43 @@ const styles = StyleSheet.create({
65
67
  flexImage: { height: "100%", aspectRatio: 1, flex: -1 },
66
68
  });
67
69
 
68
- const logger = createLogger({
69
- category: Categories.PLAYER,
70
- subsystem: Subsystem,
71
- });
72
-
73
- const isTvOS = isTvOSPlatform();
74
-
75
- const { width: SCREEN_WIDTH, height: SCREEN_HEIGHT } = Dimensions.get("screen");
76
-
77
- const getScreenAspectRatio = () => {
78
- const longEdge = Math.max(SCREEN_WIDTH, SCREEN_HEIGHT);
79
- const shortEdge = Math.min(SCREEN_WIDTH, SCREEN_HEIGHT);
80
-
81
- return longEdge / shortEdge;
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;
82
94
  };
83
95
 
84
96
  export default class VideoPlayer extends React.Component<
85
- QuickBrickPlayer.PlayerProps,
86
- 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
+ }
87
107
  > {
88
108
  public static propTypes = {};
89
109
  _root: any;
@@ -94,7 +114,26 @@ export default class VideoPlayer extends React.Component<
94
114
  private onVideoLoadCalled: boolean;
95
115
  playNextData: PlayNextData;
96
116
 
97
- 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
98
137
  audioTrackId: null,
99
138
  textTrackId: null,
100
139
  isCasting: this.props.isCasting,
@@ -140,6 +179,8 @@ export default class VideoPlayer extends React.Component<
140
179
  : { flexBasis: "auto", flexGrow: 1, flexShrink: 1 };
141
180
 
142
181
  this.playerPluginId = this.props?.playerPluginIdentifier;
182
+
183
+ this.renderPlayerContent = this.renderPlayerContent.bind(this);
143
184
  }
144
185
 
145
186
  componentWillUnmount() {
@@ -708,163 +749,219 @@ export default class VideoPlayer extends React.Component<
708
749
  return this.props.controller.supportNativeCast();
709
750
  }
710
751
 
711
- render() {
712
- const {
713
- docked,
714
- entry,
715
- fullscreen,
716
- hideTransportControls,
717
- inline,
718
- isModal,
719
- isTabletPortrait,
720
- muted,
721
- style,
722
- castAction,
723
- startComponentsAnimation,
724
- } = this.props;
725
-
726
- const { audioTrackId, keyEvent, paused, seek, textTrackId, rate } =
727
- this.state;
752
+ renderPlayerContent(playerDimensions: ViewStyle) {
753
+ const { entry, docked } = this.props;
754
+ const { isAd, piped } = this.state;
728
755
 
756
+ const pluginConfiguration = this.getPluginConfiguration();
729
757
  const isAudioItem = isAudioItemUtil(entry);
730
758
 
731
- const getStyle = (dimensions) => {
732
- const generalStyles = isAudioItem ? styles.audio : styles.video;
759
+ const backgroundImageKey = pluginConfiguration?.player_preview_image_key;
733
760
 
734
- if (isAndroidPlatform() && isAudioItem) {
735
- return generalStyles;
736
- }
761
+ const shouldRenderBackground =
762
+ !R.isNil(backgroundImageKey) &&
763
+ backgroundImageKey.length > 0 &&
764
+ !this.onVideoLoadCalled &&
765
+ !isAd;
737
766
 
738
- return { ...generalStyles, ...dimensions };
739
- };
767
+ const shouldRenderNativePlayer =
768
+ !this.props.isCasting || this.supportNativeCast;
740
769
 
741
- const getBackgroundImageStyle = (dimensions): any =>
742
- Object.assign(
743
- {},
744
- isAudioItem ? styles.audio : { position: "absolute" },
745
- dimensions
746
- );
770
+ const playerContainerStyles = this.getPlayerContainerStyles(isAudioItem);
747
771
 
748
- const nativeResizeMode = "ScaleAspectFit";
772
+ const backgroundImageStyle = this.getBackgroundImageStyle(
773
+ playerDimensions,
774
+ isAudioItem
775
+ );
749
776
 
750
- const nowPlayingEnabled =
751
- this.props?.pluginConfiguration?.nowPlayingEnabled;
777
+ const playerViewStyle = this.getPlayerViewStyle(
778
+ playerDimensions,
779
+ isAudioItem
780
+ );
752
781
 
753
- const enableAutoAudioTrackSelection =
754
- this.props.pluginConfiguration?.enable_auto_audio_track_selection;
782
+ const nativeEvents = this.getNativeEvents();
783
+ const nativeProps = this.getNativeProps();
755
784
 
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
785
+ const renderedNativePlayer = this.renderNativePlayer(
786
+ shouldRenderNativePlayer,
787
+ playerViewStyle,
788
+ nativeEvents,
789
+ nativeProps,
790
+ pluginConfiguration
791
+ );
759
792
 
760
- const liveSeekingEnabled =
761
- (this.props.pluginConfiguration?.liveSeekingEnabled &&
762
- this.state?.seekableDuration >
763
- minimumAllowedSeekableDurationInSeconds) ||
764
- false;
793
+ const renderedBackgroundImage = this.renderBackgroundImage(
794
+ shouldRenderBackground,
795
+ backgroundImageKey,
796
+ entry,
797
+ backgroundImageStyle
798
+ );
765
799
 
766
- // keep undefined if not present, since not all native players have the property
767
- const liveCatchUpEnabled =
768
- this.props.pluginConfiguration?.liveCatchUpEnabled;
800
+ const renderedAudioPlayer = this.renderAudioPlayer(
801
+ isAudioItem,
802
+ entry,
803
+ pluginConfiguration
804
+ );
769
805
 
770
- // TODO: Get screen data more reliable way useCurrentScreenData
771
- const screenData = getTargetScreenDataFromEntry(this.props.playableItem);
806
+ const renderedTransportControls =
807
+ this.renderTransportControls(playerDimensions);
772
808
 
773
- const isInlineTV = isInlineTVUtil(screenData);
809
+ const renderedPlayNextOverlay = this.renderPlayNextOverlay(
810
+ piped,
811
+ entry,
812
+ docked,
813
+ pluginConfiguration
814
+ );
774
815
 
775
- let platformSpecificProps: any = isApplePlatform()
776
- ? {
777
- resizeMode: nativeResizeMode,
778
- }
779
- : {};
816
+ return (
817
+ <View style={styles.container}>
818
+ <View style={[playerContainerStyles, playerDimensions]}>
819
+ {renderedAudioPlayer}
820
+ {renderedBackgroundImage}
821
+ {renderedNativePlayer}
822
+ </View>
780
823
 
781
- const supportsNativeControls =
782
- !!this.props.controller?.supportsNativeControls?.();
824
+ {renderedTransportControls}
825
+ {renderedPlayNextOverlay}
826
+ </View>
827
+ );
828
+ }
783
829
 
784
- const controls = isTvOS
785
- ? (supportsNativeControls && !isInlineTV) || !!this.getPlayNextData()
786
- : 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;
787
837
 
788
- const shouldUseNativeControls =
789
- controls &&
790
- !startComponentsAnimation &&
791
- !docked &&
792
- !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
+ }
793
856
 
794
- platformSpecificProps = {
795
- ...platformSpecificProps,
796
- controls: shouldUseNativeControls,
797
- };
857
+ return { ...generalStyles, ...dimensions };
858
+ };
798
859
 
799
- const isFullScreenAudioPlayer =
800
- 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
+ };
801
875
 
802
- 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;
803
883
 
804
- const pictureInPictureEnabled =
805
- this.props.pluginConfiguration?.pictureInPictureEnabled && !isAudioPlayer;
884
+ return (
885
+ <PlayerImageBackground imageKey={imageKey} entry={entry} style={style} />
886
+ );
887
+ };
806
888
 
807
- const nativeProps = {
808
- entry, // Must be passed first in list
809
- ...platformSpecificProps,
810
- audioTrackId,
811
- docked,
812
- enableAutoAudioTrackSelection,
813
- fullscreen, // TODO: Check fullscreen params not crash
814
- inline,
815
- muted,
816
- nowPlayingEnabled,
817
- paused,
818
- seek,
819
- textTrackId,
820
- liveSeekingEnabled,
821
- liveCatchUpEnabled,
822
- playerId: this.props.playerId,
823
- pictureInPictureEnabled,
824
- seekStep:
825
- (this.props.pluginConfiguration?.seek_duration || SEEK_TIME) * 1000, // Only for Android
826
- accessibilityProps: getAllAccessibilityProps(
827
- this.getPluginConfiguration()
828
- ),
829
- };
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
+ };
830
915
 
831
- if (!isTvOS) {
832
- // TODO: Move to RN method and remove from props.
833
- // Causes tvOS bug: native player passes rate on fast seek,
834
- // RN returns it and it breaks playback.
835
- nativeProps.rate = rate;
836
- }
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
+ };
837
937
 
838
- if (
839
- this.isReactNativeMethodsSupported() &&
840
- RNPlayerManager?.[this.playerPluginId]?.play
841
- ) {
842
- delete nativeProps.paused;
843
- }
938
+ // Extract native events to a separate method for reusability
939
+ getNativeEvents = () => {
940
+ const { keyEvent } = this.state;
844
941
 
845
942
  const adEvents = {
846
- onAdBegin: this.onAdBegin, // ad_start
847
- onAdBreakBegin: this.onAdBreakBegin, // ad_break_start
848
- onAdBreakEnd: this.onAdBreakEnd, // ad_break_completed
849
- onAdEnd: this.onAdEnd, // ad_complete
850
- onAdError: this.onAdError, // ad_error
851
- onAdRequest: this.onAdRequest, // ad_request
852
- onAdClicked: this.onAdClicked, // ad_clicked
853
- 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,
854
951
  };
855
952
 
856
953
  const playbackEvents = {
857
954
  onPlaybackRateChange: this.onPlaybackRateChange,
858
- onPlayerPause: this.onPlayerPause, // pause
859
- onPlayerResume: this.onPlayerResume, // play
860
- onPlayerSeekComplete: this.onPlayerSeekComplete, // seek_complete
861
- onPlayerSeekStart: this.onPlayerSeekStart, // seek_start
955
+ onPlayerPause: this.onPlayerPause,
956
+ onPlayerResume: this.onPlayerResume,
957
+ onPlayerSeekComplete: this.onPlayerSeekComplete,
958
+ onPlayerSeekStart: this.onPlayerSeekStart,
862
959
  };
863
960
 
864
961
  const lifecycleEvents = {
865
962
  onReadyForDisplay: this.unHandledEvent("onReadyForDisplay"),
866
- onVideoEnd: this.onVideoEnd, // complete
867
- onVideoError: this.onVideoError, // error
963
+ onVideoEnd: this.onVideoEnd,
964
+ onVideoError: this.onVideoError,
868
965
  onVideoProgress: this.onVideoProgress,
869
966
  onVideoFullscreenPlayerDidDismiss: this.onVideoFullscreenPlayerDidDismiss,
870
967
  onVideoFullscreenPlayerDidPresent: this.onVideoFullscreenPlayerDidPresent,
@@ -908,11 +1005,11 @@ export default class VideoPlayer extends React.Component<
908
1005
  };
909
1006
 
910
1007
  const unsupportedEvents = {
911
- onBufferComplete: this.onBufferComplete, // buffer_complete
912
- onBufferStart: this.onBufferStart, // buffer_start
1008
+ onBufferComplete: this.onBufferComplete,
1009
+ onBufferStart: this.onBufferStart,
913
1010
  };
914
1011
 
915
- const nativeEvents = {
1012
+ return {
916
1013
  ...adEvents,
917
1014
  ...androidPlayerEvents,
918
1015
  ...lifecycleEvents,
@@ -921,60 +1018,204 @@ export default class VideoPlayer extends React.Component<
921
1018
  ...unsupportedEvents,
922
1019
  ...playbackEvents,
923
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,
924
1058
  };
925
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
+
926
1131
  const needToShowPlayNextOverlay =
927
1132
  isTV() && this.getPlayNextData() && !docked;
928
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
+
929
1161
  const needToShowDefaultControls = !(
930
1162
  (controls && !castAction?.state?.connected) ||
931
1163
  this.getPlayNextData()
932
1164
  );
933
1165
 
934
- const playerContainerStyles = isApplePlatform()
935
- ? styles.playerContainerBlack
936
- : isTV() && isAudioItem
937
- ? styles.playerContainerTransparent
938
- : styles.playerContainerBlack;
939
-
940
1166
  const isInline = () => {
941
1167
  if (isTV()) {
942
- return inline;
1168
+ return this.props.inline;
943
1169
  }
944
1170
 
945
- // HACK: on mobile overwrite inline value if player is fullScreen
946
- return fullscreen ? false : inline;
1171
+ return fullscreen ? false : this.props.inline;
947
1172
  };
948
1173
 
949
- const renderTransportControls = (playerDimensions) => {
950
- const needsToRender =
951
- !hideTransportControls &&
952
- !needToShowPlayNextOverlay &&
953
- 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
+ };
954
1189
 
955
- if (shouldUseNativeControls) {
956
- return null;
957
- }
1190
+ // Helper method for inline TV check
1191
+ isInlineTV = () => {
1192
+ const screenData = getTargetScreenDataFromEntry(this.props.playableItem);
958
1193
 
959
- const TransportControls = isTV()
960
- ? TransportControlsTV
961
- : TransportControlsMobile;
962
-
963
- return needsToRender ? (
964
- <TransportControls
965
- playerContent={entry}
966
- configuration={this.getPluginConfiguration()}
967
- playerDimensions={playerDimensions}
968
- inline={isInline()}
969
- docked={fullscreen ? false : docked}
970
- isModal={isModal}
971
- extraInfoText={castAction?.state?.friendlyStatus}
972
- controlsAlwaysVisible={castAction?.state?.connected}
973
- playNextData={this.getPlayNextData()}
974
- needToShowDefaultControls={needToShowDefaultControls}
975
- />
976
- ) : null;
977
- };
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;
978
1219
 
979
1220
  const sharedWrapperViewProps = {
980
1221
  style,
@@ -982,199 +1223,155 @@ export default class VideoPlayer extends React.Component<
982
1223
  inline,
983
1224
  docked,
984
1225
  isModal,
985
- pip: this.state.piped,
1226
+ pip: piped,
986
1227
  layoutState: {
987
1228
  inline,
988
1229
  docked,
989
1230
  isModal,
990
- pip: this.state.piped,
1231
+ pip: piped,
991
1232
  },
992
- configuration: this.getPluginConfiguration(),
1233
+ configuration: pluginConfiguration,
993
1234
  isTabletPortrait,
994
1235
  playNextData: this.getPlayNextData(),
995
1236
  };
996
1237
 
997
- const backgroundImageKey =
998
- this.getPluginConfiguration()?.player_preview_image_key;
999
-
1000
- const shouldRenderNativePlayer =
1001
- !this.props.isCasting || this.supportNativeCast;
1238
+ const shouldRenderNativePlayer = !isCasting || this.supportNativeCast;
1002
1239
 
1003
1240
  const aspectRatio =
1004
- !inline && isModal && !this.state.piped ? getScreenAspectRatio() : 16 / 9;
1005
-
1006
- const renderMobilePlayer = () => {
1007
- return isFullScreenAudioPlayer && isAudioItem ? (
1008
- // Ensure the View remains in the native hierarchy for proper rendering and interaction
1009
- <PlayerModal
1010
- content={
1011
- <View collapsable={false} style={styles.container}>
1012
- {shouldRenderNativePlayer ? (
1013
- <this.props.PlayerComponent
1014
- ref={this._assignRoot}
1015
- {...nativeEvents}
1016
- {...nativeProps}
1017
- listener={this.props.listener}
1018
- />
1019
- ) : null}
1020
- <AudioPlayerWrapper {...sharedWrapperViewProps} />
1021
- </View>
1022
- }
1023
- collapsedContent={
1024
- <View style={styles.container}>
1025
- <DockedControls
1026
- entry={entry}
1027
- aspectRatio={1}
1028
- layoutState={sharedWrapperViewProps.layoutState}
1029
- playNextData={sharedWrapperViewProps.playNextData}
1030
- ImageComponent={
1031
- <FlexImage entry={entry} style={styles.flexImage} />
1032
- }
1033
- />
1034
- </View>
1035
- }
1036
- />
1037
- ) : (
1038
- <VideoPlayerModal
1039
- isTabletPortrait={isTabletPortrait}
1040
- aspectRatio={aspectRatio}
1041
- pip={this.state.piped}
1042
- fullscreen={fullscreen}
1043
- modal={isModal}
1044
- style={{
1045
- tabletLandscapeWidth: getTabletWidth(
1046
- this.getPluginConfiguration().tablet_landscape_sidebar_width,
1047
- style
1048
- ),
1049
- width: style.width,
1050
- height: style.height,
1051
- }}
1052
- content={
1053
- <PlayerWrapper
1054
- {...sharedWrapperViewProps}
1055
- // eslint-disable-next-line react/no-unstable-nested-components
1056
- playerContent={(playerDimensions) => {
1057
- const shouldRenderBackground =
1058
- !R.isNil(backgroundImageKey) &&
1059
- backgroundImageKey.length > 0 &&
1060
- !this.onVideoLoadCalled &&
1061
- !this.state.isAd;
1062
-
1063
- return (
1064
- <View style={styles.container}>
1065
- <View style={[playerContainerStyles, playerDimensions]}>
1066
- {isAudioItem ? (
1067
- <AudioPlayer
1068
- style={styles.playerSize}
1069
- audio_item={entry}
1070
- plugin_configuration={this.getPluginConfiguration()}
1071
- />
1072
- ) : null}
1073
-
1074
- {shouldRenderBackground ? (
1075
- <PlayerImageBackground
1076
- imageKey={backgroundImageKey}
1077
- configuration={this.getPluginConfiguration()}
1078
- entry={entry}
1079
- style={getBackgroundImageStyle(playerDimensions)}
1080
- />
1081
- ) : null}
1082
-
1083
- {shouldRenderNativePlayer ? (
1084
- <View style={getStyle(playerDimensions)}>
1085
- <this.props.PlayerComponent
1086
- ref={this._assignRoot}
1087
- {...nativeEvents}
1088
- {...nativeProps}
1089
- listener={this.props.listener}
1090
- style={styles.playerSize}
1091
- pluginConfiguration={this.getPluginConfiguration()}
1092
- controller={this.props.controller}
1093
- >
1094
- {this.props.children}
1095
- </this.props.PlayerComponent>
1096
- </View>
1097
- ) : null}
1098
- </View>
1099
-
1100
- {renderTransportControls(playerDimensions)}
1101
- {!this.state.piped && (
1102
- <PlayNextOverlay
1103
- item={entry}
1104
- playerIsDocked={docked}
1105
- pluginConfiguration={this.getPluginConfiguration()}
1106
- playerId={this.props.playerId}
1107
- playNextData={this.getPlayNextData()}
1108
- setNextVideoPreloadThresholdPercentage={
1109
- this.props.setNextVideoPreloadThresholdPercentage
1110
- }
1111
- />
1112
- )}
1113
- </View>
1114
- );
1115
- }}
1241
+ !inline && isModal && !piped ? getScreenAspectRatio() : 16 / 9;
1242
+
1243
+ const audioPlayerProps = {
1244
+ ref: this._assignRoot,
1245
+ ...this.getNativeEvents(),
1246
+ ...this.getNativeProps(),
1247
+ };
1248
+
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} />
1269
+ }
1116
1270
  />
1117
- }
1118
- extraContent={
1119
- <PlayerDetails
1120
- configuration={this.getPluginConfiguration()}
1121
- style={{ flex: 1 }}
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
1122
1310
  entry={entry}
1123
- isTabletLandscape={!isTabletPortrait}
1124
- inline={inline}
1125
- docked={docked}
1126
- isModal={isModal}
1127
- pip={this.state.piped}
1311
+ aspectRatio={aspectRatio}
1312
+ layoutState={sharedWrapperViewProps.layoutState}
1313
+ playNextData={sharedWrapperViewProps.playNextData}
1128
1314
  />
1129
- }
1130
- collapsedContent={
1131
- <View style={styles.container}>
1132
- <DockedControls
1133
- entry={entry}
1134
- aspectRatio={aspectRatio}
1135
- layoutState={sharedWrapperViewProps.layoutState}
1136
- playNextData={sharedWrapperViewProps.playNextData}
1137
- />
1138
- </View>
1139
- }
1140
- />
1141
- );
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,
1142
1342
  };
1143
1343
 
1144
- const renderTVPlayer = () => {
1145
- return (
1146
- <View style={this.takeParentStyle} testID={"player-view"}>
1147
- <TVPlayerView
1148
- type={isAudioItem ? "audio" : "video"}
1149
- entry={entry}
1150
- inline={isInlineTV}
1151
- transportControls={renderTransportControls(this.takeParentStyle)}
1152
- PlayerComponent={this.props.PlayerComponent}
1153
- nativePlayerIsReady={this.state.nativePlayerIsReady}
1154
- pluginConfiguration={this.getPluginConfiguration()}
1155
- playerProps={{
1156
- ref: this._assignRoot,
1157
- ...nativeEvents,
1158
- ...nativeProps,
1159
- listener: this.props.listener,
1160
- style: this.takeParentStyle,
1161
- controller: this.props.controller,
1162
- }}
1163
- audioPlayerProps={{ style: getStyle(styles.playerSize) }}
1164
- playNextProps={{
1165
- playerIsDocked: docked,
1166
- playerId: this.props.playerId,
1167
- playNextData: this.getPlayNextData(),
1168
- setNextVideoPreloadThresholdPercentage:
1169
- this.props.setNextVideoPreloadThresholdPercentage,
1170
- }}
1171
- >
1172
- {this.props.children}
1173
- </TVPlayerView>
1174
- </View>
1175
- );
1344
+ const audioPlayerProps = {
1345
+ style: { ...styles.audio, ...this.takeParentStyle },
1176
1346
  };
1177
1347
 
1178
- 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();
1179
1376
  }
1180
1377
  }