Reputation: 46
So, here is the problem. I'm using audio html5 element in my react project. The typical flow for problem is: the song is playing. User presses pause on some time(let's say that time is 1.20). User locks the phone. After several minutes user unlocks his phone, presses "Play" button and here what happens: The mediaPositionState count current time as previous(1.20) PLUS current time of the audio, instead of counting just current. This extra 1.20 is remaining even when changing songs. I've tried to control it in useEffect below
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();
}
};
}, []);
but it works normally only when user is in focus with audio.
Also I have following code:
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;
});
}
};
I don't know how to normally describe the problem, let's assume that mediaposition messes up when user swipes out the MediaSession notification. I will provide more code if you ask. Also I provide the screenshots(despite I tried to force the problem similar one occured: it shows time as the end of track).
current song time is okay when paused
current song time is messed when playing
Adding updateTime function by request It is just for updating state in react.Context
const updateTime = () => {
if (audioRef.current) {
dispatch({ type: 'update_time', time: audioRef.current.currentTime });
}
};
Also, the full reducer looks like this(I don't think it would be helpful):
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;
}
}
}
I made a codesandbox, where you can see my problem. https://codesandbox.io/s/wizardly-wiles-87qi1?file=/src/App.js In order to truly understand please use an android phone
Upvotes: 1
Views: 499
Reputation: 4588
Reducer should be Pure function, use action before reducer for dispatch and other staff:
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};
...
}
In your component just call this action :
fetchAndPlay(songId)
Upvotes: 1