问题描述
所以,问题来了。我在我的反应项目中使用音频 html5 元素。 问题的典型流程是: 这首歌正在播放。 用户按下暂停键(假设时间是 1.20)。 用户锁定电话。 几分钟后,用户解锁手机,按下“播放”按钮,会发生什么: mediaPositionState 将当前时间计数为先前(1.20)加上音频的当前时间,而不是仅计算当前时间。 即使在更改歌曲时,这个额外的 1.20 仍然存在。 我已经尝试在下面的 useEffect 中控制它
useEffect(() => {
const audioEl = audioRef.current;
if (audioEl) {
audioEl.addEventListener('timeupdate',updateTime);
audioEl.addEventListener('loadeddata',updatePositionState);
}
return () => {
if (audioEl) {
audioEl.removeEventListener('timeupdate',updateTime);
audioEl.removeEventListener('loadeddata',updateTime);
updatePositionState();
}
};
},[]);
但只有当用户专注于音频时它才能正常工作。
我还有以下代码:
function updatePositionState() {
if (navigator.mediaSession?.setPositionState) {
navigator.mediaSession.setPositionState({
duration: audioRef.current?.duration ?? 0.0,position: audioRef.current?.currentTime ?? 0.0,});
}
}
const createMediaSession = (state: AudioStateType) => {
if (navigator.mediaSession) {
navigator.mediaSession.Metadata = new MediaMetadata({
title: state.currentSongName,artist: state.currentArtistName,album: state.currentAlbumName,artwork: [
{
sizes: '300x300',src: `http://storage.musicstream.app/cover/${state.currentAlbumCoverId}`,},],});
navigator.mediaSession.setActionHandler('play',function () {
dispatch({ type: 'resume' });
updatePositionState();
});
navigator.mediaSession.setActionHandler('pause',function () {
dispatch({ type: 'pause' });
updatePositionState();
});
navigator.mediaSession.setActionHandler('seekto',function (details) {
dispatch({ type: 'manual_update_time',time: details.seekTime });
updatePositionState();
});
navigator.mediaSession.setActionHandler('prevIoUstrack',() => {
return 0;
});
navigator.mediaSession.setActionHandler('nexttrack',() => {
return 0;
});
}
};
我不知道如何正常描述问题,我们假设当用户刷出 MediaSession 通知时 mediaposition 混乱。 如果你问,我会提供更多的代码。 我还提供了屏幕截图(尽管我试图强制出现类似的问题:它显示时间为轨道的结尾)。
根据请求添加updateTime函数 它只是为了更新 react.Context 中的状态
const updateTime = () => {
if (audioRef.current) {
dispatch({ type: 'update_time',time: audioRef.current.currentTime });
}
};
此外,完整的减速器看起来像这样(我认为这不会有帮助):
function audioReducer(state: AudioStateType,action: Action): AudioStateType {
switch (action.type) {
case 'fetch_and_play': {
play(action.songData?.currentSongId).then(() => {
dispatch({
type: 'play',songData: {
...action.songData,length: audioRef.current?.duration,});
});
return state;
}
case 'play': {
createMediaSession({ ...state,...action.songData });
return { ...state,...action.songData };
}
case 'pause': {
pause();
return { ...state,songIsPaused: true };
}
case 'resume': {
resume();
return { ...state,songIsPaused: false };
}
case 'update_time': {
return { ...state,currentTime: action.time };
}
case 'manual_update_time': {
if (audioRef.current) {
audioRef.current.currentTime = action.time;
return { ...state,currentTime: action.time };
} else {
return state;
}
}
default: {
return state;
}
}
}
我做了一个代码沙盒,你可以在那里看到我的问题。 https://codesandbox.io/s/wizardly-wiles-87qi1?file=/src/App.js 为了真正理解请使用安卓手机
解决方法
Reducer 应该是Pure function,在reducer 之前为dispatch 和其他人员使用action:
Action.ts
const fetchAndPlay = (currentSongId) => {
play(currentSongId).then(() => {
createMediaSession({ ...state,...action.songData });
dispatch({
type: 'play',//just pass data u need to show or you want rerender them
length: audioRef.current?.duration,});
});
}
Reducer.ts
function audioReducer(state: AudioStateType,action: Action): AudioStateType {
switch (action.type) {
case 'play':
return { ...state,length:action.length,playing: true};
...
}
在你的组件中调用这个动作:
fetchAndPlay(songId)