Reanimated 和 Expo 音频出现问题

时间:2021-02-27 17:42:24

标签: ios react-native expo expo-av react-native-reanimated-v2

我正在构建一个具有两个视图的音频播放器,一个迷你播放器(底部选项卡)和一个全屏播放器。滑动或点击以在视图之间切换。当我重新加载我的应用程序时,一切正常,我的占位符封面艺术在播放器上,我可以上下滑动。但是当我加载播放实例时,所有功能都会停止。没有滑动,没有点击。我的 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;

0 个答案:

没有答案