@applicaster/quick-brick-player 15.0.0-rc.5 → 15.0.0-rc.52

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