Calls: Fix call sounds in iOS (#2216)
This commit is contained in:
parent
cbcfa354e6
commit
6d5625ce0c
@ -5,12 +5,13 @@ import { getActions, withGlobal } from './global';
|
|||||||
import type { GlobalState } from './global/types';
|
import type { GlobalState } from './global/types';
|
||||||
import type { UiLoaderPage } from './components/common/UiLoader';
|
import type { UiLoaderPage } from './components/common/UiLoader';
|
||||||
|
|
||||||
import { IS_MULTITAB_SUPPORTED, PLATFORM_ENV } from './util/environment';
|
import { IS_INSTALL_PROMPT_SUPPORTED, IS_MULTITAB_SUPPORTED, PLATFORM_ENV } from './util/environment';
|
||||||
import { INACTIVE_MARKER, PAGE_TITLE } from './config';
|
import { INACTIVE_MARKER, PAGE_TITLE } from './config';
|
||||||
import { selectTabState } from './global/selectors';
|
import { selectTabState } from './global/selectors';
|
||||||
import { updateSizes } from './util/windowSize';
|
import { updateSizes } from './util/windowSize';
|
||||||
import { addActiveTabChangeListener } from './util/activeTabMonitor';
|
import { addActiveTabChangeListener } from './util/activeTabMonitor';
|
||||||
import { hasStoredSession } from './util/sessions';
|
import { hasStoredSession } from './util/sessions';
|
||||||
|
import { setupBeforeInstallPrompt } from './util/installPrompt';
|
||||||
import buildClassName from './util/buildClassName';
|
import buildClassName from './util/buildClassName';
|
||||||
import { parseInitialLocationHash } from './util/routing';
|
import { parseInitialLocationHash } from './util/routing';
|
||||||
import useFlag from './hooks/useFlag';
|
import useFlag from './hooks/useFlag';
|
||||||
@ -55,6 +56,12 @@ const App: FC<StateProps> = ({
|
|||||||
const { isMobile } = useAppLayout();
|
const { isMobile } = useAppLayout();
|
||||||
const isMobileOs = PLATFORM_ENV === 'iOS' || PLATFORM_ENV === 'Android';
|
const isMobileOs = PLATFORM_ENV === 'iOS' || PLATFORM_ENV === 'Android';
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (IS_INSTALL_PROMPT_SUPPORTED) {
|
||||||
|
setupBeforeInstallPrompt();
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
// Prevent drop on elements that do not accept it
|
// Prevent drop on elements that do not accept it
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const body = document.body;
|
const body = document.body;
|
||||||
|
|||||||
@ -1,4 +1,11 @@
|
|||||||
|
import { IS_IOS, IS_SAFARI } from '../util/environment';
|
||||||
|
import { initializeSoundsForSafari } from '../global/actions/ui/calls';
|
||||||
|
|
||||||
export { default as GroupCall } from '../components/calls/group/GroupCall';
|
export { default as GroupCall } from '../components/calls/group/GroupCall';
|
||||||
export { default as ActiveCallHeader } from '../components/calls/ActiveCallHeader';
|
export { default as ActiveCallHeader } from '../components/calls/ActiveCallHeader';
|
||||||
export { default as PhoneCall } from '../components/calls/phone/PhoneCall';
|
export { default as PhoneCall } from '../components/calls/phone/PhoneCall';
|
||||||
export { default as RatePhoneCallModal } from '../components/calls/phone/RatePhoneCallModal';
|
export { default as RatePhoneCallModal } from '../components/calls/phone/RatePhoneCallModal';
|
||||||
|
|
||||||
|
if (IS_SAFARI || IS_IOS) {
|
||||||
|
document.addEventListener('click', initializeSoundsForSafari, { once: true });
|
||||||
|
}
|
||||||
|
|||||||
@ -32,6 +32,8 @@ import { waitForTransitionEnd } from '../../util/cssAnimationEndListeners';
|
|||||||
import { processDeepLink } from '../../util/deeplink';
|
import { processDeepLink } from '../../util/deeplink';
|
||||||
import { parseInitialLocationHash, parseLocationHash } from '../../util/routing';
|
import { parseInitialLocationHash, parseLocationHash } from '../../util/routing';
|
||||||
import { fastRaf } from '../../util/schedulers';
|
import { fastRaf } from '../../util/schedulers';
|
||||||
|
import { Bundles, loadBundle } from '../../util/moduleLoader';
|
||||||
|
import updateIcon from '../../util/updateIcon';
|
||||||
|
|
||||||
import useEffectWithPrevDeps from '../../hooks/useEffectWithPrevDeps';
|
import useEffectWithPrevDeps from '../../hooks/useEffectWithPrevDeps';
|
||||||
import useBackgroundMode from '../../hooks/useBackgroundMode';
|
import useBackgroundMode from '../../hooks/useBackgroundMode';
|
||||||
@ -43,7 +45,7 @@ import useShowTransition from '../../hooks/useShowTransition';
|
|||||||
import { dispatchHeavyAnimationEvent } from '../../hooks/useHeavyAnimationCheck';
|
import { dispatchHeavyAnimationEvent } from '../../hooks/useHeavyAnimationCheck';
|
||||||
import useInterval from '../../hooks/useInterval';
|
import useInterval from '../../hooks/useInterval';
|
||||||
import useAppLayout from '../../hooks/useAppLayout';
|
import useAppLayout from '../../hooks/useAppLayout';
|
||||||
import updateIcon from '../../util/updateIcon';
|
import useTimeout from '../../hooks/useTimeout';
|
||||||
|
|
||||||
import StickerSetModal from '../common/StickerSetModal.async';
|
import StickerSetModal from '../common/StickerSetModal.async';
|
||||||
import UnreadCount from '../common/UnreadCounter';
|
import UnreadCount from '../common/UnreadCounter';
|
||||||
@ -132,6 +134,7 @@ type StateProps = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const APP_OUTDATED_TIMEOUT_MS = 5 * 60 * 1000; // 5 min
|
const APP_OUTDATED_TIMEOUT_MS = 5 * 60 * 1000; // 5 min
|
||||||
|
const CALL_BUNDLE_LOADING_DELAY_MS = 5000; // 5 sec
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||||
let DEBUG_isLogged = false;
|
let DEBUG_isLogged = false;
|
||||||
@ -222,6 +225,11 @@ const Main: FC<OwnProps & StateProps> = ({
|
|||||||
console.log('>>> RENDER MAIN');
|
console.log('>>> RENDER MAIN');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Preload Calls bundle to initialize sounds for iOS
|
||||||
|
useTimeout(() => {
|
||||||
|
void loadBundle(Bundles.Calls);
|
||||||
|
}, CALL_BUNDLE_LOADING_DELAY_MS);
|
||||||
|
|
||||||
const { isDesktop } = useAppLayout();
|
const { isDesktop } = useAppLayout();
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!isLeftColumnOpen && !isMiddleColumnOpen && !isDesktop) {
|
if (!isLeftColumnOpen && !isMiddleColumnOpen && !isDesktop) {
|
||||||
|
|||||||
@ -6,7 +6,7 @@ import { updateChat } from '../../reducers';
|
|||||||
import { ARE_CALLS_SUPPORTED } from '../../../util/environment';
|
import { ARE_CALLS_SUPPORTED } from '../../../util/environment';
|
||||||
import { notifyAboutCall } from '../../../util/notifications';
|
import { notifyAboutCall } from '../../../util/notifications';
|
||||||
import { selectGroupCall, selectPhoneCallUser } from '../../selectors/calls';
|
import { selectGroupCall, selectPhoneCallUser } from '../../selectors/calls';
|
||||||
import { checkNavigatorUserMediaPermissions, initializeSoundsForSafari } from '../ui/calls';
|
import { checkNavigatorUserMediaPermissions, initializeSounds } from '../ui/calls';
|
||||||
import { onTickEnd } from '../../../util/schedulers';
|
import { onTickEnd } from '../../../util/schedulers';
|
||||||
import type { ActionReturnType } from '../../types';
|
import type { ActionReturnType } from '../../types';
|
||||||
import { updateTabState } from '../../reducers/tabs';
|
import { updateTabState } from '../../reducers/tabs';
|
||||||
@ -115,7 +115,7 @@ addActionHandler('apiUpdate', (global, actions, update): ActionReturnType => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
void initializeSoundsForSafari();
|
initializeSounds();
|
||||||
void checkNavigatorUserMediaPermissions(global, actions, call.isVideo, getCurrentTabId());
|
void checkNavigatorUserMediaPermissions(global, actions, call.isVideo, getCurrentTabId());
|
||||||
global = {
|
global = {
|
||||||
...global,
|
...global,
|
||||||
|
|||||||
@ -26,16 +26,43 @@ import * as langProvider from '../../../util/langProvider';
|
|||||||
import { updateTabState } from '../../reducers/tabs';
|
import { updateTabState } from '../../reducers/tabs';
|
||||||
import { getCurrentTabId } from '../../../util/establishMultitabRole';
|
import { getCurrentTabId } from '../../../util/establishMultitabRole';
|
||||||
|
|
||||||
// Workaround for Safari not playing audio without user interaction
|
// This is a tiny MP3 file that is silent - retrieved from https://bigsoundbank.com and then modified
|
||||||
|
// eslint-disable-next-line max-len
|
||||||
|
const silentSound = 'data:audio/mpeg;base64,SUQzBAAAAAABEVRYWFgAAAAtAAADY29tbWVudABCaWdTb3VuZEJhbmsuY29tIC8gTGFTb25vdGhlcXVlLm9yZwBURU5DAAAAHQAAA1N3aXRjaCBQbHVzIMKpIE5DSCBTb2Z0d2FyZQBUSVQyAAAABgAAAzIyMzUAVFNTRQAAAA8AAANMYXZmNTcuODMuMTAwAAAAAAAAAAAAAAD/80DEAAAAA0gAAAAATEFNRTMuMTAwVVVVVVVVVVVVVUxBTUUzLjEwMFVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVf/zQsRbAAADSAAAAABVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVf/zQMSkAAADSAAAAABVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV';
|
||||||
|
|
||||||
let audioElement: HTMLAudioElement | undefined;
|
let audioElement: HTMLAudioElement | undefined;
|
||||||
let audioContext: AudioContext | undefined;
|
let audioContext: AudioContext | undefined;
|
||||||
|
|
||||||
let sounds: Record<CallSound, HTMLAudioElement>;
|
let sounds: Record<CallSound, HTMLAudioElement>;
|
||||||
let initializationPromise: Promise<void> | undefined = Promise.resolve();
|
|
||||||
|
|
||||||
export const initializeSoundsForSafari = () => {
|
// Workaround: this function is called once on the first user interaction.
|
||||||
if (!initializationPromise) return Promise.resolve();
|
// After that, it will be possible to play the notification on iOS without problems.
|
||||||
|
// https://rosswintle.uk/2019/01/skirting-the-ios-safari-audio-auto-play-policy-for-ui-sound-effects/
|
||||||
|
export function initializeSoundsForSafari() {
|
||||||
|
initializeSounds();
|
||||||
|
|
||||||
|
return Promise.all(Object.values(sounds).map((sound) => {
|
||||||
|
const prevSrc = sound.src;
|
||||||
|
sound.src = silentSound;
|
||||||
|
sound.muted = true;
|
||||||
|
sound.volume = 0.0001;
|
||||||
|
return sound.play()
|
||||||
|
.then(() => {
|
||||||
|
sound.pause();
|
||||||
|
sound.volume = 1;
|
||||||
|
sound.currentTime = 0;
|
||||||
|
sound.muted = false;
|
||||||
|
|
||||||
|
requestAnimationFrame(() => {
|
||||||
|
sound.src = prevSrc;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
export function initializeSounds() {
|
||||||
|
if (sounds) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
const joinAudio = new Audio('./voicechat_join.mp3');
|
const joinAudio = new Audio('./voicechat_join.mp3');
|
||||||
const connectingAudio = new Audio('./voicechat_connecting.mp3');
|
const connectingAudio = new Audio('./voicechat_connecting.mp3');
|
||||||
connectingAudio.loop = true;
|
connectingAudio.loop = true;
|
||||||
@ -60,22 +87,7 @@ export const initializeSoundsForSafari = () => {
|
|||||||
busy: busyAudio,
|
busy: busyAudio,
|
||||||
ringing: ringingAudio,
|
ringing: ringingAudio,
|
||||||
};
|
};
|
||||||
|
}
|
||||||
initializationPromise = Promise.all(Object.values(sounds).map((sound) => {
|
|
||||||
sound.muted = true;
|
|
||||||
sound.volume = 0.0001;
|
|
||||||
return sound.play().then(() => {
|
|
||||||
sound.pause();
|
|
||||||
sound.volume = 1;
|
|
||||||
sound.currentTime = 0;
|
|
||||||
sound.muted = false;
|
|
||||||
});
|
|
||||||
})).then(() => {
|
|
||||||
initializationPromise = undefined;
|
|
||||||
});
|
|
||||||
|
|
||||||
return initializationPromise;
|
|
||||||
};
|
|
||||||
|
|
||||||
async function fetchGroupCall<T extends GlobalState>(global: T, groupCall: Partial<ApiGroupCall>) {
|
async function fetchGroupCall<T extends GlobalState>(global: T, groupCall: Partial<ApiGroupCall>) {
|
||||||
const result = await callApi('getGroupCall', {
|
const result = await callApi('getGroupCall', {
|
||||||
@ -258,7 +270,7 @@ addActionHandler('joinGroupCall', async (global, actions, payload): Promise<void
|
|||||||
|
|
||||||
createAudioElement();
|
createAudioElement();
|
||||||
|
|
||||||
await initializeSoundsForSafari();
|
initializeSounds();
|
||||||
global = getGlobal();
|
global = getGlobal();
|
||||||
void checkNavigatorUserMediaPermissions(global, actions, true, tabId);
|
void checkNavigatorUserMediaPermissions(global, actions, true, tabId);
|
||||||
|
|
||||||
@ -338,11 +350,7 @@ addActionHandler('playGroupCallSound', (global, actions, payload): ActionReturnT
|
|||||||
safePlay(sounds[sound]);
|
safePlay(sounds[sound]);
|
||||||
};
|
};
|
||||||
|
|
||||||
if (initializationPromise) {
|
doPlay();
|
||||||
initializationPromise.then(doPlay);
|
|
||||||
} else {
|
|
||||||
doPlay();
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
addActionHandler('loadMoreGroupCallParticipants', (global): ActionReturnType => {
|
addActionHandler('loadMoreGroupCallParticipants', (global): ActionReturnType => {
|
||||||
@ -376,7 +384,7 @@ addActionHandler('requestCall', async (global, actions, payload): Promise<void>
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
await initializeSoundsForSafari();
|
initializeSounds();
|
||||||
global = getGlobal();
|
global = getGlobal();
|
||||||
void checkNavigatorUserMediaPermissions(global, actions, isVideo, tabId);
|
void checkNavigatorUserMediaPermissions(global, actions, isVideo, tabId);
|
||||||
|
|
||||||
|
|||||||
@ -8,8 +8,7 @@ import {
|
|||||||
getActions, getGlobal,
|
getActions, getGlobal,
|
||||||
} from './global';
|
} from './global';
|
||||||
import updateWebmanifest from './util/updateWebmanifest';
|
import updateWebmanifest from './util/updateWebmanifest';
|
||||||
import { setupBeforeInstallPrompt } from './util/installPrompt';
|
import { IS_MULTITAB_SUPPORTED } from './util/environment';
|
||||||
import { IS_INSTALL_PROMPT_SUPPORTED, IS_MULTITAB_SUPPORTED } from './util/environment';
|
|
||||||
import './global/init';
|
import './global/init';
|
||||||
|
|
||||||
import { APP_VERSION, DEBUG, MULTITAB_LOCALSTORAGE_KEY } from './config';
|
import { APP_VERSION, DEBUG, MULTITAB_LOCALSTORAGE_KEY } from './config';
|
||||||
@ -59,10 +58,6 @@ async function init() {
|
|||||||
|
|
||||||
updateWebmanifest();
|
updateWebmanifest();
|
||||||
|
|
||||||
if (IS_INSTALL_PROMPT_SUPPORTED) {
|
|
||||||
setupBeforeInstallPrompt();
|
|
||||||
}
|
|
||||||
|
|
||||||
TeactDOM.render(
|
TeactDOM.render(
|
||||||
<App />,
|
<App />,
|
||||||
document.getElementById('root')!,
|
document.getElementById('root')!,
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user