You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
278 lines
7.5 KiB
TypeScript
278 lines
7.5 KiB
TypeScript
// Audio Player
|
|
import { useEffect, useRef, useState } from 'react';
|
|
import H5AudioPlayer, { RHAP_UI } from 'react-h5-audio-player';
|
|
import { useDispatch, useSelector } from 'react-redux';
|
|
import styled from 'styled-components';
|
|
import { useEncryptedFileFetch } from '../../hooks/useEncryptedFileFetch';
|
|
import { setNextMessageToPlayId } from '../../state/ducks/conversations';
|
|
import { useMessageDirection, useMessageSelected } from '../../state/selectors';
|
|
import {
|
|
getNextMessageToPlayId,
|
|
getSortedMessagesOfSelectedConversation,
|
|
isMessageSelectionMode,
|
|
} from '../../state/selectors/conversations';
|
|
import { getAudioAutoplay } from '../../state/selectors/userConfig';
|
|
import { SessionButton, SessionButtonType } from '../basic/SessionButton';
|
|
import { SessionIcon } from '../icon';
|
|
|
|
const StyledSpeedButton = styled.div`
|
|
padding: var(--margins-xs);
|
|
transition: none;
|
|
|
|
.session-button {
|
|
transition: none;
|
|
width: 34px;
|
|
padding: 0px;
|
|
}
|
|
`;
|
|
|
|
export const StyledH5AudioPlayer = styled(H5AudioPlayer)<{ dropShadow?: boolean }>`
|
|
&.rhap_container {
|
|
min-width: 220px;
|
|
padding: 0px;
|
|
outline: none;
|
|
padding: var(--padding-message-content);
|
|
border-radius: var(--border-radius-message-box);
|
|
|
|
svg {
|
|
transition: fill var(--default-duration);
|
|
}
|
|
|
|
button {
|
|
outline: none;
|
|
}
|
|
}
|
|
|
|
.rhap_progress-container {
|
|
margin: 0 0 0 calc(10px + 1%);
|
|
outline: none;
|
|
}
|
|
|
|
.rhap_total-time {
|
|
display: none;
|
|
}
|
|
|
|
.rhap_current-time {
|
|
margin: 0 5px 0 4px;
|
|
flex-shrink: 0;
|
|
}
|
|
|
|
.rhap_play-pause-button {
|
|
display: flex;
|
|
justify-content: center;
|
|
align-items: center;
|
|
}
|
|
|
|
.rhap_volume-bar {
|
|
display: none;
|
|
}
|
|
|
|
.rhap_volume-button {
|
|
.module-message__container--incoming & {
|
|
color: var(--message-bubbles-received-text-color);
|
|
}
|
|
.module-message__container--outgoing & {
|
|
color: var(--message-bubbles-sent-text-color);
|
|
}
|
|
}
|
|
|
|
.rhap_volume-container div[role='progressbar'] {
|
|
display: none;
|
|
}
|
|
|
|
.rhap_time {
|
|
.module-message__container--incoming & {
|
|
color: var(--message-bubbles-received-text-color);
|
|
}
|
|
.module-message__container--outgoing & {
|
|
color: var(--message-bubbles-sent-text-color);
|
|
}
|
|
|
|
font-size: 12px;
|
|
}
|
|
|
|
.rhap_progress-bar {
|
|
box-sizing: border-box;
|
|
position: relative;
|
|
z-index: 0;
|
|
width: 100%;
|
|
height: 5px;
|
|
border-radius: 2px;
|
|
}
|
|
|
|
.rhap_progress-filled {
|
|
padding-left: 5px;
|
|
}
|
|
|
|
.rhap_download-progress {
|
|
height: 100%;
|
|
position: absolute;
|
|
z-index: 1;
|
|
border-radius: 2px;
|
|
}
|
|
|
|
.rhap_progress-indicator {
|
|
z-index: 3;
|
|
width: 15px;
|
|
height: 15px;
|
|
top: -5px;
|
|
margin-left: -10px;
|
|
box-shadow: rgba(0, 0, 0, 0.5) 0 0 5px !important;
|
|
}
|
|
|
|
.rhap_controls-section {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
}
|
|
|
|
.rhap_additional-controls {
|
|
display: none;
|
|
}
|
|
|
|
.rhap_play-pause-button {
|
|
width: unset;
|
|
height: unset;
|
|
}
|
|
|
|
.rhap_controls-section {
|
|
flex: unset;
|
|
justify-content: flex-start;
|
|
}
|
|
|
|
.rhap_volume-button {
|
|
font-size: 20px;
|
|
width: 20px;
|
|
height: 20px;
|
|
margin-right: 0px;
|
|
}
|
|
|
|
${props => props.dropShadow && 'box-shadow: var(--drop-shadow);'}
|
|
`;
|
|
|
|
export const AudioPlayerWithEncryptedFile = (props: {
|
|
src: string;
|
|
contentType: string;
|
|
messageId: string;
|
|
}) => {
|
|
const { messageId, contentType, src } = props;
|
|
const dispatch = useDispatch();
|
|
const [playbackSpeed, setPlaybackSpeed] = useState(1.0);
|
|
const { urlToLoad } = useEncryptedFileFetch(src, contentType, false);
|
|
const player = useRef<H5AudioPlayer | null>(null);
|
|
|
|
const autoPlaySetting = useSelector(getAudioAutoplay);
|
|
const messageProps = useSelector(getSortedMessagesOfSelectedConversation);
|
|
const nextMessageToPlayId = useSelector(getNextMessageToPlayId);
|
|
const multiSelectMode = useSelector(isMessageSelectionMode);
|
|
const selected = useMessageSelected(messageId);
|
|
const direction = useMessageDirection(messageId);
|
|
const iconColor =
|
|
direction === 'incoming'
|
|
? 'var(--message-bubbles-received-text-color)'
|
|
: 'var(--message-bubbles-sent-text-color)';
|
|
const dataTestId = `audio-${messageId}`;
|
|
|
|
const triggerPlayNextMessageIfNeeded = (endedMessageId: string) => {
|
|
const justEndedMessageIndex = messageProps.findIndex(
|
|
m => m.propsForMessage.id === endedMessageId
|
|
);
|
|
if (justEndedMessageIndex === -1) {
|
|
// make sure that even with switching convo or stuff, the next message to play is unset
|
|
dispatch(setNextMessageToPlayId(undefined));
|
|
|
|
return;
|
|
}
|
|
|
|
const isLastMessage = justEndedMessageIndex === 0;
|
|
|
|
// to prevent autoplaying as soon as a message is received.
|
|
if (isLastMessage) {
|
|
dispatch(setNextMessageToPlayId(undefined));
|
|
return;
|
|
}
|
|
|
|
// justEndedMessageIndex cannot be -1 nor 0, so it is >= 1
|
|
const nextMessageIndex = justEndedMessageIndex - 1;
|
|
// stop auto-playing when the audio messages change author.
|
|
const prevAuthorNumber = messageProps[justEndedMessageIndex].propsForMessage.sender;
|
|
const nextAuthorNumber = messageProps[nextMessageIndex].propsForMessage.sender;
|
|
const differentAuthor = prevAuthorNumber !== nextAuthorNumber;
|
|
if (differentAuthor) {
|
|
dispatch(setNextMessageToPlayId(undefined));
|
|
} else {
|
|
dispatch(setNextMessageToPlayId(messageProps[nextMessageIndex].propsForMessage.id));
|
|
}
|
|
};
|
|
|
|
const onEnded = () => {
|
|
// if audio autoplay is enabled, call method to start playing
|
|
// the next playable message
|
|
if (autoPlaySetting === true && messageId) {
|
|
triggerPlayNextMessageIfNeeded(messageId);
|
|
}
|
|
};
|
|
|
|
useEffect(() => {
|
|
// Updates datatestId once rendered
|
|
if (
|
|
player.current?.audio.current &&
|
|
player.current?.container.current &&
|
|
player.current.container.current.dataset.testId !== dataTestId
|
|
) {
|
|
// NOTE we can't assign the value using dataset.testId because the result is data-test-id not data-testid which is our convention
|
|
player.current.container.current.setAttribute('data-testid', dataTestId);
|
|
}
|
|
}, [dataTestId, player]);
|
|
|
|
useEffect(() => {
|
|
// updates playback speed to value selected in context menu
|
|
if (
|
|
player.current?.audio.current &&
|
|
player.current?.audio.current?.playbackRate !== playbackSpeed
|
|
) {
|
|
player.current.audio.current.playbackRate = playbackSpeed;
|
|
}
|
|
}, [playbackSpeed, player]);
|
|
|
|
useEffect(() => {
|
|
if (messageId !== undefined && messageId === nextMessageToPlayId) {
|
|
void player.current?.audio.current?.play();
|
|
}
|
|
}, [messageId, nextMessageToPlayId, player]);
|
|
|
|
return (
|
|
<StyledH5AudioPlayer
|
|
src={urlToLoad}
|
|
preload="metadata"
|
|
style={{ pointerEvents: multiSelectMode ? 'none' : 'inherit' }}
|
|
layout="horizontal-reverse"
|
|
showSkipControls={false}
|
|
autoPlay={false}
|
|
autoPlayAfterSrcChange={false}
|
|
showJumpControls={false}
|
|
showDownloadProgress={false}
|
|
listenInterval={100}
|
|
onEnded={onEnded}
|
|
ref={player}
|
|
customControlsSection={[
|
|
RHAP_UI.MAIN_CONTROLS,
|
|
<StyledSpeedButton key="togglePlaybackSpeed">
|
|
<SessionButton
|
|
text={`${playbackSpeed}x`}
|
|
onClick={() => {
|
|
setPlaybackSpeed(playbackSpeed === 1 ? 1.5 : 1);
|
|
}}
|
|
buttonType={SessionButtonType.Simple}
|
|
/>
|
|
</StyledSpeedButton>,
|
|
]}
|
|
customIcons={{
|
|
play: <SessionIcon iconType="play" iconSize="small" iconColor={iconColor} />,
|
|
pause: <SessionIcon iconType="pause" iconSize="small" iconColor={iconColor} />,
|
|
}}
|
|
dropShadow={selected}
|
|
/>
|
|
);
|
|
};
|