From c6882007f389c19e79e977512aa1fb646d091e07 Mon Sep 17 00:00:00 2001 From: Donald McEachern <94866715+dmceachernmsft@users.noreply.github.com> Date: Thu, 25 Apr 2024 17:28:53 -0700 Subject: [PATCH] [Release-blocker] composite audio context updates (#4539) * move audio context to react context to minimize hook usage * fix build * Change files * Fix test --- ...-fda0563f-1d07-44a9-b28f-c1e635e0b286.json | 9 +++++ ...-fda0563f-1d07-44a9-b28f-c1e635e0b286.json | 9 +++++ common/config/jest/jestSetup.js | 4 ++- .../src/components/Dialpad/Dialpad.test.tsx | 4 --- .../CallComposite/CallComposite.test.tsx | 4 --- .../CallComposite/CallComposite.tsx | 5 +-- .../CallWithChatComposite.test.tsx | 4 --- .../src/composites/common/AudioProvider.tsx | 36 +++++++++++++++++++ .../src/composites/common/BaseComposite.tsx | 9 ++++- 9 files changed, 68 insertions(+), 16 deletions(-) create mode 100644 change-beta/@azure-communication-react-fda0563f-1d07-44a9-b28f-c1e635e0b286.json create mode 100644 change/@azure-communication-react-fda0563f-1d07-44a9-b28f-c1e635e0b286.json create mode 100644 packages/react-composites/src/composites/common/AudioProvider.tsx diff --git a/change-beta/@azure-communication-react-fda0563f-1d07-44a9-b28f-c1e635e0b286.json b/change-beta/@azure-communication-react-fda0563f-1d07-44a9-b28f-c1e635e0b286.json new file mode 100644 index 00000000000..1b1738452a0 --- /dev/null +++ b/change-beta/@azure-communication-react-fda0563f-1d07-44a9-b28f-c1e635e0b286.json @@ -0,0 +1,9 @@ +{ + "type": "patch", + "area": "fix", + "workstream": "Calling Sounds", + "comment": "Move internal CallComposite AudioContext to a react context to avoid over creation of audio contexts", + "packageName": "@azure/communication-react", + "email": "94866715+dmceachernmsft@users.noreply.github.com", + "dependentChangeType": "patch" +} diff --git a/change/@azure-communication-react-fda0563f-1d07-44a9-b28f-c1e635e0b286.json b/change/@azure-communication-react-fda0563f-1d07-44a9-b28f-c1e635e0b286.json new file mode 100644 index 00000000000..1b1738452a0 --- /dev/null +++ b/change/@azure-communication-react-fda0563f-1d07-44a9-b28f-c1e635e0b286.json @@ -0,0 +1,9 @@ +{ + "type": "patch", + "area": "fix", + "workstream": "Calling Sounds", + "comment": "Move internal CallComposite AudioContext to a react context to avoid over creation of audio contexts", + "packageName": "@azure/communication-react", + "email": "94866715+dmceachernmsft@users.noreply.github.com", + "dependentChangeType": "patch" +} diff --git a/common/config/jest/jestSetup.js b/common/config/jest/jestSetup.js index 0cec3ff37e4..d4d7c628868 100644 --- a/common/config/jest/jestSetup.js +++ b/common/config/jest/jestSetup.js @@ -10,6 +10,8 @@ console.error = (...args) => { console.warning = (...args) => { throw args; }; - +window.AudioContext = jest.fn().mockImplementation(() => { + return {}; +}); // Add `ResizeObserver` to globals. Without this GridLayout jest tests fail with "ReferenceError: ResizeObserver is not defined" global.ResizeObserver = require('resize-observer-polyfill'); diff --git a/packages/react-components/src/components/Dialpad/Dialpad.test.tsx b/packages/react-components/src/components/Dialpad/Dialpad.test.tsx index ae1640500b8..017e7ef440f 100644 --- a/packages/react-components/src/components/Dialpad/Dialpad.test.tsx +++ b/packages/react-components/src/components/Dialpad/Dialpad.test.tsx @@ -14,10 +14,6 @@ const onSendDtmfTone = (dtmfTone: DtmfTone): Promise => { return Promise.resolve(); }; -window.AudioContext = jest.fn().mockImplementation(() => { - return {}; -}); - describe('Dialpad tests', () => { beforeAll(() => { registerIcons({ diff --git a/packages/react-composites/src/composites/CallComposite/CallComposite.test.tsx b/packages/react-composites/src/composites/CallComposite/CallComposite.test.tsx index 64c46944b3a..748eb385570 100644 --- a/packages/react-composites/src/composites/CallComposite/CallComposite.test.tsx +++ b/packages/react-composites/src/composites/CallComposite/CallComposite.test.tsx @@ -7,10 +7,6 @@ import { _MockCallAdapter } from './MockCallAdapter'; import { CallComposite } from './CallComposite'; import { render } from '@testing-library/react'; -window.AudioContext = jest.fn().mockImplementation(() => { - return {}; -}); - describe('CallComposite device permission test for different roles', () => { let audioDevicePermissionRequests = 0; let videoDevicePermissionRequests = 0; diff --git a/packages/react-composites/src/composites/CallComposite/CallComposite.tsx b/packages/react-composites/src/composites/CallComposite/CallComposite.tsx index 04465d22d66..9bd65c5bb20 100644 --- a/packages/react-composites/src/composites/CallComposite/CallComposite.tsx +++ b/packages/react-composites/src/composites/CallComposite/CallComposite.tsx @@ -61,6 +61,7 @@ import { useTrackedCapabilityChangedNotifications } from './utils/TrackCapabilit import { useEndedCallConsoleErrors } from './utils/useConsoleErrors'; /* @conditional-compile-remove(end-of-call-survey) */ import { SurveyPage } from './pages/SurveyPage'; +import { useAudio } from '../common/AudioProvider'; /** * Props for {@link CallComposite}. @@ -411,7 +412,7 @@ const MainScreen = (props: MainScreenProps): JSX.Element => { }; }, [adapter]); - const compositeAudioContext = useRef(new AudioContext()); + const compositeAudioContext = useAudio(); const capabilitiesChangedInfoAndRole = useSelector(capabilitiesChangedInfoAndRoleSelector); @@ -593,7 +594,7 @@ const MainScreen = (props: MainScreenProps): JSX.Element => { capabilitiesChangedNotificationBarProps={capabilitiesChangedNotificationBarProps} pinnedParticipants={pinnedParticipants} setPinnedParticipants={setPinnedParticipants} - compositeAudioContext={compositeAudioContext.current} + compositeAudioContext={compositeAudioContext} /> ); break; diff --git a/packages/react-composites/src/composites/CallWithChatComposite/CallWithChatComposite.test.tsx b/packages/react-composites/src/composites/CallWithChatComposite/CallWithChatComposite.test.tsx index 2c0c4890a34..d3565075f5f 100644 --- a/packages/react-composites/src/composites/CallWithChatComposite/CallWithChatComposite.test.tsx +++ b/packages/react-composites/src/composites/CallWithChatComposite/CallWithChatComposite.test.tsx @@ -10,10 +10,6 @@ import { CallWithChatComposite } from './CallWithChatComposite'; import { CallWithChatAdapterState } from './state/CallWithChatAdapterState'; import { registerIcons } from '@fluentui/react'; -window.AudioContext = jest.fn().mockImplementation(() => { - return {}; -}); - function createMockCallWithChatAdapter(): CallWithChatAdapter { const callWithChatAdapter = {} as CallWithChatAdapter; callWithChatAdapter.onStateChange = jest.fn(); diff --git a/packages/react-composites/src/composites/common/AudioProvider.tsx b/packages/react-composites/src/composites/common/AudioProvider.tsx new file mode 100644 index 00000000000..53ab21b6046 --- /dev/null +++ b/packages/react-composites/src/composites/common/AudioProvider.tsx @@ -0,0 +1,36 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +import React, { useContext, createContext } from 'react'; + +/** + * @private + */ +export interface ACSAudioProviderProps { + audioContext: AudioContext; + children: JSX.Element; +} + +/** + * + * @param props + * @returns + */ +export const ACSAudioProvider = (props: ACSAudioProviderProps): JSX.Element => { + const { audioContext, children } = props; + const alreadyWrapped = useAudio(); + if (alreadyWrapped) { + return <>{children}; + } + return {props.children}; +}; + +/** + * @private + */ +const ACSAudioContext = createContext(new AudioContext()); + +/** + * @private + */ +export const useAudio = (): AudioContext => useContext(ACSAudioContext); diff --git a/packages/react-composites/src/composites/common/BaseComposite.tsx b/packages/react-composites/src/composites/common/BaseComposite.tsx index ffc3e4b9f0a..3ce57961767 100644 --- a/packages/react-composites/src/composites/common/BaseComposite.tsx +++ b/packages/react-composites/src/composites/common/BaseComposite.tsx @@ -18,6 +18,7 @@ import { AvatarPersonaDataCallback } from './AvatarPersona'; import { CallCompositeIcons, CallWithChatCompositeIcons, ChatCompositeIcons, DEFAULT_COMPOSITE_ICONS } from './icons'; import { globalLayerHostStyle } from './styles/GlobalHostLayer.styles'; import { useId } from '@fluentui/react-hooks'; +import { ACSAudioProvider } from './AudioProvider'; /** * Properties common to all composites exported from this library. * @@ -106,13 +107,19 @@ export const BaseProvider = ( */ registerIcons({ icons: { ...iconsToRegister, ...props.icons } }); + /** + * We need to create one context for the AudioProvider to ensure that we only have one instance of the AudioContext. + */ + const compositeAudioContext = new AudioContext(); // we use Customizer to override default LayerHost injected to // which stop polluting global dom tree and increase compatibility with react-full-screen const CompositeElement = ( - {props.children} + + {props.children} +