diff --git a/ts/components/conversation/H5AudioPlayer.tsx b/ts/components/conversation/H5AudioPlayer.tsx index 7ce6a01f3..546ca229f 100644 --- a/ts/components/conversation/H5AudioPlayer.tsx +++ b/ts/components/conversation/H5AudioPlayer.tsx @@ -1,46 +1,83 @@ // Audio Player import React, { useEffect, useRef, useState } from 'react'; import H5AudioPlayer, { RHAP_UI } from 'react-h5-audio-player'; +import { useDispatch, useSelector } from 'react-redux'; import { useTheme } from 'styled-components'; import { useEncryptedFileFetch } from '../../hooks/useEncryptedFileFetch'; +import { setNextMessageToPlayId } from '../../state/ducks/conversations'; +import { + getNextMessageToPlayId, + getSortedMessagesOfSelectedConversation, +} from '../../state/selectors/conversations'; +import { getAudioAutoplay } from '../../state/selectors/userConfig'; import { SessionIcon, SessionIconSize, SessionIconType } from '../session/icon'; import { SessionButton, SessionButtonColor, SessionButtonType } from '../session/SessionButton'; export const AudioPlayerWithEncryptedFile = (props: { src: string; contentType: string; - playNextMessage?: (index: number) => void; - playableMessageIndex?: number; - nextMessageToPlay?: number; + messageId: string; }) => { const theme = useTheme(); + const dispatch = useDispatch(); const [playbackSpeed, setPlaybackSpeed] = useState(1.0); const { urlToLoad } = useEncryptedFileFetch(props.src, props.contentType); const player = useRef(null); + const autoPlaySetting = useSelector(getAudioAutoplay); + const messageProps = useSelector(getSortedMessagesOfSelectedConversation); + const nextMessageToPlayId = useSelector(getNextMessageToPlayId); + useEffect(() => { // updates playback speed to value selected in context menu if (player.current?.audio.current?.playbackRate) { player.current.audio.current.playbackRate = playbackSpeed; } - }, [playbackSpeed]); + }, [playbackSpeed, player]); useEffect(() => { - if (props.playableMessageIndex === props.nextMessageToPlay) { + if (props.messageId === nextMessageToPlayId) { player.current?.audio.current?.play(); } - }); + }, [props.messageId, nextMessageToPlayId, player]); + + 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.authorPhoneNumber; + const nextAuthorNumber = messageProps[nextMessageIndex].propsForMessage.authorPhoneNumber; + 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 ( - window.inboxStore?.getState().userConfig.audioAutoplay === true && - props.playNextMessage && - props.playableMessageIndex !== undefined - ) { - props.playNextMessage(props.playableMessageIndex); + if (autoPlaySetting === true && props.messageId) { + triggerPlayNextMessageIfNeeded(props.messageId); } }; diff --git a/ts/components/conversation/Message.tsx b/ts/components/conversation/Message.tsx index a7babefe9..d7dba0c48 100644 --- a/ts/components/conversation/Message.tsx +++ b/ts/components/conversation/Message.tsx @@ -268,9 +268,7 @@ class MessageInner extends React.PureComponent { ); diff --git a/ts/components/session/conversation/SessionMessagesList.tsx b/ts/components/session/conversation/SessionMessagesList.tsx index 1350f8dee..81ddf314b 100644 --- a/ts/components/session/conversation/SessionMessagesList.tsx +++ b/ts/components/session/conversation/SessionMessagesList.tsx @@ -13,14 +13,12 @@ import { export const SessionMessagesList = (props: { scrollToQuoteMessage: (options: QuoteClickOptions) => Promise; - playNextMessage?: (value: number) => void; }) => { const messagesProps = useSelector(getSortedMessagesOfSelectedConversation); - let playableMessageIndex = 0; return ( <> - {messagesProps.map((messageProps: SortedMessageModelProps, index: number) => { + {messagesProps.map((messageProps: SortedMessageModelProps) => { const timerProps = messageProps.propsForTimerNotification; const propsForGroupInvitation = messageProps.propsForGroupInvitation; const propsForDataExtractionNotification = messageProps.propsForDataExtractionNotification; @@ -64,18 +62,14 @@ export const SessionMessagesList = (props: { return; } - playableMessageIndex++; - // firstMessageOfSeries tells us to render the avatar only for the first message // in a series of messages from the same user return ( ); })} diff --git a/ts/components/session/conversation/SessionMessagesListContainer.tsx b/ts/components/session/conversation/SessionMessagesListContainer.tsx index 76849d8cf..606e24d4c 100644 --- a/ts/components/session/conversation/SessionMessagesListContainer.tsx +++ b/ts/components/session/conversation/SessionMessagesListContainer.tsx @@ -6,7 +6,6 @@ import { contextMenu } from 'react-contexify'; import { quotedMessageToAnimate, ReduxConversationType, - setNextMessageToPlay, showScrollToBottomButton, SortedMessageModelProps, updateHaveDoneFirstScroll, @@ -108,43 +107,13 @@ class SessionMessagesListContainerInner extends React.Component { key="typing-bubble" /> - + ); } - /** - * Sets the targeted index for the next - * @param index index of message that just completed - */ - private playNextMessage(index: any) { - const { messagesProps } = this.props; - let nextIndex: number | undefined = index - 1; - - // to prevent autoplaying as soon as a message is received. - const latestMessagePlayed = index <= 0 || messagesProps.length < index - 1; - if (latestMessagePlayed) { - nextIndex = undefined; - window.inboxStore?.dispatch(setNextMessageToPlay(nextIndex)); - return; - } - - // stop auto-playing when the audio messages change author. - const prevAuthorNumber = messagesProps[index].propsForMessage.authorPhoneNumber; - const nextAuthorNumber = messagesProps[index - 1].propsForMessage.authorPhoneNumber; - const differentAuthor = prevAuthorNumber !== nextAuthorNumber; - if (differentAuthor) { - nextIndex = undefined; - } - - window.inboxStore?.dispatch(setNextMessageToPlay(nextIndex)); - } - // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // ~~~~~~~~~~~~ SCROLLING METHODS ~~~~~~~~~~~~~ // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/ts/components/session/conversation/SessionMessagesTypes.tsx b/ts/components/session/conversation/SessionMessagesTypes.tsx index 34df2635f..dfc44dd60 100644 --- a/ts/components/session/conversation/SessionMessagesTypes.tsx +++ b/ts/components/session/conversation/SessionMessagesTypes.tsx @@ -13,7 +13,6 @@ import { } from '../../../state/ducks/conversations'; import { getFirstUnreadMessageId, - getNextMessageToPlayIndex, isMessageSelectionMode, } from '../../../state/selectors/conversations'; import { DataExtractionNotification } from '../../conversation/DataExtractionNotification'; @@ -86,12 +85,9 @@ export const TimerNotificationItem = (props: { timerProps: PropsForExpirationTim export const GenericMessageItem = (props: { messageId: string; messageProps: SortedMessageModelProps; - playableMessageIndex?: number; scrollToQuoteMessage: (options: QuoteClickOptions) => Promise; - playNextMessage?: (value: number) => void; }) => { const multiSelectMode = useSelector(isMessageSelectionMode); - const nextMessageToPlay = useSelector(getNextMessageToPlayIndex); const messageId = props.messageId; @@ -103,19 +99,12 @@ export const GenericMessageItem = (props: { ...props.messageProps.propsForMessage, firstMessageOfSeries: props.messageProps.firstMessageOfSeries, multiSelectMode, - nextMessageToPlay, - playNextMessage: props.playNextMessage, onQuoteClick, }; return ( - + ); diff --git a/ts/models/messageType.ts b/ts/models/messageType.ts index f2270ca83..aabea47b7 100644 --- a/ts/models/messageType.ts +++ b/ts/models/messageType.ts @@ -212,8 +212,4 @@ export type MessageRenderingProps = PropsForMessage & { multiSelectMode: boolean; firstMessageOfSeries: boolean; onQuoteClick?: (options: QuoteClickOptions) => Promise; - - playableMessageIndex?: number; - nextMessageToPlay?: number; - playNextMessage?: (value: number) => void; }; diff --git a/ts/state/ducks/conversations.ts b/ts/state/ducks/conversations.ts index 7322b5179..9ec7d65d1 100644 --- a/ts/state/ducks/conversations.ts +++ b/ts/state/ducks/conversations.ts @@ -265,7 +265,7 @@ export type ConversationsStateType = { showScrollButton: boolean; animateQuotedMessageId?: string; - nextMessageToPlay?: number; + nextMessageToPlayId?: string; mentionMembers: MentionsMembersType; }; @@ -713,8 +713,11 @@ const conversationsSlice = createSlice({ state.animateQuotedMessageId = action.payload; return state; }, - setNextMessageToPlay(state: ConversationsStateType, action: PayloadAction) { - state.nextMessageToPlay = action.payload; + setNextMessageToPlayId( + state: ConversationsStateType, + action: PayloadAction + ) { + state.nextMessageToPlayId = action.payload; return state; }, updateMentionsMembers( @@ -782,7 +785,7 @@ export const { quoteMessage, showScrollToBottomButton, quotedMessageToAnimate, - setNextMessageToPlay, + setNextMessageToPlayId, updateMentionsMembers, } = actions; diff --git a/ts/state/selectors/conversations.ts b/ts/state/selectors/conversations.ts index a3f1423de..e2f6ba45f 100644 --- a/ts/state/selectors/conversations.ts +++ b/ts/state/selectors/conversations.ts @@ -335,9 +335,9 @@ export const getQuotedMessageToAnimate = createSelector( (state: ConversationsStateType): string | undefined => state.animateQuotedMessageId || undefined ); -export const getNextMessageToPlayIndex = createSelector( +export const getNextMessageToPlayId = createSelector( getConversations, - (state: ConversationsStateType): number | undefined => state.nextMessageToPlay || undefined + (state: ConversationsStateType): string | undefined => state.nextMessageToPlayId || undefined ); export const getMentionsInput = createSelector( @@ -398,11 +398,13 @@ function sortMessages( // for non public convos, we order by sent_at or received_at timestamp. // we assume that a message has either a sent_at or a received_at field set. - const messagesSorted = messages.sort( - (a, b) => - (b.propsForMessage.timestamp || b.propsForMessage.receivedAt || 0) - - (a.propsForMessage.timestamp || a.propsForMessage.receivedAt || 0) - ); + const messagesSorted = messages + .slice() + .sort( + (a, b) => + (b.propsForMessage.timestamp || b.propsForMessage.receivedAt || 0) - + (a.propsForMessage.timestamp || a.propsForMessage.receivedAt || 0) + ); return messagesSorted; }