UNPKG

@zezosoft/zezo-ott-react-native-video-player

Version:

Production-ready React Native OTT video player library for Android & iOS. Features: playlists, seasons, auto-next playback, subtitles (SRT/VTT), custom theming, analytics tracking, fullscreen mode, gesture controls, ads player (pre-roll/mid-roll/post-roll

248 lines (242 loc) 7.38 kB
"use strict"; import { useEffect, useRef, useImperativeHandle, forwardRef, useCallback, useMemo } from 'react'; import { View } from 'react-native'; import globalStyles from "../VideoPlayer/styles/globalStyles.js"; import Video from 'react-native-video'; import { useAdsPlayerStore } from "./store/adsPlayerStore.js"; import { AdMediaControlsProvider } from "./MediaControls/index.js"; import { Gesture, GestureDetector } from 'react-native-gesture-handler'; import { runOnJS } from 'react-native-reanimated'; import { toggleAdControls, showAdControls } from "./utils/controls.js"; import { resetAdState } from "./utils/adStateReset.js"; import { useAdTracking } from "./utils/useAdTracking.js"; import { jsx as _jsx } from "react/jsx-runtime"; const AdsPlayer = /*#__PURE__*/forwardRef(({ onAdEnd, onAdSkip, onAdError, onAdTracking, onClose, insets }, ref) => { const { currentAd, adDuration, adCurrentTime, isAdPaused, isAdMuted, isAdPlaying, setIsAdPaused, setAdCurrentTime, setAdDuration, setShowSkipButton, setIsAdBuffering } = useAdsPlayerStore(); const adVideoRef = useRef(null); const trackedQuartilesRef = useRef({ firstQuartile: false, midpoint: false, thirdQuartile: false }); const trackedImpressionRef = useRef(null); const { trackAdImpression, trackAdStart, trackAdComplete, trackAdQuartile } = useAdTracking({ onAdTracking }); const trigger = useCallback(() => { toggleAdControls(); }, []); const tapGesture = useMemo(() => Gesture.Tap().onEnd(() => { runOnJS(trigger)(); }).maxDuration(250), [trigger]); useImperativeHandle(ref, () => ({ seek: time => { adVideoRef.current?.seek(time); }, getCurrentTime: () => adCurrentTime, getDuration: () => adDuration })); // Track impression only once per ad useEffect(() => { if (currentAd && isAdPlaying && trackedImpressionRef.current !== currentAd.id) { try { trackedImpressionRef.current = currentAd.id; trackAdImpression(currentAd); } catch (error) { console.error('Error in ad impression tracking:', error); } } }, [currentAd, isAdPlaying, trackAdImpression]); // Separate effect for ensuring ad is not paused useEffect(() => { if (currentAd && isAdPlaying) { setIsAdPaused(false); showAdControls(); } }, [currentAd, isAdPlaying, setIsAdPaused]); useEffect(() => { if (currentAd?.skippable) { if (currentAd.skipAfter > 0) { const timer = setTimeout(() => { setShowSkipButton(true); }, currentAd.skipAfter * 1000); return () => clearTimeout(timer); } setShowSkipButton(true); return undefined; } setShowSkipButton(false); return undefined; }, [currentAd, setShowSkipButton]); const handleAdProgress = data => { setAdCurrentTime(data.currentTime); setAdDuration(data.seekableDuration || 0); // Reset buffering state when progress updates (playback has resumed) const { isAdBuffering } = useAdsPlayerStore.getState(); if (isAdBuffering) { setIsAdBuffering(false); } if (currentAd && adDuration > 0) { const progress = data.currentTime / adDuration; if (progress >= 0.25 && !trackedQuartilesRef.current.firstQuartile) { trackedQuartilesRef.current.firstQuartile = true; trackAdQuartile(currentAd, 'firstQuartile'); } if (progress >= 0.5 && !trackedQuartilesRef.current.midpoint) { trackedQuartilesRef.current.midpoint = true; trackAdQuartile(currentAd, 'midpoint'); } if (progress >= 0.75 && !trackedQuartilesRef.current.thirdQuartile) { trackedQuartilesRef.current.thirdQuartile = true; trackAdQuartile(currentAd, 'thirdQuartile'); } } }; const handleAdLoadStart = _ => { setIsAdBuffering(true); }; const handleAdLoad = () => { try { setIsAdBuffering(false); if (currentAd) { setAdDuration(currentAd.duration); trackAdStart(currentAd); } } catch (error) { console.error('Error in handleAdLoad:', error); if (currentAd && onAdError) { onAdError(error instanceof Error ? error : new Error('Ad load failed'), currentAd); } } }; const handleAdEnd = () => { if (!currentAd) return; try { trackAdComplete(currentAd); resetAdState(); trackedQuartilesRef.current = { firstQuartile: false, midpoint: false, thirdQuartile: false }; // Reset impression tracking for next ad trackedImpressionRef.current = null; onAdEnd(currentAd); } catch (error) { console.error('Error in handleAdEnd:', error); resetAdState(); trackedImpressionRef.current = null; onAdEnd(currentAd); } }; const handleAdError = error => { if (!currentAd) return; try { const errorMessage = error.error?.localizedDescription || error.error?.errorString || 'Ad playback failed'; console.error('Ad playback error:', errorMessage, error); resetAdState(); // Reset impression tracking on error trackedImpressionRef.current = null; if (onAdError) { onAdError(new Error(errorMessage), currentAd); } else { // Fallback: treat error as ad end onAdEnd(currentAd); } } catch (err) { console.error('Error in handleAdError:', err); resetAdState(); trackedImpressionRef.current = null; if (currentAd && onAdEnd) { onAdEnd(currentAd); } } }; // Memoize video style for performance const videoStyle = useMemo(() => [globalStyles.absoluteFill, { transform: [{ scaleX: 1.24 }, { scaleY: 1.15 }] }], []); // Memoize container style const containerStyle = useMemo(() => [globalStyles.absoluteFill, { backgroundColor: '#000' }], []); if (!currentAd || !isAdPlaying) { return null; } return /*#__PURE__*/_jsx(GestureDetector, { gesture: tapGesture, children: /*#__PURE__*/_jsx(View, { style: containerStyle, collapsable: false, children: /*#__PURE__*/_jsx(AdMediaControlsProvider, { onAdSkip: onAdSkip, onAdTracking: onAdTracking, onClose: onClose, insets: insets, children: /*#__PURE__*/_jsx(Video, { ref: adVideoRef, source: { uri: currentAd.source }, paused: isAdPaused, muted: isAdMuted, onProgress: handleAdProgress, onLoad: handleAdLoad, onEnd: handleAdEnd, onError: handleAdError, onBuffer: ({ isBuffering }) => { if (isBuffering) { setIsAdBuffering(true); } else { setTimeout(() => setIsAdBuffering(false), 150); } }, onLoadStart: handleAdLoadStart, resizeMode: "contain", style: videoStyle, controls: false, playInBackground: false, playWhenInactive: false, pointerEvents: "box-none", ignoreSilentSwitch: "ignore", progressUpdateInterval: 250 }) }) }) }); }); AdsPlayer.displayName = 'AdsPlayer'; export default AdsPlayer; //# sourceMappingURL=AdsPlayer.js.map