成果展示
昨天又玩了一下,把播放歌曲功能做了,现在可以播放歌曲,导航条会跟随歌曲时间前进,也可以拖拽进度条跳到某个节点继续播放,虽然这样对普通开发没有打作用,但可以提高前端对js的控制能力和数据的处理能力。通过这个项目我也多多少少在开发上了点新的认识,希望你们一样。
/* 导入组件 */
import React, { memo, useState, useEffect, useRef, useCallback } from 'react';
import { shallowEqual, useDispatch, useSelector } from 'react-redux';
import { NavLink } from 'react-router-dom';
import { Slider } from 'antd';
/* 导入功能 */
import { getSongDetailAction } from '../store/actionCreators';
import { getSizeImage, formatDate, getPlaySong } from '@/utils/format-utils';
/* 导入UI */
import { PlaybarWrapper, Control, PlayInfo, Operator } from './style';
/* 组件:底部播放条 */
export default memo(function RTAppPlayBar() {
/* 组件 state 模块 */
// hooks:useState 缓存数据
const [currentTime, setCurrentTime] = useState(0); // 当前播放时间
const [progress, setProgress] = useState(0); // 进度条当前位置
const [isChanging, setIsChanging] = useState(false); // 进度条是否被拖拽
const [isPlaying, setIsPlaying] = useState(false); // 进度条是否被拖拽
/* 共享 state 模块 */
// 使用 redux-hooks:useSelector 获取共享数据-当前播放歌曲
const { currentSong } = useSelector(state => ({
currentSong: state.getIn(["player", "currentSong"])
}), shallowEqual); // 共享数据浅对比,改变则重绘
/* 默认参数模块 */
const picUrl = (currentSong.al && currentSong.al.picUrl) || ""; // 歌手图像url
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"); // 显示当前播放时间
/* 功能模块 */
// 使用 redux-hooks:useDispatch 获取 dispatch
const dispatch = useDispatch();
// 使用 hooks:useEffect 函数挂载后执行
// 获取播放歌曲明细请求接口
useEffect(() => {
dispatch(getSongDetailAction(167876))
}, [dispatch])
// 根据id请求歌曲mp3文件
useEffect(() => {
audioRef.current.src = getPlaySong(currentSong.id);
}, [currentSong])
// 操作播放器 DOM
const audioRef = useRef();
// 播放功能
const playMpusic = useCallback(() => {
isPlaying ? audioRef.current.pause() : audioRef.current.play(); // true:播放 false:暂停
setIsPlaying(!isPlaying);
},[isPlaying])
// 时间更新功能
const tiemUpdate = (e) => {
if (!isChanging) { // 进度条没被拖拽时更新
setCurrentTime(e.target.currentTime * 1000); // 设置当前播放时间
setProgress(currentTime / duration * 100); // 当进度条没有被拖动时更新进度条当前位置
}
}
// 当函数传入子组件时,父组件重绘函数也会被重新生成,新的函数与旧函数地址不一致,子组件也会触发重绘。
// 使用 hooks:useCallback 包裹,会将函数保存到缓存,父组件重绘后返回同一个函数,则不会引发子组件重绘提高性能
// 拖动进度条改变事件
const sliderChange = useCallback((value) => {
setIsChanging(true); // 设置进度条正在被拖拽
const currentTime = value / 100 * duration / 1000; // 计算当前播放时间
setCurrentTime(currentTime * 1000); // 设置当前播放时间
setProgress(value); // 设置进度条当前位置
}, [duration]); // 歌曲总时长改变则重绘
// 鼠标放开进度条改变事件
const sliderAfterChange = useCallback((value) => {
const currentTime = value / 100 * duration / 1000; // 计算当前播放时间
setCurrentTime(currentTime * 1000); // 设置当前播放时间
audioRef.current.currentTime = currentTime; // 设置进度条当前位置与歌曲播放位置
setIsChanging(false); // 设置进度条没被拖拽
if(!isPlaying) playMpusic();
}, [duration, isPlaying, playMpusic]); // 歌曲总时长改变则重绘
/* UI布局模块 */
return (
<PlaybarWrapper className="sprite_player">
<div className="content wrap-v2">
<Control isPlaying={isPlaying}>
<button className="sprite_player prev"></button>
<button className="sprite_player play" onClick={e => playMpusic()}></button>
<button className="sprite_player next"></button>
</Control>
<PlayInfo>
<div className="image">
<NavLink to="/discover/player" >
<img src={getSizeImage(picUrl, 35)} alt="" />
</NavLink>
</div>
<div className="info">
<div className="song">
<span className="song-name">{currentSong.name}</span>
<a href="/todo" className="singer-name">{singerName}</a>
</div>
<div className="progress">
<Slider defaultValue={30}
value={progress}
onChange={sliderChange}
onAfterChange={sliderAfterChange} />
<div className="time">
<span className="now-time">{showCurrentTime}</span>
<span className="divider">/</span>
<span className="duration">{showDuration}</span>
</div>
</div>
</div>
</PlayInfo>
<Operator>
<div className="left">
<button className="sprite_player btn favor"></button>
<button className="sprite_player btn share"></button>
</div>
<div className="right sprite_player">
<button className="sprite_player btn volume"></button>
<button className="sprite_player btn loop"></button>
<button className="sprite_player btn playlist"></button>
</div>
</Operator>
</div>
<audio ref={audioRef} onTimeUpdate={tiemUpdate} />
</PlaybarWrapper>
)
})