React实现(Web端)网易云音乐项目(五),错过了真的可惜呀

今天我们做歌曲的单曲循环,按序播放,随机播放以及通过手动点击上一首,下一首这些功能哈,下一篇博客就写我们歌词滚动功能
在这里插入图片描述

由于我每篇都和前面是联系在一起的,如果想获取整个项目,可以去我的github下载源码

首先到我们的组件中,分三步来写(之前的逻辑我没删除)

第一步,导入的配置

import React, { memo, useState, useEffect, useRef, useCallback } from 'react';
import { useDispatch, useSelector, shallowEqual } from 'react-redux';

import { getSizeImage, formatDate, getPlaySong } from '@/utils/format-utils';

import { NavLink } from 'react-router-dom';
import { Slider,message } from 'antd';

import { getSongDetailAction,
         changeSequenceAction,
         changeCurrentIndexAndSongAction,
         changeCurrentLyricIndexAction } 
  from '../store/actionCreators';
import {
  PlaybarWrapper,
  Control,
  PlayInfo,
  Operator
} from './style';

这些就没必要说了

第二步,我们的逻辑代码

 // props and state
   const [currentTime, setCurrentTime] = useState(0);
   const [progress, setProgress] = useState(0);
   const [isChanging, setIsChanging] = useState(false);
   const [isPlaying, setIsPlaying] = useState(false);
   const [showPanel, setShowPanel] = useState(false);
 
  // redux hook
  const { currentSong,sequence,playList,lyricList,currentLyricIndex } = useSelector(state => ({
    playList:state.getIn(["player","playList"]),
    currentSong: state.getIn(["player", "currentSong"]),
    sequence: state.getIn(["player", "sequence"]),
    lyricList: state.getIn(["player", "lyricList"]),
    currentLyricIndex: state.getIn(["player", "currentLyricIndex"])
  }), shallowEqual);
  const dispatch = useDispatch();
  
  // other hooks
  const audioRef = useRef();
  useEffect(() => {
    dispatch(getSongDetailAction(167876));
  }, [dispatch]);
  useEffect(() =>  {
    audioRef.current.src = getPlaySong(currentSong.id);
    audioRef.current.play().then(res => {
      setIsPlaying(true);
    }).catch(err => {
      setIsPlaying(false);
    });
  }, [currentSong]);
  
  // other handle
  const picUrl = (currentSong.al && currentSong.al.picUrl) || "";
  const singerName = (currentSong.ar && currentSong.ar[0].name) || "未知歌手";
  const duration = currentSong.dt || 0;
  const showDuration = formatDate(duration, "mm:ss");
  const showCurrentTime = formatDate(currentTime, "mm:ss");

    // handle function
    const playMusic = useCallback(() => {
      isPlaying ? audioRef.current.pause(): audioRef.current.play();
      setIsPlaying(!isPlaying);
    },[isPlaying])

    const timeUpdate = (e) => {
      const currentTime = e.target.currentTime;
      if (!isChanging) {
        setCurrentTime(e.target.currentTime * 1000);
        setProgress(currentTime * 1000 / duration * 100);
      }
    }

    //变换0,1,2,循环,单曲等
    const changeSequence = () => {
      let currentSequence = sequence + 1;
      if (currentSequence > 2) {
        currentSequence = 0;
      }
      dispatch(changeSequenceAction(currentSequence));
    }
    //上一首,下一首
    const changeMusic = (tag) => {
      dispatch(changeCurrentIndexAndSongAction(tag));
    }
    //歌曲播放完之后
    const handleMusicEnded = () => {
      if (sequence === 2) { // 单曲循环
        audioRef.current.currentTime = 0;
        audioRef.current.play();
      } else {
        dispatch(changeCurrentIndexAndSongAction(1));
      }
    }

    //useCallback:当把一个回调函数传到一个自定义组件内部时候用
    const sliderChange = useCallback((value) => {
      setIsChanging(true);
      const currentTime = value / 100 * duration;
      setCurrentTime(currentTime);
      setProgress(value);
    }, [duration]);

    const sliderAfterChange = useCallback((value) => {
      const currentTime = value / 100 * duration / 1000;
      audioRef.current.currentTime = currentTime;
      setCurrentTime(currentTime * 1000);
      setIsChanging(false);
  
      if (!isPlaying) {
        playMusic();
      }
    }, [duration,isPlaying, playMusic]);

我就说一下,跟我们这个demo有关的一些

 //变换0,1,2,循环,单曲等
    const changeSequence = () => {
      let currentSequence = sequence + 1;
      if (currentSequence > 2) {
        currentSequence = 0;
      }
      dispatch(changeSequenceAction(currentSequence));
    }
    //上一首,下一首
    const changeMusic = (tag) => {
      dispatch(changeCurrentIndexAndSongAction(tag));
    }
    //歌曲播放完之后
    const handleMusicEnded = () => {
      if (sequence === 2) { // 单曲循环
        audioRef.current.currentTime = 0;
        audioRef.current.play();
      } else {
        dispatch(changeCurrentIndexAndSongAction(1));
      }
    }

我们在redux里面定义了一个变量sequence

sequence: 0, // 0 循环 1 随机 2 单曲

解析:

changeSequence点击事件:我们初始化是0,也就是最开始的时候是根据列表顺序循环,点击一次就+1,也就是成随机循环了,依次类推三个循环,再通过dispatch改变我们redux里面的sequence

store里面

reducer.js

import { Map } from 'immutable';

import * as actionTypes from './constants';

const defaultState = Map({
  sequence: 0, // 0 循环 1 随机 2 单曲
});

function reducer(state = defaultState, action) {
  switch(action.type) {
    case actionTypes.CHANGE_SEQUENCE:
      return state.set("sequence", action.sequence);
    default:
      return state;
  }
}

export default reducer;

constants.js

export const CHANGE_SEQUENCE = "player/CHANGE_SEQUENCE";

actionCreators.js

export const changeSequenceAction = (sequence) => ({
  type: actionTypes.CHANGE_SEQUENCE,
  sequence
});

然后回到我的们组件

  //上一首,下一首
    const changeMusic = (tag) => {
      dispatch(changeCurrentIndexAndSongAction(tag));
    }

解析

这两个button,第一个点击就是上一首,第二个下一首

<button className="sprite_player prev" onClick={e => changeMusic(-1)}></button>
 <button className="sprite_player next" onClick={e => changeMusic(1)}></button>

通过点击事件再发送一个dispatch,changeCurrentIndexAndSongAction把tag传过去

到我们的actionCreators.js

export const changeCurrentIndexAndSongAction = (tag) =>{
  return (dispatch, getState) => {
    const playList = getState().getIn(["player", "playList"]);
    const sequence = getState().getIn(["player", "sequence"]);
    let currentSongIndex = getState().getIn(["player", "currentSongIndex"]);

    switch (sequence) {
      case 1:
        let randomIndex = getRandomNumber(playList.length);
        while (randomIndex === currentSongIndex) {
          randomIndex = getRandomNumber(playList.length);
        }
        currentSongIndex = randomIndex;
        break;
      default: 
        currentSongIndex += tag;
        if (currentSongIndex >= playList.length) currentSongIndex = 0;
        if (currentSongIndex < 0) currentSongIndex = playList.length - 1;
    }
    const currentSong = playList[currentSongIndex];
    dispatch(changeCurrentSongAction(currentSong));
    dispatch(changeCurrentSongIndexAction(currentSongIndex));
  }
}

解析1

先获取到我们的playList,这个是我们的歌曲数组,sequence哪个状态,currentSongIndex是歌曲的下标,也是在redux中定义的

解析2

先通过判断sequence是多少,随机播放,这个地方需要一个随机数

getRandomNumber

export function getRandomNumber(num) {
  return Math.floor(Math.random() * num);
}

然后是这个

  //歌曲播放完之后
    const handleMusicEnded = () => {
      if (sequence === 2) { // 单曲循环
        audioRef.current.currentTime = 0;
        audioRef.current.play();
      } else {
        dispatch(changeCurrentIndexAndSongAction(1));
      }
    }

解析:

当我们不点击,等歌曲播放完之后,这个要根据我们选定点哪种状态,是循环,随机还是啥的来进行下一步

<audio  onEnded={e => handleMusicEnded()}/>

总的来说,跟前面几篇博客关系都太紧密了,可能分开后,有些没有写好,如果有不懂的可以去我github下载源码来进行学习

github项目地址:https://github.com/lsh555/WYY-Music

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值