@applicaster/quick-brick-player 14.0.0-rc.9 → 14.0.0-rc.90

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