12-5 RN之播放 上一首、下一首

71 阅读2分钟

在这一节中,我们将实现音频的上一首和下一首功能。点击按钮播放上一首或下一首时,我们需要获取上一首和下一首的音频信息。

1. 保存上一首、下一首音频的信息

首先,在跳转到频道详情页面时,将上一首和下一首的音频 ID 保存在页面的 model 中。

const mapStateToProps = ({ album }: RootState) => ({
  list: album.list,
});

onItemPress = (item: IAlbum, index: number) => {
  const { navigation, dispatch, list } = this.props;
  const previousItem: IAlbum = list[index - 1];
  const nextItem: IAlbum = list[index + 1];
  
  dispatch({
    type: 'player/setState',
    payload: {
      previousId: previousItem ? previousItem.id : '',
      nextId: nextItem ? nextItem.id : '',
    },
  });
};

为了避免点击一次后无法获取下一首的音频信息,我们需要将整个音频列表保存在 model 中:

playList: list.map(item => ({ id: item.id, title: item.title })),

model 中定义类型:

export interface PlayerModelState {
  previousId: string;
  nextId: string;
  playList: { id: string; title: string }[];
}

const initialState: PlayerModelState = {
  previousId: '',
  nextId: '',
  playList: [],
};

2. 添加 nextprevious 函数

effects 中声明 nextprevious 函数:

effects: {
  next: Effect;
  previous: Effect;
};

为了避免同时播放多首音频,我们需要在切换音频时先停止之前的音频。我们在 sound.ts 中添加 stop 函数:

const stop = () => {
  return new Promise(resolve => {
    if (sound) {
      sound.stop(() => {
        resolve();
      });
    } else {
      resolve();
    }
  });
};

export { stop };

3. 实现上一首功能

effects 中定义上一首的逻辑,暂停当前音频并切换到上一首:

*previous(payload, { put, call, select }) {
  yield call(stop);

  const { id, sounds }: PlayerModelState = yield select(({ player }: RootState) => player);
  const index = sounds.findIndex(item => item.id === id);
  let currentIndex = index - 1;
  const currentItem = playList[currentIndex];
  const previousItem = playList[currentIndex - 1];

  yield put({
    type: 'setState',
    payload: {
      playState: 'paused',
      id: currentItem.id,
      title: currentItem.title,
      previousId: previousItem ? previousItem.id : '',
      nextId: id,
    },
  });

  yield put({
    type: 'fetchShow',
    payload: { id: currentItem.id },
  });
}

4. 实现下一首功能

下一首的实现与上一首类似,只需调整音频索引:

*next(payload, { put, call, select }) {
  yield call(stop);

  const { id, sounds }: PlayerModelState = yield select(({ player }: RootState) => player);
  const index = sounds.findIndex(item => item.id === id);
  let currentIndex = index + 1;
  const currentItem = playList[currentIndex];
  const nextItem = playList[currentIndex + 1];

  yield put({
    type: 'setState',
    payload: {
      playState: 'paused',
      id: currentItem.id,
      title: currentItem.title,
      previousId: id,
      nextId: id,
    },
  });

  yield put({
    type: 'fetchShow',
    payload: { id: currentItem.id },
  });
}

5. 添加上一首和下一首按钮

PlayControl 组件中,添加上一首和下一首按钮,并根据 previousIdnextId 的状态禁用按钮:

const { previousId, nextId } = this.props;

<View style={styles.control}>
  <Touchable
    onPress={this.previous}
    style={styles.button}
    disabled={!previousId}>
    <Icon name="icon-shangyishou" size={30} color="#fff" />
  </Touchable>
  <Touchable
    onPress={this.next}
    style={styles.button}
    disabled={!nextId}>
    <Icon name="icon-xiayishou" size={30} color="#fff" />
  </Touchable>
</View>

const styles = StyleSheet.create({
  control: {
    flexDirection: 'row',
    justifyContent: 'space-between',
    alignItems: 'center',
    marginVertical: 15,
    marginHorizontal: 90,
  },
  button: {
    marginHorizontal: 10,
  },
});

在点击按钮时触发 previousnext 函数:

previous = () => {
  const { dispatch } = this.props;
  dispatch({
    type: 'player/previous',
  });
};

next = () => {
  const { dispatch } = this.props;
  dispatch({
    type: 'player/next',
  });
};

6. 更新标题栏

在页面加载时,我们将当前播放音频的标题显示在标题栏中:

const mapStateToProps = ({ player, loading }: RootState) => ({
  title: player.title,
});

componentDidMount() {
  const { navigation, title } = this.props;
  navigation.setOptions({
    headerTitle: title,
  });
}

componentDidUpdate(prevProps: IProps) {
  if (prevProps.title !== this.props.title) {
    this.props.navigation.setOptions({
      headerTitle: this.props.title,
    });
  }
}

7. 自定义 Touchable 组件

为了控制按钮的禁用样式,我们自定义了 Touchable 组件:

const Touchable: React.FC<TouchableOpacityProps> = React.memo(props => {
  const { style, disabled, ...rest } = props;
  const disabledStyle = disabled && styles.disabled;
  return (
    <TouchableOpacity
      disabled={disabled}
      style={[style, disabledStyle]}
      activeOpacity={0.8}
      {...rest}
    />
  );
});

const styles = StyleSheet.create({
  disabled: {
    opacity: 0.5,
  },
});

总结

本节实现了上一首和下一首功能,并通过按钮进行控制。通过调整音频索引和暂停当前音频来切换音频。同时,还更新了标题栏显示当前播放的音频标题。下一节我们将实现图片动画和渐变色效果。