今天我们做歌曲的单曲循环,按序播放,随机播放以及通过手动点击上一首,下一首这些功能哈,下一篇博客就写我们歌词滚动功能
由于我每篇都和前面是联系在一起的,如果想获取整个项目,可以去我的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下载源码来进行学习