Audio Player: Fix resuming playback (#6542)

This commit is contained in:
zubiden 2025-12-22 22:54:08 +01:00 committed by Alexander Zinchuk
parent 61ec53ac2b
commit 9a9e8d03aa
8 changed files with 36 additions and 19 deletions

View File

@ -10,6 +10,7 @@ import type { IconName } from '../../../types/icons';
import { PLAYBACK_RATE_FOR_AUDIO_MIN_DURATION } from '../../../config'; import { PLAYBACK_RATE_FOR_AUDIO_MIN_DURATION } from '../../../config';
import { import {
getMediaFormat,
getMessageContent, isMessageLocal, getMessageContent, isMessageLocal,
} from '../../../global/helpers'; } from '../../../global/helpers';
import { getPeerTitle } from '../../../global/helpers/peers'; import { getPeerTitle } from '../../../global/helpers/peers';
@ -20,7 +21,6 @@ import { selectMessageMediaDuration } from '../../../global/selectors/media';
import { makeTrackId } from '../../../util/audioPlayer'; import { makeTrackId } from '../../../util/audioPlayer';
import { IS_IOS, IS_TOUCH_ENV } from '../../../util/browser/windowEnvironment'; import { IS_IOS, IS_TOUCH_ENV } from '../../../util/browser/windowEnvironment';
import buildClassName from '../../../util/buildClassName'; import buildClassName from '../../../util/buildClassName';
import * as mediaLoader from '../../../util/mediaLoader';
import { clearMediaSession } from '../../../util/mediaSession'; import { clearMediaSession } from '../../../util/mediaSession';
import renderText from '../../common/helpers/renderText'; import renderText from '../../common/helpers/renderText';
@ -30,6 +30,7 @@ import useAudioPlayer from '../../../hooks/useAudioPlayer';
import useContextMenuHandlers from '../../../hooks/useContextMenuHandlers'; import useContextMenuHandlers from '../../../hooks/useContextMenuHandlers';
import useCurrentOrPrev from '../../../hooks/useCurrentOrPrev'; import useCurrentOrPrev from '../../../hooks/useCurrentOrPrev';
import useLastCallback from '../../../hooks/useLastCallback'; import useLastCallback from '../../../hooks/useLastCallback';
import useMedia from '../../../hooks/useMedia';
import useMessageMediaMetadata from '../../../hooks/useMessageMediaMetadata'; import useMessageMediaMetadata from '../../../hooks/useMessageMediaMetadata';
import useOldLang from '../../../hooks/useOldLang'; import useOldLang from '../../../hooks/useOldLang';
import useShowTransition from '../../../hooks/useShowTransition'; import useShowTransition from '../../../hooks/useShowTransition';
@ -109,8 +110,10 @@ const AudioPlayer: FC<OwnProps & StateProps> = ({
const shouldRenderPlaybackButton = isVoice || (audio?.duration || 0) > PLAYBACK_RATE_FOR_AUDIO_MIN_DURATION; const shouldRenderPlaybackButton = isVoice || (audio?.duration || 0) > PLAYBACK_RATE_FOR_AUDIO_MIN_DURATION;
const senderName = sender ? getPeerTitle(lang, sender) : undefined; const senderName = sender ? getPeerTitle(lang, sender) : undefined;
const playableMedia = voice || video || audio;
const mediaHash = useMessageMediaHash(renderingMessage, 'inline'); const mediaHash = useMessageMediaHash(renderingMessage, 'inline');
const mediaData = mediaHash && mediaLoader.getFromMemory(mediaHash); const mediaFormat = playableMedia && getMediaFormat(playableMedia, 'inline');
const mediaData = useMedia(mediaHash, false, mediaFormat);
const mediaMetadata = useMessageMediaMetadata(renderingMessage, sender, chat); const mediaMetadata = useMessageMediaMetadata(renderingMessage, sender, chat);
const { const {
@ -424,8 +427,9 @@ export default withGlobal<OwnProps>(
const sender = message && selectSender(global, message); const sender = message && selectSender(global, message);
const chat = message && selectChat(global, message.chatId); const chat = message && selectChat(global, message.chatId);
const { const {
volume, playbackRate, isMuted, isPlaybackRateActive, timestamp, playbackRate, isMuted, isPlaybackRateActive, timestamp,
} = selectTabState(global).audioPlayer; } = selectTabState(global).audioPlayer;
const { volume } = global.audioPlayer;
const mediaDuration = message ? selectMessageMediaDuration(global, message) : undefined; const mediaDuration = message ? selectMessageMediaDuration(global, message) : undefined;

View File

@ -183,7 +183,7 @@ addActionHandler('replyToNextMessage', (global, actions, payload): ActionReturnT
addActionHandler('openAudioPlayer', (global, actions, payload): ActionReturnType => { addActionHandler('openAudioPlayer', (global, actions, payload): ActionReturnType => {
const { const {
chatId, threadId, messageId, origin, volume, playbackRate, isMuted, timestamp, chatId, threadId, messageId, origin, playbackRate, isMuted, timestamp,
tabId = getCurrentTabId(), tabId = getCurrentTabId(),
} = payload; } = payload;
@ -195,7 +195,6 @@ addActionHandler('openAudioPlayer', (global, actions, payload): ActionReturnType
messageId, messageId,
timestamp, timestamp,
origin: origin ?? tabState.audioPlayer.origin, origin: origin ?? tabState.audioPlayer.origin,
volume: volume ?? tabState.audioPlayer.volume,
playbackRate: playbackRate || tabState.audioPlayer.playbackRate || global.audioPlayer.lastPlaybackRate, playbackRate: playbackRate || tabState.audioPlayer.playbackRate || global.audioPlayer.lastPlaybackRate,
isPlaybackRateActive: (tabState.audioPlayer.isPlaybackRateActive === undefined isPlaybackRateActive: (tabState.audioPlayer.isPlaybackRateActive === undefined
? global.audioPlayer.isLastPlaybackRateActive ? global.audioPlayer.isLastPlaybackRateActive
@ -210,13 +209,20 @@ addActionHandler('setAudioPlayerVolume', (global, actions, payload): ActionRetur
volume, tabId = getCurrentTabId(), volume, tabId = getCurrentTabId(),
} = payload; } = payload;
return updateTabState(global, { global = updateTabState(global, {
audioPlayer: { audioPlayer: {
...selectTabState(global, tabId).audioPlayer, ...selectTabState(global, tabId).audioPlayer,
volume,
isMuted: false, isMuted: false,
}, },
}, tabId); }, tabId);
global = {
...global,
audioPlayer: {
...global.audioPlayer,
volume,
},
};
return global;
}); });
addActionHandler('setAudioPlayerPlaybackRate', (global, actions, payload): ActionReturnType => { addActionHandler('setAudioPlayerPlaybackRate', (global, actions, payload): ActionReturnType => {
@ -273,7 +279,6 @@ addActionHandler('closeAudioPlayer', (global, actions, payload): ActionReturnTyp
const tabState = selectTabState(global, tabId); const tabState = selectTabState(global, tabId);
return updateTabState(global, { return updateTabState(global, {
audioPlayer: { audioPlayer: {
volume: tabState.audioPlayer.volume,
playbackRate: tabState.audioPlayer.playbackRate, playbackRate: tabState.audioPlayer.playbackRate,
isPlaybackRateActive: tabState.audioPlayer.isPlaybackRateActive, isPlaybackRateActive: tabState.audioPlayer.isPlaybackRateActive,
isMuted: tabState.audioPlayer.isMuted, isMuted: tabState.audioPlayer.isMuted,

View File

@ -367,6 +367,10 @@ function unsafeMigrateCache(cached: GlobalState, initialState: GlobalState) {
cached.auth = initialState.auth; cached.auth = initialState.auth;
cached.auth.rememberMe = untypedCached.rememberMe; cached.auth.rememberMe = untypedCached.rememberMe;
} }
if (cached.audioPlayer.volume === undefined) {
cached.audioPlayer.volume = initialState.audioPlayer.volume;
}
} }
function updateCache(force?: boolean) { function updateCache(force?: boolean) {

View File

@ -106,6 +106,7 @@ export const INITIAL_GLOBAL_STATE: GlobalState = {
appConfig: DEFAULT_APP_CONFIG, appConfig: DEFAULT_APP_CONFIG,
audioPlayer: { audioPlayer: {
volume: DEFAULT_VOLUME,
lastPlaybackRate: DEFAULT_PLAYBACK_RATE, lastPlaybackRate: DEFAULT_PLAYBACK_RATE,
}, },
@ -426,7 +427,6 @@ export const INITIAL_TAB_STATE: TabState = {
}, },
audioPlayer: { audioPlayer: {
volume: DEFAULT_VOLUME,
playbackRate: DEFAULT_PLAYBACK_RATE, playbackRate: DEFAULT_PLAYBACK_RATE,
isMuted: false, isMuted: false,
}, },

View File

@ -105,6 +105,7 @@ export type GlobalState = {
botFreezeAppealId?: string; botFreezeAppealId?: string;
audioPlayer: { audioPlayer: {
volume: number;
lastPlaybackRate: number; lastPlaybackRate: number;
isLastPlaybackRateActive?: boolean; isLastPlaybackRateActive?: boolean;
}; };

View File

@ -378,7 +378,6 @@ export type TabState = {
messageId?: number; messageId?: number;
threadId?: ThreadId; threadId?: ThreadId;
origin?: AudioOrigin; origin?: AudioOrigin;
volume: number;
playbackRate: number; playbackRate: number;
isPlaybackRateActive?: boolean; isPlaybackRateActive?: boolean;
timestamp?: number; timestamp?: number;

View File

@ -65,17 +65,21 @@ const useAudioPlayer = (
const { const {
setVolume, setPlaybackRate, toggleMuted, proxy, setVolume, setPlaybackRate, toggleMuted, proxy,
} = controllerRef.current!; } = controllerRef.current!;
const global = getGlobal();
setIsPlaying(true); setIsPlaying(true);
if (trackType !== 'oneTimeVoice') { if (trackType !== 'oneTimeVoice') {
registerMediaSession(metadata, makeMediaHandlers(controllerRef)); registerMediaSession(metadata, makeMediaHandlers(controllerRef));
} }
setPlaybackState('playing'); setPlaybackState('playing');
const { audioPlayer } = selectTabState(getGlobal());
setVolume(audioPlayer.volume); const { audioPlayer: tabAudioPlayerState } = selectTabState(global);
toggleMuted(Boolean(audioPlayer.isMuted)); const { audioPlayer: globalAudioPlayerState } = global;
setVolume(globalAudioPlayerState.volume);
toggleMuted(Boolean(tabAudioPlayerState.isMuted));
const duration = proxy.duration && Number.isFinite(proxy.duration) ? proxy.duration : originalDuration; const duration = proxy.duration && Number.isFinite(proxy.duration) ? proxy.duration : originalDuration;
if (trackType === 'voice' || duration > PLAYBACK_RATE_FOR_AUDIO_MIN_DURATION) { if (trackType === 'voice' || duration > PLAYBACK_RATE_FOR_AUDIO_MIN_DURATION) {
setPlaybackRate(audioPlayer.playbackRate); setPlaybackRate(tabAudioPlayerState.playbackRate);
} }
setPositionState({ setPositionState({
duration: proxy.duration || 0, duration: proxy.duration || 0,

View File

@ -4,7 +4,6 @@ import type { ApiMessage } from '../api/types';
import type { MessageKey } from './keys/messageKey'; import type { MessageKey } from './keys/messageKey';
import { AudioOrigin, GlobalSearchContent } from '../types'; import { AudioOrigin, GlobalSearchContent } from '../types';
import { requestNextMutation } from '../lib/fasterdom/fasterdom';
import { selectCurrentMessageList, selectTabState } from '../global/selectors'; import { selectCurrentMessageList, selectTabState } from '../global/selectors';
import { IS_SAFARI } from './browser/windowEnvironment'; import { IS_SAFARI } from './browser/windowEnvironment';
import { getMessageServerKey, parseMessageKey } from './keys/messageKey'; import { getMessageServerKey, parseMessageKey } from './keys/messageKey';
@ -179,14 +178,15 @@ export function register(
stop() { stop() {
if (currentTrackId === trackId) { if (currentTrackId === trackId) {
// Hack, reset `src` to remove default media session notification
const prevSrc = audio.src; const prevSrc = audio.src;
audio.pause(); audio.pause();
// `onPause` not called otherwise, but required to sync UI
requestNextMutation(() => { // `onPause` is required to reset UI state
audio.addEventListener('pause', () => {
// Hack, reset `src` to remove default media session notification
audio.src = ''; audio.src = '';
audio.src = prevSrc; audio.src = prevSrc;
}); }, { once: true });
} }
}, },