@applicaster/quick-brick-player 15.0.0-rc.13 → 15.0.0-rc.131

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