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}
 | 
						|
    />
 | 
						|
  );
 | 
						|
};
 |