@applicaster/quick-brick-player 15.0.0-rc.8 → 15.0.0-rc.80

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 (32) hide show
  1. package/package.json +6 -6
  2. package/src/Player/AudioLayer/AudioPlayerWrapper.tsx +20 -8
  3. package/src/Player/AudioLayer/Layout/DockedControls/index.tsx +78 -65
  4. package/src/Player/AudioLayer/Layout/MobileLayout.tsx +1 -6
  5. package/src/Player/AudioLayer/Layout/PlayerImage/index.tsx +27 -25
  6. package/src/Player/AudioLayer/Layout/PlayerImage/styles.ts +6 -1
  7. package/src/Player/AudioLayer/Layout/TabletLandscapeLayout.tsx +5 -8
  8. package/src/Player/AudioLayer/Layout/TabletPortraitLayout.tsx +1 -6
  9. package/src/Player/AudioLayer/utils.ts +1 -27
  10. package/src/Player/PlayNextOverlay/index.tsx +2 -2
  11. package/src/Player/PlayerModal/PlayerModal.tsx +16 -15
  12. package/src/Player/PlayerModal/VideoPlayerModal.tsx +138 -120
  13. package/src/Player/PlayerModal/consts/index.ts +2 -19
  14. package/src/Player/PlayerModal/hooks/index.ts +115 -139
  15. package/src/Player/PlayerModal/styles.ts +5 -0
  16. package/src/Player/PlayerModal/utils/index.ts +111 -0
  17. package/src/Player/PlayerView/types.ts +1 -2
  18. package/src/Player/Utils/index.tsx +9 -9
  19. package/src/Player/hooks/progressStates/__tests__/utils.test.ts +23 -0
  20. package/src/Player/hooks/progressStates/useLiveProgressState.tsx +78 -0
  21. package/src/Player/hooks/progressStates/useProgressState.tsx +30 -0
  22. package/src/Player/hooks/progressStates/useVodProgressState.tsx +115 -0
  23. package/src/Player/hooks/progressStates/utils.ts +33 -0
  24. package/src/Player/index.tsx +553 -355
  25. package/src/utils/dimensions.ts +29 -0
  26. package/src/utils/index.ts +12 -0
  27. package/src/utils/logger.ts +6 -0
  28. package/src/utils/playerHelpers.ts +11 -0
  29. package/src/utils/playerStyles.ts +50 -0
  30. package/src/Player/AudioLayer/Layout/PlayerImage/AnimatedImage.tsx +0 -82
  31. package/src/Player/AudioLayer/Layout/PlayerImage/hooks/index.ts +0 -1
  32. package/src/Player/AudioLayer/Layout/PlayerImage/hooks/useChangePlayerState.ts +0 -99
@@ -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,42 @@ 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
+ playableItem?: any;
87
+ playerPluginIdentifier?: string;
88
+ controller?: {
89
+ supportNativeCast?: () => boolean;
90
+ supportsNativeControls?: () => boolean;
91
+ };
92
+ children?: React.ReactNode;
82
93
  };
83
94
 
84
95
  export default class VideoPlayer extends React.Component<
85
- QuickBrickPlayer.PlayerProps,
86
- QuickBrickPlayer.NativePlayerState
96
+ VideoPlayerProps,
97
+ QuickBrickPlayer.NativePlayerState & {
98
+ isCastConnected?: boolean;
99
+ audioTrackId: string | null;
100
+ textTrackId: string | null;
101
+ keyEvent?: any;
102
+ isLive?: boolean;
103
+ seekableDuration?: number;
104
+ displayOverlay?: boolean;
105
+ }
87
106
  > {
88
107
  public static propTypes = {};
89
108
  _root: any;
@@ -94,7 +113,26 @@ export default class VideoPlayer extends React.Component<
94
113
  private onVideoLoadCalled: boolean;
95
114
  playNextData: PlayNextData;
96
115
 
97
- state: QuickBrickPlayer.NativePlayerState = {
116
+ state: QuickBrickPlayer.NativePlayerState & {
117
+ isCastConnected?: boolean;
118
+ audioTrackId: string | null;
119
+ textTrackId: string | null;
120
+ keyEvent?: any;
121
+ isLive?: boolean;
122
+ seekableDuration?: number;
123
+ displayOverlay?: boolean;
124
+ } = {
125
+ // Base required properties from NativePlayerState
126
+ buffering: false,
127
+ docked: false,
128
+ fullscreen: false,
129
+ inline: false,
130
+ isModal: false,
131
+ isTabletPortrait: false,
132
+ muted: false,
133
+ showPoster: false,
134
+
135
+ // Extended properties
98
136
  audioTrackId: null,
99
137
  textTrackId: null,
100
138
  isCasting: this.props.isCasting,
@@ -140,6 +178,8 @@ export default class VideoPlayer extends React.Component<
140
178
  : { flexBasis: "auto", flexGrow: 1, flexShrink: 1 };
141
179
 
142
180
  this.playerPluginId = this.props?.playerPluginIdentifier;
181
+
182
+ this.renderPlayerContent = this.renderPlayerContent.bind(this);
143
183
  }
144
184
 
145
185
  componentWillUnmount() {
@@ -708,163 +748,219 @@ export default class VideoPlayer extends React.Component<
708
748
  return this.props.controller.supportNativeCast();
709
749
  }
710
750
 
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;
751
+ renderPlayerContent(playerDimensions: ViewStyle) {
752
+ const { entry, docked } = this.props;
753
+ const { isAd, piped } = this.state;
728
754
 
755
+ const pluginConfiguration = this.getPluginConfiguration();
729
756
  const isAudioItem = isAudioItemUtil(entry);
730
757
 
731
- const getStyle = (dimensions) => {
732
- const generalStyles = isAudioItem ? styles.audio : styles.video;
758
+ const backgroundImageKey = pluginConfiguration?.player_preview_image_key;
733
759
 
734
- if (isAndroidPlatform() && isAudioItem) {
735
- return generalStyles;
736
- }
760
+ const shouldRenderBackground =
761
+ !R.isNil(backgroundImageKey) &&
762
+ backgroundImageKey.length > 0 &&
763
+ !this.onVideoLoadCalled &&
764
+ !isAd;
737
765
 
738
- return { ...generalStyles, ...dimensions };
739
- };
766
+ const shouldRenderNativePlayer =
767
+ !this.props.isCasting || this.supportNativeCast;
740
768
 
741
- const getBackgroundImageStyle = (dimensions): any =>
742
- Object.assign(
743
- {},
744
- isAudioItem ? styles.audio : { position: "absolute" },
745
- dimensions
746
- );
769
+ const playerContainerStyles = this.getPlayerContainerStyles(isAudioItem);
747
770
 
748
- const nativeResizeMode = "ScaleAspectFit";
771
+ const backgroundImageStyle = this.getBackgroundImageStyle(
772
+ playerDimensions,
773
+ isAudioItem
774
+ );
749
775
 
750
- const nowPlayingEnabled =
751
- this.props?.pluginConfiguration?.nowPlayingEnabled;
776
+ const playerViewStyle = this.getPlayerViewStyle(
777
+ playerDimensions,
778
+ isAudioItem
779
+ );
752
780
 
753
- const enableAutoAudioTrackSelection =
754
- this.props.pluginConfiguration?.enable_auto_audio_track_selection;
781
+ const nativeEvents = this.getNativeEvents();
782
+ const nativeProps = this.getNativeProps();
755
783
 
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
784
+ const renderedNativePlayer = this.renderNativePlayer(
785
+ shouldRenderNativePlayer,
786
+ playerViewStyle,
787
+ nativeEvents,
788
+ nativeProps,
789
+ pluginConfiguration
790
+ );
759
791
 
760
- const liveSeekingEnabled =
761
- (this.props.pluginConfiguration?.liveSeekingEnabled &&
762
- this.state?.seekableDuration >
763
- minimumAllowedSeekableDurationInSeconds) ||
764
- false;
792
+ const renderedBackgroundImage = this.renderBackgroundImage(
793
+ shouldRenderBackground,
794
+ backgroundImageKey,
795
+ entry,
796
+ backgroundImageStyle
797
+ );
765
798
 
766
- // keep undefined if not present, since not all native players have the property
767
- const liveCatchUpEnabled =
768
- this.props.pluginConfiguration?.liveCatchUpEnabled;
799
+ const renderedAudioPlayer = this.renderAudioPlayer(
800
+ isAudioItem,
801
+ entry,
802
+ pluginConfiguration
803
+ );
769
804
 
770
- // TODO: Get screen data more reliable way useCurrentScreenData
771
- const screenData = getTargetScreenDataFromEntry(this.props.playableItem);
805
+ const renderedTransportControls =
806
+ this.renderTransportControls(playerDimensions);
772
807
 
773
- const isInlineTV = isInlineTVUtil(screenData);
808
+ const renderedPlayNextOverlay = this.renderPlayNextOverlay(
809
+ piped,
810
+ entry,
811
+ docked,
812
+ pluginConfiguration
813
+ );
774
814
 
775
- let platformSpecificProps: any = isApplePlatform()
776
- ? {
777
- resizeMode: nativeResizeMode,
778
- }
779
- : {};
815
+ return (
816
+ <View style={styles.container}>
817
+ <View style={[playerContainerStyles, playerDimensions]}>
818
+ {renderedAudioPlayer}
819
+ {renderedBackgroundImage}
820
+ {renderedNativePlayer}
821
+ </View>
780
822
 
781
- const supportsNativeControls =
782
- !!this.props.controller?.supportsNativeControls?.();
823
+ {renderedTransportControls}
824
+ {renderedPlayNextOverlay}
825
+ </View>
826
+ );
827
+ }
783
828
 
784
- const controls = isTvOS
785
- ? (supportsNativeControls && !isInlineTV) || !!this.getPlayNextData()
786
- : supportsNativeControls && !this.getPlayNextData();
829
+ /* Returns player container style based on platform and content type (audio/video) */
830
+ private getPlayerContainerStyles = (isAudioItem: boolean) =>
831
+ isApplePlatform()
832
+ ? styles.playerContainerBlack
833
+ : isTV() && isAudioItem
834
+ ? styles.playerContainerTransparent
835
+ : styles.playerContainerBlack;
787
836
 
788
- const shouldUseNativeControls =
789
- controls &&
790
- !startComponentsAnimation &&
791
- !docked &&
792
- !castAction?.state?.connected;
837
+ private getBackgroundImageStyle = (
838
+ dimensions: ViewStyle,
839
+ isAudioItem: boolean
840
+ ): ViewStyle[] => [
841
+ isAudioItem ? styles.audio : { position: "absolute" },
842
+ dimensions,
843
+ ];
844
+
845
+ /* Returns player view style based on platform and content type (audio/video) */
846
+ private getPlayerViewStyle = (
847
+ dimensions: ViewStyle,
848
+ isAudioItem: boolean
849
+ ): ViewStyle | ViewStyle[] => {
850
+ const generalStyles = isAudioItem ? styles.audio : undefined;
851
+
852
+ if (isAndroidPlatform() && isAudioItem) {
853
+ return generalStyles;
854
+ }
793
855
 
794
- platformSpecificProps = {
795
- ...platformSpecificProps,
796
- controls: shouldUseNativeControls,
797
- };
856
+ return [generalStyles, dimensions];
857
+ };
798
858
 
799
- const isFullScreenAudioPlayer =
800
- this.getPluginConfiguration()?.full_screen_audio_player || false;
859
+ private renderAudioPlayer = (
860
+ isAudioItem: boolean,
861
+ entry: ZappEntry,
862
+ pluginConfiguration: Record<string, any>
863
+ ) => {
864
+ if (!isAudioItem) return null;
865
+
866
+ return (
867
+ <AudioPlayer
868
+ style={styles.playerSize}
869
+ audio_item={entry}
870
+ plugin_configuration={pluginConfiguration}
871
+ />
872
+ );
873
+ };
801
874
 
802
- const isAudioPlayer = isFullScreenAudioPlayer && isAudioItem;
875
+ private renderBackgroundImage = (
876
+ shouldRender: boolean,
877
+ imageKey: string,
878
+ entry: ZappEntry,
879
+ style: ViewStyle | ViewStyle[]
880
+ ) => {
881
+ if (!shouldRender) return null;
803
882
 
804
- const pictureInPictureEnabled =
805
- this.props.pluginConfiguration?.pictureInPictureEnabled && !isAudioPlayer;
883
+ return (
884
+ <PlayerImageBackground imageKey={imageKey} entry={entry} style={style} />
885
+ );
886
+ };
806
887
 
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
- };
888
+ private renderNativePlayer = (
889
+ shouldRender: boolean,
890
+ style: ViewStyle | ViewStyle[],
891
+ nativeEvents: ReturnType<typeof this.getNativeEvents>,
892
+ nativeProps: ReturnType<typeof this.getNativeProps>,
893
+ pluginConfiguration: Record<string, any>
894
+ ) => {
895
+ if (!shouldRender) return null;
896
+ const PlayerComponent = this.props.PlayerComponent;
897
+
898
+ return (
899
+ <View style={style}>
900
+ <PlayerComponent
901
+ ref={this._assignRoot}
902
+ {...nativeEvents}
903
+ {...nativeProps}
904
+ listener={this.props.listener}
905
+ style={styles.playerSize}
906
+ pluginConfiguration={pluginConfiguration}
907
+ controller={this.props.controller}
908
+ >
909
+ {this.props.children}
910
+ </PlayerComponent>
911
+ </View>
912
+ );
913
+ };
830
914
 
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
- }
915
+ private renderPlayNextOverlay = (
916
+ piped: boolean,
917
+ entry: ZappEntry,
918
+ docked: boolean,
919
+ pluginConfiguration: Record<string, any>
920
+ ) => {
921
+ if (piped) return null;
922
+
923
+ return (
924
+ <PlayNextOverlay
925
+ item={entry}
926
+ playerIsDocked={docked}
927
+ pluginConfiguration={pluginConfiguration}
928
+ playerId={this.props.playerId}
929
+ playNextData={this.getPlayNextData()}
930
+ setNextVideoPreloadThresholdPercentage={
931
+ this.props.setNextVideoPreloadThresholdPercentage
932
+ }
933
+ />
934
+ );
935
+ };
837
936
 
838
- if (
839
- this.isReactNativeMethodsSupported() &&
840
- RNPlayerManager?.[this.playerPluginId]?.play
841
- ) {
842
- delete nativeProps.paused;
843
- }
937
+ // Extract native events to a separate method for reusability
938
+ getNativeEvents = () => {
939
+ const { keyEvent } = this.state;
844
940
 
845
941
  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
942
+ onAdBegin: this.onAdBegin,
943
+ onAdBreakBegin: this.onAdBreakBegin,
944
+ onAdBreakEnd: this.onAdBreakEnd,
945
+ onAdEnd: this.onAdEnd,
946
+ onAdError: this.onAdError,
947
+ onAdRequest: this.onAdRequest,
948
+ onAdClicked: this.onAdClicked,
949
+ onAdTapped: this.onAdTapped,
854
950
  };
855
951
 
856
952
  const playbackEvents = {
857
953
  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
954
+ onPlayerPause: this.onPlayerPause,
955
+ onPlayerResume: this.onPlayerResume,
956
+ onPlayerSeekComplete: this.onPlayerSeekComplete,
957
+ onPlayerSeekStart: this.onPlayerSeekStart,
862
958
  };
863
959
 
864
960
  const lifecycleEvents = {
865
961
  onReadyForDisplay: this.unHandledEvent("onReadyForDisplay"),
866
- onVideoEnd: this.onVideoEnd, // complete
867
- onVideoError: this.onVideoError, // error
962
+ onVideoEnd: this.onVideoEnd,
963
+ onVideoError: this.onVideoError,
868
964
  onVideoProgress: this.onVideoProgress,
869
965
  onVideoFullscreenPlayerDidDismiss: this.onVideoFullscreenPlayerDidDismiss,
870
966
  onVideoFullscreenPlayerDidPresent: this.onVideoFullscreenPlayerDidPresent,
@@ -908,11 +1004,11 @@ export default class VideoPlayer extends React.Component<
908
1004
  };
909
1005
 
910
1006
  const unsupportedEvents = {
911
- onBufferComplete: this.onBufferComplete, // buffer_complete
912
- onBufferStart: this.onBufferStart, // buffer_start
1007
+ onBufferComplete: this.onBufferComplete,
1008
+ onBufferStart: this.onBufferStart,
913
1009
  };
914
1010
 
915
- const nativeEvents = {
1011
+ return {
916
1012
  ...adEvents,
917
1013
  ...androidPlayerEvents,
918
1014
  ...lifecycleEvents,
@@ -921,60 +1017,199 @@ export default class VideoPlayer extends React.Component<
921
1017
  ...unsupportedEvents,
922
1018
  ...playbackEvents,
923
1019
  ...androidLifecyclePiPEvents,
1020
+ } as const;
1021
+ };
1022
+
1023
+ // Extract native props to a separate method for reusability
1024
+ getNativeProps = () => {
1025
+ const { entry, docked, fullscreen, inline, muted, castAction } = this.props;
1026
+ const { audioTrackId, textTrackId, paused, seek, rate } = this.state;
1027
+
1028
+ const nativeResizeMode = "ScaleAspectFit";
1029
+
1030
+ const isFullScreenAudioPlayer =
1031
+ this.getPluginConfiguration()?.full_screen_audio_player || false;
1032
+
1033
+ const isAudioItem = isAudioItemUtil(entry);
1034
+ const isAudioPlayer = isFullScreenAudioPlayer && isAudioItem;
1035
+
1036
+ let platformSpecificProps: Record<string, any> = isApplePlatform()
1037
+ ? { resizeMode: nativeResizeMode }
1038
+ : {};
1039
+
1040
+ const supportsNativeControls =
1041
+ !!this.props.controller?.supportsNativeControls?.();
1042
+
1043
+ const controls = isTvOS
1044
+ ? (supportsNativeControls && !this.isInlineTV()) ||
1045
+ !!this.getPlayNextData()
1046
+ : supportsNativeControls && !this.getPlayNextData();
1047
+
1048
+ const shouldUseNativeControls =
1049
+ controls && !docked && !castAction?.state?.connected;
1050
+
1051
+ platformSpecificProps = {
1052
+ ...platformSpecificProps,
1053
+ controls: shouldUseNativeControls,
924
1054
  };
925
1055
 
1056
+ const pictureInPictureEnabled =
1057
+ this.props.pluginConfiguration?.pictureInPictureEnabled && !isAudioPlayer;
1058
+
1059
+ const nowPlayingEnabled =
1060
+ this.props?.pluginConfiguration?.nowPlayingEnabled;
1061
+
1062
+ const enableAutoAudioTrackSelection =
1063
+ this.props.pluginConfiguration?.enable_auto_audio_track_selection;
1064
+
1065
+ const minimumAllowedSeekableDurationInSeconds =
1066
+ this.props?.pluginConfiguration?.minimumAllowedSeekableDurationInSeconds;
1067
+
1068
+ const liveSeekingEnabled =
1069
+ (this.props.pluginConfiguration?.liveSeekingEnabled &&
1070
+ this.state?.seekableDuration >
1071
+ minimumAllowedSeekableDurationInSeconds) ||
1072
+ false;
1073
+
1074
+ const liveCatchUpEnabled =
1075
+ this.props.pluginConfiguration?.liveCatchUpEnabled;
1076
+
1077
+ const nativeProps = {
1078
+ rate: undefined,
1079
+ entry,
1080
+ ...platformSpecificProps,
1081
+ audioTrackId,
1082
+ docked,
1083
+ enableAutoAudioTrackSelection,
1084
+ fullscreen,
1085
+ inline,
1086
+ muted,
1087
+ nowPlayingEnabled,
1088
+ paused,
1089
+ seek,
1090
+ textTrackId,
1091
+ liveSeekingEnabled,
1092
+ liveCatchUpEnabled,
1093
+ playerId: this.props.playerId,
1094
+ pictureInPictureEnabled,
1095
+ seekStep:
1096
+ (this.props.pluginConfiguration?.seek_duration || SEEK_TIME) * 1000,
1097
+ accessibilityProps: getAllAccessibilityProps(
1098
+ this.getPluginConfiguration()
1099
+ ),
1100
+ };
1101
+
1102
+ if (!isTvOS) {
1103
+ nativeProps.rate = rate;
1104
+ }
1105
+
1106
+ if (
1107
+ this.isReactNativeMethodsSupported() &&
1108
+ RNPlayerManager?.[this.playerPluginId]?.play
1109
+ ) {
1110
+ delete nativeProps.paused;
1111
+ }
1112
+
1113
+ return nativeProps;
1114
+ };
1115
+
1116
+ // Extract transport controls rendering to a separate method
1117
+ renderTransportControls = (playerDimensions) => {
1118
+ const {
1119
+ hideTransportControls,
1120
+ castAction,
1121
+ docked,
1122
+ fullscreen,
1123
+ isModal,
1124
+ entry,
1125
+ } = this.props;
1126
+
926
1127
  const needToShowPlayNextOverlay =
927
1128
  isTV() && this.getPlayNextData() && !docked;
928
1129
 
1130
+ const needsToRender =
1131
+ !hideTransportControls &&
1132
+ !needToShowPlayNextOverlay &&
1133
+ this.props.mode !== "PIP" &&
1134
+ !docked;
1135
+
1136
+ const supportsNativeControls =
1137
+ !!this.props.controller?.supportsNativeControls?.();
1138
+
1139
+ const controls = isTvOS
1140
+ ? (supportsNativeControls && !this.isInlineTV()) ||
1141
+ !!this.getPlayNextData()
1142
+ : supportsNativeControls && !this.getPlayNextData();
1143
+
1144
+ const shouldUseNativeControls =
1145
+ controls && !docked && !castAction?.state?.connected;
1146
+
1147
+ if (shouldUseNativeControls) {
1148
+ return null;
1149
+ }
1150
+
1151
+ const TransportControls = isTV()
1152
+ ? TransportControlsTV
1153
+ : TransportControlsMobile;
1154
+
929
1155
  const needToShowDefaultControls = !(
930
1156
  (controls && !castAction?.state?.connected) ||
931
1157
  this.getPlayNextData()
932
1158
  );
933
1159
 
934
- const playerContainerStyles = isApplePlatform()
935
- ? styles.playerContainerBlack
936
- : isTV() && isAudioItem
937
- ? styles.playerContainerTransparent
938
- : styles.playerContainerBlack;
939
-
940
1160
  const isInline = () => {
941
1161
  if (isTV()) {
942
- return inline;
1162
+ return this.props.inline;
943
1163
  }
944
1164
 
945
- // HACK: on mobile overwrite inline value if player is fullScreen
946
- return fullscreen ? false : inline;
1165
+ return fullscreen ? false : this.props.inline;
947
1166
  };
948
1167
 
949
- const renderTransportControls = (playerDimensions) => {
950
- const needsToRender =
951
- !hideTransportControls &&
952
- !needToShowPlayNextOverlay &&
953
- this.props.mode !== "PIP";
1168
+ return needsToRender ? (
1169
+ <TransportControls
1170
+ playerContent={entry}
1171
+ configuration={this.getPluginConfiguration()}
1172
+ playerDimensions={playerDimensions}
1173
+ inline={isInline()}
1174
+ docked={fullscreen ? false : docked}
1175
+ isModal={isModal}
1176
+ extraInfoText={castAction?.state?.friendlyStatus}
1177
+ controlsAlwaysVisible={castAction?.state?.connected}
1178
+ playNextData={this.getPlayNextData()}
1179
+ needToShowDefaultControls={needToShowDefaultControls}
1180
+ />
1181
+ ) : null;
1182
+ };
954
1183
 
955
- if (shouldUseNativeControls) {
956
- return null;
957
- }
1184
+ // Helper method for inline TV check
1185
+ isInlineTV = () => {
1186
+ const screenData = getTargetScreenDataFromEntry(this.props.playableItem);
958
1187
 
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
- };
1188
+ return isInlineTVUtil(screenData);
1189
+ };
1190
+
1191
+ renderMobile = () => {
1192
+ const {
1193
+ docked,
1194
+ entry,
1195
+ fullscreen,
1196
+ inline,
1197
+ isModal,
1198
+ isTabletPortrait,
1199
+ style,
1200
+ isCasting,
1201
+ PlayerComponent,
1202
+ listener,
1203
+ } = this.props;
1204
+
1205
+ const { piped } = this.state;
1206
+
1207
+ const pluginConfiguration = this.getPluginConfiguration();
1208
+
1209
+ const isAudioItem = isAudioItemUtil(entry);
1210
+
1211
+ const isFullScreenAudioPlayer =
1212
+ pluginConfiguration?.full_screen_audio_player || false;
978
1213
 
979
1214
  const sharedWrapperViewProps = {
980
1215
  style,
@@ -982,199 +1217,162 @@ export default class VideoPlayer extends React.Component<
982
1217
  inline,
983
1218
  docked,
984
1219
  isModal,
985
- pip: this.state.piped,
1220
+ pip: piped,
986
1221
  layoutState: {
987
1222
  inline,
988
1223
  docked,
989
1224
  isModal,
990
- pip: this.state.piped,
1225
+ pip: piped,
991
1226
  },
992
- configuration: this.getPluginConfiguration(),
1227
+ configuration: pluginConfiguration,
993
1228
  isTabletPortrait,
994
1229
  playNextData: this.getPlayNextData(),
995
1230
  };
996
1231
 
997
- const backgroundImageKey =
998
- this.getPluginConfiguration()?.player_preview_image_key;
999
-
1000
- const shouldRenderNativePlayer =
1001
- !this.props.isCasting || this.supportNativeCast;
1232
+ const shouldRenderNativePlayer = !isCasting || this.supportNativeCast;
1002
1233
 
1003
1234
  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
- }}
1235
+ !inline && isModal && !piped ? getScreenAspectRatio() : 16 / 9;
1236
+
1237
+ const audioPlayerProps = {
1238
+ ref: this._assignRoot,
1239
+ ...this.getNativeEvents(),
1240
+ ...this.getNativeProps(),
1241
+ };
1242
+
1243
+ return isFullScreenAudioPlayer && isAudioItem ? (
1244
+ // Ensure the View remains in the native hierarchy for proper rendering and interaction
1245
+ <PlayerModal
1246
+ content={
1247
+ <View collapsable={false} style={styles.container}>
1248
+ {shouldRenderNativePlayer ? (
1249
+ <PlayerComponent {...audioPlayerProps} listener={listener} />
1250
+ ) : null}
1251
+ <AudioPlayerWrapper {...sharedWrapperViewProps} />
1252
+ </View>
1253
+ }
1254
+ collapsedContent={
1255
+ <View style={styles.container}>
1256
+ <DockedControls
1257
+ entry={entry}
1258
+ aspectRatio={1}
1259
+ layoutState={sharedWrapperViewProps.layoutState}
1260
+ playNextData={sharedWrapperViewProps.playNextData}
1261
+ ImageComponent={
1262
+ <FlexImage entry={entry} style={styles.flexImage} />
1263
+ }
1116
1264
  />
1117
- }
1118
- extraContent={
1119
- <PlayerDetails
1120
- configuration={this.getPluginConfiguration()}
1121
- style={{ flex: 1 }}
1265
+ </View>
1266
+ }
1267
+ />
1268
+ ) : isModal ? (
1269
+ <VideoPlayerModal
1270
+ isTabletPortrait={isTabletPortrait}
1271
+ aspectRatio={aspectRatio}
1272
+ pip={piped}
1273
+ fullscreen={fullscreen}
1274
+ modal
1275
+ style={{
1276
+ tabletLandscapeWidth: getTabletWidth(
1277
+ this.getPluginConfiguration().tablet_landscape_sidebar_width,
1278
+ style
1279
+ ),
1280
+ width: style.width,
1281
+ height: style.height,
1282
+ }}
1283
+ content={
1284
+ <PlayerWrapper
1285
+ {...sharedWrapperViewProps}
1286
+ playerContent={this.renderPlayerContent}
1287
+ />
1288
+ }
1289
+ extraContent={
1290
+ <PlayerDetails
1291
+ configuration={this.getPluginConfiguration()}
1292
+ style={{ flex: 1 }}
1293
+ entry={entry}
1294
+ isTabletLandscape={!isTabletPortrait}
1295
+ inline={inline}
1296
+ docked={docked}
1297
+ isModal={isModal}
1298
+ pip={piped}
1299
+ />
1300
+ }
1301
+ collapsedContent={
1302
+ <View style={styles.container}>
1303
+ <DockedControls
1122
1304
  entry={entry}
1123
- isTabletLandscape={!isTabletPortrait}
1124
- inline={inline}
1125
- docked={docked}
1126
- isModal={isModal}
1127
- pip={this.state.piped}
1305
+ aspectRatio={aspectRatio}
1306
+ layoutState={sharedWrapperViewProps.layoutState}
1307
+ playNextData={sharedWrapperViewProps.playNextData}
1128
1308
  />
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
- }
1309
+ </View>
1310
+ }
1311
+ />
1312
+ ) : (
1313
+ <View style={style} testID={"player-view"}>
1314
+ <PlayerWrapper
1315
+ {...sharedWrapperViewProps}
1316
+ playerContent={this.renderPlayerContent}
1140
1317
  />
1141
- );
1318
+ </View>
1319
+ );
1320
+ };
1321
+
1322
+ renderTV = () => {
1323
+ const {
1324
+ docked,
1325
+ entry,
1326
+ listener,
1327
+ controller,
1328
+ PlayerComponent,
1329
+ playerId,
1330
+ setNextVideoPreloadThresholdPercentage,
1331
+ children,
1332
+ } = this.props;
1333
+
1334
+ const isAudioItem = isAudioItemUtil(entry);
1335
+
1336
+ const playerProps = {
1337
+ ref: this._assignRoot,
1338
+ ...this.getNativeEvents(),
1339
+ ...this.getNativeProps(),
1340
+ listener,
1341
+ style: this.takeParentStyle,
1342
+ controller,
1142
1343
  };
1143
1344
 
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
- );
1345
+ const audioPlayerProps = {
1346
+ style: { ...styles.audio, ...this.takeParentStyle },
1176
1347
  };
1177
1348
 
1178
- return isTV() ? renderTVPlayer() : renderMobilePlayer();
1349
+ return (
1350
+ <View style={this.takeParentStyle} testID={"player-view"}>
1351
+ <TVPlayerView
1352
+ type={isAudioItem ? "audio" : "video"}
1353
+ entry={entry}
1354
+ inline={this.isInlineTV()}
1355
+ transportControls={this.renderTransportControls(this.takeParentStyle)}
1356
+ PlayerComponent={PlayerComponent}
1357
+ nativePlayerIsReady={this.state.nativePlayerIsReady}
1358
+ pluginConfiguration={this.getPluginConfiguration()}
1359
+ playerProps={playerProps}
1360
+ audioPlayerProps={audioPlayerProps}
1361
+ playNextProps={{
1362
+ playerIsDocked: docked,
1363
+ playerId: playerId,
1364
+ playNextData: this.getPlayNextData(),
1365
+ setNextVideoPreloadThresholdPercentage:
1366
+ setNextVideoPreloadThresholdPercentage,
1367
+ }}
1368
+ >
1369
+ {children}
1370
+ </TVPlayerView>
1371
+ </View>
1372
+ );
1373
+ };
1374
+
1375
+ render() {
1376
+ return isTV() ? this.renderTV() : this.renderMobile();
1179
1377
  }
1180
1378
  }