UNPKG

@8man/react-native-media-console

Version:
638 lines (624 loc) 24.7 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.VideoPlayer = void 0; var _react = _interopRequireWildcard(require("react")); var _reactNative = require("react-native"); var _reactNativeVideo = _interopRequireWildcard(require("react-native-video")); var _hooks = require("./hooks"); var _components = require("./components"); var _OSSupport = require("./OSSupport"); var _utils = require("./utils"); var _styles2 = require("./styles"); var _Gestures = _interopRequireDefault(require("@8man/react-native-media-console/src/components/Gestures")); function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; } function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r = new WeakMap(), n = new WeakMap(); return (_interopRequireWildcard = function (e, t) { if (!t && e && e.__esModule) return e; var o, i, f = { __proto__: null, default: e }; if (null === e || "object" != typeof e && "function" != typeof e) return f; if (o = t ? n : r) { if (o.has(e)) return o.get(e); o.set(e, f); } for (const t in e) "default" !== t && {}.hasOwnProperty.call(e, t) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, t)) && (i.get || i.set) ? o(f, t, i) : f[t] = e[t]); return f; })(e, t); } function _extends() { return _extends = Object.assign ? Object.assign.bind() : function (n) { for (var e = 1; e < arguments.length; e++) { var t = arguments[e]; for (var r in t) ({}).hasOwnProperty.call(t, r) && (n[r] = t[r]); } return n; }, _extends.apply(null, arguments); } const volumeWidth = 150; const iconOffset = 0; const AnimatedVideoPlayer = props => { const { animations, toggleResizeModeOnFullscreen, doubleTapTime = 130, resizeMode = _reactNativeVideo.ResizeMode.CONTAIN, isFullscreen = false, showOnStart = false, showOnEnd = false, alwaysShowControls = false, paused = false, muted = false, volume = 1, title = { primary: '', secondary: '' }, rate = 1, showDuration = false, showTimeRemaining = false, showHours = false, onSeek, onError, onBack, onEnd, onEnterFullscreen = () => {}, onExitFullscreen = () => {}, onHideControls = () => {}, onShowControls = () => {}, onPause, onPlay, onLoad, onLoadStart, onProgress, controlTimeoutDelay = 15000, tapAnywhereToPause = false, videoStyle = {}, containerStyle = {}, seekColor = '', source, disableBack = false, disableVolume = false, disableFullscreen = false, disableTimer = false, disableSeekbar = false, disablePlayPause = false, disableSeekButtons = false, disableOverlay, navigator, rewindTime = 15, pan: { horizontal: horizontalPan, inverted: invertedPan } = {}, testID, disableGesture = false, hideAllControlls = false } = props; const mounted = (0, _react.useRef)(false); const _videoRef = (0, _react.useRef)(null); const controlTimeout = (0, _react.useRef)(setTimeout(() => {})).current; const tapActionTimeout = (0, _react.useRef)(null); const [_resizeMode, setResizeMode] = (0, _react.useState)(_reactNativeVideo.ResizeMode.CONTAIN); const [_paused, setPaused] = (0, _react.useState)(paused); const [_muted, setMuted] = (0, _react.useState)(muted); const [_volume, setVolume] = (0, _react.useState)(volume); const [_isFullscreen, setIsFullscreen] = (0, _react.useState)(isFullscreen || resizeMode === 'cover' || false); const [_playbackRate, setPlaybackRate] = (0, _react.useState)(rate); const [_showTimeRemaining, setShowTimeRemaining] = (0, _react.useState)(showTimeRemaining); const [volumeTrackWidth, setVolumeTrackWidth] = (0, _react.useState)(0); const [volumeFillWidth, setVolumeFillWidth] = (0, _react.useState)(0); const [seekerFillWidth, setSeekerFillWidth] = (0, _react.useState)(0); const [showControls, setShowControls] = (0, _react.useState)(showOnStart); const [volumePosition, setVolumePositionState] = (0, _react.useState)(0); const [seekerPosition, setSeekerPositionState] = (0, _react.useState)(0); const [volumeOffset, setVolumeOffset] = (0, _react.useState)(0); const [seekerOffset, setSeekerOffset] = (0, _react.useState)(0); const [seekerWidth, setSeekerWidth] = (0, _react.useState)(0); const [seeking, setSeeking] = (0, _react.useState)(false); const [loading, setLoading] = (0, _react.useState)(true); const [currentTime, setCurrentTime] = (0, _react.useState)(0); const [error, setError] = (0, _react.useState)(false); const [duration, setDuration] = (0, _react.useState)(0); const [buffering, setBuffering] = (0, _react.useState)(false); const [cachedDuration, setCachedDuration] = (0, _react.useState)(0); const [cachedPosition, setCachedPosition] = (0, _react.useState)(0); const videoRef = props.videoRef || _videoRef; const { clearControlTimeout, resetControlTimeout, setControlTimeout } = (0, _hooks.useControlTimeout)({ controlTimeout, controlTimeoutDelay, mounted: mounted.current, showControls, setShowControls, alwaysShowControls }); const toggleFullscreen = (0, _react.useCallback)(() => setIsFullscreen(prevState => !prevState), []); const toggleControls = (0, _react.useCallback)(() => setShowControls(prevState => alwaysShowControls || !prevState), [alwaysShowControls]); const toggleTimer = (0, _react.useCallback)(() => setShowTimeRemaining(prevState => !prevState), []); const togglePlayPause = (0, _react.useCallback)(() => { setPaused(prevState => !prevState); }, []); const styles = (0, _react.useMemo)(() => ({ videoStyle, containerStyle: containerStyle }), [videoStyle, containerStyle]); const _onSeek = (0, _react.useCallback)(obj => { try { if (!seeking) { setControlTimeout(); } // Ensure currentTime is valid const validCurrentTime = Math.max(0, Math.min(obj.currentTime || 0, duration)); setCurrentTime(validCurrentTime); console.log('Seek completed:', obj); if (typeof onSeek === 'function') { onSeek(obj); } } catch (error) { console.error('Error in _onSeek:', error); } }, [seeking, setControlTimeout, onSeek, duration]); const _onEnd = (0, _react.useCallback)(() => { if (currentTime < duration) { setCurrentTime(duration); setPaused(!props.repeat); if (showOnEnd) { setShowControls(!props.repeat); } } if (typeof onEnd === 'function') { onEnd(); } }, [currentTime, duration, props.repeat, showOnEnd, onEnd]); const _onError = (0, _react.useCallback)(() => { setError(true); setLoading(false); }, []); const _onLoadStart = (0, _react.useCallback)(e => { setLoading(true); if (typeof onLoadStart === 'function') { onLoadStart(e); } }, [onLoadStart]); const _onLoad = (0, _react.useCallback)(data => { setDuration(data.duration); setLoading(false); if (showControls) { setControlTimeout(); } if (typeof onLoad === 'function') { onLoad(data); } }, [showControls, setControlTimeout, onLoad]); const _onProgress = (0, _react.useCallback)(data => { setLoading(false); if (!seeking && !buffering) { const newCurrentTime = data.currentTime; const newCachedDuration = data.playableDuration; // Update current time setCurrentTime(newCurrentTime); setCachedDuration(newCachedDuration); // Update seekbar position based on current time and duration if (duration > 0 && seekerWidth > 0) { const progress = newCurrentTime / duration; const position = progress * seekerWidth; setSeekerPosition(position); } // Update cached position for buffer indicator if (duration > 0 && seekerWidth > 0) { const bufferProgress = newCachedDuration / duration; const cachedPos = bufferProgress * seekerWidth; setCachedPosition(cachedPos); } if (typeof onProgress === 'function') { onProgress(data); } } }, [seeking, buffering, onProgress, duration, seekerWidth]); const _onScreenTouch = (0, _react.useCallback)(() => { if (tapActionTimeout.current) { // This is a double tap - clear timeout and toggle fullscreen clearTimeout(tapActionTimeout.current); tapActionTimeout.current = null; toggleFullscreen(); if (showControls) { resetControlTimeout(); } } else { // This is a single tap - set timeout to handle single tap action tapActionTimeout.current = setTimeout(() => { if (tapAnywhereToPause && showControls) { togglePlayPause(); resetControlTimeout(); } else { toggleControls(); } tapActionTimeout.current = null; }, doubleTapTime); } }, [toggleFullscreen, showControls, resetControlTimeout, tapAnywhereToPause, togglePlayPause, toggleControls, doubleTapTime]); const _onPlaybackRateChange = (0, _react.useCallback)(playBack => { if (playBack.playbackRate === 0 && !buffering) { setTimeout(() => { !buffering && setPaused(true); }); } else { setPaused(false); } console.log(playBack); }, [buffering]); const events = (0, _react.useMemo)(() => ({ onError: onError || _onError, onBack: onBack || (0, _utils._onBack)(navigator), onEnd: _onEnd, onScreenTouch: _onScreenTouch, onEnterFullscreen, onExitFullscreen, onShowControls, onHideControls, onLoadStart: _onLoadStart, onProgress: _onProgress, onSeek: _onSeek, onLoad: _onLoad, onPause, onPlay, onPlaybackRateChange: _onPlaybackRateChange }), [onError, _onError, onBack, navigator, _onEnd, _onScreenTouch, onEnterFullscreen, onExitFullscreen, onShowControls, onHideControls, _onLoadStart, _onProgress, _onSeek, _onLoad, onPause, onPlay, _onPlaybackRateChange]); const constrainToSeekerMinMax = (0, _react.useCallback)((val = 0) => { if (val <= 0) { return 0; } else if (val >= seekerWidth) { return seekerWidth; } return val; }, [seekerWidth]); const constrainToVolumeMinMax = (0, _react.useCallback)((val = 0) => { if (val <= 0) { return 0; } else if (val >= volumeWidth + 9) { return volumeWidth + 9; } return val; }, []); const setSeekerPosition = (0, _react.useCallback)((position = 0) => { const positionValue = constrainToSeekerMinMax(position); // Batch state updates to prevent excessive re-renders setSeekerPositionState(positionValue); setSeekerOffset(positionValue); setSeekerFillWidth(positionValue); }, [constrainToSeekerMinMax]); const setVolumePosition = (0, _react.useCallback)((position = 0) => { const positionValue = constrainToVolumeMinMax(position); // Batch state updates setVolumePositionState(positionValue + iconOffset); if (positionValue < 0) { setVolumeFillWidth(0); } else { setVolumeFillWidth(positionValue); } }, [constrainToVolumeMinMax]); const seekVideo = (0, _react.useCallback)(time => { try { var _videoRef$current, _videoRef$current2; console.log('seekVideo called with time:', time); console.log('videoRef.current:', !!(videoRef !== null && videoRef !== void 0 && videoRef.current)); console.log('videoRef.current.seek:', !!(videoRef !== null && videoRef !== void 0 && (_videoRef$current = videoRef.current) !== null && _videoRef$current !== void 0 && _videoRef$current.seek)); if (videoRef !== null && videoRef !== void 0 && (_videoRef$current2 = videoRef.current) !== null && _videoRef$current2 !== void 0 && _videoRef$current2.seek && typeof videoRef.current.seek === 'function') { console.log('Calling videoRef.current.seek with time:', time); // Try seeking with tolerance parameter for better compatibility videoRef.current.seek(time, 100); } else if (videoRef !== null && videoRef !== void 0 && videoRef.current) { var _seek, _ref; // Fallback: try calling seek directly on the ref if available console.log('Trying fallback seek method'); (_seek = (_ref = videoRef.current).seek) === null || _seek === void 0 || _seek.call(_ref, time); } else { var _videoRef$current3; console.warn('Video seek function not available', { ref: !!(videoRef !== null && videoRef !== void 0 && videoRef.current), seekFunction: !!(videoRef !== null && videoRef !== void 0 && (_videoRef$current3 = videoRef.current) !== null && _videoRef$current3 !== void 0 && _videoRef$current3.seek) }); } } catch (error) { console.error('Error seeking video:', error); } }, []); const { volumePanResponder, seekPanResponder } = (0, _hooks.usePanResponders)({ duration, seekerOffset, volumeOffset, loading, seekerWidth, seeking, seekerPosition, seek: seekVideo, clearControlTimeout, setVolumePosition, setSeekerPosition, setSeeking, setControlTimeout, onEnd: events.onEnd, horizontal: horizontalPan, inverted: invertedPan }); (0, _react.useEffect)(() => { if (currentTime >= duration && duration > 0) { var _videoRef$current4; videoRef === null || videoRef === void 0 || (_videoRef$current4 = videoRef.current) === null || _videoRef$current4 === void 0 || _videoRef$current4.seek(0); } }, [currentTime, duration, videoRef]); (0, _react.useEffect)(() => { if (toggleResizeModeOnFullscreen) { setResizeMode(_isFullscreen ? _reactNativeVideo.ResizeMode.CONTAIN : _reactNativeVideo.ResizeMode.COVER); } if (mounted.current) { if (_isFullscreen) { typeof events.onEnterFullscreen === 'function' && events.onEnterFullscreen(); } else { typeof events.onExitFullscreen === 'function' && events.onExitFullscreen(); } } // eslint-disable-next-line react-hooks/exhaustive-deps }, [_isFullscreen, toggleResizeModeOnFullscreen]); (0, _react.useEffect)(() => { setIsFullscreen(isFullscreen); }, [isFullscreen]); (0, _react.useEffect)(() => { setPaused(paused); }, [paused]); (0, _react.useEffect)(() => { if (_paused) { typeof events.onPause === 'function' && events.onPause(); } else { typeof events.onPlay === 'function' && events.onPlay(); } // eslint-disable-next-line react-hooks/exhaustive-deps }, [_paused]); // Optimize seekbar position updates with throttling const updateSeekerPositionRef = (0, _react.useRef)(null); (0, _react.useEffect)(() => { if (!seeking && currentTime && duration && seekerWidth) { if (updateSeekerPositionRef.current) { cancelAnimationFrame(updateSeekerPositionRef.current); } updateSeekerPositionRef.current = requestAnimationFrame(() => { const percent = currentTime / duration; const position = seekerWidth * percent; const cachedPercent = cachedDuration / duration; const _cachedPosition = seekerWidth * cachedPercent; const newCachedPosition = constrainToSeekerMinMax(_cachedPosition); setCachedPosition(newCachedPosition); setSeekerPosition(position); updateSeekerPositionRef.current = null; }); } return () => { if (updateSeekerPositionRef.current) { cancelAnimationFrame(updateSeekerPositionRef.current); } }; // eslint-disable-next-line react-hooks/exhaustive-deps }, [currentTime, duration, seekerWidth, setSeekerPosition, constrainToSeekerMinMax]); // set current time when seeking (0, _react.useEffect)(() => { if (seeking && seekerPosition && seekerWidth && duration) { const percent = seekerPosition / seekerWidth; const newTime = duration * percent; setCurrentTime(newTime); } // eslint-disable-next-line react-hooks/exhaustive-deps }, [seeking, seekerPosition]); (0, _react.useEffect)(() => { if (showControls) { animations.showControlAnimation(); setControlTimeout(); typeof events.onShowControls === 'function' && events.onShowControls(); } else { animations.hideControlAnimation(); clearControlTimeout(); typeof events.onHideControls === 'function' && events.onHideControls(); } // eslint-disable-next-line react-hooks/exhaustive-deps }, [showControls, loading]); (0, _react.useEffect)(() => { setMuted(muted); }, [muted]); // Optimize volume updates with throttling const updateVolumeRef = (0, _react.useRef)(null); (0, _react.useEffect)(() => { if (updateVolumeRef.current) { cancelAnimationFrame(updateVolumeRef.current); } updateVolumeRef.current = requestAnimationFrame(() => { const newVolume = volumePosition / volumeWidth; if (newVolume <= 0) { setMuted(true); } else { setMuted(false); } setVolume(newVolume); setVolumeOffset(volumePosition); const newVolumeTrackWidth = volumeWidth - volumeFillWidth; if (newVolumeTrackWidth > 150) { setVolumeTrackWidth(150); } else { setVolumeTrackWidth(newVolumeTrackWidth); } updateVolumeRef.current = null; }); return () => { if (updateVolumeRef.current) { cancelAnimationFrame(updateVolumeRef.current); } }; }, [volumeFillWidth, volumePosition]); (0, _react.useEffect)(() => { const position = volumeWidth * _volume; setVolumePosition(position); setVolumeOffset(position); mounted.current = true; return () => { mounted.current = false; clearControlTimeout(); // Clean up any pending animation frames if (updateSeekerPositionRef.current) { cancelAnimationFrame(updateSeekerPositionRef.current); } if (updateVolumeRef.current) { cancelAnimationFrame(updateVolumeRef.current); } }; // eslint-disable-next-line react-hooks/exhaustive-deps }, []); (0, _react.useEffect)(() => { setPlaybackRate(rate); }, [rate]); const rewind = (0, _react.useCallback)(time => { var _videoRef$current5; const newTime = typeof time === 'number' ? currentTime - time : currentTime - rewindTime; setCurrentTime(newTime); videoRef === null || videoRef === void 0 || (_videoRef$current5 = videoRef.current) === null || _videoRef$current5 === void 0 || _videoRef$current5.seek(newTime); }, [currentTime, rewindTime, videoRef]); const forward = (0, _react.useCallback)(time => { var _videoRef$current6; const newTime = typeof time === 'number' ? currentTime + time : currentTime + rewindTime; setCurrentTime(newTime); videoRef === null || videoRef === void 0 || (_videoRef$current6 = videoRef.current) === null || _videoRef$current6 === void 0 || _videoRef$current6.seek(newTime); }, [currentTime, rewindTime, videoRef]); // Memoize onBuffer callback const onBuffer = (0, _react.useCallback)(e => { setBuffering(e.isBuffering); if (!e.isBuffering) { setPaused(false); } }, []); // Memoize source URI for dependency comparison - use deep comparison for stability const sourceUri = (0, _react.useMemo)(() => { if (!source) return null; if (typeof source === 'object' && 'uri' in source) { return source.uri; } if (typeof source === 'object') { // For other source objects, create a stable string representation return JSON.stringify(source); } return String(source); }, [source]); // Keep track of previous source to prevent unnecessary resets const prevSourceUri = (0, _react.useRef)(sourceUri); const hasInitialized = (0, _react.useRef)(false); // reset on url change - only reset if source actually changed and component has initialized (0, _react.useEffect)(() => { if (hasInitialized.current && sourceUri !== prevSourceUri.current && sourceUri !== null) { prevSourceUri.current = sourceUri; setLoading(true); setSeekerFillWidth(0); setSeekerPosition(0); setCachedPosition(0); setCurrentTime(0); } else if (!hasInitialized.current) { prevSourceUri.current = sourceUri; hasInitialized.current = true; } }, [sourceUri, setSeekerPosition]); return /*#__PURE__*/_react.default.createElement(_OSSupport.PlatformSupport, { showControls: showControls, containerStyles: styles.containerStyle, onScreenTouch: events.onScreenTouch, testID: testID }, /*#__PURE__*/_react.default.createElement(_reactNative.View, { style: [_styles2._styles.player.container, styles.containerStyle] }, /*#__PURE__*/_react.default.createElement(_reactNativeVideo.default, _extends({ controls: false }, props, events, { ref: videoRef || _videoRef, resizeMode: resizeMode, volume: _volume, paused: _paused, muted: _muted, rate: _playbackRate, style: [_styles2._styles.player.video, styles.videoStyle], source: source, onBuffer: onBuffer })), /*#__PURE__*/_react.default.createElement(_react.default.Fragment, null, /*#__PURE__*/_react.default.createElement(_components.Error, { error: error }), !hideAllControlls && /*#__PURE__*/_react.default.createElement(_react.default.Fragment, null, !disableOverlay && /*#__PURE__*/_react.default.createElement(_components.Overlay, { animations: animations }), /*#__PURE__*/_react.default.createElement(_components.TopControls, { title: title, panHandlers: volumePanResponder.panHandlers, animations: animations, disableBack: disableBack, disableVolume: disableVolume, volumeFillWidth: volumeFillWidth, volumeTrackWidth: volumeTrackWidth, volumePosition: volumePosition, onBack: events.onBack, resetControlTimeout: resetControlTimeout, showControls: showControls }), loading ? /*#__PURE__*/_react.default.createElement(_components.Loader, { color: seekColor }) : /*#__PURE__*/_react.default.createElement(_components.PlayPause, { animations: animations, disablePlayPause: disablePlayPause, disableSeekButtons: disableSeekButtons, paused: _paused // pauseLabel={pauseLabel} , togglePlayPause: togglePlayPause, resetControlTimeout: resetControlTimeout, showControls: showControls, onPressRewind: rewind, onPressForward: forward, buffering: buffering, primaryColor: seekColor }), /*#__PURE__*/_react.default.createElement(_Gestures.default, { forward: forward, rewind: rewind, togglePlayPause: togglePlayPause, doubleTapTime: doubleTapTime, seekerWidth: seekerWidth, rewindTime: rewindTime, toggleControls: toggleControls, tapActionTimeout: tapActionTimeout, tapAnywhereToPause: tapAnywhereToPause, showControls: showControls, disableGesture: disableGesture, setPlayback: setPlaybackRate }), /*#__PURE__*/_react.default.createElement(_components.BottomControls, { animations: animations, panHandlers: seekPanResponder.panHandlers, disableTimer: disableTimer, disableSeekbar: disableSeekbar, showHours: showHours, showDuration: showDuration, paused: _paused, showTimeRemaining: _showTimeRemaining, currentTime: currentTime, duration: duration, seekColor: seekColor, toggleTimer: toggleTimer, resetControlTimeout: resetControlTimeout, seekerFillWidth: seekerFillWidth, seekerPosition: seekerPosition, setSeekerWidth: setSeekerWidth, cachedPosition: cachedPosition, isFullscreen: isFullscreen, disableFullscreen: disableFullscreen, toggleFullscreen: toggleFullscreen, showControls: showControls }))))); }; const CustomAnimations = ({ useAnimations, controlAnimationTiming = 450, ...props }) => { const animations = useAnimations(controlAnimationTiming); return /*#__PURE__*/_react.default.createElement(AnimatedVideoPlayer, _extends({ animations: animations }, props)); }; const JSAnimations = props => { const animations = (0, _hooks.useJSAnimations)(props.controlAnimationTiming); return /*#__PURE__*/_react.default.createElement(AnimatedVideoPlayer, _extends({ animations: animations }, props)); }; const VideoPlayer = props => { if (props !== null && props !== void 0 && props.useAnimations) { return /*#__PURE__*/_react.default.createElement(CustomAnimations, _extends({ useAnimations: props === null || props === void 0 ? void 0 : props.useAnimations }, props)); } return /*#__PURE__*/_react.default.createElement(JSAnimations, props); }; exports.VideoPlayer = VideoPlayer; //# sourceMappingURL=VideoPlayer.js.map