diff --git a/examples/ExpoMessaging/app.json b/examples/ExpoMessaging/app.json index 9641df44dd..9f3de76f90 100644 --- a/examples/ExpoMessaging/app.json +++ b/examples/ExpoMessaging/app.json @@ -51,16 +51,16 @@ } ], [ - "expo-av", + "expo-video", { - "microphonePermission": "$(PRODUCT_NAME) would like to use your microphone for voice recording." + "supportsBackgroundPlayback": true, + "supportsPictureInPicture": true } ], [ - "expo-video", + "expo-audio", { - "supportsBackgroundPlayback": true, - "supportsPictureInPicture": true + "microphonePermission": "$(PRODUCT_NAME) would like to use your microphone for voice recording." } ] ] diff --git a/examples/ExpoMessaging/components/ChatWrapper.tsx b/examples/ExpoMessaging/components/ChatWrapper.tsx index 1402547de8..258ab2c358 100644 --- a/examples/ExpoMessaging/components/ChatWrapper.tsx +++ b/examples/ExpoMessaging/components/ChatWrapper.tsx @@ -16,7 +16,7 @@ const streami18n = new Streami18n({ }); SqliteClient.logger = (level, message, extraData) => { - console.log(level, `SqliteClient: ${message}`, extraData); + // console.log(level, `SqliteClient: ${message}`, extraData); }; export const ChatWrapper = ({ children }: PropsWithChildren<{}>) => { diff --git a/examples/ExpoMessaging/package.json b/examples/ExpoMessaging/package.json index 2cd0da9e12..898801baa9 100644 --- a/examples/ExpoMessaging/package.json +++ b/examples/ExpoMessaging/package.json @@ -14,7 +14,7 @@ "@react-native-community/netinfo": "11.4.1", "@react-navigation/elements": "^1.3.31", "expo": "^53.0.12", - "expo-av": "~15.1.6", + "expo-audio": "~0.4.6", "expo-clipboard": "~7.1.4", "expo-constants": "~17.1.6", "expo-document-picker": "~13.1.5", diff --git a/examples/ExpoMessaging/yarn.lock b/examples/ExpoMessaging/yarn.lock index 00be4201ed..035bba5488 100644 --- a/examples/ExpoMessaging/yarn.lock +++ b/examples/ExpoMessaging/yarn.lock @@ -1905,14 +1905,6 @@ find-up "^5.0.0" js-yaml "^4.1.0" -"@gorhom/bottom-sheet@^5.1.1": - version "5.1.1" - resolved "https://registry.yarnpkg.com/@gorhom/bottom-sheet/-/bottom-sheet-5.1.1.tgz#43ecb9e7b4d4ca4b4cefdf3b6b497f7715f350bc" - integrity sha512-Y8FiuRmeZYaP+ZGQ0axDwWrrKqVp4ByYRs1D2fTJTxHMt081MHHRQsqmZ3SK7AFp3cSID+vTqnD8w/KAASpy+w== - dependencies: - "@gorhom/portal" "1.0.14" - invariant "^2.2.4" - "@gorhom/bottom-sheet@^5.1.6": version "5.1.6" resolved "https://registry.yarnpkg.com/@gorhom/bottom-sheet/-/bottom-sheet-5.1.6.tgz#92365894ae4d4eefdbaa577408cfaf62463a9490" @@ -3360,11 +3352,6 @@ csstype@^3.0.2: resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.1.2.tgz#1d4bf9d572f11c14031f0436e1c10bc1f571f50b" integrity sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ== -dayjs@1.10.5: - version "1.10.5" - resolved "https://registry.yarnpkg.com/dayjs/-/dayjs-1.10.5.tgz#5600df4548fc2453b3f163ebb2abbe965ccfb986" - integrity sha512-BUFis41ikLz+65iH6LHQCDm4YPMj5r1YFLdupPIyM4SGcXMmtiLQ7U37i+hGS8urIuqe7I/ou3IS1jVc4nbN4g== - dayjs@1.11.13: version "1.11.13" resolved "https://registry.yarnpkg.com/dayjs/-/dayjs-1.11.13.tgz#92430b0139055c3ebb60150aa13e860a4b5a366c" @@ -3521,11 +3508,6 @@ electron-to-chromium@^1.5.41: resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.5.69.tgz#7268a4251e82fc83a7cdc9ab51e7154bb2813038" integrity sha512-zz4e7EbJqqtdQtwt61ZYKrfEYlV0HpGbIGRVFGOO9YBZIhg0BDXtBcWxpqyAm6oyPl2Zp8tc5FrPpCZQH/Yazg== -emoji-regex@^10.3.0: - version "10.3.0" - resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-10.3.0.tgz#76998b9268409eb3dae3de989254d456e70cfe23" - integrity sha512-QpLs9D9v9kArv4lfDEgg1X/gN5XLnf/A6l9cs8SPZLRZR3ZkY9+kwIQTxm+fsSej5UMYGE8fdoaZVIBlqG0XTw== - emoji-regex@^10.4.0: version "10.4.0" resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-10.4.0.tgz#03553afea80b3975749cfcb36f776ca268e413d4" @@ -3646,10 +3628,10 @@ expo-asset@~11.1.5: "@expo/image-utils" "^0.7.4" expo-constants "~17.1.5" -expo-av@~15.1.6: - version "15.1.6" - resolved "https://registry.yarnpkg.com/expo-av/-/expo-av-15.1.6.tgz#f1c4a404672500feb0274144a64bb3a956e85bdd" - integrity sha512-5ZbeXdCmdckZHwtEV+8tRZqLlUWR96gkkUIxpyZAEvK0L+aI/BnyhDCpjnSKWwZo4ZA6lx8/su9kyFNV/mQ/sQ== +expo-audio@~0.4.6: + version "0.4.6" + resolved "https://registry.yarnpkg.com/expo-audio/-/expo-audio-0.4.6.tgz#39def71b91f2cb5c4d21a51aef6ffabcc4421ad4" + integrity sha512-/pgz0AnQHnyJWkPfTp/3gBDib6FZYnscpktFZgmPeTeCK8KkPV4+eV2oEDDbSe2ngUrqDA0WVO4MX2Mfeec9ZA== expo-clipboard@~7.1.4: version "7.1.4" @@ -4131,7 +4113,7 @@ hyphenate-style-name@^1.0.3: resolved "https://registry.yarnpkg.com/hyphenate-style-name/-/hyphenate-style-name-1.0.4.tgz#691879af8e220aea5750e8827db4ef62a54e361d" integrity sha512-ygGZLjmXfPHj+ZWh6LwbC37l43MhfztxetbFCoYTM2VjkIUpeHgSNn7QIyVFj7YQ1Wl9Cbw5sholVJPzWvC2MQ== -i18next@^21.10.0, i18next@^21.6.14: +i18next@^21.10.0: version "21.10.0" resolved "https://registry.yarnpkg.com/i18next/-/i18next-21.10.0.tgz#85429af55fdca4858345d0e16b584ec29520197d" integrity sha512-YeuIBmFsGjUfO3qBmMOc0rQaun4mIpGKET5WDwvu8lU7gvwpcariZLNtL0Fzj+zazcHUrlXHiptcFhBMFaxzfg== @@ -4591,11 +4573,6 @@ lines-and-columns@^1.1.6: resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.2.4.tgz#eca284f75d2965079309dc0ad9255abb2ebc1632" integrity sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg== -linkifyjs@^4.1.1: - version "4.1.1" - resolved "https://registry.yarnpkg.com/linkifyjs/-/linkifyjs-4.1.1.tgz#73d427e3bbaaf4ca8e71c589ad4ffda11a9a5fde" - integrity sha512-zFN/CTVmbcVef+WaDXT63dNzzkfRBKT1j464NJQkV7iSgJU0sLBus9W0HBwnXK13/hf168pbrx/V/bjEHOXNHA== - linkifyjs@^4.2.0: version "4.2.0" resolved "https://registry.yarnpkg.com/linkifyjs/-/linkifyjs-4.2.0.tgz#9dd30222b9cbabec9c950e725ec00031c7fa3f08" @@ -4959,7 +4936,7 @@ mime-db@1.52.0, "mime-db@>= 1.43.0 < 2": resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.52.0.tgz#bbabcdc02859f4987301c856e3387ce5ec43bf70" integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg== -mime-types@^2.1.12, mime-types@^2.1.27, mime-types@^2.1.34, mime-types@^2.1.35, mime-types@~2.1.34: +mime-types@^2.1.12, mime-types@^2.1.27, mime-types@^2.1.35, mime-types@~2.1.34: version "2.1.35" resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.35.tgz#381a871b62a734450660ae3deee44813f70d959a" integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw== @@ -5625,13 +5602,6 @@ react-native-svg@15.11.2: css-tree "^1.1.3" warn-once "0.1.1" -react-native-url-polyfill@^1.3.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/react-native-url-polyfill/-/react-native-url-polyfill-1.3.0.tgz#c1763de0f2a8c22cc3e959b654c8790622b6ef6a" - integrity sha512-w9JfSkvpqqlix9UjDvJjm1EjSt652zVQ6iwCIj1cVVkwXf4jQhQgTNXY6EVTwuAmUjg6BC6k9RHCBynoLFo3IQ== - dependencies: - whatwg-url-without-unicode "8.0.0-3" - react-native-url-polyfill@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/react-native-url-polyfill/-/react-native-url-polyfill-2.0.0.tgz#db714520a2985cff1d50ab2e66279b9f91ffd589" @@ -6140,44 +6110,29 @@ stream-buffers@2.2.x: version "0.0.0" uid "" -stream-chat-react-native-core@7.1.1: - version "7.1.1" - resolved "https://registry.yarnpkg.com/stream-chat-react-native-core/-/stream-chat-react-native-core-7.1.1.tgz#b22faf35fa5defd24c730873aeba30c172556089" - integrity sha512-9AkSKWzywN2FfsMgDfeoCatr/qoG+zJzM2u5j3PU6WU7qIhZtM/7+2UB0WKAY7fA5MjaoMEzV1mBF+hILP1KOw== +stream-chat-react-native-core@7.1.2: + version "7.1.2" + resolved "https://registry.yarnpkg.com/stream-chat-react-native-core/-/stream-chat-react-native-core-7.1.2.tgz#5870a1188ecbf8c3b705d74379d19ff77efce2c5" + integrity sha512-Ob+V8tt+7L+7BRkWyWbFlju6E/8MAoB/NUZ8ENtEEijq5QBNWnVvZctQSZuekIOVrfoP9EenlIOPadHnN/mvYA== dependencies: - "@gorhom/bottom-sheet" "^5.1.1" - dayjs "1.10.5" - emoji-regex "^10.3.0" - i18next "^21.6.14" + "@gorhom/bottom-sheet" "^5.1.6" + dayjs "1.11.13" + emoji-regex "^10.4.0" + i18next "^21.10.0" intl-pluralrules "^2.0.1" - linkifyjs "^4.1.1" + linkifyjs "^4.3.1" lodash-es "4.17.21" - mime-types "^2.1.34" + mime-types "^2.1.35" path "0.12.7" react-native-markdown-package "1.8.2" - react-native-url-polyfill "^1.3.0" - stream-chat "^9.3.0" - use-sync-external-store "^1.4.0" + react-native-url-polyfill "^2.0.0" + stream-chat "^9.7.0" + use-sync-external-store "^1.5.0" "stream-chat-react-native-core@link:../../package": version "0.0.0" uid "" -stream-chat@^9.3.0: - version "9.3.0" - resolved "https://registry.yarnpkg.com/stream-chat/-/stream-chat-9.3.0.tgz#35ca4db9e841eb92d07413ae156de0500ad77b23" - integrity sha512-S73B3HrvmQvJjq58Zjo50vh74juhsWsVRpT+OBjGAxSGxlA+ITkZ3vKs8Y/r2eDK7mBTMmX5QCruFaDJH5dRuw== - dependencies: - "@types/jsonwebtoken" "^9.0.8" - "@types/ws" "^8.5.14" - axios "^1.6.0" - base64-js "^1.5.1" - form-data "^4.0.0" - isomorphic-ws "^5.0.0" - jsonwebtoken "^9.0.2" - linkifyjs "^4.2.0" - ws "^8.18.1" - stream-chat@^9.7.0: version "9.7.0" resolved "https://registry.yarnpkg.com/stream-chat/-/stream-chat-9.7.0.tgz#8302a4dfd2b68115c57cd0a102976542a79cf132" @@ -6540,11 +6495,6 @@ use-latest-callback@^0.2.3: resolved "https://registry.yarnpkg.com/use-latest-callback/-/use-latest-callback-0.2.3.tgz#2d644d3063040b9bc2d4c55bb525a13ae3de9e16" integrity sha512-7vI3fBuyRcP91pazVboc4qu+6ZqM8izPWX9k7cRnT8hbD5svslcknsh3S9BUhaK11OmgTV4oWZZVSeQAiV53SQ== -use-sync-external-store@^1.4.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/use-sync-external-store/-/use-sync-external-store-1.4.0.tgz#adbc795d8eeb47029963016cefdf89dc799fcebc" - integrity sha512-9WXSPC5fMv61vaupRkCKCxsPxBocVnwakBEkMIHHpkTTg6icbJtg6jzgtLDm4bl3cSHAca52rYWih0k4K3PfHw== - use-sync-external-store@^1.5.0: version "1.5.0" resolved "https://registry.yarnpkg.com/use-sync-external-store/-/use-sync-external-store-1.5.0.tgz#55122e2a3edd2a6c106174c27485e0fd59bcfca0" diff --git a/package/expo-package/package.json b/package/expo-package/package.json index d47c802d5c..ae5f287d7b 100644 --- a/package/expo-package/package.json +++ b/package/expo-package/package.json @@ -24,12 +24,16 @@ "expo-image-picker": "*", "expo-media-library": "*", "expo-sharing": "*", - "expo-video": "*" + "expo-video": "*", + "expo-audio": "*" }, "peerDependenciesMeta": { "expo-av": { "optional": true }, + "expo-audio": { + "optional": true + }, "expo-video": { "optional": true }, @@ -57,7 +61,8 @@ }, "devDependencies": { "expo": "^53.0.12", - "expo-image-manipulator": "^12.0.5" + "expo-image-manipulator": "^12.0.5", + "expo-audio": "~0.4.6" }, "scripts": { "prepack": " cp ../../README.md .", diff --git a/package/expo-package/src/optionalDependencies/Audio.ts b/package/expo-package/src/optionalDependencies/Audio.ts index 6856969952..3a59bb4b5a 100644 --- a/package/expo-package/src/optionalDependencies/Audio.ts +++ b/package/expo-package/src/optionalDependencies/Audio.ts @@ -1,3 +1,5 @@ +import { Platform } from 'react-native'; + import { AndroidAudioEncoder, AndroidOutputFormat, @@ -5,10 +7,22 @@ import { IOSAudioQuality, IOSOutputFormat, ExpoRecordingOptions as RecordingOptions, + RecordingStatus, } from 'stream-chat-react-native-core'; import { AudioComponent, RecordingObject } from './AudioVideo'; +let ExpoAudioComponent; +let ExpoRecordingComponent; + +try { + const { AudioModule } = require('expo-audio'); + ExpoAudioComponent = AudioModule; + ExpoRecordingComponent = AudioModule.AudioRecorder; +} catch (e) { + // do nothing +} + const sleep = (ms: number) => new Promise((resolve) => { setTimeout(() => { @@ -16,7 +30,7 @@ const sleep = (ms: number) => }, ms); }); -class _Audio { +class _AudioExpoAV { recording: typeof RecordingObject | null = null; audioRecordingConfiguration: AudioRecordingConfiguration = { mode: { @@ -105,8 +119,233 @@ class _Audio { }; } +class _AudioExpoAudio { + recording: typeof RecordingObject | null = null; + audioRecordingConfiguration: AudioRecordingConfiguration = { + mode: { + allowsRecordingIOS: true, + playsInSilentModeIOS: true, + }, + options: { + android: { + audioEncoder: AndroidAudioEncoder.AAC, + extension: '.aac', + outputFormat: AndroidOutputFormat.AAC_ADTS, + }, + ios: { + audioQuality: IOSAudioQuality.HIGH, + bitRate: 128000, + extension: '.aac', + numberOfChannels: 2, + outputFormat: IOSOutputFormat.MPEG4AAC, + sampleRate: 44100, + }, + isMeteringEnabled: true, + web: {}, + }, + }; + + startRecording = async (recordingOptions: RecordingOptions, onRecordingStatusUpdate) => { + try { + const permissions = await ExpoAudioComponent.getRecordingPermissionsAsync(); + const permissionsStatus = permissions.status; + let permissionsGranted = permissions.granted; + + // If permissions have not been determined yet, ask the user for permissions. + if (permissionsStatus === 'undetermined') { + const newPermissions = await ExpoAudioComponent.requestRecordingPermissionsAsync(); + permissionsGranted = newPermissions.granted; + } + + // If they are explicitly denied after this, exit early by throwing an error + // that will be caught in the catch block below (as a single source of not + // starting the player). The player would error itself anyway if we did not do + // this, but there's no reason to run the asynchronous calls when we know + // immediately that the player will not be run. + if (!permissionsGranted) { + throw new Error('Missing audio recording permission.'); + } + await ExpoAudioComponent.setAudioModeAsync( + expoAvToExpoAudioModeAdapter(this.audioRecordingConfiguration.mode), + ); + const options = { + ...recordingOptions, + ...this.audioRecordingConfiguration.options, + }; + + this.recording = new ExpoAudioRecordingAdapter(options); + await this.recording.createAsync( + Platform.OS === 'android' ? 100 : 60, + onRecordingStatusUpdate, + ); + return { accessGranted: true, recording: this.recording }; + } catch (error) { + console.error('Failed to start recording', error); + this.recording = null; + return { accessGranted: false, recording: null }; + } + }; + stopRecording = async () => { + try { + if (this.recording) { + await this.recording.stopAndUnloadAsync(); + } + this.recording = null; + } catch (error) { + console.log('Error stopping recoding', error); + } + }; +} + +class ExpoAudioRecordingAdapter { + private recording; + private recordingStateInterval; + private uri; + private options; + + constructor(options: RecordingOptions) { + // Currently, expo-audio has a bug where isMeteringEnabled is not respected + // whenever we pass it to the Recording class constructor - but rather it is + // only respected whenever you pass it to prepareToRecordAsync. That in turn + // however, means that all other audio related configuration will be overwritten + // and forgotten. So, we snapshot the configuration whenever we create an instance + // of a recorder and pass it to both places. Furthermore, the type of the options + // in prepareToRecordAsync is wrong - it's supposed to be the flattened config; + // otherwise none of the quality properties get respected either (only the top level + // ones). + this.options = flattenExpoAudioRecordingOptions(options); + this.recording = new ExpoRecordingComponent(this.options); + this.uri = null; + } + + createAsync = async ( + progressUpdateInterval: number = 500, + onRecordingStatusUpdate: (status: RecordingStatus) => void, + ) => { + this.recordingStateInterval = setInterval(() => { + const status = this.recording.getStatus(); + onRecordingStatusUpdate(status); + }, progressUpdateInterval); + this.uri = null; + await this.recording.prepareToRecordAsync(this.options); + this.recording.record(); + }; + + stopAndUnloadAsync = async () => { + clearInterval(this.recordingStateInterval); + await this.recording.stop(); + this.uri = this.recording.uri; + this.recording.release(); + }; + + getURI = () => this.uri; +} + export const overrideAudioRecordingConfiguration = ( audioRecordingConfiguration: AudioRecordingConfiguration, ) => audioRecordingConfiguration; -export const Audio = AudioComponent ? new _Audio() : null; +const flattenExpoAudioRecordingOptions = ( + options: RecordingOptions & { + bitRate?: number; + extension?: string; + numberOfChannels?: number; + sampleRate?: number; + }, +) => { + let commonOptions = { + bitRate: options.bitRate, + extension: options.extension, + isMeteringEnabled: options.isMeteringEnabled ?? false, + numberOfChannels: options.numberOfChannels, + sampleRate: options.sampleRate, + }; + + if (Platform.OS === 'ios') { + commonOptions = { + ...commonOptions, + ...options.ios, + }; + } else if (Platform.OS === 'android') { + const audioEncoder = options.android.audioEncoder; + const audioEncoderConfig = audioEncoder + ? { audioEncoder: expoAvToExpoAudioAndroidEncoderAdapter(audioEncoder) } + : {}; + const outputFormat = options.android.outputFormat; + const outputFormatConfig = outputFormat + ? { outputFormat: expoAvToExpoAudioAndroidOutputAdapter(outputFormat) } + : {}; + commonOptions = { + ...commonOptions, + ...options.android, + ...audioEncoderConfig, + ...outputFormatConfig, + }; + } + return commonOptions; +}; + +const expoAvToExpoAudioModeAdapter = (mode: AudioRecordingConfiguration['mode']) => { + const { + allowsRecordingIOS, + interruptionModeAndroid, + interruptionModeIOS, + playsInSilentModeIOS, + playThroughEarpieceAndroid, + staysActiveInBackground, + } = mode; + + return { + allowsRecording: allowsRecordingIOS, + interruptionMode: interruptionModeIOS, + interruptionModeAndroid, + playsInSilentMode: playsInSilentModeIOS, + shouldPlayInBackground: staysActiveInBackground, + shouldRouteThroughEarpiece: playThroughEarpieceAndroid, + }; +}; + +const expoAvToExpoAudioAndroidEncoderAdapter = ( + audioEncoder: AudioRecordingConfiguration['options']['android']['audioEncoder'], +) => { + const encoderMap = { + 0: 'default', + 1: 'amr_nb', + 2: 'amr_wb', + 3: 'aac', + 4: 'he_aac', + 5: 'aac_eld', + }; + + return Object.keys(encoderMap).includes(audioEncoder.toString()) + ? encoderMap[audioEncoder] + : 'default'; +}; + +const expoAvToExpoAudioAndroidOutputAdapter = ( + outputFormat: AudioRecordingConfiguration['options']['android']['outputFormat'], +) => { + const outputFormatMap = { + 0: 'default', + 1: '3gp', + 2: 'mpeg4', + 3: 'amrnb', + 4: 'amrwb', + 5: 'default', + 6: 'aac_adts', + 7: 'default', + 8: 'mpeg2ts', + 9: 'webm', + }; + + return Object.keys(outputFormatMap).includes(outputFormat.toString()) + ? outputFormatMap[outputFormat] + : 'default'; +}; + +// Always try to prioritize expo-audio if it's there. +export const Audio = ExpoRecordingComponent + ? new _AudioExpoAudio() + : AudioComponent + ? new _AudioExpoAV() + : null; diff --git a/package/expo-package/src/optionalDependencies/Sound.ts b/package/expo-package/src/optionalDependencies/Sound.ts index 281359543d..be186f6cf1 100644 --- a/package/expo-package/src/optionalDependencies/Sound.ts +++ b/package/expo-package/src/optionalDependencies/Sound.ts @@ -1,18 +1,177 @@ +import type { PlaybackStatus, SoundReturnType } from 'stream-chat-react-native-core'; + import { AudioComponent } from './AudioVideo'; +let ExpoAudioComponent; +let expoCreateSoundPlayer; + +try { + const { createAudioPlayer, AudioModule } = require('expo-audio'); + ExpoAudioComponent = AudioModule; + expoCreateSoundPlayer = createAudioPlayer; +} catch (e) { + // do nothing +} + export const Sound = { - initializeSound: AudioComponent + // Always try to prioritize expo-audio if it's there. + initializeSound: ExpoAudioComponent ? async (source, initialStatus, onPlaybackStatusUpdate: (playbackStatus) => void) => { - await AudioComponent.setAudioModeAsync({ - playsInSilentModeIOS: true, + await ExpoAudioComponent.setAudioModeAsync({ + playsInSilentMode: true, }); - const { sound } = await AudioComponent.Sound.createAsync( - source, - initialStatus, - onPlaybackStatusUpdate, - ); + const sound = new ExpoAudioSoundAdapter(onPlaybackStatusUpdate); + await sound.loadAsync(source, initialStatus); return sound; } - : null, + : AudioComponent + ? async (source, initialStatus, onPlaybackStatusUpdate: (playbackStatus) => void) => { + await AudioComponent.setAudioModeAsync({ + playsInSilentModeIOS: true, + }); + const { sound } = await AudioComponent.Sound.createAsync( + source, + initialStatus, + onPlaybackStatusUpdate, + ); + return sound; + } + : null, Player: null, }; + +type ExpoAudioPlaybackStatus = { + currentTime: number; + didJustFinish: boolean; + duration: number; + id: number; + isBuffering: boolean; + isLoaded: boolean; + loop: boolean; + mute: boolean; + playbackRate: number; + playbackState: string; + playing: boolean; + reasonForWaitingToPlay: string; + shouldCorrectPitch: boolean; + timeControlStatus: string; +}; + +class ExpoAudioSoundAdapter { + private player; + private statusEventListener; + private initialPitchCorrectionQuality; + private initialShouldCorrectPitch; + private onPlaybackStatusUpdate; + + constructor(onPlaybackStatusUpdate: (playbackStatus: PlaybackStatus) => void) { + this.onPlaybackStatusUpdate = (playbackStatus: ExpoAudioPlaybackStatus) => { + onPlaybackStatusUpdate(expoAudioToExpoAvStatusAdapter(playbackStatus)); + if (playbackStatus.didJustFinish) { + this.unsubscribeStatusEventListener(); + } + }; + } + + subscribeStatusEventListener = () => { + if (this.statusEventListener) { + this.unsubscribeStatusEventListener(); + } + this.statusEventListener = this.player.addListener( + 'playbackStatusUpdate', + this.onPlaybackStatusUpdate, + ); + }; + + unsubscribeStatusEventListener = () => { + if (this.statusEventListener) { + this.statusEventListener.remove(); + this.statusEventListener = null; + } + }; + + // eslint-disable-next-line require-await + loadAsync = async (source, initialStatus) => { + this.player = expoCreateSoundPlayer?.(source, initialStatus.progressUpdateIntervalMillis); + this.initialShouldCorrectPitch = initialStatus.shouldCorrectPitch; + this.initialPitchCorrectionQuality = initialStatus.pitchCorrectionQuality; + }; + + // eslint-disable-next-line require-await + stopAsync: SoundReturnType['stopAsync'] = async () => { + this.unsubscribeStatusEventListener(); + this.player.seekTo(0); + this.player.pause(); + }; + + // eslint-disable-next-line require-await + unloadAsync: SoundReturnType['unloadAsync'] = async () => { + this.unsubscribeStatusEventListener(); + this.player.release(); + }; + + // eslint-disable-next-line require-await + playAsync: SoundReturnType['playAsync'] = async () => { + this.subscribeStatusEventListener(); + this.player.play(); + }; + + // eslint-disable-next-line require-await + pauseAsync: SoundReturnType['pauseAsync'] = async () => { + this.unsubscribeStatusEventListener(); + this.player.pause(); + }; + + // eslint-disable-next-line require-await + replayAsync: SoundReturnType['replayAsync'] = async () => { + this.subscribeStatusEventListener(); + this.player.seekTo(0); + }; + + // eslint-disable-next-line require-await + setPositionAsync: SoundReturnType['setPositionAsync'] = async (milliseconds) => { + const seconds = milliseconds / 1000; + this.player.seekTo(seconds); + }; + + // eslint-disable-next-line require-await + setRateAsync: SoundReturnType['setRateAsync'] = async ( + rate, + shouldCorrectPitch = this.initialShouldCorrectPitch, + pitchCorrectionQuality = this.initialPitchCorrectionQuality, + ) => { + // On Android, pitch correction sets the playback speed to 1f every time + // as seen here: https://github.com/expo/expo/blob/f9d82c5af6d472c257b14c2657938db1be4a1b2c/packages/expo-audio/android/src/main/java/expo/modules/audio/AudioModule.kt#L409 + // Pitch correction is set to true whenever the pitchCorrectionQuality parameter is set, + // so there isn't much we can do about it for now. + // This is wrong and will likely be fixed within the library. + if (shouldCorrectPitch && pitchCorrectionQuality) { + this.player.setPlaybackRate(rate, pitchCorrectionQuality); + return; + } + this.player.setPlaybackRate(rate); + }; +} + +const expoAudioToExpoAvStatusAdapter = ( + playbackStatus: ExpoAudioPlaybackStatus, +): PlaybackStatus => { + const { currentTime, didJustFinish, duration, isBuffering, isLoaded, loop, mute, playing } = + playbackStatus; + + return { + currentPosition: undefined, // not present in the expo-av api, breaks things if set + didJustFinish, + duration: undefined, // not present in the expo-av api, breaks things if set + durationMillis: duration * 1000, + error: null, // TODO: check how we can see if there is an error + isBuffering, + isLoaded, + isLooping: loop, + isMuted: mute, + isPlaying: playing, + isSeeking: false, // we don't use this anywhere, so just defaulting to a safe value since nothing similar exists in expo-audio + positionMillis: currentTime * 1000, + shouldPlay: undefined, // we cannot determine whether the audio should be playing or not + }; +}; diff --git a/package/expo-package/yarn.lock b/package/expo-package/yarn.lock index fd226b40f7..611ab74a91 100644 --- a/package/expo-package/yarn.lock +++ b/package/expo-package/yarn.lock @@ -1493,10 +1493,10 @@ find-up "^5.0.0" js-yaml "^4.1.0" -"@gorhom/bottom-sheet@^5.1.1": - version "5.1.1" - resolved "https://registry.yarnpkg.com/@gorhom/bottom-sheet/-/bottom-sheet-5.1.1.tgz#43ecb9e7b4d4ca4b4cefdf3b6b497f7715f350bc" - integrity sha512-Y8FiuRmeZYaP+ZGQ0axDwWrrKqVp4ByYRs1D2fTJTxHMt081MHHRQsqmZ3SK7AFp3cSID+vTqnD8w/KAASpy+w== +"@gorhom/bottom-sheet@^5.1.6": + version "5.1.6" + resolved "https://registry.yarnpkg.com/@gorhom/bottom-sheet/-/bottom-sheet-5.1.6.tgz#92365894ae4d4eefdbaa577408cfaf62463a9490" + integrity sha512-0b5tQj4fTaZAjST1PnkCp0p7d8iRqMezibTcqc8Kkn3N23Vn6upORNTD1fH0bLfwRt6e0WnZ7DjAmq315lrcKQ== dependencies: "@gorhom/portal" "1.0.14" invariant "^2.2.4" @@ -2224,10 +2224,10 @@ csstype@^3.0.2: resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.1.2.tgz#1d4bf9d572f11c14031f0436e1c10bc1f571f50b" integrity sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ== -dayjs@1.10.5: - version "1.10.5" - resolved "https://registry.yarnpkg.com/dayjs/-/dayjs-1.10.5.tgz#5600df4548fc2453b3f163ebb2abbe965ccfb986" - integrity sha512-BUFis41ikLz+65iH6LHQCDm4YPMj5r1YFLdupPIyM4SGcXMmtiLQ7U37i+hGS8urIuqe7I/ou3IS1jVc4nbN4g== +dayjs@1.11.13: + version "1.11.13" + resolved "https://registry.yarnpkg.com/dayjs/-/dayjs-1.11.13.tgz#92430b0139055c3ebb60150aa13e860a4b5a366c" + integrity sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg== debug@2.6.9, debug@^2.2.0, debug@^2.6.9: version "2.6.9" @@ -2345,10 +2345,10 @@ electron-to-chromium@^1.5.73: resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.5.112.tgz#8d3d95d4d5653836327890282c8eda5c6f26626d" integrity sha512-oen93kVyqSb3l+ziUgzIOlWt/oOuy4zRmpwestMn4rhFWAoFJeFuCVte9F2fASjeZZo7l/Cif9TiyrdW4CwEMA== -emoji-regex@^10.3.0: - version "10.3.0" - resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-10.3.0.tgz#76998b9268409eb3dae3de989254d456e70cfe23" - integrity sha512-QpLs9D9v9kArv4lfDEgg1X/gN5XLnf/A6l9cs8SPZLRZR3ZkY9+kwIQTxm+fsSej5UMYGE8fdoaZVIBlqG0XTw== +emoji-regex@^10.4.0: + version "10.4.0" + resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-10.4.0.tgz#03553afea80b3975749cfcb36f776ca268e413d4" + integrity sha512-EC+0oUMY1Rqm4O6LLrgjtYDvcVYTy7chDnM4Q7030tP4Kwj3u/pR6gP9ygnp2CJMK5Gq+9Q2oqmrFJAz01DXjw== emoji-regex@^8.0.0: version "8.0.0" @@ -2428,6 +2428,11 @@ expo-asset@~11.1.5: "@expo/image-utils" "^0.7.4" expo-constants "~17.1.5" +expo-audio@~0.4.6: + version "0.4.6" + resolved "https://registry.yarnpkg.com/expo-audio/-/expo-audio-0.4.6.tgz#39def71b91f2cb5c4d21a51aef6ffabcc4421ad4" + integrity sha512-/pgz0AnQHnyJWkPfTp/3gBDib6FZYnscpktFZgmPeTeCK8KkPV4+eV2oEDDbSe2ngUrqDA0WVO4MX2Mfeec9ZA== + expo-constants@~17.1.5, expo-constants@~17.1.6: version "17.1.6" resolved "https://registry.yarnpkg.com/expo-constants/-/expo-constants-17.1.6.tgz#a31b019216f7f7bb4907aeffa2d6bf856751985e" @@ -2689,7 +2694,7 @@ http-errors@2.0.0: statuses "2.0.1" toidentifier "1.0.1" -i18next@^21.6.14: +i18next@^21.10.0: version "21.10.0" resolved "https://registry.yarnpkg.com/i18next/-/i18next-21.10.0.tgz#85429af55fdca4858345d0e16b584ec29520197d" integrity sha512-YeuIBmFsGjUfO3qBmMOc0rQaun4mIpGKET5WDwvu8lU7gvwpcariZLNtL0Fzj+zazcHUrlXHiptcFhBMFaxzfg== @@ -2957,16 +2962,16 @@ lines-and-columns@^1.1.6: resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.2.4.tgz#eca284f75d2965079309dc0ad9255abb2ebc1632" integrity sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg== -linkifyjs@^4.1.1: - version "4.1.1" - resolved "https://registry.yarnpkg.com/linkifyjs/-/linkifyjs-4.1.1.tgz#73d427e3bbaaf4ca8e71c589ad4ffda11a9a5fde" - integrity sha512-zFN/CTVmbcVef+WaDXT63dNzzkfRBKT1j464NJQkV7iSgJU0sLBus9W0HBwnXK13/hf168pbrx/V/bjEHOXNHA== - linkifyjs@^4.2.0: version "4.2.0" resolved "https://registry.yarnpkg.com/linkifyjs/-/linkifyjs-4.2.0.tgz#9dd30222b9cbabec9c950e725ec00031c7fa3f08" integrity sha512-pCj3PrQyATaoTYKHrgWRF3SJwsm61udVh+vuls/Rl6SptiDhgE7ziUIudAedRY9QEfynmM7/RmLEfPUyw1HPCw== +linkifyjs@^4.3.1: + version "4.3.1" + resolved "https://registry.yarnpkg.com/linkifyjs/-/linkifyjs-4.3.1.tgz#1f246ebf4be040002accd1f4535b6af7c7e37898" + integrity sha512-DRSlB9DKVW04c4SUdGvKK5FR6be45lTU9M76JnngqPeeGDqPwYc0zdUErtsNVMtxPXgUWV4HbXbnC4sNyBxkYg== + locate-path@^6.0.0: version "6.0.0" resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-6.0.0.tgz#55321eb309febbc59c4801d931a72452a681d286" @@ -3072,7 +3077,7 @@ mime-db@1.52.0: resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.53.0.tgz#3cb63cd820fc29896d9d4e8c32ab4fcd74ccb447" integrity sha512-oHlN/w+3MQ3rba9rqFr6V/ypF10LSkdwUysQL7GkXoTgIWeV+tcXGA852TBxH+gsh8UWoyhR1hKcoMJTuWflpg== -mime-types@^2.1.12, mime-types@^2.1.34, mime-types@~2.1.34: +mime-types@^2.1.12, mime-types@^2.1.35, mime-types@~2.1.34: version "2.1.35" resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.35.tgz#381a871b62a734450660ae3deee44813f70d959a" integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw== @@ -3522,10 +3527,10 @@ react-native-markdown-package@1.8.2: react-native-lightbox "^0.7.0" simple-markdown "^0.7.1" -react-native-url-polyfill@^1.3.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/react-native-url-polyfill/-/react-native-url-polyfill-1.3.0.tgz#c1763de0f2a8c22cc3e959b654c8790622b6ef6a" - integrity sha512-w9JfSkvpqqlix9UjDvJjm1EjSt652zVQ6iwCIj1cVVkwXf4jQhQgTNXY6EVTwuAmUjg6BC6k9RHCBynoLFo3IQ== +react-native-url-polyfill@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/react-native-url-polyfill/-/react-native-url-polyfill-2.0.0.tgz#db714520a2985cff1d50ab2e66279b9f91ffd589" + integrity sha512-My330Do7/DvKnEvwQc0WdcBnFPploYKp9CYlefDXzIdEaA+PAhDYllkvGeEroEzvc4Kzzj2O4yVdz8v6fjRvhA== dependencies: whatwg-url-without-unicode "8.0.0-3" @@ -3834,29 +3839,29 @@ stream-buffers@2.2.x: resolved "https://registry.yarnpkg.com/stream-buffers/-/stream-buffers-2.2.0.tgz#91d5f5130d1cef96dcfa7f726945188741d09ee4" integrity sha512-uyQK/mx5QjHun80FLJTfaWE7JtwfRMKBLkMne6udYOmvH0CawotVa7TfgYHzAnpphn4+TweIx1QKMnRIbipmUg== -stream-chat-react-native-core@7.1.1: - version "7.1.1" - resolved "https://registry.yarnpkg.com/stream-chat-react-native-core/-/stream-chat-react-native-core-7.1.1.tgz#b22faf35fa5defd24c730873aeba30c172556089" - integrity sha512-9AkSKWzywN2FfsMgDfeoCatr/qoG+zJzM2u5j3PU6WU7qIhZtM/7+2UB0WKAY7fA5MjaoMEzV1mBF+hILP1KOw== +stream-chat-react-native-core@7.1.2: + version "7.1.2" + resolved "https://registry.yarnpkg.com/stream-chat-react-native-core/-/stream-chat-react-native-core-7.1.2.tgz#5870a1188ecbf8c3b705d74379d19ff77efce2c5" + integrity sha512-Ob+V8tt+7L+7BRkWyWbFlju6E/8MAoB/NUZ8ENtEEijq5QBNWnVvZctQSZuekIOVrfoP9EenlIOPadHnN/mvYA== dependencies: - "@gorhom/bottom-sheet" "^5.1.1" - dayjs "1.10.5" - emoji-regex "^10.3.0" - i18next "^21.6.14" + "@gorhom/bottom-sheet" "^5.1.6" + dayjs "1.11.13" + emoji-regex "^10.4.0" + i18next "^21.10.0" intl-pluralrules "^2.0.1" - linkifyjs "^4.1.1" + linkifyjs "^4.3.1" lodash-es "4.17.21" - mime-types "^2.1.34" + mime-types "^2.1.35" path "0.12.7" react-native-markdown-package "1.8.2" - react-native-url-polyfill "^1.3.0" - stream-chat "^9.3.0" - use-sync-external-store "^1.4.0" + react-native-url-polyfill "^2.0.0" + stream-chat "^9.7.0" + use-sync-external-store "^1.5.0" -stream-chat@^9.3.0: - version "9.5.1" - resolved "https://registry.yarnpkg.com/stream-chat/-/stream-chat-9.5.1.tgz#b8260bc1d1470ae3c91d8c40d22f41e9c4523d7b" - integrity sha512-X9w22JfEp2cTggAwyt0gyvwe8VBy1qvJENliNen/2FJDpS3k6PCaeSO6MHNXz3c0Qy21hqxuu8/b32jCSe4LSA== +stream-chat@^9.7.0: + version "9.7.0" + resolved "https://registry.yarnpkg.com/stream-chat/-/stream-chat-9.7.0.tgz#8302a4dfd2b68115c57cd0a102976542a79cf132" + integrity sha512-8K4RQAUFfznCxpJ5CMIrMQQLroaZ1snB4aR/Xnwa9UpxNCzn3kIi61AVkfsaHTHGojPz5LA3c3faVb251u4HnA== dependencies: "@types/jsonwebtoken" "^9.0.8" "@types/ws" "^8.5.14" @@ -4110,10 +4115,10 @@ update-browserslist-db@^1.1.1: escalade "^3.2.0" picocolors "^1.1.1" -use-sync-external-store@^1.4.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/use-sync-external-store/-/use-sync-external-store-1.4.0.tgz#adbc795d8eeb47029963016cefdf89dc799fcebc" - integrity sha512-9WXSPC5fMv61vaupRkCKCxsPxBocVnwakBEkMIHHpkTTg6icbJtg6jzgtLDm4bl3cSHAca52rYWih0k4K3PfHw== +use-sync-external-store@^1.5.0: + version "1.5.0" + resolved "https://registry.yarnpkg.com/use-sync-external-store/-/use-sync-external-store-1.5.0.tgz#55122e2a3edd2a6c106174c27485e0fd59bcfca0" + integrity sha512-Rb46I4cGGVBmjamjphe8L/UnvJD+uPPtTkNvX5mZgqdbavhI4EbgIWJiIHXJ8bc/i9EQGPRh4DwEURJ552Do0A== util@^0.10.3: version "0.10.4" diff --git a/package/native-package/yarn.lock b/package/native-package/yarn.lock index ba4b069d3f..f2cc9d7164 100644 --- a/package/native-package/yarn.lock +++ b/package/native-package/yarn.lock @@ -375,10 +375,10 @@ "@babel/helper-validator-identifier" "^7.24.7" to-fast-properties "^2.0.0" -"@gorhom/bottom-sheet@^5.1.1": - version "5.1.1" - resolved "https://registry.yarnpkg.com/@gorhom/bottom-sheet/-/bottom-sheet-5.1.1.tgz#43ecb9e7b4d4ca4b4cefdf3b6b497f7715f350bc" - integrity sha512-Y8FiuRmeZYaP+ZGQ0axDwWrrKqVp4ByYRs1D2fTJTxHMt081MHHRQsqmZ3SK7AFp3cSID+vTqnD8w/KAASpy+w== +"@gorhom/bottom-sheet@^5.1.6": + version "5.1.6" + resolved "https://registry.yarnpkg.com/@gorhom/bottom-sheet/-/bottom-sheet-5.1.6.tgz#92365894ae4d4eefdbaa577408cfaf62463a9490" + integrity sha512-0b5tQj4fTaZAjST1PnkCp0p7d8iRqMezibTcqc8Kkn3N23Vn6upORNTD1fH0bLfwRt6e0WnZ7DjAmq315lrcKQ== dependencies: "@gorhom/portal" "1.0.14" invariant "^2.2.4" @@ -1153,10 +1153,10 @@ d@1, d@^1.0.1: es5-ext "^0.10.50" type "^1.0.1" -dayjs@1.10.5: - version "1.10.5" - resolved "https://registry.yarnpkg.com/dayjs/-/dayjs-1.10.5.tgz#5600df4548fc2453b3f163ebb2abbe965ccfb986" - integrity sha512-BUFis41ikLz+65iH6LHQCDm4YPMj5r1YFLdupPIyM4SGcXMmtiLQ7U37i+hGS8urIuqe7I/ou3IS1jVc4nbN4g== +dayjs@1.11.13: + version "1.11.13" + resolved "https://registry.yarnpkg.com/dayjs/-/dayjs-1.11.13.tgz#92430b0139055c3ebb60150aa13e860a4b5a366c" + integrity sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg== debug@2.6.9, debug@^2.2.0, debug@^2.6.9: version "2.6.9" @@ -1218,10 +1218,10 @@ electron-to-chromium@^1.5.73: resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.5.112.tgz#8d3d95d4d5653836327890282c8eda5c6f26626d" integrity sha512-oen93kVyqSb3l+ziUgzIOlWt/oOuy4zRmpwestMn4rhFWAoFJeFuCVte9F2fASjeZZo7l/Cif9TiyrdW4CwEMA== -emoji-regex@^10.3.0: - version "10.3.0" - resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-10.3.0.tgz#76998b9268409eb3dae3de989254d456e70cfe23" - integrity sha512-QpLs9D9v9kArv4lfDEgg1X/gN5XLnf/A6l9cs8SPZLRZR3ZkY9+kwIQTxm+fsSej5UMYGE8fdoaZVIBlqG0XTw== +emoji-regex@^10.4.0: + version "10.4.0" + resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-10.4.0.tgz#03553afea80b3975749cfcb36f776ca268e413d4" + integrity sha512-EC+0oUMY1Rqm4O6LLrgjtYDvcVYTy7chDnM4Q7030tP4Kwj3u/pR6gP9ygnp2CJMK5Gq+9Q2oqmrFJAz01DXjw== emoji-regex@^8.0.0: version "8.0.0" @@ -1499,7 +1499,7 @@ https-proxy-agent@^7.0.5: agent-base "^7.1.2" debug "4" -i18next@^21.6.14: +i18next@^21.10.0: version "21.10.0" resolved "https://registry.yarnpkg.com/i18next/-/i18next-21.10.0.tgz#85429af55fdca4858345d0e16b584ec29520197d" integrity sha512-YeuIBmFsGjUfO3qBmMOc0rQaun4mIpGKET5WDwvu8lU7gvwpcariZLNtL0Fzj+zazcHUrlXHiptcFhBMFaxzfg== @@ -1797,16 +1797,16 @@ lighthouse-logger@^1.0.0: debug "^2.6.9" marky "^1.2.2" -linkifyjs@^4.1.1: - version "4.1.1" - resolved "https://registry.yarnpkg.com/linkifyjs/-/linkifyjs-4.1.1.tgz#73d427e3bbaaf4ca8e71c589ad4ffda11a9a5fde" - integrity sha512-zFN/CTVmbcVef+WaDXT63dNzzkfRBKT1j464NJQkV7iSgJU0sLBus9W0HBwnXK13/hf168pbrx/V/bjEHOXNHA== - linkifyjs@^4.2.0: version "4.2.0" resolved "https://registry.yarnpkg.com/linkifyjs/-/linkifyjs-4.2.0.tgz#9dd30222b9cbabec9c950e725ec00031c7fa3f08" integrity sha512-pCj3PrQyATaoTYKHrgWRF3SJwsm61udVh+vuls/Rl6SptiDhgE7ziUIudAedRY9QEfynmM7/RmLEfPUyw1HPCw== +linkifyjs@^4.3.1: + version "4.3.1" + resolved "https://registry.yarnpkg.com/linkifyjs/-/linkifyjs-4.3.1.tgz#1f246ebf4be040002accd1f4535b6af7c7e37898" + integrity sha512-DRSlB9DKVW04c4SUdGvKK5FR6be45lTU9M76JnngqPeeGDqPwYc0zdUErtsNVMtxPXgUWV4HbXbnC4sNyBxkYg== + locate-path@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-5.0.0.tgz#1afba396afd676a6d42504d0a67a3a7eb9f62aa0" @@ -2106,7 +2106,7 @@ mime-db@1.52.0: resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.52.0.tgz#bbabcdc02859f4987301c856e3387ce5ec43bf70" integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg== -mime-types@^2.1.12, mime-types@^2.1.27, mime-types@^2.1.34, mime-types@~2.1.34: +mime-types@^2.1.12, mime-types@^2.1.27, mime-types@^2.1.35, mime-types@~2.1.34: version "2.1.35" resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.35.tgz#381a871b62a734450660ae3deee44813f70d959a" integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw== @@ -2382,10 +2382,10 @@ react-native-markdown-package@1.8.2: react-native-lightbox "^0.7.0" simple-markdown "^0.7.1" -react-native-url-polyfill@^1.3.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/react-native-url-polyfill/-/react-native-url-polyfill-1.3.0.tgz#c1763de0f2a8c22cc3e959b654c8790622b6ef6a" - integrity sha512-w9JfSkvpqqlix9UjDvJjm1EjSt652zVQ6iwCIj1cVVkwXf4jQhQgTNXY6EVTwuAmUjg6BC6k9RHCBynoLFo3IQ== +react-native-url-polyfill@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/react-native-url-polyfill/-/react-native-url-polyfill-2.0.0.tgz#db714520a2985cff1d50ab2e66279b9f91ffd589" + integrity sha512-My330Do7/DvKnEvwQc0WdcBnFPploYKp9CYlefDXzIdEaA+PAhDYllkvGeEroEzvc4Kzzj2O4yVdz8v6fjRvhA== dependencies: whatwg-url-without-unicode "8.0.0-3" @@ -2611,29 +2611,29 @@ statuses@~1.5.0: resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.5.0.tgz#161c7dac177659fd9811f43771fa99381478628c" integrity sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA== -stream-chat-react-native-core@7.1.1: - version "7.1.1" - resolved "https://registry.yarnpkg.com/stream-chat-react-native-core/-/stream-chat-react-native-core-7.1.1.tgz#b22faf35fa5defd24c730873aeba30c172556089" - integrity sha512-9AkSKWzywN2FfsMgDfeoCatr/qoG+zJzM2u5j3PU6WU7qIhZtM/7+2UB0WKAY7fA5MjaoMEzV1mBF+hILP1KOw== +stream-chat-react-native-core@7.1.2: + version "7.1.2" + resolved "https://registry.yarnpkg.com/stream-chat-react-native-core/-/stream-chat-react-native-core-7.1.2.tgz#5870a1188ecbf8c3b705d74379d19ff77efce2c5" + integrity sha512-Ob+V8tt+7L+7BRkWyWbFlju6E/8MAoB/NUZ8ENtEEijq5QBNWnVvZctQSZuekIOVrfoP9EenlIOPadHnN/mvYA== dependencies: - "@gorhom/bottom-sheet" "^5.1.1" - dayjs "1.10.5" - emoji-regex "^10.3.0" - i18next "^21.6.14" + "@gorhom/bottom-sheet" "^5.1.6" + dayjs "1.11.13" + emoji-regex "^10.4.0" + i18next "^21.10.0" intl-pluralrules "^2.0.1" - linkifyjs "^4.1.1" + linkifyjs "^4.3.1" lodash-es "4.17.21" - mime-types "^2.1.34" + mime-types "^2.1.35" path "0.12.7" react-native-markdown-package "1.8.2" - react-native-url-polyfill "^1.3.0" - stream-chat "^9.3.0" - use-sync-external-store "^1.4.0" + react-native-url-polyfill "^2.0.0" + stream-chat "^9.7.0" + use-sync-external-store "^1.5.0" -stream-chat@^9.3.0: - version "9.5.1" - resolved "https://registry.yarnpkg.com/stream-chat/-/stream-chat-9.5.1.tgz#b8260bc1d1470ae3c91d8c40d22f41e9c4523d7b" - integrity sha512-X9w22JfEp2cTggAwyt0gyvwe8VBy1qvJENliNen/2FJDpS3k6PCaeSO6MHNXz3c0Qy21hqxuu8/b32jCSe4LSA== +stream-chat@^9.7.0: + version "9.8.0" + resolved "https://registry.yarnpkg.com/stream-chat/-/stream-chat-9.8.0.tgz#52782073a3923367fe97638fde39ce18e4eed28a" + integrity sha512-iKFVFOKWuW2/GTWBOps9YWZoQBlXdJ05FiOKXI/AnCMCGzOpmvEyaoCtsktvdeMaetmZojVPbw/5jomP36Qg0Q== dependencies: "@types/jsonwebtoken" "^9.0.8" "@types/ws" "^8.5.14" @@ -2761,10 +2761,10 @@ update-browserslist-db@^1.1.1: escalade "^3.2.0" picocolors "^1.1.1" -use-sync-external-store@^1.4.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/use-sync-external-store/-/use-sync-external-store-1.4.0.tgz#adbc795d8eeb47029963016cefdf89dc799fcebc" - integrity sha512-9WXSPC5fMv61vaupRkCKCxsPxBocVnwakBEkMIHHpkTTg6icbJtg6jzgtLDm4bl3cSHAca52rYWih0k4K3PfHw== +use-sync-external-store@^1.5.0: + version "1.5.0" + resolved "https://registry.yarnpkg.com/use-sync-external-store/-/use-sync-external-store-1.5.0.tgz#55122e2a3edd2a6c106174c27485e0fd59bcfca0" + integrity sha512-Rb46I4cGGVBmjamjphe8L/UnvJD+uPPtTkNvX5mZgqdbavhI4EbgIWJiIHXJ8bc/i9EQGPRh4DwEURJ552Do0A== util@^0.10.3: version "0.10.4" diff --git a/package/src/components/MessageInput/hooks/useAudioController.tsx b/package/src/components/MessageInput/hooks/useAudioController.tsx index 4919a9b74e..7c5eb6269c 100644 --- a/package/src/components/MessageInput/hooks/useAudioController.tsx +++ b/package/src/components/MessageInput/hooks/useAudioController.tsx @@ -126,16 +126,17 @@ export const useAudioController = () => { if (recording && typeof recording !== 'string') { const uri = recording.getURI(); if (uri && NativeHandlers.Sound?.initializeSound) { - soundRef.current = await NativeHandlers.Sound.initializeSound( - { uri }, - {}, - onVoicePlayerPlaybackStatusUpdate, - ); - if (soundRef.current?.playAsync && soundRef.current.setProgressUpdateIntervalAsync) { - await soundRef.current.playAsync(); - await soundRef.current.setProgressUpdateIntervalAsync( - Platform.OS === 'android' ? 100 : 60, + if (soundRef.current?.replayAsync) { + await soundRef.current.replayAsync({}); + } else { + soundRef.current = await NativeHandlers.Sound.initializeSound( + { uri }, + { progressUpdateIntervalMillis: Platform.OS === 'android' ? 100 : 60 }, + onVoicePlayerPlaybackStatusUpdate, ); + if (soundRef.current?.playAsync) { + await soundRef.current.playAsync(); + } } } } @@ -187,7 +188,7 @@ export const useAudioController = () => { if (accessGranted) { setPermissionsGranted(true); const recording = recordingInfo.recording; - if (recording && typeof recording !== 'string') { + if (recording && typeof recording !== 'string' && recording.setProgressUpdateInterval) { recording.setProgressUpdateInterval(Platform.OS === 'android' ? 100 : 60); } setRecording(recording); diff --git a/package/src/index.ts b/package/src/index.ts index 15fa2a8dfe..8ff841d528 100644 --- a/package/src/index.ts +++ b/package/src/index.ts @@ -4,7 +4,7 @@ import './polyfills'; export * from './components'; export * from './hooks'; -export { registerNativeHandlers } from './native'; +export { registerNativeHandlers, SoundReturnType, PlaybackStatus, RecordingStatus } from './native'; export * from './contexts'; export * from './emoji-data';