Fixes to voice notes playback
This commit is contained in:
parent
fad0529080
commit
3d4248e070
9 changed files with 285 additions and 274 deletions
|
@ -1,22 +1,154 @@
|
|||
// Copyright 2021 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
import { connect } from 'react-redux';
|
||||
import { mapDispatchToProps } from '../actions';
|
||||
import React, { useEffect } from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
import type { VoiceNotesPlaybackProps } from '../../components/VoiceNotesPlaybackContext';
|
||||
import { VoiceNotesPlaybackProvider } from '../../components/VoiceNotesPlaybackContext';
|
||||
import type { StateType } from '../reducer';
|
||||
import { getSelectedConversationId } from '../selectors/conversations';
|
||||
import { isPaused } from '../selectors/audioPlayer';
|
||||
import { selectAudioPlayerActive } from '../selectors/audioPlayer';
|
||||
import { useAudioPlayerActions } from '../ducks/audioPlayer';
|
||||
import { globalMessageAudio } from '../../services/globalMessageAudio';
|
||||
import { strictAssert } from '../../util/assert';
|
||||
import * as log from '../../logging/log';
|
||||
import { Sound } from '../../util/Sound';
|
||||
import { getConversations } from '../selectors/conversations';
|
||||
import { SeenStatus } from '../../MessageSeenStatus';
|
||||
import { markViewed } from '../ducks/conversations';
|
||||
import * as Errors from '../../types/errors';
|
||||
import { usePrevious } from '../../hooks/usePrevious';
|
||||
|
||||
const mapStateToProps = (state: StateType) => {
|
||||
return {
|
||||
conversationId: getSelectedConversationId(state),
|
||||
isPaused: isPaused(state),
|
||||
};
|
||||
};
|
||||
const stateChangeConfirmUpSound = new Sound({
|
||||
src: 'sounds/state-change_confirm-up.ogg',
|
||||
});
|
||||
const stateChangeConfirmDownSound = new Sound({
|
||||
src: 'sounds/state-change_confirm-down.ogg',
|
||||
});
|
||||
|
||||
const smart = connect(mapStateToProps, mapDispatchToProps);
|
||||
/**
|
||||
* Synchronizes the audioPlayer redux state with globalMessageAudio
|
||||
*/
|
||||
export function SmartVoiceNotesPlaybackProvider(
|
||||
props: VoiceNotesPlaybackProps
|
||||
): JSX.Element | null {
|
||||
const active = useSelector(selectAudioPlayerActive);
|
||||
const conversations = useSelector(getConversations);
|
||||
|
||||
export const SmartVoiceNotesPlaybackProvider = smart(
|
||||
VoiceNotesPlaybackProvider
|
||||
);
|
||||
const previousStartPosition = usePrevious(undefined, active?.startPosition);
|
||||
|
||||
const content = active?.content;
|
||||
const current = content?.current;
|
||||
const url = current?.url;
|
||||
|
||||
const {
|
||||
messageAudioEnded,
|
||||
currentTimeUpdated,
|
||||
durationChanged,
|
||||
unloadMessageAudio,
|
||||
} = useAudioPlayerActions();
|
||||
|
||||
useEffect(() => {
|
||||
// if we don't have a new audio source
|
||||
// just control playback
|
||||
if (!content || !current || !url || url === globalMessageAudio.url) {
|
||||
if (!active?.playing && globalMessageAudio.playing) {
|
||||
globalMessageAudio.pause();
|
||||
}
|
||||
|
||||
if (active?.playing && !globalMessageAudio.playing) {
|
||||
globalMessageAudio.play();
|
||||
}
|
||||
|
||||
if (active && active.playbackRate !== globalMessageAudio.playbackRate) {
|
||||
globalMessageAudio.playbackRate = active.playbackRate;
|
||||
}
|
||||
|
||||
if (
|
||||
active &&
|
||||
active.startPosition !== undefined &&
|
||||
active.startPosition !== previousStartPosition
|
||||
) {
|
||||
globalMessageAudio.currentTime =
|
||||
active.startPosition * globalMessageAudio.duration;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// otherwise we have a new audio source
|
||||
// we just load it and play it
|
||||
globalMessageAudio.load({
|
||||
url,
|
||||
playbackRate: active.playbackRate,
|
||||
onLoadedMetadata() {
|
||||
strictAssert(
|
||||
!Number.isNaN(globalMessageAudio.duration),
|
||||
'Audio should have definite duration on `loadedmetadata` event'
|
||||
);
|
||||
log.info(
|
||||
'SmartVoiceNotesPlaybackProvider: `loadedmetadata` event',
|
||||
current.id
|
||||
);
|
||||
if (active.startPosition !== 0) {
|
||||
globalMessageAudio.currentTime =
|
||||
active.startPosition * globalMessageAudio.duration;
|
||||
}
|
||||
},
|
||||
onDurationChange() {
|
||||
log.info(
|
||||
'SmartVoiceNotesPlaybackProvider: `durationchange` event',
|
||||
current.id
|
||||
);
|
||||
const reportedDuration = globalMessageAudio.duration;
|
||||
|
||||
// the underlying Audio element can return NaN if the audio hasn't loaded
|
||||
// we filter out 0 or NaN as they are not useful values downstream
|
||||
const newDuration =
|
||||
Number.isNaN(reportedDuration) || reportedDuration === 0
|
||||
? undefined
|
||||
: reportedDuration;
|
||||
durationChanged(newDuration);
|
||||
},
|
||||
onTimeUpdate() {
|
||||
currentTimeUpdated(globalMessageAudio.currentTime);
|
||||
},
|
||||
onEnded() {
|
||||
if (content.isConsecutive && content.queue.length === 0) {
|
||||
void stateChangeConfirmDownSound.play();
|
||||
}
|
||||
messageAudioEnded();
|
||||
},
|
||||
onError(error) {
|
||||
log.error(
|
||||
'SmartVoiceNotesPlaybackProvider: playback error',
|
||||
current.messageIdForLogging,
|
||||
Errors.toLogFormat(error)
|
||||
);
|
||||
unloadMessageAudio();
|
||||
},
|
||||
});
|
||||
|
||||
// if this message was part of the queue (consecutive, added indirectly)
|
||||
// we play a note to let the user we're onto a new message
|
||||
// (false for the first message in a consecutive group, since the user initiated it)
|
||||
if (content.isConsecutive) {
|
||||
// eslint-disable-next-line more/no-then
|
||||
void stateChangeConfirmUpSound.play().then(() => {
|
||||
globalMessageAudio.play();
|
||||
});
|
||||
} else {
|
||||
globalMessageAudio.play();
|
||||
}
|
||||
|
||||
if (!current.isPlayed) {
|
||||
const message = conversations.messagesLookup[current.id];
|
||||
if (message && message.seenStatus !== SeenStatus.Unseen) {
|
||||
markViewed(current.id);
|
||||
}
|
||||
} else {
|
||||
log.info('SmartVoiceNotesPlaybackProvider: message already played', {
|
||||
message: current.messageIdForLogging,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
return <VoiceNotesPlaybackProvider {...props} />;
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue