本节代码对应 GitHub 分支: chapter9
# 播放器逻辑
首先,将 Player/index.js 中的获取歌词的代码完善一下。
// 记得引入插件
import Lyric from './../../api/lyric-parser';
const handleLyric = ({lineNum, txt}) => {
if (!currentLyric.current) return;
currentLineNum.current = lineNum;
setPlayingLyric (txt);
};
const getLyric = id => {
let lyric = "";
if (currentLyric.current) {
currentLyric.current.stop ();
}
// 避免 songReady 恒为 false 的情况
getLyricRequest (id)
.then (data => {
lyric = data.lrc.lyric;
if (!lyric) {
currentLyric.current = null;
return;
}
currentLyric.current = new Lyric (lyric, handleLyric);
currentLyric.current.play ();
currentLineNum.current = 0;
currentLyric.current.seek (0);
})
.catch (() => {
songReady.current = true;
audioRef.current.play ();
});
};
对于歌词功能,已经有了一个 currentLyric 对象,但同时我们还有一条即时歌词,因此要再声明一个 currentPlayingLyric 变量:
const [currentPlayingLyric, setPlayingLyric] = useState ("");
当然,还有一个记录当前行数的 currentLineNum:
const currentLineNum = useRef (0);
然后,将这些属性传递给 nornalPlayer 处理:
<NormalPlayer
//...
currentLyric={currentLyric.current}
currentPlayingLyric={currentPlayingLyric}
currentLineNum={currentLineNum.current}
></NormalPlayer>
对于歌曲播放的过程,还有两个非常重要的逻辑需要处理,一个是歌曲暂停,一个是歌曲进度更新,这两种情况,歌词都是需要跟着改变的。
歌曲暂停 / 播放:
const clickPlaying = (e, state) => {
//...
if (currentLyric.current) {
currentLyric.current.togglePlay (currentTime*1000);
}
};
歌曲进度更新:
const onProgressChange = curPercent => {
//...
if (currentLyric.current) {
currentLyric.current.seek (newTime * 1000);
}
};
# normalPlayer 中集成
先从父组件接收歌词相关的属性:
const {
currentLineNum,
currentPlayingLyric,
currentLyric
} = props;
我们希望点击中间的 CD 之后切换为歌词,因此中间部分可以保存一个状态,根据它来显示不同的内容。
import Scroll from "../../../baseUI/scroll";
import { LyricContainer, LyricWrapper } from "./style";
const currentState = useRef ("");
const lyricScrollRef = useRef ();
const lyricLineRefs = useRef ([]);
// 在 Middle 组件内
<Middle ref={cdWrapperRef} onClick={toggleCurrentState}>
<CSSTransition
timeout={400}
classNames="fade"
in={currentState.current !== "lyric"}
>
<CDWrapper style={{visibility: currentState.current !== "lyric" ? "visible" : "hidden"}}>
// 其余跟以前保持一致
<p className="playing_lyric">{currentPlayingLyric}</p>
</CDWrapper>
</CSSTransition>
<CSSTransition
timeout={400}
classNames="fade"
in={currentState.current === "lyric"}
>
<LyricContainer>
<Scroll ref={lyricScrollRef}>
<LyricWrapper
style={{visibility: currentState.current === "lyric" ? "visible" : "hidden"}}
className="lyric_wrapper"
>
{
currentLyric
? currentLyric.lines.map ((item, index) => {
// 拿到每一行歌词的 DOM 对象,后面滚动歌词需要!
lyricLineRefs.current [index] = React.createRef ();
return (
<p
className={`text ${
currentLineNum === index ? "current" : ""
}`}
key={item + index}
ref={lyricLineRefs.current [index]}
>
{item.txt}
</p>
);
})
: <p className="text pure"> 纯音乐,请欣赏。</p>}
</LyricWrapper>
</Scroll>
</LyricContainer>
</CSSTransition>
</Middle>
对应的 style.js 中,相应的样式代码如下:
export const LyricContainer = styled.div`
position: absolute;
left: 0;
right: 0;
top: 0;
bottom: 0;
`;
export const LyricWrapper = styled.div`
position: absolute;
left: 0;
right: 0;
width: 100%;
box-sizing: border-box;
text-align: center;
p {
line-height: 32px;
color: rgba (255, 255, 255, 0.5);
white-space: normal;
font-size: ${style ["font-size-l"]};
&.current {
color: #fff;
}
&.pure {
position: relative;
top: 30vh;
}
}
`;
其中,toggleCurrentState 为改变 Middle 状态的方法,定义如下:
const toggleCurrentState = () => {
if (currentState.current !== "lyric") {
currentState.current = "lyric";
} else {
currentState.current = "";
}
};
这个时候打开播放器,可以完整的看到歌词了,但是你滑动进度条,歌词并没有跟着动。那这是什么原因呢?
因为父组件 currentLine 已经改变,而 normalPlayer 的歌词并没有滚动到相应位置。
现在我们就来监听 currentLine 变量,当它改变时,来进行一些歌词滚动操作。
import { useEffect } from "react";
useEffect (() => {
if (!lyricScrollRef.current) return;
let bScroll = lyricScrollRef.current.getBScroll ();
if (currentLineNum > 5) {
// 保持当前歌词在第 5 条的位置
let lineEl = lyricLineRefs.current [currentLineNum - 5].current;
bScroll.scrollToElement (lineEl, 1000);
} else {
// 当前歌词行数 <=5, 直接滚动到最顶端
bScroll.scrollTo (0, 0, 1000);
}
}, [currentLineNum]);
现在歌词的功能就非常正常了。
不过还有一个小小的 bug,当在歌词界面退出播放器的时候,下次进来的时候并不是 CD 先进来,我们在退出播放器的时候将状态还原。
const afterLeave = () => {
//...
currentState.current = "";
};
到目前为止,歌词的功能就集成完毕了。从下小节开始,我们进入到搜索模块的开发。大家加油!
阅读全文