我正在构建一个具有两个视图的音频播放器,一个迷你播放器(底部选项卡)和一个全屏播放器。滑动或点击以在视图之间切换。当我重新加载我的应用程序时,一切正常,我的占位符封面艺术在播放器上,我可以上下滑动。但是当我加载播放实例时,所有功能都会停止。没有滑动,没有点击。我的 onPress 仍在接收点击,但没有动作。对此有何想法?我将一些控制台日志放入 useCode 中,看起来它仅在初始加载时被调用,我认为这是有道理的。
import React, { useState, useEffect } from 'react';
import { StyleSheet, useWindowDimensions } from 'react-native';
import MiniPlayer, { MINI_PLAYER_HEIGHT } from './MiniPlayer';
import Animated, {
Clock,
Value,
cond,
useCode,
set,
block,
not,
clockRunning,
interpolate,
Extrapolate,
} from 'react-native-reanimated';
import {
clamp,
timing,
onGestureEvent,
withSpring,
} from 'react-native-redash/lib/module/v1';
import FullPlayer from './FullPlayer';
import { PanGestureHandler, State } from 'react-native-gesture-handler';
import { Audio } from 'expo-av';
import { useSelector } from '../../customHooks/useSelector';
import { audioRoute } from '../../constants/apiConfigs';
import { useStateIfMounted } from 'use-state-if-mounted';
import { useDispatch } from 'react-redux';
import { updateSleepTimer, postIsLoading } from '../../redux/media/thunks';
const springConfig = {
damping: 15,
mass: 1,
stiffness: 150,
overshootClamping: true,
restSpeedThreshold: 0.1,
restDisplacementThreshold: 0.1,
};
const Player = () => {
const { height } = useWindowDimensions();
const dispatch = useDispatch();
const shouldLoad =
useSelector((state) => state.media.audioInfo?.shouldLoad) || null;
const audioPost = useSelector((state) => state.media.audioInfo?.post) || null;
const storedTrackBySlug =
useSelector((state) => state.media.storedTrackBySlug) || null;
const [playbackInstance, setPlaybackInstance] = useState(null);
const [isPlaying, setIsPlaying] = useStateIfMounted(false);
const [currentPositionMillis, setCurrentPositionMillis] = useStateIfMounted(
0
);
const [totalDurationMillis, setTotalDurationMillis] = useStateIfMounted(0);
const [volume] = useStateIfMounted(1.0);
const [isBuffering, setIsBuffering] = useStateIfMounted(false);
const [rate, setRate] = useStateIfMounted(1.0);
const timerRemainingInMillis = useSelector(
(state) => state.media.audioTimer?.remainingMillis
);
const timerLastPositionInMillis = useSelector(
(state) => state.media.audioTimer?.lastPositionMillis
);
const increaseRateByQuarter = async (): Promise<void> => {
let playbackRate = rate + 0.25;
if (playbackRate > 2.0) {
playbackRate = 0.5;
}
await playbackInstance.setRateAsync(
playbackRate,
true,
Audio.PitchCorrectionQuality.Medium
);
};
const seekPositionBySeconds = async (position: number): Promise<void> => {
await playbackInstance.setPositionAsync(position * 1000);
if (timerRemainingInMillis > 0) {
dispatch(
updateSleepTimer(
Math.round(
(timerRemainingInMillis -
(currentPositionMillis - timerLastPositionInMillis)) /
1000
) *
1000 -
10,
position * 1000
)
);
}
};
const handlePlayPause = async (): Promise<void> => {
isPlaying
? await playbackInstance.pauseAsync()
: await playbackInstance.playAsync();
setIsPlaying(!isPlaying);
};
const handleSkipBack = async (): Promise<void> => {
seekPositionBySeconds(currentPositionMillis / 1000 - 15);
};
const handleSkipForward = async (): Promise<void> => {
seekPositionBySeconds(currentPositionMillis / 1000 + 15);
};
const translationY = new Value(0);
const velocityY = new Value(0);
const state = new Value(State.UNDETERMINED);
const gestureHandler = onGestureEvent({ translationY, state, velocityY });
const [] = useState(true);
const SNAP_TOP = 0;
const SNAP_BOTTOM = height - MINI_PLAYER_HEIGHT;
const offset = new Value(SNAP_BOTTOM);
const goUp: Value<0 | 1> = new Animated.Value(0);
const goDown: Value<0 | 1> = new Animated.Value(0);
const translateY = clamp(
withSpring({
state,
offset,
value: translationY,
velocity: velocityY,
snapPoints: [SNAP_TOP, SNAP_BOTTOM],
config: springConfig,
}),
SNAP_TOP,
SNAP_BOTTOM
);
const miniPlayerOpacity = interpolate(translateY, {
inputRange: [SNAP_BOTTOM - MINI_PLAYER_HEIGHT, SNAP_BOTTOM],
outputRange: [0, 1],
extrapolate: Extrapolate.CLAMP,
});
const overlayOpacity = interpolate(translateY, {
inputRange: [
SNAP_BOTTOM - MINI_PLAYER_HEIGHT * 2,
SNAP_BOTTOM - MINI_PLAYER_HEIGHT,
],
outputRange: [0, 1],
extrapolate: Extrapolate.CLAMP,
});
const clock = new Clock();
useEffect(() => {
if (audioPost && shouldLoad) {
initPlaybackInstance();
dispatch(postIsLoading());
}
}, [shouldLoad]);
const initPlaybackInstance = async () => {
if (playbackInstance != null) {
playbackInstance.unloadAsync();
}
await Audio.setAudioModeAsync({
allowsRecordingIOS: false,
interruptionModeIOS: Audio.INTERRUPTION_MODE_IOS_DO_NOT_MIX,
playsInSilentModeIOS: true,
interruptionModeAndroid: Audio.INTERRUPTION_MODE_ANDROID_DO_NOT_MIX,
shouldDuckAndroid: false,
staysActiveInBackground: true,
playThroughEarpieceAndroid: true,
});
await loadAudio();
};
const loadAudio = async () => {
const audioInstance = new Audio.Sound();
const source = {
uri: storedTrackBySlug[audioPost.slug]?.uri ?? audioRoute(audioPost.slug),
};
const status = {
shouldPlay: true,
volume: volume,
};
audioInstance.setOnPlaybackStatusUpdate(onPlaybackStatusUpdate);
await audioInstance.loadAsync(source, status, false);
setPlaybackInstance(audioInstance);
};
const onPlaybackStatusUpdate = (status): void => {
setIsBuffering(status.isBuffering);
setIsPlaying(status.isPlaying);
setCurrentPositionMillis(status.positionMillis);
setTotalDurationMillis(status.durationMillis);
setRate(status.rate);
};
// PERHAPS REMOVE USE CODE AND ONLY USE CLICK FROM SNACK EXAMPLE AND SWIPE FROM SPOTIFY EXAMPLE
useCode(() => {
console.log('useCode CALLED');
console.log(JSON.stringify(goUp));
return block([
cond(goUp, [
set(
offset,
timing({
clock,
from: offset,
to: SNAP_TOP,
})
),
cond(not(clockRunning(clock)), [set(goUp, 0)]),
]),
cond(goDown, [
set(
offset,
timing({
clock,
from: offset,
to: SNAP_BOTTOM,
})
),
cond(not(clockRunning(clock)), [set(goDown, 0)]),
]),
]);
}, []);
return (
<PanGestureHandler {...gestureHandler}>
<Animated.View
style={[styles.playerSheet, { transform: [{ translateY }] }]}
>
<FullPlayer
post={audioPost}
isPlaying={isPlaying}
isBuffering={isBuffering}
currentPositionMillis={currentPositionMillis}
totalDurationMillis={totalDurationMillis}
rate={rate}
handleSkipBack={handleSkipBack}
handleSkipForward={handleSkipForward}
handlePlayPause={handlePlayPause}
seekPositionBySeconds={seekPositionBySeconds}
increaseRateByQuarter={increaseRateByQuarter}
onPress={() => {
console.log('AUDIOPLAYER CLICKED');
goDown.setValue(1);
}}
/>
<Animated.View
style={{
...StyleSheet.absoluteFillObject,
opacity: overlayOpacity,
}}
pointerEvents='none'
/>
<Animated.View
style={{
opacity: miniPlayerOpacity,
position: 'absolute',
top: 0,
left: 0,
right: 0,
height: MINI_PLAYER_HEIGHT,
}}
>
<MiniPlayer
postTitle={audioPost?.title}
isPlaying={isPlaying}
isBuffering={isBuffering}
handlePlayPause={handlePlayPause}
onPress={() => {
console.log('MINIAUDIOPLAYER CLICKED');
goUp.setValue(1);
//setIsPlayerUp(!isPlayerUp);
}}
/>
</Animated.View>
</Animated.View>
</PanGestureHandler>
);
};
const styles = StyleSheet.create({
thumbnailOverlay: {
...StyleSheet.absoluteFillObject,
padding: 16,
},
scroll: {
flex: 1,
marginHorizontal: 10,
padding: 16,
overflow: 'hidden',
},
playerSheet: {
...StyleSheet.absoluteFillObject,
backgroundColor: 'cyan',
},
});
export default Player;