diff --git a/apps/native-component-list/src/screens/Audio/AudioPreloadScreen.tsx b/apps/native-component-list/src/screens/Audio/AudioPreloadScreen.tsx
new file mode 100644
index 00000000000000..623311d01c0023
--- /dev/null
+++ b/apps/native-component-list/src/screens/Audio/AudioPreloadScreen.tsx
@@ -0,0 +1,234 @@
+import {
+ preload,
+ getPreloadedSources,
+ clearAllPreloadedSources,
+ useAudioPlayer,
+ useAudioPlayerStatus,
+ AudioPlayer,
+ AudioSource,
+ AudioStatus,
+} from 'expo-audio';
+import React from 'react';
+import { Pressable, ScrollView, StyleSheet, Text, View } from 'react-native';
+
+import HeadingText from '../../components/HeadingText';
+import ListButton from '../../components/ListButton';
+import Colors from '../../constants/Colors';
+
+export const sfx1: AudioSource = {
+ uri: 'https://cdn.freesound.org/previews/370/370182_6430986-hq.mp3',
+};
+
+export const sfx2: AudioSource = {
+ uri: 'https://cdn.freesound.org/previews/401/401542_2331641-hq.mp3',
+};
+
+export default function AudioPreloadScreen(props: any) {
+ React.useLayoutEffect(() => {
+ props.navigation.setOptions({
+ title: 'Audio Preloading',
+ });
+ });
+
+ return (
+
+ Sound Effects
+
+ Both sounds were preloaded in module scope. Tap the buttons to play — they should start
+ near-instantly.
+
+
+
+ Preload Cache
+
+
+ Preloaded Player
+
+ This player uses a preloaded source. Try replacing to swap between the two preloaded tracks.
+
+
+
+ );
+}
+
+function SoundEffectButtons() {
+ const player1 = useAudioPlayer(sfx1, { keepAudioSessionActive: true });
+ const player2 = useAudioPlayer(sfx2, { keepAudioSessionActive: true });
+ const status1 = useAudioPlayerStatus(player1);
+ const status2 = useAudioPlayerStatus(player2);
+
+ const playSfx = (player: AudioPlayer, status: AudioStatus) => {
+ if (status.playing) {
+ player.seekTo(0);
+ } else {
+ if (status.didJustFinish || status.currentTime > 0) {
+ player.seekTo(0);
+ }
+ player.play();
+ }
+ };
+
+ return (
+
+ playSfx(player1, status1)}
+ />
+ playSfx(player2, status2)}
+ />
+
+ );
+}
+
+function SfxButton({
+ label,
+ isLoaded,
+ onPress,
+}: {
+ label: string;
+ isLoaded: boolean;
+ onPress: () => void;
+}) {
+ return (
+ [styles.sfxButton, pressed && styles.sfxButtonPressed]}
+ onPress={onPress}>
+ {label}
+ {isLoaded ? 'Ready' : 'Loading...'}
+
+ );
+}
+
+function PreloadCacheInfo() {
+ const [sources, setSources] = React.useState([]);
+
+ const refresh = async () => setSources(await getPreloadedSources());
+
+ return (
+
+
+ Preloaded sources are consumed when a player is created with a matching URL. Use the buttons
+ below to preload, inspect, and clear the cache.
+
+ {
+ await preload(sfx1);
+ await preload(sfx2);
+ refresh();
+ }}
+ />
+
+
+ {sources.length === 0 ? 'Cache is empty.' : `${sources.length} source(s) in cache:`}
+
+ {sources.map((uri) => (
+
+ {uri}
+
+ ))}
+ {
+ await clearAllPreloadedSources();
+ refresh();
+ }}
+ />
+
+ );
+}
+
+function PreloadedPlayer() {
+ const player = useAudioPlayer(sfx1);
+ const status = useAudioPlayerStatus(player);
+ const [currentSource, setCurrentSource] = React.useState<1 | 2>(1);
+
+ const handleReplace = () => {
+ if (currentSource === 1) {
+ player.replace(sfx2);
+ setCurrentSource(2);
+ } else {
+ player.replace(sfx1);
+ setCurrentSource(1);
+ }
+ };
+
+ return (
+
+ Current: Sound {currentSource}
+
+ {status.isLoaded ? `Loaded — ${Math.round(status.duration)}s` : 'Loading...'}
+ {status.playing ? ' — Playing' : ''}
+
+
+ {
+ if (status.playing) {
+ player.pause();
+ } else {
+ if (status.didJustFinish) player.seekTo(0);
+ player.play();
+ }
+ }}
+ />
+
+
+
+ );
+}
+
+const styles = StyleSheet.create({
+ contentContainer: {
+ padding: 10,
+ },
+ hint: {
+ marginVertical: 4,
+ fontSize: 13,
+ color: '#666',
+ },
+ sfxRow: {
+ flexDirection: 'row',
+ gap: 12,
+ marginVertical: 12,
+ },
+ sfxButton: {
+ flex: 1,
+ backgroundColor: Colors.tintColor,
+ borderRadius: 12,
+ paddingVertical: 20,
+ alignItems: 'center',
+ justifyContent: 'center',
+ },
+ sfxButtonPressed: {
+ opacity: 0.7,
+ },
+ sfxButtonText: {
+ color: '#fff',
+ fontSize: 16,
+ fontWeight: '700',
+ },
+ sfxButtonStatus: {
+ color: 'rgba(255,255,255,0.7)',
+ fontSize: 12,
+ marginTop: 4,
+ },
+ cacheUri: {
+ fontSize: 11,
+ color: '#999',
+ fontFamily: 'monospace',
+ marginLeft: 8,
+ marginVertical: 2,
+ },
+ buttonRow: {
+ flexDirection: 'row',
+ gap: 8,
+ },
+});
diff --git a/apps/native-component-list/src/screens/Audio/AudioScreen.tsx b/apps/native-component-list/src/screens/Audio/AudioScreen.tsx
index fcba58c5858a75..3f000133858615 100644
--- a/apps/native-component-list/src/screens/Audio/AudioScreen.tsx
+++ b/apps/native-component-list/src/screens/Audio/AudioScreen.tsx
@@ -1,6 +1,12 @@
+import { preload } from 'expo-audio';
+
+import { sfx1, sfx2 } from './AudioPreloadScreen';
import { optionalRequire } from '../../navigation/routeBuilder';
import ComponentListScreen, { apiScreensToListElements } from '../ComponentListScreen';
+preload(sfx1);
+preload(sfx2);
+
export const AudioScreens = [
{
name: 'Expo Audio Player',
@@ -34,7 +40,6 @@ export const AudioScreens = [
return optionalRequire(() => require('./CreateAudioPlayerScreen'));
},
},
-
{
name: 'Expo Audio Lock Screen Controls',
route: 'audio/expo-audio-controls',
@@ -51,6 +56,14 @@ export const AudioScreens = [
return optionalRequire(() => require('./AudioEventsScreen'));
},
},
+ {
+ name: 'Expo Audio Preloading',
+ route: 'audio/expo-audio-preload',
+ options: {},
+ getComponent() {
+ return optionalRequire(() => require('./AudioPreloadScreen'));
+ },
+ },
];
export default function AudioScreen() {
diff --git a/apps/native-component-list/src/screens/Video/VideoAudioTracksScreen.tsx b/apps/native-component-list/src/screens/Video/VideoAudioTracksScreen.tsx
index 64aa1767566d77..a13e4844f87633 100644
--- a/apps/native-component-list/src/screens/Video/VideoAudioTracksScreen.tsx
+++ b/apps/native-component-list/src/screens/Video/VideoAudioTracksScreen.tsx
@@ -75,13 +75,16 @@ export default function VideoAudioTracksScreen() {
handleAudioTrackChange(value);
}}>
{availableAudioTracks &&
- availableAudioTracks.map((source, index) => (
-
- ))}
+ availableAudioTracks.map((source, index) => {
+ let label = availableAudioTracks[index]?.label ?? 'Off';
+ const name = availableAudioTracks[index]?.name;
+ // Apple uses a weird algorithm to determine whether to add the name tag to the track label
+ // This way we will get the same results on Android and iOS in the picker
+ if (name && !label.includes(name)) {
+ label = `${name} - ${label}`;
+ }
+ return ;
+ })}
Current audio track: {audioTrack?.label ?? availableAudioTracks[0]?.label}
diff --git a/expo-audio/android/src/main/java/expo/modules/audio/Playable.kt b/expo-audio/android/src/main/java/expo/modules/audio/Playable.kt
deleted file mode 100644
index 06c28423eafbe3..00000000000000
--- a/expo-audio/android/src/main/java/expo/modules/audio/Playable.kt
+++ /dev/null
@@ -1,56 +0,0 @@
-package expo.modules.audio
-
-import androidx.media3.common.C
-import androidx.media3.common.Player
-import expo.modules.kotlin.AppContext
-import kotlinx.coroutines.launch
-
-/**
- * Common interface for audio playback objects (AudioPlayer, AudioPlaylist).
- * Provides default implementations for operations shared between player types.
- */
-interface Playable {
- val id: String
- var isPaused: Boolean
- var isMuted: Boolean
- var previousVolume: Float
- var onPlaybackStateChange: ((Boolean) -> Unit)?
-
- val player: Player
- val appContext: AppContext?
-
- val currentTime: Double get() = player.currentPosition / 1000.0
- val duration: Double get() = if (player.duration != C.TIME_UNSET) player.duration / 1000.0 else 0.0
- val isPlaying: Boolean get() = player.isPlaying
- val volume: Float get() = player.volume
-
- fun play() {
- player.play()
- }
-
- fun pause() {
- player.pause()
- }
-
- fun seekTo(seconds: Double) {
- player.seekTo((seconds * 1000L).toLong())
- }
-
- fun setVolume(volume: Float?) {
- appContext?.mainQueue?.launch {
- val boundedVolume = volume?.coerceIn(0f, 1f) ?: 1f
- if (isMuted) {
- if (boundedVolume > 0f) {
- previousVolume = boundedVolume
- }
- player.volume = 0f
- } else {
- previousVolume = boundedVolume
- player.volume = boundedVolume
- }
- }
- }
-
- fun setPlaybackRate(rate: Float)
- fun currentStatus(): Map
-}
diff --git a/packages/@expo/cli/CHANGELOG.md b/packages/@expo/cli/CHANGELOG.md
index dd0d42e95dc878..0cb37405546fd9 100644
--- a/packages/@expo/cli/CHANGELOG.md
+++ b/packages/@expo/cli/CHANGELOG.md
@@ -10,6 +10,8 @@
### 💡 Others
+- Force `forceNodeFilesystemAPI` when watchman is enabled (the default) but not present ([#43251](https://github.com/expo/expo/pull/43251) by [@kitten](https://github.com/kitten))
+
## 55.0.9 — 2026-02-16
_This version does not introduce any user-facing changes._
diff --git a/packages/@expo/cli/src/start/server/metro/instantiateMetro.ts b/packages/@expo/cli/src/start/server/metro/instantiateMetro.ts
index 361dab98b2732a..304cc37edc6f07 100644
--- a/packages/@expo/cli/src/start/server/metro/instantiateMetro.ts
+++ b/packages/@expo/cli/src/start/server/metro/instantiateMetro.ts
@@ -8,7 +8,7 @@ import MetroHmrServer, { Client as MetroHmrClient } from '@expo/metro/metro/HmrS
import RevisionNotFoundError from '@expo/metro/metro/IncrementalBundler/RevisionNotFoundError';
import type MetroServer from '@expo/metro/metro/Server';
import formatBundlingError from '@expo/metro/metro/lib/formatBundlingError';
-import { mergeConfig, resolveConfig, type ConfigT } from '@expo/metro/metro-config';
+import { InputConfigT, mergeConfig, resolveConfig, type ConfigT } from '@expo/metro/metro-config';
import { Terminal } from '@expo/metro/metro-core';
import { createStableModuleIdFactory, getDefaultConfig } from '@expo/metro-config';
import chalk from 'chalk';
@@ -160,6 +160,15 @@ export async function loadMetroConfigAsync(
},
};
+ // NOTE(@kitten): `useWatchman` is currently enabled by default, but it also disables `forceNodeFilesystemAPI`.
+ // If we instead set it to the special value `null`, it gets enables but also bypasses the "native find" codepath,
+ // which is slower than just using the Node filesystem API
+ // See: https://github.com/facebook/metro/blob/b9c243f/packages/metro-file-map/src/index.js#L326
+ // See: https://github.com/facebook/metro/blob/b9c243f/packages/metro/src/node-haste/DependencyGraph/createFileMap.js#L109
+ if (config.resolver.useWatchman === true) {
+ asWritable(config.resolver).useWatchman = null as any;
+ }
+
globalThis.__requireCycleIgnorePatterns = config.resolver?.requireCycleIgnorePatterns;
if (isExporting) {
diff --git a/packages/@expo/fingerprint/CHANGELOG.md b/packages/@expo/fingerprint/CHANGELOG.md
index 51feebc04a1d9b..f3c7b66725b418 100644
--- a/packages/@expo/fingerprint/CHANGELOG.md
+++ b/packages/@expo/fingerprint/CHANGELOG.md
@@ -8,6 +8,8 @@
### 🐛 Bug fixes
+- Fix resolution to `expo -> @expo/cli -> @expo/env` being unstable ([#42764](https://github.com/expo/expo/pull/42764) by [@kitten](https://github.com/kitten))
+
### 💡 Others
## 0.16.3 — 2026-02-03
diff --git a/packages/@expo/fingerprint/build/ExpoConfigLoader.js b/packages/@expo/fingerprint/build/ExpoConfigLoader.js
index 247200aad38501..35a71dcdfba9bc 100644
--- a/packages/@expo/fingerprint/build/ExpoConfigLoader.js
+++ b/packages/@expo/fingerprint/build/ExpoConfigLoader.js
@@ -9,11 +9,9 @@ Object.defineProperty(exports, "__esModule", { value: true });
exports.getExpoConfigLoaderPath = getExpoConfigLoaderPath;
const promises_1 = __importDefault(require("fs/promises"));
const module_1 = __importDefault(require("module"));
-const node_assert_1 = __importDefault(require("node:assert"));
const node_process_1 = __importDefault(require("node:process"));
const path_1 = __importDefault(require("path"));
const resolve_from_1 = __importDefault(require("resolve-from"));
-const ExpoResolver_1 = require("./ExpoResolver");
const Options_1 = require("./Options");
const Path_1 = require("./utils/Path");
async function runAsync(programName, args = []) {
@@ -23,15 +21,11 @@ async function runAsync(programName, args = []) {
}
const projectRoot = path_1.default.resolve(args[0]);
const ignoredFile = args[1] ? path_1.default.resolve(args[1]) : null;
- // @ts-expect-error: module internal _cache
- const loadedModulesBefore = new Set(Object.keys(module_1.default._cache));
- const expoEnvPath = (0, ExpoResolver_1.resolveExpoEnvPath)(projectRoot);
- (0, node_assert_1.default)(expoEnvPath, `Could not find '@expo/env' package for the project from ${projectRoot}.`);
- require(expoEnvPath).load(projectRoot);
setNodeEnv('development');
+ require('@expo/env').load(projectRoot);
+ const loadedModulesBefore = new Set(Object.keys(module_1.default._cache));
const { getConfig } = require((0, resolve_from_1.default)(path_1.default.resolve(projectRoot), 'expo/config'));
const config = await getConfig(projectRoot, { skipSDKVersionRequirement: true });
- // @ts-expect-error: module internal _cache
const loadedModules = Object.keys(module_1.default._cache)
.filter((modulePath) => !loadedModulesBefore.has(modulePath))
.map((modulePath) => path_1.default.relative(projectRoot, modulePath));
diff --git a/packages/@expo/fingerprint/build/ExpoConfigLoader.js.map b/packages/@expo/fingerprint/build/ExpoConfigLoader.js.map
index d2133276b4774a..5e8d77f7e894fb 100644
--- a/packages/@expo/fingerprint/build/ExpoConfigLoader.js.map
+++ b/packages/@expo/fingerprint/build/ExpoConfigLoader.js.map
@@ -1 +1 @@
-{"version":3,"file":"ExpoConfigLoader.js","sourceRoot":"","sources":["../src/ExpoConfigLoader.ts"],"names":[],"mappings":"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"}
\ No newline at end of file
+{"version":3,"file":"ExpoConfigLoader.js","sourceRoot":"","sources":["../src/ExpoConfigLoader.ts"],"names":[],"mappings":";AAAA;;GAEG;;;;;AAsFH,0DAEC;AAtFD,2DAA6B;AAC7B,oDAA4B;AAC5B,gEAAmC;AACnC,gDAAwB;AACxB,gEAAuC;AAEvC,uCAAiD;AACjD,uCAA6C;AAE7C,KAAK,UAAU,QAAQ,CAAC,WAAmB,EAAE,OAAiB,EAAE;IAC9D,IAAI,IAAI,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACpB,OAAO,CAAC,GAAG,CAAC,UAAU,WAAW,8BAA8B,CAAC,CAAC;QACjE,OAAO;IACT,CAAC;IAED,MAAM,WAAW,GAAG,cAAI,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC;IAC1C,MAAM,WAAW,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,cAAI,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;IAE3D,UAAU,CAAC,aAAa,CAAC,CAAC;IAC1B,OAAO,CAAC,WAAW,CAAC,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;IAEvC,MAAM,mBAAmB,GAAG,IAAI,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,gBAAM,CAAC,MAAM,CAAC,CAAC,CAAC;IAEhE,MAAM,EAAE,SAAS,EAAE,GAAG,OAAO,CAAC,IAAA,sBAAW,EAAC,cAAI,CAAC,OAAO,CAAC,WAAW,CAAC,EAAE,aAAa,CAAC,CAAC,CAAC;IACrF,MAAM,MAAM,GAAG,MAAM,SAAS,CAAC,WAAW,EAAE,EAAE,yBAAyB,EAAE,IAAI,EAAE,CAAC,CAAC;IAEjF,MAAM,aAAa,GAAG,MAAM,CAAC,IAAI,CAAC,gBAAM,CAAC,MAAM,CAAC;SAC7C,MAAM,CAAC,CAAC,UAAU,EAAE,EAAE,CAAC,CAAC,mBAAmB,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC;SAC5D,GAAG,CAAC,CAAC,UAAU,EAAE,EAAE,CAAC,cAAI,CAAC,QAAQ,CAAC,WAAW,EAAE,UAAU,CAAC,CAAC,CAAC;IAE/D,MAAM,YAAY,GAAG;QACnB,GAAG,mCAAmC;QACtC,GAAG,CAAC,MAAM,qBAAqB,CAAC,WAAW,CAAC,CAAC;KAC9C,CAAC;IACF,MAAM,qBAAqB,GAAG,aAAa,CAAC,MAAM,CAChD,CAAC,UAAU,EAAE,EAAE,CAAC,CAAC,IAAA,oBAAa,EAAC,UAAU,EAAE,YAAY,CAAC,CACzD,CAAC;IACF,MAAM,MAAM,GAAG,IAAI,CAAC,SAAS,CAAC,EAAE,MAAM,EAAE,aAAa,EAAE,qBAAqB,EAAE,CAAC,CAAC;IAChF,IAAI,sBAAO,CAAC,IAAI,EAAE,CAAC;QACjB,sBAAO,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;IACvB,CAAC;SAAM,CAAC;QACN,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;IACtB,CAAC;AACH,CAAC;AAED,mCAAmC;AACnC,IAAI,OAAO,CAAC,IAAI,EAAE,QAAQ,KAAK,UAAU,EAAE,CAAC;IAC1C,CAAC,KAAK,IAAI,EAAE;QACV,MAAM,YAAY,GAAG,sBAAO,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,KAAK,UAAU,CAAC,CAAC;QACzE,IAAI,CAAC;YACH,MAAM,QAAQ,CAAC,sBAAO,CAAC,IAAI,CAAC,YAAY,CAAC,EAAE,sBAAO,CAAC,IAAI,CAAC,KAAK,CAAC,YAAY,GAAG,CAAC,CAAC,CAAC,CAAC;QACnF,CAAC;QAAC,OAAO,CAAC,EAAE,CAAC;YACX,OAAO,CAAC,KAAK,CAAC,gBAAgB,EAAE,CAAC,CAAC,CAAC;YACnC,sBAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAClB,CAAC;IACH,CAAC,CAAC,EAAE,CAAC;AACP,CAAC;AAED;;GAEG;AACH,KAAK,UAAU,qBAAqB,CAAC,WAA0B;IAC7D,IAAI,CAAC,WAAW,EAAE,CAAC;QACjB,OAAO,8BAAoB,CAAC;IAC9B,CAAC;IAED,MAAM,WAAW,GAAG,EAAE,CAAC;IACvB,IAAI,CAAC;QACH,MAAM,iBAAiB,GAAG,MAAM,kBAAE,CAAC,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC,CAAC;QACjE,MAAM,sBAAsB,GAAG,iBAAiB,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;QAC7D,KAAK,MAAM,IAAI,IAAI,sBAAsB,EAAE,CAAC;YAC1C,MAAM,WAAW,GAAG,IAAI,CAAC,IAAI,EAAE,CAAC;YAChC,IAAI,WAAW,EAAE,CAAC;gBAChB,WAAW,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;YAChC,CAAC;QACH,CAAC;IACH,CAAC;IAAC,MAAM,CAAC,CAAA,CAAC;IAEV,OAAO,WAAW,CAAC;AACrB,CAAC;AAED;;GAEG;AACH,SAAgB,uBAAuB;IACrC,OAAO,cAAI,CAAC,IAAI,CAAC,SAAS,EAAE,qBAAqB,CAAC,CAAC;AACrD,CAAC;AAED;;;GAGG;AACH,SAAS,UAAU,CAAC,IAAkC;IACpD,sBAAO,CAAC,GAAG,CAAC,QAAQ,GAAG,sBAAO,CAAC,GAAG,CAAC,QAAQ,IAAI,IAAI,CAAC;IACpD,sBAAO,CAAC,GAAG,CAAC,SAAS,GAAG,sBAAO,CAAC,GAAG,CAAC,SAAS,IAAI,sBAAO,CAAC,GAAG,CAAC,QAAQ,CAAC;IAEtE,+FAA+F;IAC/F,UAAU,CAAC,OAAO,GAAG,sBAAO,CAAC,GAAG,CAAC,QAAQ,KAAK,YAAY,CAAC;AAC7D,CAAC;AAED,6DAA6D;AAC7D,MAAM,mCAAmC,GAAG;IAC1C,2FAA2F;IAC3F,cAAc;IAEd,6BAA6B;IAC7B,4BAA4B;IAC5B,kCAAkC;IAClC,gCAAgC;IAChC,wCAAwC;IACxC,oBAAoB;QAClB,KAAK;QACL,aAAa;QACb,cAAc;QACd,aAAa;QACb,OAAO;QACP,OAAO;QACP,QAAQ;QACR,eAAe;QACf,sBAAsB;QACtB,QAAQ;QACR,aAAa;QACb,iBAAiB;QACjB,UAAU;QACV,UAAU;QACV,aAAa;QACb,WAAW;QACX,OAAO;QACP,sBAAsB;QACtB,IAAI;QACJ,YAAY;QACZ,mBAAmB;QACnB,qBAAqB;QACrB,cAAc;QACd,cAAc;QACd,aAAa;QACb,SAAS;QACT,gBAAgB;QAChB,sBAAsB;QACtB,mBAAmB;KACpB,CAAC,IAAI,CAAC,GAAG,CAAC,QAAQ;CACpB,CAAC"}
\ No newline at end of file
diff --git a/packages/@expo/fingerprint/build/ExpoResolver.d.ts b/packages/@expo/fingerprint/build/ExpoResolver.d.ts
index 6a353bfb560191..812d67e8113d24 100644
--- a/packages/@expo/fingerprint/build/ExpoResolver.d.ts
+++ b/packages/@expo/fingerprint/build/ExpoResolver.d.ts
@@ -2,10 +2,6 @@
* Resolve the version of `expo` package in the project.
*/
export declare function resolveExpoVersion(projectRoot: string): string | null;
-/**
- * Resolve the path to the `@expo/env` package in the project.
- */
-export declare function resolveExpoEnvPath(projectRoot: string): string | null;
/**
* Resolve the package root of `expo-modules-autolinking` package in the project.
*/
diff --git a/packages/@expo/fingerprint/build/ExpoResolver.js b/packages/@expo/fingerprint/build/ExpoResolver.js
index 99f809af700ec9..12eebac5ef50cb 100644
--- a/packages/@expo/fingerprint/build/ExpoResolver.js
+++ b/packages/@expo/fingerprint/build/ExpoResolver.js
@@ -4,7 +4,6 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.resolveExpoVersion = resolveExpoVersion;
-exports.resolveExpoEnvPath = resolveExpoEnvPath;
exports.resolveExpoAutolinkingPackageRoot = resolveExpoAutolinkingPackageRoot;
exports.resolveExpoAutolinkingCliPath = resolveExpoAutolinkingCliPath;
exports.resolveExpoAutolinkingVersion = resolveExpoAutolinkingVersion;
@@ -25,17 +24,6 @@ function resolveExpoVersion(projectRoot) {
}
return null;
}
-/**
- * Resolve the path to the `@expo/env` package in the project.
- */
-function resolveExpoEnvPath(projectRoot) {
- const expoPackageRoot = resolve_from_1.default.silent(projectRoot, 'expo/package.json');
- const expoEnvPackageJsonPath = resolve_from_1.default.silent(expoPackageRoot ?? projectRoot, '@expo/env/package.json');
- if (expoEnvPackageJsonPath) {
- return path_1.default.dirname(expoEnvPackageJsonPath);
- }
- return null;
-}
/**
* Resolve the package root of `expo-modules-autolinking` package in the project.
*/
diff --git a/packages/@expo/fingerprint/build/ExpoResolver.js.map b/packages/@expo/fingerprint/build/ExpoResolver.js.map
index fb2cc2eb66b525..3c6db050ecdabd 100644
--- a/packages/@expo/fingerprint/build/ExpoResolver.js.map
+++ b/packages/@expo/fingerprint/build/ExpoResolver.js.map
@@ -1 +1 @@
-{"version":3,"file":"ExpoResolver.js","sourceRoot":"","sources":["../src/ExpoResolver.ts"],"names":[],"mappings":"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"}
\ No newline at end of file
+{"version":3,"file":"ExpoResolver.js","sourceRoot":"","sources":["../src/ExpoResolver.ts"],"names":[],"mappings":";;;;;AASA,gDAOC;AAKD,8EAkBC;AAMD,sEAMC;AAKD,sEAOC;AAKD,kFAEC;AAMD,gDAMC;AAlFD,gDAAwB;AACxB,gEAAuC;AACvC,oDAA4B;AAE5B,IAAI,gCAAgC,GAA4B,IAAI,CAAC;AAErE;;GAEG;AACH,SAAgB,kBAAkB,CAAC,WAAmB;IACpD,MAAM,mBAAmB,GAAG,sBAAW,CAAC,MAAM,CAAC,WAAW,EAAE,mBAAmB,CAAC,CAAC;IACjF,IAAI,mBAAmB,EAAE,CAAC;QACxB,MAAM,eAAe,GAAG,OAAO,CAAC,mBAAmB,CAAC,CAAC;QACrD,OAAO,eAAe,CAAC,OAAO,CAAC;IACjC,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED;;GAEG;AACH,SAAgB,iCAAiC,CAAC,WAAmB;IACnE,IAAI,gCAAgC,EAAE,CAAC;QACrC,MAAM,CAAC,iBAAiB,EAAE,iBAAiB,CAAC,GAAG,gCAAgC,CAAC;QAChF,IAAI,iBAAiB,KAAK,WAAW,EAAE,CAAC;YACtC,OAAO,iBAAiB,CAAC;QAC3B,CAAC;IACH,CAAC;IACD,MAAM,eAAe,GAAG,sBAAW,CAAC,MAAM,CAAC,WAAW,EAAE,mBAAmB,CAAC,CAAC;IAC7E,MAAM,0BAA0B,GAAG,sBAAW,CAAC,MAAM,CACnD,eAAe,IAAI,WAAW,EAC9B,uCAAuC,CACxC,CAAC;IACF,IAAI,0BAA0B,EAAE,CAAC;QAC/B,MAAM,sBAAsB,GAAG,cAAI,CAAC,OAAO,CAAC,0BAA0B,CAAC,CAAC;QACxE,gCAAgC,GAAG,CAAC,WAAW,EAAE,sBAAsB,CAAC,CAAC;QACzE,OAAO,sBAAsB,CAAC;IAChC,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED;;;GAGG;AACH,SAAgB,6BAA6B,CAAC,WAAmB;IAC/D,MAAM,sBAAsB,GAAG,iCAAiC,CAAC,WAAW,CAAC,CAAC;IAC9E,IAAI,sBAAsB,IAAI,IAAI,EAAE,CAAC;QACnC,MAAM,IAAI,KAAK,CAAC,iEAAiE,CAAC,CAAC;IACrF,CAAC;IACD,OAAO,cAAI,CAAC,IAAI,CAAC,sBAAsB,EAAE,KAAK,EAAE,6BAA6B,CAAC,CAAC;AACjF,CAAC;AAED;;GAEG;AACH,SAAgB,6BAA6B,CAAC,WAAmB;IAC/D,MAAM,sBAAsB,GAAG,iCAAiC,CAAC,WAAW,CAAC,CAAC;IAC9E,IAAI,sBAAsB,EAAE,CAAC;QAC3B,MAAM,sBAAsB,GAAG,OAAO,CAAC,cAAI,CAAC,IAAI,CAAC,sBAAsB,EAAE,cAAc,CAAC,CAAC,CAAC;QAC1F,OAAO,sBAAsB,CAAC,OAAO,CAAC;IACxC,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED;;GAEG;AACH,SAAgB,mCAAmC,CAAC,WAAmB;IACrE,OAAO,sBAAW,CAAC,MAAM,CAAC,WAAW,EAAE,qBAAqB,CAAC,IAAI,IAAI,CAAC;AACxE,CAAC;AAED;;;GAGG;AACH,SAAgB,kBAAkB,CAAC,WAAmB,EAAE,KAAa;IACnE,MAAM,WAAW,GAAG,kBAAkB,CAAC,WAAW,CAAC,CAAC;IACpD,IAAI,WAAW,EAAE,CAAC;QAChB,OAAO,gBAAM,CAAC,SAAS,CAAC,WAAW,EAAE,KAAK,CAAC,CAAC;IAC9C,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC"}
\ No newline at end of file
diff --git a/packages/@expo/fingerprint/package.json b/packages/@expo/fingerprint/package.json
index 239b931cfe9912..2c36dab3d87cae 100644
--- a/packages/@expo/fingerprint/package.json
+++ b/packages/@expo/fingerprint/package.json
@@ -43,6 +43,7 @@
"license": "MIT",
"homepage": "https://github.com/expo/expo/tree/main/packages/@expo/fingerprint#readme",
"dependencies": {
+ "@expo/env": "^2.0.11",
"@expo/spawn-async": "^1.7.2",
"arg": "^5.0.2",
"chalk": "^4.1.2",
diff --git a/packages/@expo/fingerprint/src/ExpoConfigLoader.ts b/packages/@expo/fingerprint/src/ExpoConfigLoader.ts
index ca00f244201cc0..cf92105d0ac387 100644
--- a/packages/@expo/fingerprint/src/ExpoConfigLoader.ts
+++ b/packages/@expo/fingerprint/src/ExpoConfigLoader.ts
@@ -4,12 +4,10 @@
import fs from 'fs/promises';
import module from 'module';
-import assert from 'node:assert';
import process from 'node:process';
import path from 'path';
import resolveFrom from 'resolve-from';
-import { resolveExpoEnvPath } from './ExpoResolver';
import { DEFAULT_IGNORE_PATHS } from './Options';
import { isIgnoredPath } from './utils/Path';
@@ -22,16 +20,14 @@ async function runAsync(programName: string, args: string[] = []) {
const projectRoot = path.resolve(args[0]);
const ignoredFile = args[1] ? path.resolve(args[1]) : null;
- // @ts-expect-error: module internal _cache
+ setNodeEnv('development');
+ require('@expo/env').load(projectRoot);
+
const loadedModulesBefore = new Set(Object.keys(module._cache));
- const expoEnvPath = resolveExpoEnvPath(projectRoot);
- assert(expoEnvPath, `Could not find '@expo/env' package for the project from ${projectRoot}.`);
- require(expoEnvPath).load(projectRoot);
- setNodeEnv('development');
const { getConfig } = require(resolveFrom(path.resolve(projectRoot), 'expo/config'));
const config = await getConfig(projectRoot, { skipSDKVersionRequirement: true });
- // @ts-expect-error: module internal _cache
+
const loadedModules = Object.keys(module._cache)
.filter((modulePath) => !loadedModulesBefore.has(modulePath))
.map((modulePath) => path.relative(projectRoot, modulePath));
diff --git a/packages/@expo/fingerprint/src/ExpoResolver.ts b/packages/@expo/fingerprint/src/ExpoResolver.ts
index 2954c0c74484b9..7426a67dccdfb9 100644
--- a/packages/@expo/fingerprint/src/ExpoResolver.ts
+++ b/packages/@expo/fingerprint/src/ExpoResolver.ts
@@ -16,21 +16,6 @@ export function resolveExpoVersion(projectRoot: string): string | null {
return null;
}
-/**
- * Resolve the path to the `@expo/env` package in the project.
- */
-export function resolveExpoEnvPath(projectRoot: string): string | null {
- const expoPackageRoot = resolveFrom.silent(projectRoot, 'expo/package.json');
- const expoEnvPackageJsonPath = resolveFrom.silent(
- expoPackageRoot ?? projectRoot,
- '@expo/env/package.json'
- );
- if (expoEnvPackageJsonPath) {
- return path.dirname(expoEnvPackageJsonPath);
- }
- return null;
-}
-
/**
* Resolve the package root of `expo-modules-autolinking` package in the project.
*/
diff --git a/packages/@expo/fingerprint/src/types/module.d.ts b/packages/@expo/fingerprint/src/types/module.d.ts
new file mode 100644
index 00000000000000..b78b434c33d1e3
--- /dev/null
+++ b/packages/@expo/fingerprint/src/types/module.d.ts
@@ -0,0 +1,7 @@
+import * as __module from 'module';
+
+declare module 'module' {
+ namespace Module {
+ export const _cache: Record;
+ }
+}
diff --git a/packages/@expo/metro-config/CHANGELOG.md b/packages/@expo/metro-config/CHANGELOG.md
index 01ef65b1ee3213..14e94f5ce9c18e 100644
--- a/packages/@expo/metro-config/CHANGELOG.md
+++ b/packages/@expo/metro-config/CHANGELOG.md
@@ -10,6 +10,8 @@
### 💡 Others
+- Set default `resolver.useWatchman: undefined` value (enables it by default, as before) ([#43251](https://github.com/expo/expo/pull/43251) by [@kitten](https://github.com/kitten))
+
## 55.0.6 — 2026-02-16
_This version does not introduce any user-facing changes._
diff --git a/packages/@expo/metro-config/build/ExpoMetroConfig.d.ts b/packages/@expo/metro-config/build/ExpoMetroConfig.d.ts
index 312a382e1d3f17..35c2bc1e1a8e86 100644
--- a/packages/@expo/metro-config/build/ExpoMetroConfig.d.ts
+++ b/packages/@expo/metro-config/build/ExpoMetroConfig.d.ts
@@ -45,6 +45,7 @@ export declare function getDefaultConfig(projectRoot: string, { mode, isCSSEnabl
sourceExts: string[];
nodeModulesPaths: string[];
blockList: RegExp[];
+ useWatchman: undefined;
};
cacheStores: FileStore[];
watcher: {
diff --git a/packages/@expo/metro-config/build/ExpoMetroConfig.js b/packages/@expo/metro-config/build/ExpoMetroConfig.js
index 780c091c670a6c..46a1d47e1f93b5 100644
--- a/packages/@expo/metro-config/build/ExpoMetroConfig.js
+++ b/packages/@expo/metro-config/build/ExpoMetroConfig.js
@@ -224,6 +224,7 @@ function getDefaultConfig(projectRoot, { mode, isCSSEnabled = true, unstable_bef
// This prevents unwanted fast refresh on the declaration files changes.
/\.expo[\\/]types/,
].concat(metroDefaultValues.resolver.blockList ?? []),
+ useWatchman: undefined,
},
cacheStores: [cacheStore],
watcher: {
diff --git a/packages/@expo/metro-config/build/ExpoMetroConfig.js.map b/packages/@expo/metro-config/build/ExpoMetroConfig.js.map
index 9d738bb5c51d4a..93233c80465aab 100644
--- a/packages/@expo/metro-config/build/ExpoMetroConfig.js.map
+++ b/packages/@expo/metro-config/build/ExpoMetroConfig.js.map
@@ -1 +1 @@
-{"version":3,"file":"ExpoMetroConfig.js","sourceRoot":"","sources":["../src/ExpoMetroConfig.ts"],"names":[],"mappings":"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"}
\ No newline at end of file
+{"version":3,"file":"ExpoMetroConfig.js","sourceRoot":"","sources":["../src/ExpoMetroConfig.ts"],"names":[],"mappings":"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"}
\ No newline at end of file
diff --git a/packages/@expo/metro-config/src/ExpoMetroConfig.ts b/packages/@expo/metro-config/src/ExpoMetroConfig.ts
index 72a85fd8cd0576..48f7b266e14a25 100644
--- a/packages/@expo/metro-config/src/ExpoMetroConfig.ts
+++ b/packages/@expo/metro-config/src/ExpoMetroConfig.ts
@@ -312,6 +312,8 @@ export function getDefaultConfig(
// This prevents unwanted fast refresh on the declaration files changes.
/\.expo[\\/]types/,
].concat(metroDefaultValues.resolver.blockList ?? []),
+
+ useWatchman: undefined,
},
cacheStores: [cacheStore],
watcher: {
diff --git a/packages/create-expo-module/src/create-expo-module.ts b/packages/create-expo-module/src/create-expo-module.ts
index 73ab570ae76e9a..b2bf7162a78c04 100644
--- a/packages/create-expo-module/src/create-expo-module.ts
+++ b/packages/create-expo-module/src/create-expo-module.ts
@@ -3,6 +3,7 @@ import chalk from 'chalk';
import { Command } from 'commander';
import ejs from 'ejs';
import fs from 'node:fs';
+import os from 'node:os';
import path from 'node:path';
import prompts from 'prompts';
@@ -23,7 +24,7 @@ import { eventCreateExpoModule, getTelemetryClient, logEventAsync } from './tele
import type { CommandOptions, LocalSubstitutionData, SubstitutionData } from './types';
import { env } from './utils/env';
import { newStep } from './utils/ora';
-import { downloadAndExtractTarball } from './utils/tar';
+import { extractLocalTarball } from './utils/tar';
const debug = require('debug')('create-expo-module:main') as typeof console.log;
const packageJson = require('../package.json');
@@ -204,12 +205,41 @@ async function getFilesAsync(root: string, dir: string | null = null): Promise {
- debug(`Using module template ${chalk.bold(packageName)}@${chalk.bold(version)}`);
- const { stdout } = await spawnAsync('npm', ['view', `${packageName}@${version}`, 'dist.tarball']);
- return stdout.trim();
+async function npmPackAsync(packageName: string, cwd: string): Promise {
+ const npm = process.platform === 'win32' ? 'npm.cmd' : 'npm';
+ const cmd = ['pack', packageName, '--json'];
+ const cmdString = `${npm} ${cmd.join(' ')}`;
+ debug('Run:', cmdString, `(cwd: ${cwd})`);
+
+ let results: string;
+ try {
+ results = (await spawnAsync(npm, cmd, { cwd })).stdout?.trim();
+ } catch (error: any) {
+ if (error?.stderr?.match(/npm ERR! code E404/)) {
+ const pkg =
+ error.stderr.match(/npm ERR! 404\s+'(.*)' is not in this registry\./)?.[1] ?? error.stderr;
+ throw new Error(`NPM package not found: ` + pkg);
+ }
+ throw error;
+ }
+
+ if (!results) {
+ throw new Error(`No output from "${cmdString}"`);
+ }
+
+ try {
+ const json = JSON.parse(results);
+ if (!Array.isArray(json) || !json[0]?.filename) {
+ throw new Error(`Invalid response from npm: ${results}`);
+ }
+ return json[0].filename;
+ } catch (error: any) {
+ throw new Error(
+ `Could not parse JSON returned from "${cmdString}".\n\n${results}\n\nError: ${error.message}`
+ );
+ }
}
/**
@@ -255,12 +285,13 @@ async function downloadPackageAsync(targetDir: string, isLocal = false): Promise
return await newStep('Downloading module template from npm', async (step) => {
const templateVersion = await getTemplateVersion(isLocal);
const packageName = isLocal ? 'expo-module-template-local' : 'expo-module-template';
+ const tmpDir = path.join(os.tmpdir(), '.create-expo-module');
+ await fs.promises.mkdir(tmpDir, { recursive: true });
+
+ let filename: string;
try {
- await downloadAndExtractTarball({
- url: await getNpmTarballUrl(packageName, templateVersion),
- dir: targetDir,
- });
+ filename = await npmPackAsync(`${packageName}@${templateVersion}`, tmpDir);
} catch {
console.log();
console.warn(
@@ -268,12 +299,16 @@ async function downloadPackageAsync(targetDir: string, isLocal = false): Promise
"Couldn't download the versioned template from npm, falling back to the latest version."
)
);
- await downloadAndExtractTarball({
- url: await getNpmTarballUrl(packageName, 'latest'),
- dir: targetDir,
- });
+ filename = await npmPackAsync(`${packageName}@latest`, tmpDir);
}
+ await extractLocalTarball({
+ filePath: path.join(tmpDir, filename),
+ dir: targetDir,
+ });
+
+ await fs.promises.rm(tmpDir, { recursive: true, force: true });
+
step.succeed('Downloaded module template from npm registry.');
return path.join(targetDir, 'package');
diff --git a/packages/create-expo-module/src/utils/tar.ts b/packages/create-expo-module/src/utils/tar.ts
index dba89eb5580008..795c9caca33577 100644
--- a/packages/create-expo-module/src/utils/tar.ts
+++ b/packages/create-expo-module/src/utils/tar.ts
@@ -1,35 +1,11 @@
-import { Readable, Stream } from 'node:stream';
+import fs from 'node:fs';
+import { Stream } from 'node:stream';
import { promisify } from 'node:util';
import { extract as tarExtract } from 'tar';
-import { fetch } from './fetch';
-
-const debug = require('debug')('create-expo-module:tar') as typeof console.log;
const pipeline = promisify(Stream.pipeline);
-type DownloadAndExtractTarballOptions = {
- /** The publicly accessible URL to download the tarball from */
- url: string;
- /** The local folder to extract to */
- dir: string;
-};
-
-/** Download and extract tarballs directly from a publicly accessible URL */
-export async function downloadAndExtractTarball({ url, dir }: DownloadAndExtractTarballOptions) {
- const response = await fetch(url);
-
- if (!response.ok) {
- debug(`Failed to fetch "%s", received response: %O`, url, response);
- throw new Error(`Failed to download "${url}", received response status ${response.status}.`);
- }
-
- if (!response.body) {
- debug(`Failed to fetch "%s", received no response body: %O`, url, response);
- throw new Error(`Failed to download "${url}", received no response body.`);
- }
-
- await pipeline(
- Readable.fromWeb(response.body as ReadableStream),
- tarExtract({ cwd: dir })
- );
+/** Extract a local tarball file to a directory */
+export async function extractLocalTarball({ filePath, dir }: { filePath: string; dir: string }) {
+ await pipeline(fs.createReadStream(filePath), tarExtract({ cwd: dir }));
}
diff --git a/packages/expo-audio/CHANGELOG.md b/packages/expo-audio/CHANGELOG.md
index 02ca59e5bb26e8..1e683b345a4396 100644
--- a/packages/expo-audio/CHANGELOG.md
+++ b/packages/expo-audio/CHANGELOG.md
@@ -8,6 +8,8 @@
### 🐛 Bug fixes
+- [Web] Fix potential `nan` duration. ([#43268](https://github.com/expo/expo/pull/43268) by [@alanjhughes](https://github.com/alanjhughes))
+
### 💡 Others
## 55.0.6 — 2026-02-16
@@ -21,6 +23,7 @@
- [Web] Add support for selecting recording inputs. ([#43151](https://github.com/expo/expo/pull/43151) by [@alanjhughes](https://github.com/alanjhughes))
- [Web] Add support for recording metering. ([#43152](https://github.com/expo/expo/pull/43152) by [@alanjhughes](https://github.com/alanjhughes))
- [Web] Enable `setIsAudioActiveAsync`. ([#43142](https://github.com/expo/expo/pull/43142) by [@alanjhughes](https://github.com/alanjhughes))
+- Add support for preloading audio sources. ([#43063](https://github.com/expo/expo/pull/43063) by [@alanjhughes](https://github.com/alanjhughes))
### 🐛 Bug fixes
diff --git a/packages/expo-audio/android/build.gradle b/packages/expo-audio/android/build.gradle
index d7861f0170fb62..e992f9156ab867 100644
--- a/packages/expo-audio/android/build.gradle
+++ b/packages/expo-audio/android/build.gradle
@@ -24,12 +24,14 @@ repositories {
dependencies {
implementation 'androidx.appcompat:appcompat:1.7.1'
+ implementation 'androidx.core:core-ktx:1.15.0'
def androidxMedia3Version = "1.8.0"
implementation "androidx.media3:media3-session:$androidxMedia3Version"
implementation "androidx.media3:media3-ui:$androidxMedia3Version"
implementation "androidx.media3:media3-exoplayer:$androidxMedia3Version"
implementation "androidx.media3:media3-exoplayer-dash:$androidxMedia3Version"
+ implementation "androidx.media3:media3-datasource:$androidxMedia3Version"
implementation "androidx.media3:media3-datasource-okhttp:$androidxMedia3Version"
implementation "androidx.media3:media3-exoplayer-hls:$androidxMedia3Version"
implementation "androidx.media3:media3-exoplayer-smoothstreaming:$androidxMedia3Version"
diff --git a/packages/expo-audio/android/src/main/java/expo/modules/audio/AudioModule.kt b/packages/expo-audio/android/src/main/java/expo/modules/audio/AudioModule.kt
index 93fc8b6fcbf275..f2cae68d90254f 100644
--- a/packages/expo-audio/android/src/main/java/expo/modules/audio/AudioModule.kt
+++ b/packages/expo-audio/android/src/main/java/expo/modules/audio/AudioModule.kt
@@ -34,12 +34,16 @@ import expo.modules.kotlin.functions.Coroutine
import expo.modules.kotlin.functions.Queues
import expo.modules.kotlin.modules.Module
import expo.modules.kotlin.modules.ModuleDefinition
+import kotlinx.coroutines.DelicateCoroutinesApi
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import okhttp3.OkHttpClient
import java.io.File
import java.util.concurrent.ConcurrentHashMap
+@DelicateCoroutinesApi
@androidx.annotation.OptIn(androidx.media3.common.util.UnstableApi::class)
class AudioModule : Module() {
private lateinit var audioManager: AudioManager
@@ -218,6 +222,25 @@ class AudioModule : Module() {
Permissions.getPermissionsWithPermissionsManager(appContext.permissions, promise, Manifest.permission.RECORD_AUDIO)
}
+ AsyncFunction("preload") Coroutine { source: AudioSource, _: Double ->
+ val uri = source.uri ?: return@Coroutine
+ val upstreamFactory = httpDataSourceFactory(source.headers)
+ AudioPreloadCache.preload(context, uri, upstreamFactory)
+ }
+
+ AsyncFunction("clearPreloadedSource") Coroutine { source: AudioSource ->
+ val uri = source.uri ?: return@Coroutine
+ AudioPreloadCache.clearSource(context, uri)
+ }
+
+ AsyncFunction("clearAllPreloadedSources") Coroutine { ->
+ AudioPreloadCache.clearAll(context)
+ }
+
+ AsyncFunction("getPreloadedSources") {
+ AudioPreloadCache.getPreloadedSources()
+ }
+
OnActivityEntersBackground {
if (!shouldPlayInBackground) {
releaseAudioFocus()
@@ -263,7 +286,7 @@ class AudioModule : Module() {
}
OnDestroy {
- appContext.mainQueue.launch {
+ GlobalScope.launch(Dispatchers.Main) {
releaseAudioFocus()
players.values.forEach {
it.ref.stop()
@@ -276,18 +299,21 @@ class AudioModule : Module() {
recorders.values.forEach {
it.stopRecording()
}
+ AudioPreloadCache.release()
}
}
Class(AudioPlayer::class) {
- Constructor { source: AudioSource?, updateInterval: Double, keepAudioSessionActive: Boolean ->
+ Constructor { source: AudioSource?, updateInterval: Double, keepAudioSessionActive: Boolean, preferredForwardBufferDuration: Double ->
val mediaSource = createMediaItem(source)
+ val bufferDurationMs = (preferredForwardBufferDuration * 1000).toLong()
runOnMain {
val player = AudioPlayer(
context,
appContext,
mediaSource,
- updateInterval
+ updateInterval,
+ bufferDurationMs
)
player.onPlaybackStateChange = { isPlaying ->
if (!isPlaying && shouldReleaseFocus()) {
@@ -760,7 +786,9 @@ class AudioModule : Module() {
}
val factory = when (uri.scheme) {
- "http", "https" -> httpDataSourceFactory(source.headers)
+ "http", "https" -> {
+ AudioPreloadCache.createCacheDataSourceFactory(context, httpDataSourceFactory(source.headers))
+ }
else -> DefaultDataSource.Factory(context)
}
return buildMediaSourceFactory(factory, mediaItem)
diff --git a/packages/expo-audio/android/src/main/java/expo/modules/audio/AudioPlayer.kt b/packages/expo-audio/android/src/main/java/expo/modules/audio/AudioPlayer.kt
index 878750c8b25672..f7c43641fc12b6 100644
--- a/packages/expo-audio/android/src/main/java/expo/modules/audio/AudioPlayer.kt
+++ b/packages/expo-audio/android/src/main/java/expo/modules/audio/AudioPlayer.kt
@@ -11,6 +11,7 @@ import androidx.media3.common.MediaItem
import androidx.media3.common.PlaybackParameters
import androidx.media3.common.Player
import androidx.media3.common.util.UnstableApi
+import androidx.media3.exoplayer.DefaultLoadControl
import androidx.media3.exoplayer.ExoPlayer
import androidx.media3.exoplayer.source.MediaSource
import androidx.media3.session.MediaSession
@@ -43,13 +44,28 @@ class AudioPlayer(
context: Context,
appContext: AppContext,
source: MediaSource?,
- private val updateInterval: Double
+ private val updateInterval: Double,
+ bufferDurationMs: Long = 0
) : SharedRef(
ExoPlayer.Builder(context)
.setLooper(context.mainLooper)
.setAudioAttributes(AudioAttributes.DEFAULT, false)
.setSeekForwardIncrementMs(SEEK_JUMP_INTERVAL_MS)
.setSeekBackIncrementMs(SEEK_JUMP_INTERVAL_MS)
+ .apply {
+ if (bufferDurationMs > 0) {
+ setLoadControl(
+ DefaultLoadControl.Builder()
+ .setBufferDurationsMs(
+ DefaultLoadControl.DEFAULT_MIN_BUFFER_MS,
+ bufferDurationMs.toInt().coerceAtLeast(DefaultLoadControl.DEFAULT_MIN_BUFFER_MS),
+ DefaultLoadControl.DEFAULT_BUFFER_FOR_PLAYBACK_MS,
+ DefaultLoadControl.DEFAULT_BUFFER_FOR_PLAYBACK_AFTER_REBUFFER_MS
+ )
+ .build()
+ )
+ }
+ }
.build(),
appContext
),
diff --git a/packages/expo-audio/android/src/main/java/expo/modules/audio/AudioPreloadCache.kt b/packages/expo-audio/android/src/main/java/expo/modules/audio/AudioPreloadCache.kt
new file mode 100644
index 00000000000000..d1c644552d1135
--- /dev/null
+++ b/packages/expo-audio/android/src/main/java/expo/modules/audio/AudioPreloadCache.kt
@@ -0,0 +1,89 @@
+package expo.modules.audio
+
+import android.content.Context
+import androidx.core.net.toUri
+import androidx.media3.datasource.DataSource
+import androidx.media3.datasource.DataSpec
+import androidx.media3.datasource.DefaultDataSource
+import androidx.media3.datasource.cache.CacheDataSource
+import androidx.media3.datasource.cache.CacheWriter
+import androidx.media3.datasource.cache.LeastRecentlyUsedCacheEvictor
+import androidx.media3.datasource.cache.SimpleCache
+import androidx.media3.database.StandaloneDatabaseProvider
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.withContext
+import java.io.File
+
+private const val CACHE_DIR_NAME = "expo_audio_preload"
+private const val MAX_CACHE_SIZE_BYTES = 100L * 1024 * 1024
+
+@androidx.annotation.OptIn(androidx.media3.common.util.UnstableApi::class)
+object AudioPreloadCache {
+ private val lock = Any()
+ private var cache: SimpleCache? = null
+ private val preloadedUris = mutableSetOf()
+
+ fun getCache(context: Context): SimpleCache = synchronized(lock) {
+ cache ?: run {
+ val cacheDir = File(context.cacheDir, CACHE_DIR_NAME)
+ val evictor = LeastRecentlyUsedCacheEvictor(MAX_CACHE_SIZE_BYTES)
+ val databaseProvider = StandaloneDatabaseProvider(context)
+ SimpleCache(cacheDir, evictor, databaseProvider).also { cache = it }
+ }
+ }
+
+ fun createCacheDataSourceFactory(
+ context: Context,
+ upstreamFactory: DataSource.Factory
+ ): CacheDataSource.Factory =
+ CacheDataSource.Factory()
+ .setCache(getCache(context))
+ .setUpstreamDataSourceFactory(upstreamFactory)
+ .setFlags(CacheDataSource.FLAG_IGNORE_CACHE_ON_ERROR)
+
+ suspend fun preload(context: Context, uri: String, upstreamFactory: DataSource.Factory? = null) = withContext(Dispatchers.IO) {
+ val factory = upstreamFactory ?: DefaultDataSource.Factory(context)
+ val cacheDataSourceFactory = CacheDataSource.Factory()
+ .setCache(getCache(context))
+ .setUpstreamDataSourceFactory(factory)
+ .setFlags(CacheDataSource.FLAG_IGNORE_CACHE_ON_ERROR)
+
+ val dataSpec = DataSpec.Builder().setUri(uri.toUri()).build()
+ val dataSource = cacheDataSourceFactory.createDataSource()
+ CacheWriter(dataSource, dataSpec, null, null).cache()
+ synchronized(lock) { preloadedUris.add(uri) }
+ }
+
+ suspend fun clearSource(context: Context, uri: String) = withContext(Dispatchers.IO) {
+ val simpleCache = getCache(context)
+ simpleCache.getCachedSpans(uri).forEach { span ->
+ simpleCache.removeSpan(span)
+ }
+ synchronized(lock) { preloadedUris.remove(uri) }
+ }
+
+ suspend fun clearAll(context: Context) = withContext(Dispatchers.IO) {
+ synchronized(lock) {
+ cache?.let { simpleCache ->
+ simpleCache.keys.toList().forEach { key ->
+ simpleCache.getCachedSpans(key).forEach { span ->
+ simpleCache.removeSpan(span)
+ }
+ }
+ }
+ preloadedUris.clear()
+ }
+ }
+
+ suspend fun release() = withContext(Dispatchers.IO) {
+ synchronized(lock) {
+ cache?.release()
+ cache = null
+ preloadedUris.clear()
+ }
+ }
+
+ fun getPreloadedSources(): List = synchronized(lock) {
+ preloadedUris.toList()
+ }
+}
diff --git a/packages/expo-audio/build/Audio.types.d.ts b/packages/expo-audio/build/Audio.types.d.ts
index 2f88e3f2ba1ce3..4aba6ad4505f98 100644
--- a/packages/expo-audio/build/Audio.types.d.ts
+++ b/packages/expo-audio/build/Audio.types.d.ts
@@ -116,6 +116,39 @@ export type AudioPlayerOptions = {
* @default false
*/
keepAudioSessionActive?: boolean;
+ /**
+ * The duration in seconds the player should buffer ahead of the current playback position.
+ * A higher value improves playback stability at the cost of more memory/network usage.
+ *
+ * - **iOS**: Maps to `AVPlayerItem.preferredForwardBufferDuration`. A value of `0` lets the system decide.
+ * - **Android**: Configures ExoPlayer's `DefaultLoadControl` max buffer duration.
+ * - **Web**: Not applicable (browser manages buffering).
+ *
+ * @default 0 (system default)
+ *
+ * @platform ios
+ * @platform android
+ */
+ preferredForwardBufferDuration?: number;
+};
+/**
+ * Options for configuring audio preloading behavior.
+ */
+export type PreloadOptions = {
+ /**
+ * The duration in seconds the player should buffer ahead of the current playback position.
+ * A higher value improves playback stability at the cost of more memory/network usage.
+ *
+ * - **iOS**: Maps to `AVPlayerItem.preferredForwardBufferDuration`. A value of `0` lets the system decide.
+ * - **Android**: Configures ExoPlayer's buffer duration.
+ * - **Web**: Not applicable (browser manages buffering).
+ *
+ * @default 10
+ *
+ * @platform ios
+ * @platform android
+ */
+ preferredForwardBufferDuration?: number;
};
/**
* @deprecated Use `AudioPlayerOptions` instead.
diff --git a/packages/expo-audio/build/Audio.types.d.ts.map b/packages/expo-audio/build/Audio.types.d.ts.map
index 41b111a5169100..d34f43d8e9755e 100644
--- a/packages/expo-audio/build/Audio.types.d.ts.map
+++ b/packages/expo-audio/build/Audio.types.d.ts.map
@@ -1 +1 @@
-{"version":3,"file":"Audio.types.d.ts","sourceRoot":"","sources":["../src/Audio.types.ts"],"names":[],"mappings":"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"}
\ No newline at end of file
+{"version":3,"file":"Audio.types.d.ts","sourceRoot":"","sources":["../src/Audio.types.ts"],"names":[],"mappings":"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"}
\ No newline at end of file
diff --git a/packages/expo-audio/build/Audio.types.js.map b/packages/expo-audio/build/Audio.types.js.map
index 12f73c729cfacc..3bb272393a0502 100644
--- a/packages/expo-audio/build/Audio.types.js.map
+++ b/packages/expo-audio/build/Audio.types.js.map
@@ -1 +1 @@
-{"version":3,"file":"Audio.types.js","sourceRoot":"","sources":["../src/Audio.types.ts"],"names":[],"mappings":"","sourcesContent":["import { AudioQuality, IOSOutputFormat } from './RecordingConstants';\n\n/**\n * Represents audio source information returned from native.\n * This is the object returned when reading sources from a queue.\n */\nexport type AudioSourceInfo = {\n /**\n * A string representing the resource identifier for the audio.\n */\n uri?: string;\n /**\n * An optional display name for the audio source.\n */\n name?: string;\n};\n\n// @docsMissing\nexport type AudioSource =\n | string\n | number\n | null\n | {\n /**\n * A string representing the resource identifier for the audio,\n * which could be an HTTPS address, a local file path, or the name of a static audio file resource.\n */\n uri?: string;\n /**\n * The asset ID of a local audio asset, acquired with the `require` function.\n * This property is exclusive with the `uri` property. When both are present, the `assetId` will be ignored.\n */\n assetId?: number;\n /**\n * An object representing the HTTP headers to send along with the request for a remote audio source.\n * On web requires the `Access-Control-Allow-Origin` header returned by the server to include the current domain.\n */\n headers?: Record;\n /**\n * An optional display name for the audio source.\n * Useful for showing track names in a queue or playlist UI.\n */\n name?: string;\n };\n\n/**\n * Options for configuring audio player behavior.\n */\nexport type AudioPlayerOptions = {\n /**\n * How often (in milliseconds) to emit playback status updates. Defaults to 500ms.\n *\n * @example\n * ```tsx\n * import { useAudioPlayer } from 'expo-audio';\n *\n * export default function App() {\n * const player = useAudioPlayer(source);\n *\n * // High-frequency updates for smooth progress bars\n * const player = useAudioPlayer(source, { updateInterval: 100 });\n *\n * // Standard updates (default behavior)\n * const player = useAudioPlayer(source, { updateInterval: 500 });\n *\n * // Low-frequency updates for better performance\n * const player = useAudioPlayer(source, { updateInterval: 1000 });\n * }\n * ```\n *\n * @default 500ms\n *\n * @platform ios\n * @platform android\n * @platform web\n */\n updateInterval?: number;\n /**\n * If set to `true`, the system will attempt to download the resource to the device before loading.\n * This value defaults to `false`.\n *\n * Works with:\n * - Local assets from `require('path/to/file')`\n * - Remote HTTP/HTTPS URLs\n * - Asset objects\n *\n * When enabled, this ensures the audio file is fully downloaded before playback begins.\n * This can improve playback performance and reduce buffering, especially for users\n * managing multiple audio players simultaneously.\n *\n * On Android and iOS, this will download the audio file to the device's tmp directory before playback begins.\n * The system will purge the file at its discretion.\n *\n * On web, this will download the audio file to the user's device memory and make it available for the user to play.\n * The system will usually purge the file from memory after a reload or on memory pressure.\n * On web, CORS restrictions apply to the blob url, so you need to make sure the server returns the `Access-Control-Allow-Origin` header.\n *\n * @platform ios\n * @platform web\n * @platform android\n */\n downloadFirst?: boolean;\n /**\n * Determines the [cross origin policy](https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Attributes/crossorigin) used by the underlying native view on web.\n * If `undefined` (default), does not use CORS at all. If set to `'anonymous'`, the audio will be loaded with CORS enabled.\n * Note that some audio may not play if CORS is enabled, depending on the CDN settings.\n * If you encounter issues, consider adjusting the `crossOrigin` property.\n *\n *\n * @platform web\n * @default undefined\n */\n crossOrigin?: 'anonymous' | 'use-credentials';\n /**\n * If set to `true`, the audio session will not be deactivated when this player pauses or finishes playback.\n * This prevents interrupting other audio sources (like videos) when the audio ends.\n *\n * Useful for sound effects that should not interfere with ongoing video playback or other audio.\n * The audio session for this player will not be deactivated automatically when the player finishes playback.\n *\n * > **Note:** If needed, you can manually deactivate the audio session using `setIsAudioActiveAsync(false)`.\n *\n * @platform ios\n * @default false\n */\n keepAudioSessionActive?: boolean;\n};\n\n/**\n * @deprecated Use `AudioPlayerOptions` instead.\n * Options for audio loading behavior.\n */\nexport type AudioLoadOptions = AudioPlayerOptions;\n\n/**\n * Represents an available audio input device for recording.\n *\n * This type describes audio input sources like built-in microphones, external microphones,\n * or other audio input devices that can be used for recording. Each input has an identifying\n * information that can be used to select the preferred recording source.\n */\nexport type RecordingInput = {\n /** Human-readable name of the audio input device. */\n name: string;\n /** Type or category of the input device (for example, 'Built-in Microphone', 'External Microphone'). */\n type: string;\n /** Unique identifier for the input device, used to select the input ('Built-in Microphone', 'External Microphone') for recording. */\n uid: string;\n};\n\n/**\n * Pitch correction quality settings for audio playback rate changes.\n *\n * When changing playback rate, pitch correction can be applied to maintain the original pitch.\n * Different quality levels offer trade-offs between processing power and audio quality.\n *\n * @platform ios\n */\nexport type PitchCorrectionQuality = 'low' | 'medium' | 'high';\n\n/**\n * Comprehensive status information for an `AudioPlayer`.\n *\n * This object contains all the current state information about audio playback,\n * including playback position, duration, loading state, and playback settings.\n * Used by `useAudioPlayerStatus()` to provide real-time status updates.\n */\nexport type AudioStatus = {\n /** Unique identifier for the player instance. */\n id: string;\n /** Current playback position in seconds. */\n currentTime: number;\n /** String representation of the player's internal playback state. */\n playbackState: string;\n /** String representation of the player's time control status (playing/paused/waiting). */\n timeControlStatus: string;\n /** Reason why the player is waiting to play (if applicable). */\n reasonForWaitingToPlay: string;\n /** Whether the player is currently muted. */\n mute: boolean;\n /** Total duration of the audio in seconds, or 0 if not yet determined. */\n duration: number;\n /** Whether the audio is currently playing. */\n playing: boolean;\n /** Whether the audio is set to loop when it reaches the end. */\n loop: boolean;\n /** Whether the audio just finished playing. */\n didJustFinish: boolean;\n /** Whether the player is currently buffering data. */\n isBuffering: boolean;\n /** Whether the audio has finished loading and is ready to play. */\n isLoaded: boolean;\n /** Current playback rate (1.0 = normal speed). */\n playbackRate: number;\n /**\n * Whether pitch correction is enabled for rate changes.\n * @default true\n */\n shouldCorrectPitch: boolean;\n /**\n * Whether the media services were reset by the system.\n * When `true`, the player was interrupted because the system's media daemon crashed.\n * The player will automatically attempt to recover by reloading the source and resuming playback.\n * @platform ios\n */\n mediaServicesDidReset?: boolean;\n};\n\n/**\n * Status information for recording operations from the event system.\n *\n * This type represents the status data emitted by `recordingStatusUpdate` events.\n * It contains high-level information about the recording session and any errors.\n * Used internally by the event system. Most users should use `useAudioRecorderState()` instead.\n */\nexport type RecordingStatus = {\n /** Unique identifier for the recording session. */\n id: string;\n /** Whether the recording has finished (stopped). */\n isFinished: boolean;\n /** Whether an error occurred during recording. */\n hasError: boolean;\n /** Error message if an error occurred, `null` otherwise. */\n error: string | null;\n /** File URL of the completed recording, if available. */\n url: string | null;\n /**\n * Whether the media services were reset by the system.\n * When `true`, the recording was interrupted because the system's media daemon crashed.\n * The recorder is now invalid and must be re-prepared by calling `prepareToRecordAsync()`.\n * @platform ios\n */\n mediaServicesDidReset?: boolean;\n};\n\n/**\n * Current state information for an `AudioRecorder`.\n *\n * This object contains detailed information about the recorder's current state,\n * including recording status, duration, and technical details. This is what you get\n * when calling `recorder.getStatus()` or using `useAudioRecorderState()`.\n */\nexport type RecorderState = {\n /** Whether the recorder is ready and able to record. */\n canRecord: boolean;\n /** Whether recording is currently in progress. */\n isRecording: boolean;\n /** Duration of the current recording in milliseconds. */\n durationMillis: number;\n /** Whether the media services have been reset (typically indicates a system interruption). */\n mediaServicesDidReset: boolean;\n /** Current audio level/volume being recorded (if metering is enabled). */\n metering?: number;\n /** File URL where the recording will be saved, if available. */\n url: string | null;\n};\n\n/**\n * Audio output format options for Android recording.\n *\n * Specifies the container format for recorded audio files on Android.\n * Different formats have different compatibility and compression characteristics.\n *\n * @platform android\n */\nexport type AndroidOutputFormat =\n | 'default'\n | '3gp'\n | 'mpeg4'\n | 'amrnb'\n | 'amrwb'\n | 'aac_adts'\n | 'mpeg2ts'\n | 'webm';\n\n/**\n * Audio encoder options for Android recording.\n *\n * Specifies the audio codec used to encode recorded audio on Android.\n * Different encoders offer different quality, compression, and compatibility trade-offs.\n *\n * @platform android\n */\nexport type AndroidAudioEncoder = 'default' | 'amr_nb' | 'amr_wb' | 'aac' | 'he_aac' | 'aac_eld';\n\n/**\n * Bit rate strategies for audio encoding.\n *\n * Determines how the encoder manages bit rate during recording, affecting\n * file size consistency and quality characteristics.\n */\nexport type BitRateStrategy = 'constant' | 'longTermAverage' | 'variableConstrained' | 'variable';\n\n/**\n * Options for controlling how audio recording is started.\n */\nexport type RecordingStartOptions = {\n /**\n * The duration in seconds after which recording should automatically stop.\n * If not provided, recording continues until manually stopped.\n *\n * @platform ios\n * @platform android\n * @platform web\n */\n forDuration?: number;\n /**\n * The time in seconds to wait before starting the recording.\n * If not provided, recording starts immediately.\n *\n * **Platform behavior:**\n * - Android: Ignored, recording starts immediately\n * - iOS: Uses native AVAudioRecorder.record(atTime:) for precise timing.\n * - Web: Ignored, recording starts immediately\n *\n * > **warning** On iOS, the recording process starts immediately (you'll see status updates),\n * but actual audio capture begins after the specified delay. This is not a countdown, since\n * the recorder is active but silent during the delay period.\n *\n * @platform ios\n */\n atTime?: number;\n};\n\nexport type RecordingOptions = {\n /**\n * A boolean that determines whether audio level information will be part of the status object under the \"metering\" key.\n */\n isMeteringEnabled?: boolean;\n /**\n * The desired file extension.\n *\n * @example .caf\n */\n extension: string;\n /**\n * The desired sample rate.\n *\n * @example 44100\n */\n sampleRate: number;\n /**\n * The desired number of channels.\n *\n * @example 2\n */\n numberOfChannels: number;\n /**\n * The desired bit rate.\n *\n * @example 128000\n */\n bitRate: number;\n /**\n * Recording options for the Android platform.\n * @platform android\n */\n android: RecordingOptionsAndroid;\n /**\n * Recording options for the iOS platform.\n * @platform ios\n */\n ios: RecordingOptionsIos;\n /**\n * Recording options for the Web platform.\n * @platform web\n */\n web: RecordingOptionsWeb;\n};\n\n/**\n * Recording options for the web.\n *\n * Web recording uses the `MediaRecorder` API, which has different capabilities\n * compared to native platforms. These options map directly to `MediaRecorder` settings.\n *\n * @platform web\n */\nexport type RecordingOptionsWeb = {\n /** MIME type for the recording (for example, 'audio/webm', 'audio/mp4'). */\n mimeType?: string;\n /** Target bits per second for the recording. */\n bitsPerSecond?: number;\n};\n\n/**\n * Recording configuration options specific to iOS.\n *\n * iOS recording uses `AVAudioRecorder` with extensive format and quality options.\n * These settings provide fine-grained control over the recording characteristics.\n *\n * @platform ios\n */\nexport type RecordingOptionsIos = {\n /**\n * The desired file extension.\n *\n * @example .caf\n */\n extension?: string;\n /**\n * The desired sample rate.\n *\n * @example 44100\n */\n sampleRate?: number;\n /**\n * The desired file format. See the [`IOSOutputFormat`](#iosoutputformat) enum for all valid values.\n */\n outputFormat?: string | IOSOutputFormat | number;\n /**\n * The desired audio quality. See the [`AudioQuality`](#audioquality) enum for all valid values.\n */\n audioQuality: AudioQuality | number;\n /**\n * The desired bit rate strategy. See the next section for an enumeration of all valid values of `bitRateStrategy`.\n */\n bitRateStrategy?: number;\n /**\n * The desired bit depth hint.\n *\n * @example 16\n */\n bitDepthHint?: number;\n /**\n * The desired PCM bit depth.\n *\n * @example 16\n */\n linearPCMBitDepth?: number;\n /**\n * A boolean describing if the PCM data should be formatted in big endian.\n */\n linearPCMIsBigEndian?: boolean;\n /**\n * A boolean describing if the PCM data should be encoded in floating point or integral values.\n */\n linearPCMIsFloat?: boolean;\n};\n\n/**\n * Recording configuration options specific to Android.\n *\n * Android recording uses `MediaRecorder` with options for format, encoder, and file constraints.\n * These settings control the output format and quality characteristics.\n *\n * @platform android\n */\nexport type RecordingOptionsAndroid = {\n /**\n * The desired file extension.\n *\n * @example .caf\n */\n extension?: string;\n /**\n * The desired sample rate.\n *\n * @example 44100\n */\n sampleRate?: number;\n /**\n * The desired file format. See the [`AndroidOutputFormat`](#androidoutputformat) type for all valid values.\n */\n outputFormat: AndroidOutputFormat;\n /**\n * The desired audio encoder. See the [`AndroidAudioEncoder`](#androidaudioencoder) type for all valid values.\n */\n audioEncoder: AndroidAudioEncoder;\n /**\n * The desired maximum file size in bytes, after which the recording will stop (but `stopAndUnloadAsync()` must still\n * be called after this point).\n *\n * @example\n * `65536`\n */\n maxFileSize?: number;\n /**\n * The desired audio Source. See the [`RecordingSource`](#recordingsource) type for all valid values.\n */\n audioSource?: RecordingSource;\n};\n\nexport type AudioMode = {\n /**\n * Determines if audio playback is allowed when the device is in silent mode.\n *\n * @platform ios\n */\n playsInSilentMode: boolean;\n /**\n * Determines how the audio session interacts with other audio sessions.\n *\n * - `'doNotMix'`: Requests exclusive audio focus. Other apps will pause their audio.\n * - `'duckOthers'`: Requests audio focus with ducking. Other apps lower their volume but continue playing.\n * - `'mixWithOthers'`: Audio plays alongside other apps without interrupting them.\n * On Android, this means no audio focus is requested. Best suited for sound effects,\n * UI feedback, or short audio clips.\n *\n * @default 'mixWithOthers'\n */\n interruptionMode: InterruptionMode;\n /**\n * Determines how the audio session interacts with other sessions on Android.\n *\n * @platform android\n * @deprecated Use `interruptionMode` instead, which now works on both platforms.\n */\n interruptionModeAndroid?: InterruptionModeAndroid;\n /**\n * Whether the audio session allows recording.\n *\n * @default false\n * @platform ios\n */\n allowsRecording: boolean;\n /**\n * Whether the audio session stays active when the app moves to the background.\n *\n * > **Note**: On Android, you have to enable the lockscreen controls with [`setActiveForLockScreen`](#setactiveforlockscreenactive-metadata-options) for sustained background playback. Otherwise, the audio will stop after approximately 3 minutes of background playback (OS limitation). Make sure to also appropriately [configure the config-plugin](#configuration-in-app-config).\n * @default false\n */\n shouldPlayInBackground: boolean;\n /**\n * Whether the audio should route through the earpiece.\n * On iOS, this only has an effect when `allowsRecording` is `true` (i.e., the audio session\n * category is `.playAndRecord`). When `false` (the default), audio is routed through the speaker.\n * @default false\n */\n shouldRouteThroughEarpiece: boolean;\n /**\n * Whether audio recording should continue when the app moves to the background.\n *\n * @default false\n * @platform ios\n * @platform android\n */\n allowsBackgroundRecording?: boolean;\n};\n\n/**\n * Audio interruption behavior modes.\n *\n * Controls how your app's audio interacts with other apps' audio.\n *\n * - `'doNotMix'`: Requests exclusive audio focus. Other apps will pause their audio.\n * - `'duckOthers'`: Requests audio focus with ducking. Other apps lower their volume but continue playing.\n * - `'mixWithOthers'`: Audio plays alongside other apps without interrupting them.\n *\n * On Android, this means no audio focus is requested. Best suited for sound effects,\n * UI feedback, or short audio clips. Note that on Android your app won't receive\n * audio focus loss callbacks (for example, during phone calls) when using this mode.\n *\n * > **Note:** When using `setActiveForLockScreen`, this must be set to `doNotMix`.\n *\n * @default 'mixWithOthers'\n */\nexport type InterruptionMode = 'mixWithOthers' | 'doNotMix' | 'duckOthers';\n\n/**\n * @deprecated Use `InterruptionMode` instead, which now works on both platforms.\n */\nexport type InterruptionModeAndroid = InterruptionMode;\n\n/**\n * Recording source for android.\n *\n * An audio source defines both a default physical source of audio signal, and a recording configuration.\n *\n * - `camcorder`: Microphone audio source tuned for video recording, with the same orientation as the camera if available.\n * - `default`: The default audio source.\n * - `mic`: Microphone audio source.\n * - `unprocessed`: Microphone audio source tuned for unprocessed (raw) sound if available, behaves like `default` otherwise.\n * - `voice_communication`: Microphone audio source tuned for voice communications such as VoIP. It will for instance take advantage of echo cancellation or automatic gain control if available.\n * - `voice_performance`: Source for capturing audio meant to be processed in real time and played back for live performance (e.g karaoke). The capture path will minimize latency and coupling with playback path.\n * - `voice_recognition`: Microphone audio source tuned for voice recognition.\n *\n * @see https://developer.android.com/reference/android/media/MediaRecorder.AudioSource\n * @platform android\n */\nexport type RecordingSource =\n | 'camcorder'\n | 'default'\n | 'mic'\n | 'remote_submix'\n | 'unprocessed'\n | 'voice_communication'\n | 'voice_performance'\n | 'voice_recognition';\n\n// @docsMissing\nexport type AudioMetadata = {\n title?: string;\n artist?: string;\n albumTitle?: string;\n artworkUrl?: string;\n};\n\n/**\n * Loop mode for audio playlist playback.\n *\n * - `'none'`: No looping. Playback stops after the last track.\n * - `'single'`: Loops the current track indefinitely.\n * - `'all'`: Loops the entire playlist, returning to the first track after the last.\n */\nexport type AudioPlaylistLoopMode = 'none' | 'single' | 'all';\n\n/**\n * Options for configuring an audio playlist.\n */\nexport type AudioPlaylistOptions = {\n /**\n * Initial sources to add to the playlist. Each source can be a local asset, remote URL, or null.\n * @default []\n */\n sources?: AudioSource[];\n\n /**\n * How often (in milliseconds) to emit playback status updates. Defaults to 500ms.\n * @default 500\n */\n updateInterval?: number;\n\n /**\n * Loop mode for the playlist.\n * - `'none'`: No looping (default)\n * - `'single'`: Loop the current track\n * - `'all'`: Loop the entire playlist\n * @default 'none'\n */\n loop?: AudioPlaylistLoopMode;\n\n /**\n * Sets the `crossOrigin` attribute on the `` elements used for playback.\n * Required for CORS-enabled audio files when you need to access audio data.\n * @platform web\n * @default undefined\n */\n crossOrigin?: 'anonymous' | 'use-credentials';\n};\n\n/**\n * Status information for an audio playlist.\n */\nexport type AudioPlaylistStatus = {\n /** Unique identifier for the playlist instance. */\n id: string;\n /** Index of the currently playing track in the playlist. */\n currentIndex: number;\n /** Total number of tracks in the playlist. */\n trackCount: number;\n /** Current playback position in seconds. */\n currentTime: number;\n /** Total duration of the current track in seconds. */\n duration: number;\n /** Whether the player is currently playing. */\n playing: boolean;\n /** Whether the player is buffering. */\n isBuffering: boolean;\n /** Whether the current track has finished loading. */\n isLoaded: boolean;\n /** Current playback rate (1.0 = normal speed). */\n playbackRate: number;\n /** Whether the player is muted. */\n muted: boolean;\n /** Current volume level (0.0 to 1.0). */\n volume: number;\n /** Current loop mode. */\n loop: AudioPlaylistLoopMode;\n /** Whether the current track just finished playing. */\n didJustFinish: boolean;\n};\n"]}
\ No newline at end of file
+{"version":3,"file":"Audio.types.js","sourceRoot":"","sources":["../src/Audio.types.ts"],"names":[],"mappings":"","sourcesContent":["import { AudioQuality, IOSOutputFormat } from './RecordingConstants';\n\n/**\n * Represents audio source information returned from native.\n * This is the object returned when reading sources from a queue.\n */\nexport type AudioSourceInfo = {\n /**\n * A string representing the resource identifier for the audio.\n */\n uri?: string;\n /**\n * An optional display name for the audio source.\n */\n name?: string;\n};\n\n// @docsMissing\nexport type AudioSource =\n | string\n | number\n | null\n | {\n /**\n * A string representing the resource identifier for the audio,\n * which could be an HTTPS address, a local file path, or the name of a static audio file resource.\n */\n uri?: string;\n /**\n * The asset ID of a local audio asset, acquired with the `require` function.\n * This property is exclusive with the `uri` property. When both are present, the `assetId` will be ignored.\n */\n assetId?: number;\n /**\n * An object representing the HTTP headers to send along with the request for a remote audio source.\n * On web requires the `Access-Control-Allow-Origin` header returned by the server to include the current domain.\n */\n headers?: Record;\n /**\n * An optional display name for the audio source.\n * Useful for showing track names in a queue or playlist UI.\n */\n name?: string;\n };\n\n/**\n * Options for configuring audio player behavior.\n */\nexport type AudioPlayerOptions = {\n /**\n * How often (in milliseconds) to emit playback status updates. Defaults to 500ms.\n *\n * @example\n * ```tsx\n * import { useAudioPlayer } from 'expo-audio';\n *\n * export default function App() {\n * const player = useAudioPlayer(source);\n *\n * // High-frequency updates for smooth progress bars\n * const player = useAudioPlayer(source, { updateInterval: 100 });\n *\n * // Standard updates (default behavior)\n * const player = useAudioPlayer(source, { updateInterval: 500 });\n *\n * // Low-frequency updates for better performance\n * const player = useAudioPlayer(source, { updateInterval: 1000 });\n * }\n * ```\n *\n * @default 500ms\n *\n * @platform ios\n * @platform android\n * @platform web\n */\n updateInterval?: number;\n /**\n * If set to `true`, the system will attempt to download the resource to the device before loading.\n * This value defaults to `false`.\n *\n * Works with:\n * - Local assets from `require('path/to/file')`\n * - Remote HTTP/HTTPS URLs\n * - Asset objects\n *\n * When enabled, this ensures the audio file is fully downloaded before playback begins.\n * This can improve playback performance and reduce buffering, especially for users\n * managing multiple audio players simultaneously.\n *\n * On Android and iOS, this will download the audio file to the device's tmp directory before playback begins.\n * The system will purge the file at its discretion.\n *\n * On web, this will download the audio file to the user's device memory and make it available for the user to play.\n * The system will usually purge the file from memory after a reload or on memory pressure.\n * On web, CORS restrictions apply to the blob url, so you need to make sure the server returns the `Access-Control-Allow-Origin` header.\n *\n * @platform ios\n * @platform web\n * @platform android\n */\n downloadFirst?: boolean;\n /**\n * Determines the [cross origin policy](https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Attributes/crossorigin) used by the underlying native view on web.\n * If `undefined` (default), does not use CORS at all. If set to `'anonymous'`, the audio will be loaded with CORS enabled.\n * Note that some audio may not play if CORS is enabled, depending on the CDN settings.\n * If you encounter issues, consider adjusting the `crossOrigin` property.\n *\n *\n * @platform web\n * @default undefined\n */\n crossOrigin?: 'anonymous' | 'use-credentials';\n /**\n * If set to `true`, the audio session will not be deactivated when this player pauses or finishes playback.\n * This prevents interrupting other audio sources (like videos) when the audio ends.\n *\n * Useful for sound effects that should not interfere with ongoing video playback or other audio.\n * The audio session for this player will not be deactivated automatically when the player finishes playback.\n *\n * > **Note:** If needed, you can manually deactivate the audio session using `setIsAudioActiveAsync(false)`.\n *\n * @platform ios\n * @default false\n */\n keepAudioSessionActive?: boolean;\n /**\n * The duration in seconds the player should buffer ahead of the current playback position.\n * A higher value improves playback stability at the cost of more memory/network usage.\n *\n * - **iOS**: Maps to `AVPlayerItem.preferredForwardBufferDuration`. A value of `0` lets the system decide.\n * - **Android**: Configures ExoPlayer's `DefaultLoadControl` max buffer duration.\n * - **Web**: Not applicable (browser manages buffering).\n *\n * @default 0 (system default)\n *\n * @platform ios\n * @platform android\n */\n preferredForwardBufferDuration?: number;\n};\n\n/**\n * Options for configuring audio preloading behavior.\n */\nexport type PreloadOptions = {\n /**\n * The duration in seconds the player should buffer ahead of the current playback position.\n * A higher value improves playback stability at the cost of more memory/network usage.\n *\n * - **iOS**: Maps to `AVPlayerItem.preferredForwardBufferDuration`. A value of `0` lets the system decide.\n * - **Android**: Configures ExoPlayer's buffer duration.\n * - **Web**: Not applicable (browser manages buffering).\n *\n * @default 10\n *\n * @platform ios\n * @platform android\n */\n preferredForwardBufferDuration?: number;\n};\n\n/**\n * @deprecated Use `AudioPlayerOptions` instead.\n * Options for audio loading behavior.\n */\nexport type AudioLoadOptions = AudioPlayerOptions;\n\n/**\n * Represents an available audio input device for recording.\n *\n * This type describes audio input sources like built-in microphones, external microphones,\n * or other audio input devices that can be used for recording. Each input has an identifying\n * information that can be used to select the preferred recording source.\n */\nexport type RecordingInput = {\n /** Human-readable name of the audio input device. */\n name: string;\n /** Type or category of the input device (for example, 'Built-in Microphone', 'External Microphone'). */\n type: string;\n /** Unique identifier for the input device, used to select the input ('Built-in Microphone', 'External Microphone') for recording. */\n uid: string;\n};\n\n/**\n * Pitch correction quality settings for audio playback rate changes.\n *\n * When changing playback rate, pitch correction can be applied to maintain the original pitch.\n * Different quality levels offer trade-offs between processing power and audio quality.\n *\n * @platform ios\n */\nexport type PitchCorrectionQuality = 'low' | 'medium' | 'high';\n\n/**\n * Comprehensive status information for an `AudioPlayer`.\n *\n * This object contains all the current state information about audio playback,\n * including playback position, duration, loading state, and playback settings.\n * Used by `useAudioPlayerStatus()` to provide real-time status updates.\n */\nexport type AudioStatus = {\n /** Unique identifier for the player instance. */\n id: string;\n /** Current playback position in seconds. */\n currentTime: number;\n /** String representation of the player's internal playback state. */\n playbackState: string;\n /** String representation of the player's time control status (playing/paused/waiting). */\n timeControlStatus: string;\n /** Reason why the player is waiting to play (if applicable). */\n reasonForWaitingToPlay: string;\n /** Whether the player is currently muted. */\n mute: boolean;\n /** Total duration of the audio in seconds, or 0 if not yet determined. */\n duration: number;\n /** Whether the audio is currently playing. */\n playing: boolean;\n /** Whether the audio is set to loop when it reaches the end. */\n loop: boolean;\n /** Whether the audio just finished playing. */\n didJustFinish: boolean;\n /** Whether the player is currently buffering data. */\n isBuffering: boolean;\n /** Whether the audio has finished loading and is ready to play. */\n isLoaded: boolean;\n /** Current playback rate (1.0 = normal speed). */\n playbackRate: number;\n /**\n * Whether pitch correction is enabled for rate changes.\n * @default true\n */\n shouldCorrectPitch: boolean;\n /**\n * Whether the media services were reset by the system.\n * When `true`, the player was interrupted because the system's media daemon crashed.\n * The player will automatically attempt to recover by reloading the source and resuming playback.\n * @platform ios\n */\n mediaServicesDidReset?: boolean;\n};\n\n/**\n * Status information for recording operations from the event system.\n *\n * This type represents the status data emitted by `recordingStatusUpdate` events.\n * It contains high-level information about the recording session and any errors.\n * Used internally by the event system. Most users should use `useAudioRecorderState()` instead.\n */\nexport type RecordingStatus = {\n /** Unique identifier for the recording session. */\n id: string;\n /** Whether the recording has finished (stopped). */\n isFinished: boolean;\n /** Whether an error occurred during recording. */\n hasError: boolean;\n /** Error message if an error occurred, `null` otherwise. */\n error: string | null;\n /** File URL of the completed recording, if available. */\n url: string | null;\n /**\n * Whether the media services were reset by the system.\n * When `true`, the recording was interrupted because the system's media daemon crashed.\n * The recorder is now invalid and must be re-prepared by calling `prepareToRecordAsync()`.\n * @platform ios\n */\n mediaServicesDidReset?: boolean;\n};\n\n/**\n * Current state information for an `AudioRecorder`.\n *\n * This object contains detailed information about the recorder's current state,\n * including recording status, duration, and technical details. This is what you get\n * when calling `recorder.getStatus()` or using `useAudioRecorderState()`.\n */\nexport type RecorderState = {\n /** Whether the recorder is ready and able to record. */\n canRecord: boolean;\n /** Whether recording is currently in progress. */\n isRecording: boolean;\n /** Duration of the current recording in milliseconds. */\n durationMillis: number;\n /** Whether the media services have been reset (typically indicates a system interruption). */\n mediaServicesDidReset: boolean;\n /** Current audio level/volume being recorded (if metering is enabled). */\n metering?: number;\n /** File URL where the recording will be saved, if available. */\n url: string | null;\n};\n\n/**\n * Audio output format options for Android recording.\n *\n * Specifies the container format for recorded audio files on Android.\n * Different formats have different compatibility and compression characteristics.\n *\n * @platform android\n */\nexport type AndroidOutputFormat =\n | 'default'\n | '3gp'\n | 'mpeg4'\n | 'amrnb'\n | 'amrwb'\n | 'aac_adts'\n | 'mpeg2ts'\n | 'webm';\n\n/**\n * Audio encoder options for Android recording.\n *\n * Specifies the audio codec used to encode recorded audio on Android.\n * Different encoders offer different quality, compression, and compatibility trade-offs.\n *\n * @platform android\n */\nexport type AndroidAudioEncoder = 'default' | 'amr_nb' | 'amr_wb' | 'aac' | 'he_aac' | 'aac_eld';\n\n/**\n * Bit rate strategies for audio encoding.\n *\n * Determines how the encoder manages bit rate during recording, affecting\n * file size consistency and quality characteristics.\n */\nexport type BitRateStrategy = 'constant' | 'longTermAverage' | 'variableConstrained' | 'variable';\n\n/**\n * Options for controlling how audio recording is started.\n */\nexport type RecordingStartOptions = {\n /**\n * The duration in seconds after which recording should automatically stop.\n * If not provided, recording continues until manually stopped.\n *\n * @platform ios\n * @platform android\n * @platform web\n */\n forDuration?: number;\n /**\n * The time in seconds to wait before starting the recording.\n * If not provided, recording starts immediately.\n *\n * **Platform behavior:**\n * - Android: Ignored, recording starts immediately\n * - iOS: Uses native AVAudioRecorder.record(atTime:) for precise timing.\n * - Web: Ignored, recording starts immediately\n *\n * > **warning** On iOS, the recording process starts immediately (you'll see status updates),\n * but actual audio capture begins after the specified delay. This is not a countdown, since\n * the recorder is active but silent during the delay period.\n *\n * @platform ios\n */\n atTime?: number;\n};\n\nexport type RecordingOptions = {\n /**\n * A boolean that determines whether audio level information will be part of the status object under the \"metering\" key.\n */\n isMeteringEnabled?: boolean;\n /**\n * The desired file extension.\n *\n * @example .caf\n */\n extension: string;\n /**\n * The desired sample rate.\n *\n * @example 44100\n */\n sampleRate: number;\n /**\n * The desired number of channels.\n *\n * @example 2\n */\n numberOfChannels: number;\n /**\n * The desired bit rate.\n *\n * @example 128000\n */\n bitRate: number;\n /**\n * Recording options for the Android platform.\n * @platform android\n */\n android: RecordingOptionsAndroid;\n /**\n * Recording options for the iOS platform.\n * @platform ios\n */\n ios: RecordingOptionsIos;\n /**\n * Recording options for the Web platform.\n * @platform web\n */\n web: RecordingOptionsWeb;\n};\n\n/**\n * Recording options for the web.\n *\n * Web recording uses the `MediaRecorder` API, which has different capabilities\n * compared to native platforms. These options map directly to `MediaRecorder` settings.\n *\n * @platform web\n */\nexport type RecordingOptionsWeb = {\n /** MIME type for the recording (for example, 'audio/webm', 'audio/mp4'). */\n mimeType?: string;\n /** Target bits per second for the recording. */\n bitsPerSecond?: number;\n};\n\n/**\n * Recording configuration options specific to iOS.\n *\n * iOS recording uses `AVAudioRecorder` with extensive format and quality options.\n * These settings provide fine-grained control over the recording characteristics.\n *\n * @platform ios\n */\nexport type RecordingOptionsIos = {\n /**\n * The desired file extension.\n *\n * @example .caf\n */\n extension?: string;\n /**\n * The desired sample rate.\n *\n * @example 44100\n */\n sampleRate?: number;\n /**\n * The desired file format. See the [`IOSOutputFormat`](#iosoutputformat) enum for all valid values.\n */\n outputFormat?: string | IOSOutputFormat | number;\n /**\n * The desired audio quality. See the [`AudioQuality`](#audioquality) enum for all valid values.\n */\n audioQuality: AudioQuality | number;\n /**\n * The desired bit rate strategy. See the next section for an enumeration of all valid values of `bitRateStrategy`.\n */\n bitRateStrategy?: number;\n /**\n * The desired bit depth hint.\n *\n * @example 16\n */\n bitDepthHint?: number;\n /**\n * The desired PCM bit depth.\n *\n * @example 16\n */\n linearPCMBitDepth?: number;\n /**\n * A boolean describing if the PCM data should be formatted in big endian.\n */\n linearPCMIsBigEndian?: boolean;\n /**\n * A boolean describing if the PCM data should be encoded in floating point or integral values.\n */\n linearPCMIsFloat?: boolean;\n};\n\n/**\n * Recording configuration options specific to Android.\n *\n * Android recording uses `MediaRecorder` with options for format, encoder, and file constraints.\n * These settings control the output format and quality characteristics.\n *\n * @platform android\n */\nexport type RecordingOptionsAndroid = {\n /**\n * The desired file extension.\n *\n * @example .caf\n */\n extension?: string;\n /**\n * The desired sample rate.\n *\n * @example 44100\n */\n sampleRate?: number;\n /**\n * The desired file format. See the [`AndroidOutputFormat`](#androidoutputformat) type for all valid values.\n */\n outputFormat: AndroidOutputFormat;\n /**\n * The desired audio encoder. See the [`AndroidAudioEncoder`](#androidaudioencoder) type for all valid values.\n */\n audioEncoder: AndroidAudioEncoder;\n /**\n * The desired maximum file size in bytes, after which the recording will stop (but `stopAndUnloadAsync()` must still\n * be called after this point).\n *\n * @example\n * `65536`\n */\n maxFileSize?: number;\n /**\n * The desired audio Source. See the [`RecordingSource`](#recordingsource) type for all valid values.\n */\n audioSource?: RecordingSource;\n};\n\nexport type AudioMode = {\n /**\n * Determines if audio playback is allowed when the device is in silent mode.\n *\n * @platform ios\n */\n playsInSilentMode: boolean;\n /**\n * Determines how the audio session interacts with other audio sessions.\n *\n * - `'doNotMix'`: Requests exclusive audio focus. Other apps will pause their audio.\n * - `'duckOthers'`: Requests audio focus with ducking. Other apps lower their volume but continue playing.\n * - `'mixWithOthers'`: Audio plays alongside other apps without interrupting them.\n * On Android, this means no audio focus is requested. Best suited for sound effects,\n * UI feedback, or short audio clips.\n *\n * @default 'mixWithOthers'\n */\n interruptionMode: InterruptionMode;\n /**\n * Determines how the audio session interacts with other sessions on Android.\n *\n * @platform android\n * @deprecated Use `interruptionMode` instead, which now works on both platforms.\n */\n interruptionModeAndroid?: InterruptionModeAndroid;\n /**\n * Whether the audio session allows recording.\n *\n * @default false\n * @platform ios\n */\n allowsRecording: boolean;\n /**\n * Whether the audio session stays active when the app moves to the background.\n *\n * > **Note**: On Android, you have to enable the lockscreen controls with [`setActiveForLockScreen`](#setactiveforlockscreenactive-metadata-options) for sustained background playback. Otherwise, the audio will stop after approximately 3 minutes of background playback (OS limitation). Make sure to also appropriately [configure the config-plugin](#configuration-in-app-config).\n * @default false\n */\n shouldPlayInBackground: boolean;\n /**\n * Whether the audio should route through the earpiece.\n * On iOS, this only has an effect when `allowsRecording` is `true` (i.e., the audio session\n * category is `.playAndRecord`). When `false` (the default), audio is routed through the speaker.\n * @default false\n */\n shouldRouteThroughEarpiece: boolean;\n /**\n * Whether audio recording should continue when the app moves to the background.\n *\n * @default false\n * @platform ios\n * @platform android\n */\n allowsBackgroundRecording?: boolean;\n};\n\n/**\n * Audio interruption behavior modes.\n *\n * Controls how your app's audio interacts with other apps' audio.\n *\n * - `'doNotMix'`: Requests exclusive audio focus. Other apps will pause their audio.\n * - `'duckOthers'`: Requests audio focus with ducking. Other apps lower their volume but continue playing.\n * - `'mixWithOthers'`: Audio plays alongside other apps without interrupting them.\n *\n * On Android, this means no audio focus is requested. Best suited for sound effects,\n * UI feedback, or short audio clips. Note that on Android your app won't receive\n * audio focus loss callbacks (for example, during phone calls) when using this mode.\n *\n * > **Note:** When using `setActiveForLockScreen`, this must be set to `doNotMix`.\n *\n * @default 'mixWithOthers'\n */\nexport type InterruptionMode = 'mixWithOthers' | 'doNotMix' | 'duckOthers';\n\n/**\n * @deprecated Use `InterruptionMode` instead, which now works on both platforms.\n */\nexport type InterruptionModeAndroid = InterruptionMode;\n\n/**\n * Recording source for android.\n *\n * An audio source defines both a default physical source of audio signal, and a recording configuration.\n *\n * - `camcorder`: Microphone audio source tuned for video recording, with the same orientation as the camera if available.\n * - `default`: The default audio source.\n * - `mic`: Microphone audio source.\n * - `unprocessed`: Microphone audio source tuned for unprocessed (raw) sound if available, behaves like `default` otherwise.\n * - `voice_communication`: Microphone audio source tuned for voice communications such as VoIP. It will for instance take advantage of echo cancellation or automatic gain control if available.\n * - `voice_performance`: Source for capturing audio meant to be processed in real time and played back for live performance (e.g karaoke). The capture path will minimize latency and coupling with playback path.\n * - `voice_recognition`: Microphone audio source tuned for voice recognition.\n *\n * @see https://developer.android.com/reference/android/media/MediaRecorder.AudioSource\n * @platform android\n */\nexport type RecordingSource =\n | 'camcorder'\n | 'default'\n | 'mic'\n | 'remote_submix'\n | 'unprocessed'\n | 'voice_communication'\n | 'voice_performance'\n | 'voice_recognition';\n\n// @docsMissing\nexport type AudioMetadata = {\n title?: string;\n artist?: string;\n albumTitle?: string;\n artworkUrl?: string;\n};\n\n/**\n * Loop mode for audio playlist playback.\n *\n * - `'none'`: No looping. Playback stops after the last track.\n * - `'single'`: Loops the current track indefinitely.\n * - `'all'`: Loops the entire playlist, returning to the first track after the last.\n */\nexport type AudioPlaylistLoopMode = 'none' | 'single' | 'all';\n\n/**\n * Options for configuring an audio playlist.\n */\nexport type AudioPlaylistOptions = {\n /**\n * Initial sources to add to the playlist. Each source can be a local asset, remote URL, or null.\n * @default []\n */\n sources?: AudioSource[];\n\n /**\n * How often (in milliseconds) to emit playback status updates. Defaults to 500ms.\n * @default 500\n */\n updateInterval?: number;\n\n /**\n * Loop mode for the playlist.\n * - `'none'`: No looping (default)\n * - `'single'`: Loop the current track\n * - `'all'`: Loop the entire playlist\n * @default 'none'\n */\n loop?: AudioPlaylistLoopMode;\n\n /**\n * Sets the `crossOrigin` attribute on the `` elements used for playback.\n * Required for CORS-enabled audio files when you need to access audio data.\n * @platform web\n * @default undefined\n */\n crossOrigin?: 'anonymous' | 'use-credentials';\n};\n\n/**\n * Status information for an audio playlist.\n */\nexport type AudioPlaylistStatus = {\n /** Unique identifier for the playlist instance. */\n id: string;\n /** Index of the currently playing track in the playlist. */\n currentIndex: number;\n /** Total number of tracks in the playlist. */\n trackCount: number;\n /** Current playback position in seconds. */\n currentTime: number;\n /** Total duration of the current track in seconds. */\n duration: number;\n /** Whether the player is currently playing. */\n playing: boolean;\n /** Whether the player is buffering. */\n isBuffering: boolean;\n /** Whether the current track has finished loading. */\n isLoaded: boolean;\n /** Current playback rate (1.0 = normal speed). */\n playbackRate: number;\n /** Whether the player is muted. */\n muted: boolean;\n /** Current volume level (0.0 to 1.0). */\n volume: number;\n /** Current loop mode. */\n loop: AudioPlaylistLoopMode;\n /** Whether the current track just finished playing. */\n didJustFinish: boolean;\n};\n"]}
\ No newline at end of file
diff --git a/packages/expo-audio/build/AudioModule.types.d.ts b/packages/expo-audio/build/AudioModule.types.d.ts
index 5757145c5be959..93b69010ae4cef 100644
--- a/packages/expo-audio/build/AudioModule.types.d.ts
+++ b/packages/expo-audio/build/AudioModule.types.d.ts
@@ -10,6 +10,10 @@ export declare class NativeAudioModule extends NativeModule {
requestRecordingPermissionsAsync(): Promise;
requestNotificationPermissionsAsync(): Promise;
getRecordingPermissionsAsync(): Promise;
+ preload(source: AudioSource, preferredForwardBufferDuration: number): Promise;
+ clearPreloadedSource(source: AudioSource): Promise;
+ clearAllPreloadedSources(): Promise;
+ getPreloadedSources(): Promise;
readonly AudioPlayer: typeof AudioPlayer;
readonly AudioRecorder: typeof AudioRecorder;
readonly AudioPlaylist: typeof AudioPlaylist;
@@ -19,7 +23,7 @@ export declare class AudioPlayer extends SharedObject {
* Initializes a new audio player instance with the given source.
* @hidden
*/
- constructor(source: AudioSource, updateInterval: number, keepAudioSessionActive: boolean);
+ constructor(source: AudioSource, updateInterval: number, keepAudioSessionActive: boolean, preferredForwardBufferDuration: number);
/**
* Unique identifier for the player object.
*/
diff --git a/packages/expo-audio/build/AudioModule.types.d.ts.map b/packages/expo-audio/build/AudioModule.types.d.ts.map
index e39d3021dfa8e8..3f8d8ac23cc2f5 100644
--- a/packages/expo-audio/build/AudioModule.types.d.ts.map
+++ b/packages/expo-audio/build/AudioModule.types.d.ts.map
@@ -1 +1 @@
-{"version":3,"file":"AudioModule.types.d.ts","sourceRoot":"","sources":["../src/AudioModule.types.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,kBAAkB,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAEnF,OAAO,EACL,aAAa,EACb,SAAS,EACT,qBAAqB,EACrB,mBAAmB,EACnB,WAAW,EACX,eAAe,EACf,WAAW,EACX,sBAAsB,EACtB,aAAa,EACb,cAAc,EACd,gBAAgB,EAChB,qBAAqB,EACrB,eAAe,EAChB,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,sBAAsB,EAAE,MAAM,kBAAkB,CAAC;AAE1D;;GAEG;AACH,MAAM,CAAC,OAAO,OAAO,iBAAkB,SAAQ,YAAY;IACzD,qBAAqB,CAAC,MAAM,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC;IACrD,iBAAiB,CAAC,QAAQ,EAAE,OAAO,CAAC,SAAS,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC;IAC9D,gCAAgC,IAAI,OAAO,CAAC,kBAAkB,CAAC;IAC/D,mCAAmC,IAAI,OAAO,CAAC,kBAAkB,CAAC;IAClE,4BAA4B,IAAI,OAAO,CAAC,kBAAkB,CAAC;IAE3D,QAAQ,CAAC,WAAW,EAAE,OAAO,WAAW,CAAC;IACzC,QAAQ,CAAC,aAAa,EAAE,OAAO,aAAa,CAAC;IAC7C,QAAQ,CAAC,aAAa,EAAE,OAAO,aAAa,CAAC;CAC9C;AAED,MAAM,CAAC,OAAO,OAAO,WAAY,SAAQ,YAAY,CAAC,WAAW,CAAC;IAChE;;;OAGG;gBACS,MAAM,EAAE,WAAW,EAAE,cAAc,EAAE,MAAM,EAAE,sBAAsB,EAAE,OAAO;IAExF;;OAEG;IACH,EAAE,EAAE,MAAM,CAAC;IAEX;;OAEG;IACH,OAAO,EAAE,OAAO,CAAC;IAEjB;;OAEG;IACH,KAAK,EAAE,OAAO,CAAC;IAEf;;OAEG;IACH,IAAI,EAAE,OAAO,CAAC;IAEd;;OAEG;IACH,MAAM,EAAE,OAAO,CAAC;IAEhB;;OAEG;IACH,QAAQ,EAAE,OAAO,CAAC;IAElB;;OAEG;IACH,wBAAwB,EAAE,OAAO,CAAC;IAElC;;OAEG;IACH,WAAW,EAAE,OAAO,CAAC;IAErB;;OAEG;IACH,WAAW,EAAE,MAAM,CAAC;IAEpB;;OAEG;IACH,QAAQ,EAAE,MAAM,CAAC;IAEjB;;;;;;;;;;;;;;;;;;;;;;;OAuBG;IACH,MAAM,EAAE,MAAM,CAAC;IAEf;;;;;;;;;;;;;;;;;;;;;;;;;;OA0BG;IACH,YAAY,EAAE,MAAM,CAAC;IAErB;;OAEG;IACH,kBAAkB,EAAE,OAAO,CAAC;IAE5B;;;OAGG;IACH,aAAa,EAAE,WAAW,CAAC;IAE3B;;OAEG;IACH,IAAI,IAAI,IAAI;IAEZ;;OAEG;IACH,KAAK,IAAI,IAAI;IAEb;;OAEG;IACH,OAAO,CAAC,MAAM,EAAE,WAAW,GAAG,IAAI;IAElC;;;;;OAKG;IACH,MAAM,CACJ,OAAO,EAAE,MAAM,EACf,qBAAqB,CAAC,EAAE,MAAM,EAC9B,oBAAoB,CAAC,EAAE,MAAM,GAC5B,OAAO,CAAC,IAAI,CAAC;IAEhB;;;;;OAKG;IACH,eAAe,CAAC,IAAI,EAAE,MAAM,EAAE,sBAAsB,CAAC,EAAE,sBAAsB,GAAG,IAAI;IAEpF;;;OAGG;IACH,uBAAuB,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI;IAE/C;;;;;;;;;;OAUG;IACH,sBAAsB,CACpB,MAAM,EAAE,OAAO,EACf,QAAQ,CAAC,EAAE,aAAa,EACxB,OAAO,CAAC,EAAE,sBAAsB,GAC/B,IAAI;IAEP;;;;OAIG;IACH,wBAAwB,CAAC,QAAQ,EAAE,aAAa,GAAG,IAAI;IAEvD;;;OAGG;IACH,uBAAuB,IAAI,IAAI;IAE/B;;OAEG;IACH,MAAM,IAAI,IAAI;CACf;AAED;;;;;;GAMG;AACH,MAAM,MAAM,WAAW,GAAG;IACxB,+GAA+G;IAC/G,QAAQ,EAAE,kBAAkB,EAAE,CAAC;IAC/B,mFAAmF;IACnF,SAAS,EAAE,MAAM,CAAC;CACnB,CAAC;AAEF;;;;;GAKG;AACH,MAAM,MAAM,kBAAkB,GAAG;IAC/B,kEAAkE;IAClE,MAAM,EAAE,MAAM,EAAE,CAAC;CAClB,CAAC;AAEF;;;;;GAKG;AACH,MAAM,MAAM,WAAW,GAAG;IACxB,gFAAgF;IAChF,oBAAoB,CAAC,MAAM,EAAE,WAAW,GAAG,IAAI,CAAC;IAChD,6EAA6E;IAC7E,iBAAiB,CAAC,IAAI,EAAE,WAAW,GAAG,IAAI,CAAC;CAC5C,CAAC;AAEF,MAAM,CAAC,OAAO,OAAO,aAAc,SAAQ,YAAY,CAAC,eAAe,CAAC;IACtE;;;OAGG;gBACS,OAAO,EAAE,OAAO,CAAC,gBAAgB,CAAC;IAE9C;;OAEG;IACH,EAAE,EAAE,MAAM,CAAC;IAEX;;OAEG;IACH,WAAW,EAAE,MAAM,CAAC;IAEpB;;OAEG;IACH,WAAW,EAAE,OAAO,CAAC;IAErB;;OAEG;IACH,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;IAEnB;;;OAGG;IACH,MAAM,CAAC,OAAO,CAAC,EAAE,qBAAqB,GAAG,IAAI;IAE7C;;OAEG;IACH,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAErB;;OAEG;IACH,KAAK,IAAI,IAAI;IAEb;;;OAGG;IACH,kBAAkB,IAAI,cAAc,EAAE;IAEtC;;;OAGG;IACH,eAAe,IAAI,OAAO,CAAC,cAAc,CAAC;IAE1C;;;;OAIG;IACH,QAAQ,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI;IAEhC;;OAEG;IACH,SAAS,IAAI,aAAa;IAE1B;;;;OAIG;IACH,oBAAoB,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI;IAE3C;;OAEG;IACH,oBAAoB,CAAC,OAAO,CAAC,EAAE,OAAO,CAAC,gBAAgB,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC;IAExE;;;;OAIG;IACH,iBAAiB,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI;CACzC;AAED;;;;;GAKG;AACH,MAAM,MAAM,eAAe,GAAG;IAC5B,oFAAoF;IACpF,qBAAqB,EAAE,CAAC,MAAM,EAAE,eAAe,KAAK,IAAI,CAAC;CAC1D,CAAC;AAEF;;;;;GAKG;AACH,MAAM,MAAM,mBAAmB,GAAG;IAChC,oFAAoF;IACpF,oBAAoB,CAAC,MAAM,EAAE,mBAAmB,GAAG,IAAI,CAAC;IACxD,iEAAiE;IACjE,YAAY,CAAC,IAAI,EAAE;QAAE,aAAa,EAAE,MAAM,CAAC;QAAC,YAAY,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAC;CAC3E,CAAC;AAEF,MAAM,CAAC,OAAO,OAAO,aAAc,SAAQ,YAAY,CAAC,mBAAmB,CAAC;IAC1E;;;OAGG;gBACS,OAAO,EAAE,WAAW,EAAE,EAAE,cAAc,EAAE,MAAM,EAAE,IAAI,EAAE,qBAAqB;IAEvF;;OAEG;IACH,EAAE,EAAE,MAAM,CAAC;IAEX;;OAEG;IACH,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAE9B;;OAEG;IACH,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAE5B;;OAEG;IACH,QAAQ,CAAC,OAAO,EAAE,eAAe,EAAE,CAAC;IAEpC;;OAEG;IACH,OAAO,EAAE,OAAO,CAAC;IAEjB;;OAEG;IACH,KAAK,EAAE,OAAO,CAAC;IAEf;;OAEG;IACH,QAAQ,EAAE,OAAO,CAAC;IAElB;;OAEG;IACH,WAAW,EAAE,OAAO,CAAC;IAErB;;OAEG;IACH,WAAW,EAAE,MAAM,CAAC;IAEpB;;OAEG;IACH,QAAQ,EAAE,MAAM,CAAC;IAEjB;;OAEG;IACH,MAAM,EAAE,MAAM,CAAC;IAEf;;OAEG;IACH,YAAY,EAAE,MAAM,CAAC;IAErB;;OAEG;IACH,IAAI,EAAE,qBAAqB,CAAC;IAE5B;;;OAGG;IACH,aAAa,EAAE,mBAAmB,CAAC;IAEnC;;OAEG;IACH,IAAI,IAAI,IAAI;IAEZ;;OAEG;IACH,KAAK,IAAI,IAAI;IAEb;;;;OAIG;IACH,IAAI,IAAI,IAAI;IAEZ;;;;OAIG;IACH,QAAQ,IAAI,IAAI;IAEhB;;;OAGG;IACH,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI;IAE3B;;;OAGG;IACH,MAAM,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAEtC;;;OAGG;IACH,GAAG,CAAC,MAAM,EAAE,WAAW,GAAG,IAAI;IAE9B;;;;OAIG;IACH,MAAM,CAAC,MAAM,EAAE,WAAW,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI;IAEhD;;;OAGG;IACH,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI;IAE3B;;OAEG;IACH,KAAK,IAAI,IAAI;IAEb;;OAEG;IACH,OAAO,IAAI,IAAI;CAChB"}
\ No newline at end of file
+{"version":3,"file":"AudioModule.types.d.ts","sourceRoot":"","sources":["../src/AudioModule.types.ts"],"names":[],"mappings":"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"}
\ No newline at end of file
diff --git a/packages/expo-audio/build/AudioModule.types.js.map b/packages/expo-audio/build/AudioModule.types.js.map
index 4875f61782a97e..dbbae4592dbbc7 100644
--- a/packages/expo-audio/build/AudioModule.types.js.map
+++ b/packages/expo-audio/build/AudioModule.types.js.map
@@ -1 +1 @@
-{"version":3,"file":"AudioModule.types.js","sourceRoot":"","sources":["../src/AudioModule.types.ts"],"names":[],"mappings":"","sourcesContent":["import { NativeModule, PermissionResponse, SharedObject } from 'expo-modules-core';\n\nimport {\n AudioMetadata,\n AudioMode,\n AudioPlaylistLoopMode,\n AudioPlaylistStatus,\n AudioSource,\n AudioSourceInfo,\n AudioStatus,\n PitchCorrectionQuality,\n RecorderState,\n RecordingInput,\n RecordingOptions,\n RecordingStartOptions,\n RecordingStatus,\n} from './Audio.types';\nimport { AudioLockScreenOptions } from './AudioConstants';\n\n/**\n * @hidden\n */\nexport declare class NativeAudioModule extends NativeModule {\n setIsAudioActiveAsync(active: boolean): Promise;\n setAudioModeAsync(category: Partial): Promise;\n requestRecordingPermissionsAsync(): Promise;\n requestNotificationPermissionsAsync(): Promise;\n getRecordingPermissionsAsync(): Promise;\n\n readonly AudioPlayer: typeof AudioPlayer;\n readonly AudioRecorder: typeof AudioRecorder;\n readonly AudioPlaylist: typeof AudioPlaylist;\n}\n\nexport declare class AudioPlayer extends SharedObject {\n /**\n * Initializes a new audio player instance with the given source.\n * @hidden\n */\n constructor(source: AudioSource, updateInterval: number, keepAudioSessionActive: boolean);\n\n /**\n * Unique identifier for the player object.\n */\n id: string;\n\n /**\n * Boolean value indicating whether the player is currently playing.\n */\n playing: boolean;\n\n /**\n * Boolean value indicating whether the player is currently muted.\n */\n muted: boolean;\n\n /**\n * Boolean value indicating whether the player is currently looping.\n */\n loop: boolean;\n\n /**\n * Boolean value indicating whether the player is currently paused.\n */\n paused: boolean;\n\n /**\n * Boolean value indicating whether the player is finished loading.\n */\n isLoaded: boolean;\n\n /**\n * Boolean value indicating whether audio sampling is supported on the platform.\n */\n isAudioSamplingSupported: boolean;\n\n /**\n * Boolean value indicating whether the player is buffering.\n */\n isBuffering: boolean;\n\n /**\n * The current position through the audio item in seconds.\n */\n currentTime: number;\n\n /**\n * The total duration of the audio in seconds.\n */\n duration: number;\n\n /**\n * The current volume of the audio.\n *\n * **Range:** `0.0` to `1.0`. For example, `0.0` is completely silent (0%), `0.5` is half volume (50%), and `1.0` is full volume (100%).\n *\n *\n * @example\n * ```tsx\n * import { useAudioPlayer } from 'expo-audio';\n *\n * export default function App() {\n * const player = useAudioPlayer(source);\n *\n * // Mute the audio\n * player.volume = 0.0;\n *\n * // Set volume to 50%\n * player.volume = 0.5;\n *\n * // Set to full volume\n * player.volume = 1.0;\n * }\n * ```\n */\n volume: number;\n\n /**\n * The current playback rate of the audio. It accepts different values depending on the platform:\n * - **Android**: `0.1` to `2.0`\n * - **iOS**: `0.0` to `2.0`\n * - **Web**: Follows browser implementation\n *\n * @example\n * ```tsx\n * import { useAudioPlayer } from 'expo-audio';\n *\n * export default function App() {\n * const player = useAudioPlayer(source);\n *\n * // Normal playback speed\n * player.playbackRate = 1.0;\n *\n * // Slow motion (half speed)\n * player.playbackRate = 0.5;\n *\n * // Fast playback (1.5x speed)\n * player.playbackRate = 1.5;\n *\n * // Maximum speed on mobile\n * player.playbackRate = 2.0;\n * }\n * ```\n */\n playbackRate: number;\n\n /**\n * A boolean describing if we are correcting the pitch for a changed rate.\n */\n shouldCorrectPitch: boolean;\n\n /**\n * The current status of the audio player.\n * @hidden\n */\n currentStatus: AudioStatus;\n\n /**\n * Start playing audio.\n */\n play(): void;\n\n /**\n * Pauses the player.\n */\n pause(): void;\n\n /**\n * Replaces the current audio source with a new one.\n */\n replace(source: AudioSource): void;\n\n /**\n * Seeks the playback by the given number of seconds.\n * @param seconds The number of seconds to seek by.\n * @param toleranceMillisBefore The tolerance allowed before the requested seek time, in milliseconds. iOS only.\n * @param toleranceMillisAfter The tolerance allowed after the requested seek time, in milliseconds. iOS only.\n */\n seekTo(\n seconds: number,\n toleranceMillisBefore?: number,\n toleranceMillisAfter?: number\n ): Promise;\n\n /**\n * Sets the current playback rate of the audio.\n *\n * @param rate The playback rate of the audio. See [`playbackRate`](#playbackrate) property for detailed range information.\n * @param pitchCorrectionQuality The quality of the pitch correction.\n */\n setPlaybackRate(rate: number, pitchCorrectionQuality?: PitchCorrectionQuality): void;\n\n /**\n *\n * @hidden\n */\n setAudioSamplingEnabled(enabled: boolean): void;\n\n /**\n * Sets or removes this audio player as the active player for lock screen controls.\n * Only one player can control the lock screen at a time.\n *\n * > **Note:** For lock screen controls to work correctly, [`interruptionMode`](#interruptionmode) must be set to `doNotMix` using [`setAudioModeAsync`](#audiosetaudiomodeasyncmode).\n * > Without this, the OS might not associate lock screen controls with your player.\n *\n * @param active Whether this player should be active for lock screen controls.\n * @param metadata Optional metadata to display on the lock screen (title, artist, album, artwork).\n * @param options Optional configuration to configure the lock screen controls.\n */\n setActiveForLockScreen(\n active: boolean,\n metadata?: AudioMetadata,\n options?: AudioLockScreenOptions\n ): void;\n\n /**\n * Updates the metadata displayed on the lock screen for this player.\n * This method only has an effect if this player is currently active for lock screen controls.\n * @param metadata The metadata to display (title, artist, album, artwork).\n */\n updateLockScreenMetadata(metadata: AudioMetadata): void;\n\n /**\n * Removes this player from lock screen controls if it's currently active.\n * This will clear the lock screen's now playing info.\n */\n clearLockScreenControls(): void;\n\n /**\n * Remove the player from memory to free up resources.\n */\n remove(): void;\n}\n\n/**\n * Represents a single audio sample containing waveform data from all audio channels.\n *\n * Audio samples are provided in real-time when audio sampling is enabled on an `AudioPlayer`.\n * Each sample contains the raw PCM audio data for all channels (mono has 1 channel, stereo has 2).\n * This data can be used for audio visualization, analysis, or processing.\n */\nexport type AudioSample = {\n /** Array of audio channels, each containing PCM frame data. Stereo audio will have 2 channels (left/right). */\n channels: AudioSampleChannel[];\n /** Timestamp of this sample relative to the audio track's timeline, in seconds. */\n timestamp: number;\n};\n\n/**\n * Represents audio data for a single channel (for example, left or right in stereo audio).\n *\n * Contains the raw PCM (Pulse Code Modulation) audio frames for this channel.\n * Frame values are normalized between -1.0 and 1.0, where 0 represents silence.\n */\nexport type AudioSampleChannel = {\n /** Array of PCM audio frame values, each between -1.0 and 1.0. */\n frames: number[];\n};\n\n/**\n * Event types that an `AudioPlayer` can emit.\n *\n * These events allow you to listen for changes in playback state and receive real-time audio data.\n * Use `player.addListener()` to subscribe to these events.\n */\nexport type AudioEvents = {\n /** Fired when the player's status changes (play/pause/seek/load and so on.). */\n playbackStatusUpdate(status: AudioStatus): void;\n /** Fired when audio sampling is enabled and new sample data is available. */\n audioSampleUpdate(data: AudioSample): void;\n};\n\nexport declare class AudioRecorder extends SharedObject {\n /**\n * Initializes a new audio recorder instance with the given source.\n * @hidden\n */\n constructor(options: Partial);\n\n /**\n * Unique identifier for the recorder object.\n */\n id: string;\n\n /**\n * The current length of the recording, in seconds.\n */\n currentTime: number;\n\n /**\n * Boolean value indicating whether the recording is in progress.\n */\n isRecording: boolean;\n\n /**\n * The uri of the recording.\n */\n uri: string | null;\n\n /**\n * Starts the recording.\n * @param options Optional recording configuration options.\n */\n record(options?: RecordingStartOptions): void;\n\n /**\n * Stop the recording.\n */\n stop(): Promise;\n\n /**\n * Pause the recording.\n */\n pause(): void;\n\n /**\n * Returns a list of available recording inputs. This method can only be called if the `Recording` has been prepared.\n * @return A `Promise` that is fulfilled with an array of `RecordingInput` objects.\n */\n getAvailableInputs(): RecordingInput[];\n\n /**\n * Returns the currently-selected recording input. This method can only be called if the `Recording` has been prepared.\n * @return A `Promise` that is fulfilled with a `RecordingInput` object.\n */\n getCurrentInput(): Promise;\n\n /**\n * Sets the current recording input.\n * @param inputUid The uid of a `RecordingInput`.\n * @return A `Promise` that is resolved if successful or rejected if not.\n */\n setInput(inputUid: string): void;\n\n /**\n * Status of the current recording.\n */\n getStatus(): RecorderState;\n\n /**\n * Starts the recording at the given time.\n * @param seconds The time in seconds to start recording at.\n * @deprecated Use `record({ atTime: seconds })` instead.\n */\n startRecordingAtTime(seconds: number): void;\n\n /**\n * Prepares the recording for recording.\n */\n prepareToRecordAsync(options?: Partial): Promise;\n\n /**\n * Stops the recording once the specified time has elapsed.\n * @param seconds The time in seconds to stop recording at.\n * @deprecated Use `record({ forDuration: seconds })` instead.\n */\n recordForDuration(seconds: number): void;\n}\n\n/**\n * Event types that an `AudioRecorder` can emit.\n *\n * These events are used internally by `expo-audio` hooks to provide real-time status updates.\n * Use `useAudioRecorderState()` or the `statusListener` parameter in `useAudioRecorder()` instead of subscribing directly.\n */\nexport type RecordingEvents = {\n /** Fired when the recorder's status changes (start/stop/pause/error, and so on). */\n recordingStatusUpdate: (status: RecordingStatus) => void;\n};\n\n/**\n * Event types that an `AudioPlaylist` can emit.\n *\n * These events allow you to listen for changes in playlist playback state.\n * Use `playlist.addListener()` to subscribe to these events.\n */\nexport type AudioPlaylistEvents = {\n /** Fired when the playlist's status changes (play/pause/seek/load/track change). */\n playlistStatusUpdate(status: AudioPlaylistStatus): void;\n /** Fired when the current track changes (next/previous/skip). */\n trackChanged(data: { previousIndex: number; currentIndex: number }): void;\n};\n\nexport declare class AudioPlaylist extends SharedObject {\n /**\n * Initializes a new audio playlist instance.\n * @hidden\n */\n constructor(sources: AudioSource[], updateInterval: number, loop: AudioPlaylistLoopMode);\n\n /**\n * Unique identifier for the playlist instance.\n */\n id: string;\n\n /**\n * Index of the currently playing track in the playlist.\n */\n readonly currentIndex: number;\n\n /**\n * Total number of tracks in the playlist.\n */\n readonly trackCount: number;\n\n /**\n * The audio sources currently in the playlist.\n */\n readonly sources: AudioSourceInfo[];\n\n /**\n * Boolean value indicating whether the playlist is currently playing.\n */\n playing: boolean;\n\n /**\n * Boolean value indicating whether the playlist is currently muted.\n */\n muted: boolean;\n\n /**\n * Boolean value indicating whether the current track has finished loading.\n */\n isLoaded: boolean;\n\n /**\n * Boolean value indicating whether the playlist is buffering.\n */\n isBuffering: boolean;\n\n /**\n * Current playback position in seconds.\n */\n currentTime: number;\n\n /**\n * Duration of the current track in seconds.\n */\n duration: number;\n\n /**\n * Current volume (0.0 to 1.0).\n */\n volume: number;\n\n /**\n * Current playback rate (1.0 = normal speed).\n */\n playbackRate: number;\n\n /**\n * Current loop mode.\n */\n loop: AudioPlaylistLoopMode;\n\n /**\n * The current status of the audio playlist.\n * @hidden\n */\n currentStatus: AudioPlaylistStatus;\n\n /**\n * Start playing the current track in the playlist.\n */\n play(): void;\n\n /**\n * Pause playback.\n */\n pause(): void;\n\n /**\n * Skip to the next track in the playlist.\n * If at the end of the playlist and loop mode is 'all', wraps to the first track.\n * If loop mode is 'none' and at the end, does nothing.\n */\n next(): void;\n\n /**\n * Skip to the previous track in the playlist.\n * If at the beginning of the playlist and loop mode is 'all', wraps to the last track.\n * If loop mode is 'none' and at the beginning, does nothing.\n */\n previous(): void;\n\n /**\n * Skip to a specific track in the playlist by index.\n * @param index The index of the track to skip to.\n */\n skipTo(index: number): void;\n\n /**\n * Seeks the playback to a specific position in seconds.\n * @param seconds The position to seek to.\n */\n seekTo(seconds: number): Promise;\n\n /**\n * Add a track to the end of the playlist.\n * @param source The audio source to add.\n */\n add(source: AudioSource): void;\n\n /**\n * Insert a track at a specific position in the playlist.\n * @param source The audio source to insert.\n * @param index The position to insert at.\n */\n insert(source: AudioSource, index: number): void;\n\n /**\n * Remove a track from the playlist by index.\n * @param index The index of the track to remove.\n */\n remove(index: number): void;\n\n /**\n * Clear all tracks from the playlist.\n */\n clear(): void;\n\n /**\n * Destroy the playlist and free up resources.\n */\n destroy(): void;\n}\n"]}
\ No newline at end of file
+{"version":3,"file":"AudioModule.types.js","sourceRoot":"","sources":["../src/AudioModule.types.ts"],"names":[],"mappings":"","sourcesContent":["import { NativeModule, PermissionResponse, SharedObject } from 'expo-modules-core';\n\nimport {\n AudioMetadata,\n AudioMode,\n AudioPlaylistLoopMode,\n AudioPlaylistStatus,\n AudioSource,\n AudioSourceInfo,\n AudioStatus,\n PitchCorrectionQuality,\n RecorderState,\n RecordingInput,\n RecordingOptions,\n RecordingStartOptions,\n RecordingStatus,\n} from './Audio.types';\nimport { AudioLockScreenOptions } from './AudioConstants';\n\n/**\n * @hidden\n */\nexport declare class NativeAudioModule extends NativeModule {\n setIsAudioActiveAsync(active: boolean): Promise;\n setAudioModeAsync(category: Partial): Promise;\n requestRecordingPermissionsAsync(): Promise;\n requestNotificationPermissionsAsync(): Promise;\n getRecordingPermissionsAsync(): Promise;\n preload(source: AudioSource, preferredForwardBufferDuration: number): Promise;\n clearPreloadedSource(source: AudioSource): Promise;\n clearAllPreloadedSources(): Promise;\n getPreloadedSources(): Promise;\n\n readonly AudioPlayer: typeof AudioPlayer;\n readonly AudioRecorder: typeof AudioRecorder;\n readonly AudioPlaylist: typeof AudioPlaylist;\n}\n\nexport declare class AudioPlayer extends SharedObject {\n /**\n * Initializes a new audio player instance with the given source.\n * @hidden\n */\n constructor(\n source: AudioSource,\n updateInterval: number,\n keepAudioSessionActive: boolean,\n preferredForwardBufferDuration: number\n );\n\n /**\n * Unique identifier for the player object.\n */\n id: string;\n\n /**\n * Boolean value indicating whether the player is currently playing.\n */\n playing: boolean;\n\n /**\n * Boolean value indicating whether the player is currently muted.\n */\n muted: boolean;\n\n /**\n * Boolean value indicating whether the player is currently looping.\n */\n loop: boolean;\n\n /**\n * Boolean value indicating whether the player is currently paused.\n */\n paused: boolean;\n\n /**\n * Boolean value indicating whether the player is finished loading.\n */\n isLoaded: boolean;\n\n /**\n * Boolean value indicating whether audio sampling is supported on the platform.\n */\n isAudioSamplingSupported: boolean;\n\n /**\n * Boolean value indicating whether the player is buffering.\n */\n isBuffering: boolean;\n\n /**\n * The current position through the audio item in seconds.\n */\n currentTime: number;\n\n /**\n * The total duration of the audio in seconds.\n */\n duration: number;\n\n /**\n * The current volume of the audio.\n *\n * **Range:** `0.0` to `1.0`. For example, `0.0` is completely silent (0%), `0.5` is half volume (50%), and `1.0` is full volume (100%).\n *\n *\n * @example\n * ```tsx\n * import { useAudioPlayer } from 'expo-audio';\n *\n * export default function App() {\n * const player = useAudioPlayer(source);\n *\n * // Mute the audio\n * player.volume = 0.0;\n *\n * // Set volume to 50%\n * player.volume = 0.5;\n *\n * // Set to full volume\n * player.volume = 1.0;\n * }\n * ```\n */\n volume: number;\n\n /**\n * The current playback rate of the audio. It accepts different values depending on the platform:\n * - **Android**: `0.1` to `2.0`\n * - **iOS**: `0.0` to `2.0`\n * - **Web**: Follows browser implementation\n *\n * @example\n * ```tsx\n * import { useAudioPlayer } from 'expo-audio';\n *\n * export default function App() {\n * const player = useAudioPlayer(source);\n *\n * // Normal playback speed\n * player.playbackRate = 1.0;\n *\n * // Slow motion (half speed)\n * player.playbackRate = 0.5;\n *\n * // Fast playback (1.5x speed)\n * player.playbackRate = 1.5;\n *\n * // Maximum speed on mobile\n * player.playbackRate = 2.0;\n * }\n * ```\n */\n playbackRate: number;\n\n /**\n * A boolean describing if we are correcting the pitch for a changed rate.\n */\n shouldCorrectPitch: boolean;\n\n /**\n * The current status of the audio player.\n * @hidden\n */\n currentStatus: AudioStatus;\n\n /**\n * Start playing audio.\n */\n play(): void;\n\n /**\n * Pauses the player.\n */\n pause(): void;\n\n /**\n * Replaces the current audio source with a new one.\n */\n replace(source: AudioSource): void;\n\n /**\n * Seeks the playback by the given number of seconds.\n * @param seconds The number of seconds to seek by.\n * @param toleranceMillisBefore The tolerance allowed before the requested seek time, in milliseconds. iOS only.\n * @param toleranceMillisAfter The tolerance allowed after the requested seek time, in milliseconds. iOS only.\n */\n seekTo(\n seconds: number,\n toleranceMillisBefore?: number,\n toleranceMillisAfter?: number\n ): Promise;\n\n /**\n * Sets the current playback rate of the audio.\n *\n * @param rate The playback rate of the audio. See [`playbackRate`](#playbackrate) property for detailed range information.\n * @param pitchCorrectionQuality The quality of the pitch correction.\n */\n setPlaybackRate(rate: number, pitchCorrectionQuality?: PitchCorrectionQuality): void;\n\n /**\n *\n * @hidden\n */\n setAudioSamplingEnabled(enabled: boolean): void;\n\n /**\n * Sets or removes this audio player as the active player for lock screen controls.\n * Only one player can control the lock screen at a time.\n *\n * > **Note:** For lock screen controls to work correctly, [`interruptionMode`](#interruptionmode) must be set to `doNotMix` using [`setAudioModeAsync`](#audiosetaudiomodeasyncmode).\n * > Without this, the OS might not associate lock screen controls with your player.\n *\n * @param active Whether this player should be active for lock screen controls.\n * @param metadata Optional metadata to display on the lock screen (title, artist, album, artwork).\n * @param options Optional configuration to configure the lock screen controls.\n */\n setActiveForLockScreen(\n active: boolean,\n metadata?: AudioMetadata,\n options?: AudioLockScreenOptions\n ): void;\n\n /**\n * Updates the metadata displayed on the lock screen for this player.\n * This method only has an effect if this player is currently active for lock screen controls.\n * @param metadata The metadata to display (title, artist, album, artwork).\n */\n updateLockScreenMetadata(metadata: AudioMetadata): void;\n\n /**\n * Removes this player from lock screen controls if it's currently active.\n * This will clear the lock screen's now playing info.\n */\n clearLockScreenControls(): void;\n\n /**\n * Remove the player from memory to free up resources.\n */\n remove(): void;\n}\n\n/**\n * Represents a single audio sample containing waveform data from all audio channels.\n *\n * Audio samples are provided in real-time when audio sampling is enabled on an `AudioPlayer`.\n * Each sample contains the raw PCM audio data for all channels (mono has 1 channel, stereo has 2).\n * This data can be used for audio visualization, analysis, or processing.\n */\nexport type AudioSample = {\n /** Array of audio channels, each containing PCM frame data. Stereo audio will have 2 channels (left/right). */\n channels: AudioSampleChannel[];\n /** Timestamp of this sample relative to the audio track's timeline, in seconds. */\n timestamp: number;\n};\n\n/**\n * Represents audio data for a single channel (for example, left or right in stereo audio).\n *\n * Contains the raw PCM (Pulse Code Modulation) audio frames for this channel.\n * Frame values are normalized between -1.0 and 1.0, where 0 represents silence.\n */\nexport type AudioSampleChannel = {\n /** Array of PCM audio frame values, each between -1.0 and 1.0. */\n frames: number[];\n};\n\n/**\n * Event types that an `AudioPlayer` can emit.\n *\n * These events allow you to listen for changes in playback state and receive real-time audio data.\n * Use `player.addListener()` to subscribe to these events.\n */\nexport type AudioEvents = {\n /** Fired when the player's status changes (play/pause/seek/load and so on.). */\n playbackStatusUpdate(status: AudioStatus): void;\n /** Fired when audio sampling is enabled and new sample data is available. */\n audioSampleUpdate(data: AudioSample): void;\n};\n\nexport declare class AudioRecorder extends SharedObject {\n /**\n * Initializes a new audio recorder instance with the given source.\n * @hidden\n */\n constructor(options: Partial);\n\n /**\n * Unique identifier for the recorder object.\n */\n id: string;\n\n /**\n * The current length of the recording, in seconds.\n */\n currentTime: number;\n\n /**\n * Boolean value indicating whether the recording is in progress.\n */\n isRecording: boolean;\n\n /**\n * The uri of the recording.\n */\n uri: string | null;\n\n /**\n * Starts the recording.\n * @param options Optional recording configuration options.\n */\n record(options?: RecordingStartOptions): void;\n\n /**\n * Stop the recording.\n */\n stop(): Promise;\n\n /**\n * Pause the recording.\n */\n pause(): void;\n\n /**\n * Returns a list of available recording inputs. This method can only be called if the `Recording` has been prepared.\n * @return A `Promise` that is fulfilled with an array of `RecordingInput` objects.\n */\n getAvailableInputs(): RecordingInput[];\n\n /**\n * Returns the currently-selected recording input. This method can only be called if the `Recording` has been prepared.\n * @return A `Promise` that is fulfilled with a `RecordingInput` object.\n */\n getCurrentInput(): Promise;\n\n /**\n * Sets the current recording input.\n * @param inputUid The uid of a `RecordingInput`.\n * @return A `Promise` that is resolved if successful or rejected if not.\n */\n setInput(inputUid: string): void;\n\n /**\n * Status of the current recording.\n */\n getStatus(): RecorderState;\n\n /**\n * Starts the recording at the given time.\n * @param seconds The time in seconds to start recording at.\n * @deprecated Use `record({ atTime: seconds })` instead.\n */\n startRecordingAtTime(seconds: number): void;\n\n /**\n * Prepares the recording for recording.\n */\n prepareToRecordAsync(options?: Partial): Promise;\n\n /**\n * Stops the recording once the specified time has elapsed.\n * @param seconds The time in seconds to stop recording at.\n * @deprecated Use `record({ forDuration: seconds })` instead.\n */\n recordForDuration(seconds: number): void;\n}\n\n/**\n * Event types that an `AudioRecorder` can emit.\n *\n * These events are used internally by `expo-audio` hooks to provide real-time status updates.\n * Use `useAudioRecorderState()` or the `statusListener` parameter in `useAudioRecorder()` instead of subscribing directly.\n */\nexport type RecordingEvents = {\n /** Fired when the recorder's status changes (start/stop/pause/error, and so on). */\n recordingStatusUpdate: (status: RecordingStatus) => void;\n};\n\n/**\n * Event types that an `AudioPlaylist` can emit.\n *\n * These events allow you to listen for changes in playlist playback state.\n * Use `playlist.addListener()` to subscribe to these events.\n */\nexport type AudioPlaylistEvents = {\n /** Fired when the playlist's status changes (play/pause/seek/load/track change). */\n playlistStatusUpdate(status: AudioPlaylistStatus): void;\n /** Fired when the current track changes (next/previous/skip). */\n trackChanged(data: { previousIndex: number; currentIndex: number }): void;\n};\n\nexport declare class AudioPlaylist extends SharedObject {\n /**\n * Initializes a new audio playlist instance.\n * @hidden\n */\n constructor(sources: AudioSource[], updateInterval: number, loop: AudioPlaylistLoopMode);\n\n /**\n * Unique identifier for the playlist instance.\n */\n id: string;\n\n /**\n * Index of the currently playing track in the playlist.\n */\n readonly currentIndex: number;\n\n /**\n * Total number of tracks in the playlist.\n */\n readonly trackCount: number;\n\n /**\n * The audio sources currently in the playlist.\n */\n readonly sources: AudioSourceInfo[];\n\n /**\n * Boolean value indicating whether the playlist is currently playing.\n */\n playing: boolean;\n\n /**\n * Boolean value indicating whether the playlist is currently muted.\n */\n muted: boolean;\n\n /**\n * Boolean value indicating whether the current track has finished loading.\n */\n isLoaded: boolean;\n\n /**\n * Boolean value indicating whether the playlist is buffering.\n */\n isBuffering: boolean;\n\n /**\n * Current playback position in seconds.\n */\n currentTime: number;\n\n /**\n * Duration of the current track in seconds.\n */\n duration: number;\n\n /**\n * Current volume (0.0 to 1.0).\n */\n volume: number;\n\n /**\n * Current playback rate (1.0 = normal speed).\n */\n playbackRate: number;\n\n /**\n * Current loop mode.\n */\n loop: AudioPlaylistLoopMode;\n\n /**\n * The current status of the audio playlist.\n * @hidden\n */\n currentStatus: AudioPlaylistStatus;\n\n /**\n * Start playing the current track in the playlist.\n */\n play(): void;\n\n /**\n * Pause playback.\n */\n pause(): void;\n\n /**\n * Skip to the next track in the playlist.\n * If at the end of the playlist and loop mode is 'all', wraps to the first track.\n * If loop mode is 'none' and at the end, does nothing.\n */\n next(): void;\n\n /**\n * Skip to the previous track in the playlist.\n * If at the beginning of the playlist and loop mode is 'all', wraps to the last track.\n * If loop mode is 'none' and at the beginning, does nothing.\n */\n previous(): void;\n\n /**\n * Skip to a specific track in the playlist by index.\n * @param index The index of the track to skip to.\n */\n skipTo(index: number): void;\n\n /**\n * Seeks the playback to a specific position in seconds.\n * @param seconds The position to seek to.\n */\n seekTo(seconds: number): Promise;\n\n /**\n * Add a track to the end of the playlist.\n * @param source The audio source to add.\n */\n add(source: AudioSource): void;\n\n /**\n * Insert a track at a specific position in the playlist.\n * @param source The audio source to insert.\n * @param index The position to insert at.\n */\n insert(source: AudioSource, index: number): void;\n\n /**\n * Remove a track from the playlist by index.\n * @param index The index of the track to remove.\n */\n remove(index: number): void;\n\n /**\n * Clear all tracks from the playlist.\n */\n clear(): void;\n\n /**\n * Destroy the playlist and free up resources.\n */\n destroy(): void;\n}\n"]}
\ No newline at end of file
diff --git a/packages/expo-audio/build/AudioModule.web.d.ts b/packages/expo-audio/build/AudioModule.web.d.ts
index d8d389c2734b38..bb5ddc5a537e65 100644
--- a/packages/expo-audio/build/AudioModule.web.d.ts
+++ b/packages/expo-audio/build/AudioModule.web.d.ts
@@ -1,11 +1,16 @@
import { PermissionResponse } from 'expo-modules-core';
-import { AudioMode } from './Audio.types';
+import { AudioMode, AudioSource } from './Audio.types';
export { AudioPlayerWeb } from './AudioPlayer.web';
export { AudioPlaylistWeb } from './AudioPlaylist.web';
export { AudioRecorderWeb } from './AudioRecorder.web';
export declare let isAudioActive: boolean;
export declare function setAudioModeAsync(mode: AudioMode): Promise;
export declare function setIsAudioActiveAsync(active: boolean): Promise;
+export declare function preloadAsync(source: AudioSource): Promise;
+export declare function preload(source: AudioSource): void;
+export declare function clearPreloadedSource(source: AudioSource): void;
+export declare function clearAllPreloadedSources(): void;
+export declare function getPreloadedSources(): string[];
export declare function getRecordingPermissionsAsync(): Promise;
export declare function requestRecordingPermissionsAsync(): Promise;
//# sourceMappingURL=AudioModule.web.d.ts.map
\ No newline at end of file
diff --git a/packages/expo-audio/build/AudioModule.web.d.ts.map b/packages/expo-audio/build/AudioModule.web.d.ts.map
index 852b6a560cfc16..1c6ac09f0be1cf 100644
--- a/packages/expo-audio/build/AudioModule.web.d.ts.map
+++ b/packages/expo-audio/build/AudioModule.web.d.ts.map
@@ -1 +1 @@
-{"version":3,"file":"AudioModule.web.d.ts","sourceRoot":"","sources":["../src/AudioModule.web.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAoB,MAAM,mBAAmB,CAAC;AAEzE,OAAO,EAAE,SAAS,EAAE,MAAM,eAAe,CAAC;AAI1C,OAAO,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AACnD,OAAO,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AACvD,OAAO,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AAEvD,eAAO,IAAI,aAAa,SAAO,CAAC;AAuBhC,wBAAsB,iBAAiB,CAAC,IAAI,EAAE,SAAS,iBAAI;AAC3D,wBAAsB,qBAAqB,CAAC,MAAM,EAAE,OAAO,iBAS1D;AAED,wBAAsB,4BAA4B,IAAI,OAAO,CAAC,kBAAkB,CAAC,CAoBhF;AAED,wBAAsB,gCAAgC,IAAI,OAAO,CAAC,kBAAkB,CAAC,CAkBpF"}
\ No newline at end of file
+{"version":3,"file":"AudioModule.web.d.ts","sourceRoot":"","sources":["../src/AudioModule.web.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAoB,MAAM,mBAAmB,CAAC;AAEzE,OAAO,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAIvD,OAAO,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AACnD,OAAO,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AACvD,OAAO,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AAEvD,eAAO,IAAI,aAAa,SAAO,CAAC;AAuBhC,wBAAsB,iBAAiB,CAAC,IAAI,EAAE,SAAS,iBAAI;AAC3D,wBAAsB,qBAAqB,CAAC,MAAM,EAAE,OAAO,iBAS1D;AAED,wBAAsB,YAAY,CAAC,MAAM,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC,CAarE;AAED,wBAAgB,OAAO,CAAC,MAAM,EAAE,WAAW,GAAG,IAAI,CAEjD;AAED,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,WAAW,GAAG,IAAI,CAS9D;AAED,wBAAgB,wBAAwB,IAAI,IAAI,CAK/C;AAED,wBAAgB,mBAAmB,IAAI,MAAM,EAAE,CAE9C;AAED,wBAAsB,4BAA4B,IAAI,OAAO,CAAC,kBAAkB,CAAC,CAoBhF;AAED,wBAAsB,gCAAgC,IAAI,OAAO,CAAC,kBAAkB,CAAC,CAkBpF"}
\ No newline at end of file
diff --git a/packages/expo-audio/build/AudioModule.web.js b/packages/expo-audio/build/AudioModule.web.js
index f85ffcf62f8492..d8252d46f57867 100644
--- a/packages/expo-audio/build/AudioModule.web.js
+++ b/packages/expo-audio/build/AudioModule.web.js
@@ -1,6 +1,6 @@
import { PermissionStatus } from 'expo-modules-core';
import { activePlayers } from './AudioPlayer.web';
-import { getUserMedia } from './AudioUtils.web';
+import { getUserMedia, getSourceUri, preloadCache } from './AudioUtils.web';
export { AudioPlayerWeb } from './AudioPlayer.web';
export { AudioPlaylistWeb } from './AudioPlaylist.web';
export { AudioRecorderWeb } from './AudioRecorder.web';
@@ -35,6 +35,40 @@ export async function setIsAudioActiveAsync(active) {
}
}
}
+export async function preloadAsync(source) {
+ const uri = getSourceUri(source);
+ if (!uri || preloadCache.has(uri))
+ return;
+ const headers = source && typeof source === 'object' && !Array.isArray(source) ? source.headers : undefined;
+ const response = await fetch(uri, headers ? { headers } : undefined);
+ const blob = await response.blob();
+ const blobUrl = URL.createObjectURL(blob);
+ const audio = new Audio(blobUrl);
+ audio.preload = 'auto';
+ preloadCache.set(uri, { blobUrl, audio });
+}
+export function preload(source) {
+ preloadAsync(source).catch(() => { });
+}
+export function clearPreloadedSource(source) {
+ const uri = getSourceUri(source);
+ if (!uri)
+ return;
+ const cached = preloadCache.get(uri);
+ if (cached) {
+ URL.revokeObjectURL(cached.blobUrl);
+ preloadCache.delete(uri);
+ }
+}
+export function clearAllPreloadedSources() {
+ for (const cached of preloadCache.values()) {
+ URL.revokeObjectURL(cached.blobUrl);
+ }
+ preloadCache.clear();
+}
+export function getPreloadedSources() {
+ return Array.from(preloadCache.keys());
+}
export async function getRecordingPermissionsAsync() {
const maybeStatus = await getPermissionWithQueryAsync('microphone');
switch (maybeStatus) {
diff --git a/packages/expo-audio/build/AudioModule.web.js.map b/packages/expo-audio/build/AudioModule.web.js.map
index aa5fe87dc1f4f4..0787e5d4753235 100644
--- a/packages/expo-audio/build/AudioModule.web.js.map
+++ b/packages/expo-audio/build/AudioModule.web.js.map
@@ -1 +1 @@
-{"version":3,"file":"AudioModule.web.js","sourceRoot":"","sources":["../src/AudioModule.web.ts"],"names":[],"mappings":"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","sourcesContent":["import { PermissionResponse, PermissionStatus } from 'expo-modules-core';\n\nimport { AudioMode } from './Audio.types';\nimport { activePlayers } from './AudioPlayer.web';\nimport { getUserMedia } from './AudioUtils.web';\n\nexport { AudioPlayerWeb } from './AudioPlayer.web';\nexport { AudioPlaylistWeb } from './AudioPlaylist.web';\nexport { AudioRecorderWeb } from './AudioRecorder.web';\n\nexport let isAudioActive = true;\n\nasync function getPermissionWithQueryAsync(\n name: PermissionNameWithAdditionalValues\n): Promise {\n if (!navigator || !navigator.permissions || !navigator.permissions.query) return null;\n\n try {\n const { state } = await navigator.permissions.query({ name });\n switch (state) {\n case 'granted':\n return PermissionStatus.GRANTED;\n case 'denied':\n return PermissionStatus.DENIED;\n default:\n return PermissionStatus.UNDETERMINED;\n }\n } catch {\n // Firefox - TypeError: 'microphone' (value of 'name' member of PermissionDescriptor) is not a valid value for enumeration PermissionName.\n return PermissionStatus.UNDETERMINED;\n }\n}\n\nexport async function setAudioModeAsync(mode: AudioMode) {}\nexport async function setIsAudioActiveAsync(active: boolean) {\n isAudioActive = active;\n if (!active) {\n for (const player of activePlayers) {\n if (player.playing) {\n player.pause();\n }\n }\n }\n}\n\nexport async function getRecordingPermissionsAsync(): Promise {\n const maybeStatus = await getPermissionWithQueryAsync('microphone');\n switch (maybeStatus) {\n case PermissionStatus.GRANTED:\n return {\n status: PermissionStatus.GRANTED,\n expires: 'never',\n canAskAgain: true,\n granted: true,\n };\n case PermissionStatus.DENIED:\n return {\n status: PermissionStatus.DENIED,\n expires: 'never',\n canAskAgain: true,\n granted: false,\n };\n default:\n return await requestRecordingPermissionsAsync();\n }\n}\n\nexport async function requestRecordingPermissionsAsync(): Promise {\n try {\n const stream = await getUserMedia({ audio: true });\n stream.getTracks().forEach((track) => track.stop());\n return {\n status: PermissionStatus.GRANTED,\n expires: 'never',\n canAskAgain: true,\n granted: true,\n };\n } catch {\n return {\n status: PermissionStatus.DENIED,\n expires: 'never',\n canAskAgain: true,\n granted: false,\n };\n }\n}\n"]}
\ No newline at end of file
+{"version":3,"file":"AudioModule.web.js","sourceRoot":"","sources":["../src/AudioModule.web.ts"],"names":[],"mappings":"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","sourcesContent":["import { PermissionResponse, PermissionStatus } from 'expo-modules-core';\n\nimport { AudioMode, AudioSource } from './Audio.types';\nimport { activePlayers } from './AudioPlayer.web';\nimport { getUserMedia, getSourceUri, preloadCache } from './AudioUtils.web';\n\nexport { AudioPlayerWeb } from './AudioPlayer.web';\nexport { AudioPlaylistWeb } from './AudioPlaylist.web';\nexport { AudioRecorderWeb } from './AudioRecorder.web';\n\nexport let isAudioActive = true;\n\nasync function getPermissionWithQueryAsync(\n name: PermissionNameWithAdditionalValues\n): Promise {\n if (!navigator || !navigator.permissions || !navigator.permissions.query) return null;\n\n try {\n const { state } = await navigator.permissions.query({ name });\n switch (state) {\n case 'granted':\n return PermissionStatus.GRANTED;\n case 'denied':\n return PermissionStatus.DENIED;\n default:\n return PermissionStatus.UNDETERMINED;\n }\n } catch {\n // Firefox - TypeError: 'microphone' (value of 'name' member of PermissionDescriptor) is not a valid value for enumeration PermissionName.\n return PermissionStatus.UNDETERMINED;\n }\n}\n\nexport async function setAudioModeAsync(mode: AudioMode) {}\nexport async function setIsAudioActiveAsync(active: boolean) {\n isAudioActive = active;\n if (!active) {\n for (const player of activePlayers) {\n if (player.playing) {\n player.pause();\n }\n }\n }\n}\n\nexport async function preloadAsync(source: AudioSource): Promise {\n const uri = getSourceUri(source);\n if (!uri || preloadCache.has(uri)) return;\n\n const headers =\n source && typeof source === 'object' && !Array.isArray(source) ? source.headers : undefined;\n\n const response = await fetch(uri, headers ? { headers } : undefined);\n const blob = await response.blob();\n const blobUrl = URL.createObjectURL(blob);\n const audio = new Audio(blobUrl);\n audio.preload = 'auto';\n preloadCache.set(uri, { blobUrl, audio });\n}\n\nexport function preload(source: AudioSource): void {\n preloadAsync(source).catch(() => {});\n}\n\nexport function clearPreloadedSource(source: AudioSource): void {\n const uri = getSourceUri(source);\n if (!uri) return;\n\n const cached = preloadCache.get(uri);\n if (cached) {\n URL.revokeObjectURL(cached.blobUrl);\n preloadCache.delete(uri);\n }\n}\n\nexport function clearAllPreloadedSources(): void {\n for (const cached of preloadCache.values()) {\n URL.revokeObjectURL(cached.blobUrl);\n }\n preloadCache.clear();\n}\n\nexport function getPreloadedSources(): string[] {\n return Array.from(preloadCache.keys());\n}\n\nexport async function getRecordingPermissionsAsync(): Promise {\n const maybeStatus = await getPermissionWithQueryAsync('microphone');\n switch (maybeStatus) {\n case PermissionStatus.GRANTED:\n return {\n status: PermissionStatus.GRANTED,\n expires: 'never',\n canAskAgain: true,\n granted: true,\n };\n case PermissionStatus.DENIED:\n return {\n status: PermissionStatus.DENIED,\n expires: 'never',\n canAskAgain: true,\n granted: false,\n };\n default:\n return await requestRecordingPermissionsAsync();\n }\n}\n\nexport async function requestRecordingPermissionsAsync(): Promise {\n try {\n const stream = await getUserMedia({ audio: true });\n stream.getTracks().forEach((track) => track.stop());\n return {\n status: PermissionStatus.GRANTED,\n expires: 'never',\n canAskAgain: true,\n granted: true,\n };\n } catch {\n return {\n status: PermissionStatus.DENIED,\n expires: 'never',\n canAskAgain: true,\n granted: false,\n };\n }\n}\n"]}
\ No newline at end of file
diff --git a/packages/expo-audio/build/AudioPlayer.web.d.ts b/packages/expo-audio/build/AudioPlayer.web.d.ts
index 07f545fb53c03d..15a7eab95b8193 100644
--- a/packages/expo-audio/build/AudioPlayer.web.d.ts
+++ b/packages/expo-audio/build/AudioPlayer.web.d.ts
@@ -42,6 +42,7 @@ export declare class AudioPlayerWeb extends globalThis.expo.SharedObject();\n\nexport class AudioPlayerWeb\n extends globalThis.expo.SharedObject\n implements AudioPlayer\n{\n constructor(source: AudioSource, options: AudioPlayerOptions = {}) {\n super();\n const { updateInterval = 500, crossOrigin } = options;\n this.src = source;\n this.interval = Math.max(updateInterval, 1);\n this.crossOrigin = crossOrigin;\n this.media = this._createMediaElement();\n activePlayers.add(this);\n }\n\n id: string = nextId();\n isAudioSamplingSupported = false;\n isBuffering = false;\n shouldCorrectPitch = false;\n\n private src: AudioSource = null;\n private media: HTMLAudioElement;\n private interval = 500;\n private isPlaying = false;\n private loaded = false;\n private crossOrigin?: 'anonymous' | 'use-credentials';\n private analyser: AnalyserNode | null = null;\n private sourceNode: MediaElementAudioSourceNode | null = null;\n private samplingFrameId: number | null = null;\n private samplingEnabled = false;\n private samplingBuffer: Float32Array | null = null;\n\n get playing(): boolean {\n return this.isPlaying;\n }\n\n get muted(): boolean {\n return this.media.muted;\n }\n\n set muted(value: boolean) {\n this.media.muted = value;\n }\n\n get loop(): boolean {\n return this.media.loop;\n }\n\n set loop(value: boolean) {\n this.media.loop = value;\n }\n\n get duration(): number {\n return this.media.duration;\n }\n\n get currentTime(): number {\n return this.media.currentTime;\n }\n\n get paused(): boolean {\n return this.media.paused;\n }\n\n get isLoaded(): boolean {\n return this.loaded;\n }\n\n get playbackRate(): number {\n return this.media.playbackRate;\n }\n\n set playbackRate(value: number) {\n this.media.playbackRate = value;\n }\n\n get volume(): number {\n return this.media.volume;\n }\n\n set volume(value: number) {\n this.media.volume = value;\n }\n\n get currentStatus(): AudioStatus {\n return getStatusFromMedia(this.media, this.id);\n }\n\n play(): void {\n if (!isAudioActive) {\n return;\n }\n this.media.play();\n this.isPlaying = true;\n this.startSampling();\n }\n\n pause(): void {\n this.media.pause();\n this.isPlaying = false;\n this.stopSampling();\n }\n\n replace(source: AudioSource): void {\n const wasPlaying = this.isPlaying;\n const wasSampling = this.samplingEnabled;\n const mediaSessionState = mediaSessionController.getActiveState(this);\n\n // we need to remove the current media element and create a new one\n this.remove();\n\n this.src = source;\n this.isPlaying = false;\n this.loaded = false;\n this.media = this._createMediaElement();\n\n if (wasSampling) {\n this.setAudioSamplingEnabled(true);\n }\n\n if (mediaSessionState) {\n mediaSessionController.setActivePlayer(\n this,\n mediaSessionState.metadata ?? undefined,\n mediaSessionState.options ?? undefined\n );\n }\n\n // Resume playback if it was playing before\n if (wasPlaying) {\n this.play();\n }\n }\n\n async seekTo(\n seconds: number,\n toleranceMillisBefore?: number,\n toleranceMillisAfter?: number\n ): Promise {\n this.media.currentTime = seconds;\n }\n\n setAudioSamplingEnabled(enabled: boolean): void {\n if (enabled) {\n if (!this.media.crossOrigin && this._isCrossOrigin()) {\n this.isAudioSamplingSupported = false;\n return;\n }\n\n if (this.analyser) {\n return;\n }\n\n const ctx = getAudioContext();\n if (ctx.state === 'suspended') {\n ctx.resume();\n }\n\n if (!this.sourceNode) {\n this.sourceNode = ctx.createMediaElementSource(this.media);\n }\n\n this.analyser = ctx.createAnalyser();\n this.analyser.fftSize = 2048;\n\n this.sourceNode.disconnect();\n this.sourceNode.connect(this.analyser);\n this.analyser.connect(ctx.destination);\n\n this.samplingBuffer = new Float32Array(this.analyser.frequencyBinCount);\n this.samplingEnabled = true;\n\n if (this.isPlaying) {\n this.startSampling();\n }\n this.isAudioSamplingSupported = true;\n } else {\n this.stopSampling();\n\n if (this.analyser) {\n this.analyser.disconnect();\n this.analyser = null;\n }\n\n if (this.sourceNode) {\n const ctx = getAudioContext();\n this.sourceNode.disconnect();\n this.sourceNode.connect(ctx.destination);\n }\n\n this.samplingBuffer = null;\n this.samplingEnabled = false;\n }\n }\n\n private startSampling(): void {\n if (!this.samplingEnabled || !this.analyser || !this.samplingBuffer) {\n return;\n }\n const sampleLoop = () => {\n if (!this.analyser || !this.samplingBuffer) {\n return;\n }\n this.analyser.getFloatTimeDomainData(this.samplingBuffer);\n this.emit(AUDIO_SAMPLE_UPDATE, {\n channels: [{ frames: Array.from(this.samplingBuffer) }],\n timestamp: this.media.currentTime,\n });\n this.samplingFrameId = requestAnimationFrame(sampleLoop);\n };\n this.samplingFrameId = requestAnimationFrame(sampleLoop);\n }\n\n private stopSampling(): void {\n if (this.samplingFrameId != null) {\n cancelAnimationFrame(this.samplingFrameId);\n this.samplingFrameId = null;\n }\n }\n\n setPlaybackRate(second: number, pitchCorrectionQuality?: PitchCorrectionQuality): void {\n this.media.playbackRate = second;\n this.shouldCorrectPitch = pitchCorrectionQuality === 'high';\n this.media.preservesPitch = this.shouldCorrectPitch;\n mediaSessionController.updatePositionState(this);\n }\n\n remove(): void {\n mediaSessionController.clear(this);\n this.stopSampling();\n\n if (this.analyser) {\n this.analyser.disconnect();\n this.analyser = null;\n }\n\n if (this.sourceNode) {\n this.sourceNode.disconnect();\n this.sourceNode = null;\n }\n\n this.samplingEnabled = false;\n this.media.pause();\n // Clear event handlers to prevent memory leaks\n this.media.ontimeupdate = null;\n this.media.onplay = null;\n this.media.onpause = null;\n this.media.onseeked = null;\n this.media.onended = null;\n this.media.onloadeddata = null;\n this.media.onerror = null;\n this.media.removeAttribute('src');\n this.media.load();\n activePlayers.delete(this);\n }\n\n setActiveForLockScreen(\n active: boolean,\n metadata?: AudioMetadata,\n options?: AudioLockScreenOptions\n ): void {\n if (active) {\n mediaSessionController.setActivePlayer(this, metadata, options);\n } else {\n mediaSessionController.clear(this);\n }\n }\n\n updateLockScreenMetadata(metadata: AudioMetadata): void {\n mediaSessionController.updateMetadata(this, metadata);\n }\n\n clearLockScreenControls(): void {\n mediaSessionController.clear(this);\n }\n\n _isCrossOrigin(): boolean {\n try {\n return new URL(this.media.src).origin !== window.location.origin;\n } catch {\n return false;\n }\n }\n\n _createMediaElement(): HTMLAudioElement {\n const newSource = getSourceUri(this.src);\n const media = new Audio(newSource);\n if (this.crossOrigin !== undefined) {\n media.crossOrigin = this.crossOrigin;\n }\n\n let lastEmitTime = 0;\n const intervalSec = this.interval / 1000;\n\n // Throttled status updates based on interval\n media.ontimeupdate = () => {\n const now = media.currentTime;\n // Handle backwards time (loop/seek)\n if (now < lastEmitTime) {\n lastEmitTime = now;\n }\n if (now - lastEmitTime >= intervalSec) {\n lastEmitTime = now;\n this.emit(PLAYBACK_STATUS_UPDATE, getStatusFromMedia(media, this.id));\n mediaSessionController.updatePositionState(this);\n }\n };\n\n media.onplay = () => {\n this.isPlaying = true;\n lastEmitTime = media.currentTime;\n this.emit(PLAYBACK_STATUS_UPDATE, {\n ...getStatusFromMedia(media, this.id),\n playing: this.isPlaying,\n });\n mediaSessionController.updatePlaybackState(this);\n mediaSessionController.updatePositionState(this);\n };\n\n media.onpause = () => {\n this.isPlaying = false;\n lastEmitTime = media.currentTime;\n this.emit(PLAYBACK_STATUS_UPDATE, {\n ...getStatusFromMedia(media, this.id),\n playing: this.isPlaying,\n });\n mediaSessionController.updatePlaybackState(this);\n mediaSessionController.updatePositionState(this);\n };\n\n media.onseeked = () => {\n lastEmitTime = media.currentTime;\n this.emit(PLAYBACK_STATUS_UPDATE, getStatusFromMedia(media, this.id));\n mediaSessionController.updatePositionState(this);\n };\n\n media.onended = () => {\n lastEmitTime = 0;\n mediaSessionController.updatePlaybackState(this);\n };\n\n media.onloadeddata = () => {\n this.loaded = true;\n lastEmitTime = media.currentTime;\n this.emit(PLAYBACK_STATUS_UPDATE, {\n ...getStatusFromMedia(media, this.id),\n isLoaded: this.loaded,\n });\n mediaSessionController.updatePositionState(this);\n };\n\n media.onerror = () => {\n this.loaded = false;\n this.isPlaying = false;\n this.emit(PLAYBACK_STATUS_UPDATE, {\n ...getStatusFromMedia(media, this.id),\n isLoaded: false,\n playing: false,\n });\n };\n\n return media;\n }\n}\n"]}
\ No newline at end of file
+{"version":3,"file":"AudioPlayer.web.js","sourceRoot":"","sources":["../src/AudioPlayer.web.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n AudioMetadata,\n AudioPlayerOptions,\n AudioSource,\n AudioStatus,\n PitchCorrectionQuality,\n} from './Audio.types';\nimport { AudioLockScreenOptions } from './AudioConstants';\nimport { AUDIO_SAMPLE_UPDATE, PLAYBACK_STATUS_UPDATE } from './AudioEventKeys';\nimport { AudioPlayer, AudioEvents } from './AudioModule.types';\nimport { isAudioActive } from './AudioModule.web';\nimport {\n getAudioContext,\n getSourceUri,\n getStatusFromMedia,\n nextId,\n preloadCache,\n safeDuration,\n} from './AudioUtils.web';\nimport { mediaSessionController } from './MediaSessionController.web';\n\nexport const activePlayers = new Set();\n\nexport class AudioPlayerWeb\n extends globalThis.expo.SharedObject\n implements AudioPlayer\n{\n constructor(source: AudioSource, options: AudioPlayerOptions = {}) {\n super();\n const { updateInterval = 500, crossOrigin } = options;\n this.src = source;\n this.interval = Math.max(updateInterval, 1);\n this.crossOrigin = crossOrigin;\n this.media = this._createMediaElement();\n activePlayers.add(this);\n }\n\n id: string = nextId();\n isAudioSamplingSupported = false;\n isBuffering = false;\n shouldCorrectPitch = false;\n\n private src: AudioSource = null;\n private media: HTMLAudioElement;\n private interval = 500;\n private isPlaying = false;\n private loaded = false;\n private crossOrigin?: 'anonymous' | 'use-credentials';\n private analyser: AnalyserNode | null = null;\n private sourceNode: MediaElementAudioSourceNode | null = null;\n private samplingFrameId: number | null = null;\n private samplingEnabled = false;\n private samplingBuffer: Float32Array | null = null;\n\n get playing(): boolean {\n return this.isPlaying;\n }\n\n get muted(): boolean {\n return this.media.muted;\n }\n\n set muted(value: boolean) {\n this.media.muted = value;\n }\n\n get loop(): boolean {\n return this.media.loop;\n }\n\n set loop(value: boolean) {\n this.media.loop = value;\n }\n\n get duration(): number {\n return safeDuration(this.media.duration);\n }\n\n get currentTime(): number {\n return this.media.currentTime;\n }\n\n get paused(): boolean {\n return this.media.paused;\n }\n\n get isLoaded(): boolean {\n return this.loaded;\n }\n\n get playbackRate(): number {\n return this.media.playbackRate;\n }\n\n set playbackRate(value: number) {\n this.media.playbackRate = value;\n }\n\n get volume(): number {\n return this.media.volume;\n }\n\n set volume(value: number) {\n this.media.volume = value;\n }\n\n get currentStatus(): AudioStatus {\n return getStatusFromMedia(this.media, this.id);\n }\n\n play(): void {\n if (!isAudioActive) {\n return;\n }\n this.media.play();\n this.isPlaying = true;\n this.startSampling();\n }\n\n pause(): void {\n this.media.pause();\n this.isPlaying = false;\n this.stopSampling();\n }\n\n replace(source: AudioSource): void {\n const wasPlaying = this.isPlaying;\n const wasSampling = this.samplingEnabled;\n const mediaSessionState = mediaSessionController.getActiveState(this);\n\n // we need to remove the current media element and create a new one\n this.remove();\n\n this.src = source;\n this.isPlaying = false;\n this.loaded = false;\n this.media = this._createMediaElement();\n\n if (wasSampling) {\n this.setAudioSamplingEnabled(true);\n }\n\n if (mediaSessionState) {\n mediaSessionController.setActivePlayer(\n this,\n mediaSessionState.metadata ?? undefined,\n mediaSessionState.options ?? undefined\n );\n }\n\n // Resume playback if it was playing before\n if (wasPlaying) {\n this.play();\n }\n }\n\n async seekTo(\n seconds: number,\n toleranceMillisBefore?: number,\n toleranceMillisAfter?: number\n ): Promise {\n this.media.currentTime = seconds;\n }\n\n setAudioSamplingEnabled(enabled: boolean): void {\n if (enabled) {\n if (!this.media.crossOrigin && this._isCrossOrigin()) {\n this.isAudioSamplingSupported = false;\n return;\n }\n\n if (this.analyser) {\n return;\n }\n\n const ctx = getAudioContext();\n if (ctx.state === 'suspended') {\n ctx.resume();\n }\n\n if (!this.sourceNode) {\n this.sourceNode = ctx.createMediaElementSource(this.media);\n }\n\n this.analyser = ctx.createAnalyser();\n this.analyser.fftSize = 2048;\n\n this.sourceNode.disconnect();\n this.sourceNode.connect(this.analyser);\n this.analyser.connect(ctx.destination);\n\n this.samplingBuffer = new Float32Array(this.analyser.frequencyBinCount);\n this.samplingEnabled = true;\n\n if (this.isPlaying) {\n this.startSampling();\n }\n this.isAudioSamplingSupported = true;\n } else {\n this.stopSampling();\n\n if (this.analyser) {\n this.analyser.disconnect();\n this.analyser = null;\n }\n\n if (this.sourceNode) {\n const ctx = getAudioContext();\n this.sourceNode.disconnect();\n this.sourceNode.connect(ctx.destination);\n }\n\n this.samplingBuffer = null;\n this.samplingEnabled = false;\n }\n }\n\n private startSampling(): void {\n if (!this.samplingEnabled || !this.analyser || !this.samplingBuffer) {\n return;\n }\n const sampleLoop = () => {\n if (!this.analyser || !this.samplingBuffer) {\n return;\n }\n this.analyser.getFloatTimeDomainData(this.samplingBuffer);\n this.emit(AUDIO_SAMPLE_UPDATE, {\n channels: [{ frames: Array.from(this.samplingBuffer) }],\n timestamp: this.media.currentTime,\n });\n this.samplingFrameId = requestAnimationFrame(sampleLoop);\n };\n this.samplingFrameId = requestAnimationFrame(sampleLoop);\n }\n\n private stopSampling(): void {\n if (this.samplingFrameId != null) {\n cancelAnimationFrame(this.samplingFrameId);\n this.samplingFrameId = null;\n }\n }\n\n setPlaybackRate(second: number, pitchCorrectionQuality?: PitchCorrectionQuality): void {\n this.media.playbackRate = second;\n this.shouldCorrectPitch = pitchCorrectionQuality === 'high';\n this.media.preservesPitch = this.shouldCorrectPitch;\n mediaSessionController.updatePositionState(this);\n }\n\n remove(): void {\n mediaSessionController.clear(this);\n this.stopSampling();\n\n if (this.analyser) {\n this.analyser.disconnect();\n this.analyser = null;\n }\n\n if (this.sourceNode) {\n this.sourceNode.disconnect();\n this.sourceNode = null;\n }\n\n this.samplingEnabled = false;\n this.media.pause();\n // Clear event handlers to prevent memory leaks\n this.media.ontimeupdate = null;\n this.media.onplay = null;\n this.media.onpause = null;\n this.media.onseeked = null;\n this.media.onended = null;\n this.media.onloadeddata = null;\n this.media.onerror = null;\n this.media.removeAttribute('src');\n this.media.load();\n activePlayers.delete(this);\n }\n\n release(): void {\n this.remove();\n }\n\n setActiveForLockScreen(\n active: boolean,\n metadata?: AudioMetadata,\n options?: AudioLockScreenOptions\n ): void {\n if (active) {\n mediaSessionController.setActivePlayer(this, metadata, options);\n } else {\n mediaSessionController.clear(this);\n }\n }\n\n updateLockScreenMetadata(metadata: AudioMetadata): void {\n mediaSessionController.updateMetadata(this, metadata);\n }\n\n clearLockScreenControls(): void {\n mediaSessionController.clear(this);\n }\n\n _isCrossOrigin(): boolean {\n try {\n return new URL(this.media.src).origin !== window.location.origin;\n } catch {\n return false;\n }\n }\n\n _createMediaElement(): HTMLAudioElement {\n const newSource = getSourceUri(this.src);\n const cachedUri =\n newSource && preloadCache.has(newSource) ? preloadCache.get(newSource)!.blobUrl : newSource;\n const media = new Audio(cachedUri);\n if (this.crossOrigin !== undefined) {\n media.crossOrigin = this.crossOrigin;\n }\n\n let lastEmitTime = 0;\n const intervalSec = this.interval / 1000;\n\n // Throttled status updates based on interval\n media.ontimeupdate = () => {\n const now = media.currentTime;\n // Handle backwards time (loop/seek)\n if (now < lastEmitTime) {\n lastEmitTime = now;\n }\n if (now - lastEmitTime >= intervalSec) {\n lastEmitTime = now;\n this.emit(PLAYBACK_STATUS_UPDATE, getStatusFromMedia(media, this.id));\n mediaSessionController.updatePositionState(this);\n }\n };\n\n media.onplay = () => {\n this.isPlaying = true;\n lastEmitTime = media.currentTime;\n this.emit(PLAYBACK_STATUS_UPDATE, {\n ...getStatusFromMedia(media, this.id),\n playing: this.isPlaying,\n });\n mediaSessionController.updatePlaybackState(this);\n mediaSessionController.updatePositionState(this);\n };\n\n media.onpause = () => {\n this.isPlaying = false;\n lastEmitTime = media.currentTime;\n this.emit(PLAYBACK_STATUS_UPDATE, {\n ...getStatusFromMedia(media, this.id),\n playing: this.isPlaying,\n });\n mediaSessionController.updatePlaybackState(this);\n mediaSessionController.updatePositionState(this);\n };\n\n media.onseeked = () => {\n lastEmitTime = media.currentTime;\n this.emit(PLAYBACK_STATUS_UPDATE, getStatusFromMedia(media, this.id));\n mediaSessionController.updatePositionState(this);\n };\n\n media.onended = () => {\n lastEmitTime = 0;\n mediaSessionController.updatePlaybackState(this);\n };\n\n media.onloadeddata = () => {\n this.loaded = true;\n lastEmitTime = media.currentTime;\n this.emit(PLAYBACK_STATUS_UPDATE, {\n ...getStatusFromMedia(media, this.id),\n isLoaded: this.loaded,\n });\n mediaSessionController.updatePositionState(this);\n };\n\n media.onerror = () => {\n this.loaded = false;\n this.isPlaying = false;\n this.emit(PLAYBACK_STATUS_UPDATE, {\n ...getStatusFromMedia(media, this.id),\n isLoaded: false,\n playing: false,\n });\n };\n\n return media;\n }\n}\n"]}
\ No newline at end of file
diff --git a/packages/expo-audio/build/AudioUtils.web.d.ts b/packages/expo-audio/build/AudioUtils.web.d.ts
index 7132f7b7d75c49..43d793821bdf94 100644
--- a/packages/expo-audio/build/AudioUtils.web.d.ts
+++ b/packages/expo-audio/build/AudioUtils.web.d.ts
@@ -2,6 +2,11 @@ import { AudioSource, AudioStatus } from './Audio.types';
export declare const nextId: () => string;
export declare function getAudioContext(): AudioContext;
export declare function getUserMedia(constraints: MediaStreamConstraints): Promise;
+export declare function safeDuration(duration: number): number;
export declare function getStatusFromMedia(media: HTMLMediaElement, id: string): AudioStatus;
+export declare const preloadCache: Map;
export declare function getSourceUri(source: AudioSource): string | undefined;
//# sourceMappingURL=AudioUtils.web.d.ts.map
\ No newline at end of file
diff --git a/packages/expo-audio/build/AudioUtils.web.d.ts.map b/packages/expo-audio/build/AudioUtils.web.d.ts.map
index 536d6bfdaa1674..2bf3218a2df367 100644
--- a/packages/expo-audio/build/AudioUtils.web.d.ts.map
+++ b/packages/expo-audio/build/AudioUtils.web.d.ts.map
@@ -1 +1 @@
-{"version":3,"file":"AudioUtils.web.d.ts","sourceRoot":"","sources":["../src/AudioUtils.web.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAEzD,eAAO,MAAM,MAAM,cAGf,CAAC;AAIL,wBAAgB,eAAe,IAAI,YAAY,CAK9C;AAED,wBAAgB,YAAY,CAAC,WAAW,EAAE,sBAAsB,GAAG,OAAO,CAAC,WAAW,CAAC,CA4BtF;AAED,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,gBAAgB,EAAE,EAAE,EAAE,MAAM,GAAG,WAAW,CA0BnF;AAED,wBAAgB,YAAY,CAAC,MAAM,EAAE,WAAW,GAAG,MAAM,GAAG,SAAS,CAcpE"}
\ No newline at end of file
+{"version":3,"file":"AudioUtils.web.d.ts","sourceRoot":"","sources":["../src/AudioUtils.web.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAEzD,eAAO,MAAM,MAAM,cAGf,CAAC;AAIL,wBAAgB,eAAe,IAAI,YAAY,CAK9C;AAED,wBAAgB,YAAY,CAAC,WAAW,EAAE,sBAAsB,GAAG,OAAO,CAAC,WAAW,CAAC,CA4BtF;AAED,wBAAgB,YAAY,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAErD;AAED,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,gBAAgB,EAAE,EAAE,EAAE,MAAM,GAAG,WAAW,CA0BnF;AAGD,eAAO,MAAM,YAAY;aAA8B,MAAM;WAAS,gBAAgB;EAAK,CAAC;AAE5F,wBAAgB,YAAY,CAAC,MAAM,EAAE,WAAW,GAAG,MAAM,GAAG,SAAS,CAcpE"}
\ No newline at end of file
diff --git a/packages/expo-audio/build/AudioUtils.web.js b/packages/expo-audio/build/AudioUtils.web.js
index c9969fe02299fc..d685b4b4397d09 100644
--- a/packages/expo-audio/build/AudioUtils.web.js
+++ b/packages/expo-audio/build/AudioUtils.web.js
@@ -36,6 +36,9 @@ export function getUserMedia(constraints) {
getUserMedia.call(navigator, constraints, resolve, reject);
});
}
+export function safeDuration(duration) {
+ return isNaN(duration) || !isFinite(duration) ? 0 : duration;
+}
export function getStatusFromMedia(media, id) {
const isPlaying = !!(media.currentTime > 0 &&
!media.paused &&
@@ -44,7 +47,7 @@ export function getStatusFromMedia(media, id) {
const status = {
id,
isLoaded: true,
- duration: media.duration,
+ duration: safeDuration(media.duration),
currentTime: media.currentTime,
playbackState: '',
timeControlStatus: isPlaying ? 'playing' : 'paused',
@@ -59,6 +62,8 @@ export function getStatusFromMedia(media, id) {
};
return status;
}
+// Preload cache: maps original source URIs to pre-fetched blob URLs
+export const preloadCache = new Map();
export function getSourceUri(source) {
if (typeof source === 'string') {
return source;
diff --git a/packages/expo-audio/build/AudioUtils.web.js.map b/packages/expo-audio/build/AudioUtils.web.js.map
index a430d633a567ba..97e721e75ac5f8 100644
--- a/packages/expo-audio/build/AudioUtils.web.js.map
+++ b/packages/expo-audio/build/AudioUtils.web.js.map
@@ -1 +1 @@
-{"version":3,"file":"AudioUtils.web.js","sourceRoot":"","sources":["../src/AudioUtils.web.ts"],"names":[],"mappings":"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","sourcesContent":["import { Asset } from 'expo-asset';\n\nimport { AudioSource, AudioStatus } from './Audio.types';\n\nexport const nextId = (() => {\n let id = 0;\n return () => String(id++);\n})();\n\nlet audioContext: AudioContext | null = null;\n\nexport function getAudioContext(): AudioContext {\n if (!audioContext) {\n audioContext = new AudioContext();\n }\n return audioContext;\n}\n\nexport function getUserMedia(constraints: MediaStreamConstraints): Promise {\n if (navigator.mediaDevices && navigator.mediaDevices.getUserMedia) {\n return navigator.mediaDevices.getUserMedia(constraints);\n }\n\n // Some browsers partially implement mediaDevices. We can't just assign an object\n // with getUserMedia as it would overwrite existing properties.\n // Here, we will just add the getUserMedia property if it's missing.\n\n // First get ahold of the legacy getUserMedia, if present\n const getUserMedia =\n // TODO: this method is deprecated, migrate to https://developer.mozilla.org/en-US/docs/Web/API/MediaDevices/getUserMedia\n navigator.getUserMedia ||\n navigator.webkitGetUserMedia ||\n navigator.mozGetUserMedia ||\n function () {\n const error: any = new Error('Permission unimplemented');\n error.code = 0;\n error.name = 'NotAllowedError';\n throw error;\n };\n\n return new Promise((resolve, reject) => {\n // TODO(@kitten): The types indicates that this is incorrect.\n // Please check whether this is correct!\n // @ts-expect-error: The `successCallback` doesn't match a `resolve` function\n getUserMedia.call(navigator, constraints, resolve, reject);\n });\n}\n\nexport function getStatusFromMedia(media: HTMLMediaElement, id: string): AudioStatus {\n const isPlaying = !!(\n media.currentTime > 0 &&\n !media.paused &&\n !media.ended &&\n media.readyState > 2\n );\n\n const status: AudioStatus = {\n id,\n isLoaded: true,\n duration: media.duration,\n currentTime: media.currentTime,\n playbackState: '',\n timeControlStatus: isPlaying ? 'playing' : 'paused',\n reasonForWaitingToPlay: '',\n playing: isPlaying,\n didJustFinish: media.ended,\n isBuffering: false,\n playbackRate: media.playbackRate,\n shouldCorrectPitch: true,\n mute: media.muted,\n loop: media.loop,\n };\n\n return status;\n}\n\nexport function getSourceUri(source: AudioSource): string | undefined {\n if (typeof source === 'string') {\n return source;\n }\n if (typeof source === 'number') {\n const asset = Asset.fromModule(source);\n return asset.uri;\n }\n if (typeof source?.assetId === 'number' && !source?.uri) {\n const asset = Asset.fromModule(source.assetId);\n return asset.uri;\n }\n\n return source?.uri ?? undefined;\n}\n"]}
\ No newline at end of file
+{"version":3,"file":"AudioUtils.web.js","sourceRoot":"","sources":["../src/AudioUtils.web.ts"],"names":[],"mappings":"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","sourcesContent":["import { Asset } from 'expo-asset';\n\nimport { AudioSource, AudioStatus } from './Audio.types';\n\nexport const nextId = (() => {\n let id = 0;\n return () => String(id++);\n})();\n\nlet audioContext: AudioContext | null = null;\n\nexport function getAudioContext(): AudioContext {\n if (!audioContext) {\n audioContext = new AudioContext();\n }\n return audioContext;\n}\n\nexport function getUserMedia(constraints: MediaStreamConstraints): Promise {\n if (navigator.mediaDevices && navigator.mediaDevices.getUserMedia) {\n return navigator.mediaDevices.getUserMedia(constraints);\n }\n\n // Some browsers partially implement mediaDevices. We can't just assign an object\n // with getUserMedia as it would overwrite existing properties.\n // Here, we will just add the getUserMedia property if it's missing.\n\n // First get ahold of the legacy getUserMedia, if present\n const getUserMedia =\n // TODO: this method is deprecated, migrate to https://developer.mozilla.org/en-US/docs/Web/API/MediaDevices/getUserMedia\n navigator.getUserMedia ||\n navigator.webkitGetUserMedia ||\n navigator.mozGetUserMedia ||\n function () {\n const error: any = new Error('Permission unimplemented');\n error.code = 0;\n error.name = 'NotAllowedError';\n throw error;\n };\n\n return new Promise((resolve, reject) => {\n // TODO(@kitten): The types indicates that this is incorrect.\n // Please check whether this is correct!\n // @ts-expect-error: The `successCallback` doesn't match a `resolve` function\n getUserMedia.call(navigator, constraints, resolve, reject);\n });\n}\n\nexport function safeDuration(duration: number): number {\n return isNaN(duration) || !isFinite(duration) ? 0 : duration;\n}\n\nexport function getStatusFromMedia(media: HTMLMediaElement, id: string): AudioStatus {\n const isPlaying = !!(\n media.currentTime > 0 &&\n !media.paused &&\n !media.ended &&\n media.readyState > 2\n );\n\n const status: AudioStatus = {\n id,\n isLoaded: true,\n duration: safeDuration(media.duration),\n currentTime: media.currentTime,\n playbackState: '',\n timeControlStatus: isPlaying ? 'playing' : 'paused',\n reasonForWaitingToPlay: '',\n playing: isPlaying,\n didJustFinish: media.ended,\n isBuffering: false,\n playbackRate: media.playbackRate,\n shouldCorrectPitch: true,\n mute: media.muted,\n loop: media.loop,\n };\n\n return status;\n}\n\n// Preload cache: maps original source URIs to pre-fetched blob URLs\nexport const preloadCache = new Map();\n\nexport function getSourceUri(source: AudioSource): string | undefined {\n if (typeof source === 'string') {\n return source;\n }\n if (typeof source === 'number') {\n const asset = Asset.fromModule(source);\n return asset.uri;\n }\n if (typeof source?.assetId === 'number' && !source?.uri) {\n const asset = Asset.fromModule(source.assetId);\n return asset.uri;\n }\n\n return source?.uri ?? undefined;\n}\n"]}
\ No newline at end of file
diff --git a/packages/expo-audio/build/ExpoAudio.d.ts b/packages/expo-audio/build/ExpoAudio.d.ts
index 849386236e33f8..c2f72a224f76e1 100644
--- a/packages/expo-audio/build/ExpoAudio.d.ts
+++ b/packages/expo-audio/build/ExpoAudio.d.ts
@@ -1,5 +1,5 @@
import { PermissionResponse } from 'expo-modules-core';
-import { AudioMode, AudioPlayerOptions, AudioPlaylistOptions, AudioPlaylistStatus, AudioSource, AudioStatus, RecorderState, RecordingOptions, RecordingStatus } from './Audio.types';
+import { AudioMode, AudioPlayerOptions, AudioPlaylistOptions, AudioPlaylistStatus, AudioSource, AudioStatus, PreloadOptions, RecorderState, RecordingOptions, RecordingStatus } from './Audio.types';
import AudioModule from './AudioModule';
import { AudioPlayer, AudioPlaylist, AudioRecorder, AudioSample } from './AudioModule.types';
/**
@@ -381,5 +381,53 @@ export declare function requestNotificationPermissionsAsync(): Promise;
+/**
+ * Preloads an audio source for near-instant playback later.
+ *
+ * This should be called in module scope, before any React components render.
+ * When the source is later used with `useAudioPlayer()`, `createAudioPlayer()`, or `player.replace()`,
+ * playback begins with minimal delay.
+ *
+ * @param source The audio source to preload. Can be a URL string, a local asset via `require()`, or an audio source object.
+ * @param options Optional configuration for preloading behavior.
+ *
+ * @example
+ * ```tsx
+ * import { preload, useAudioPlayer } from 'expo-audio';
+ *
+ * const track1 = 'https://example.com/track1.mp3';
+ * const track2 = 'https://example.com/track2.mp3';
+ *
+ * // Preload at module scope — starts buffering immediately
+ * preload(track1);
+ * preload(track2, { preferredForwardBufferDuration: 20 });
+ *
+ * export default function App() {
+ * const player = useAudioPlayer(track1);
+ * // Playback starts near-instantly because the source was preloaded
+ * return player.play()} />;
+ * }
+ * ```
+ */
+export declare function preload(source: AudioSource, options?: PreloadOptions): Promise;
+/**
+ * Releases a specific preloaded audio source to free memory.
+ *
+ * @param source The audio source to release. Must match the source previously passed to `preload()`.
+ */
+export declare function clearPreloadedSource(source: AudioSource): Promise;
+/**
+ * Releases all preloaded audio sources to free memory.
+ */
+export declare function clearAllPreloadedSources(): Promise;
+/**
+ * Returns the URIs of all currently preloaded audio sources.
+ *
+ * Sources are removed from this list when consumed by `useAudioPlayer()` or `createAudioPlayer()`,
+ * or when explicitly cleared with `clearPreloadedSource()` / `clearAllPreloadedSources()`.
+ *
+ * @returns An array of URI strings for sources currently in the preload cache.
+ */
+export declare function getPreloadedSources(): Promise;
export { AudioModule };
//# sourceMappingURL=ExpoAudio.d.ts.map
\ No newline at end of file
diff --git a/packages/expo-audio/build/ExpoAudio.d.ts.map b/packages/expo-audio/build/ExpoAudio.d.ts.map
index 049f6c3ac30ac0..2ad00e5b7a806c 100644
--- a/packages/expo-audio/build/ExpoAudio.d.ts.map
+++ b/packages/expo-audio/build/ExpoAudio.d.ts.map
@@ -1 +1 @@
-{"version":3,"file":"ExpoAudio.d.ts","sourceRoot":"","sources":["../src/ExpoAudio.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,kBAAkB,EAA4B,MAAM,mBAAmB,CAAC;AAIjF,OAAO,EACL,SAAS,EACT,kBAAkB,EAClB,oBAAoB,EACpB,mBAAmB,EACnB,WAAW,EACX,WAAW,EAEX,aAAa,EACb,gBAAgB,EAEhB,eAAe,EAChB,MAAM,eAAe,CAAC;AAOvB,OAAO,WAAW,MAAM,eAAe,CAAC;AACxC,OAAO,EAAE,WAAW,EAAE,aAAa,EAAE,aAAa,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAoC7F;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsCG;AACH,wBAAgB,cAAc,CAC5B,MAAM,GAAE,WAAkB,EAC1B,OAAO,GAAE,kBAAuB,GAC/B,WAAW,CAkDb;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,WAAW,GAAG,WAAW,CAGrE;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuCG;AACH,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,WAAW,EAAE,QAAQ,EAAE,CAAC,IAAI,EAAE,WAAW,KAAK,IAAI,QAShG;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AACH,wBAAgB,gBAAgB,CAC9B,OAAO,EAAE,gBAAgB,EACzB,cAAc,CAAC,EAAE,CAAC,MAAM,EAAE,eAAe,KAAK,IAAI,GACjD,aAAa,CAcf;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,wBAAgB,qBAAqB,CAAC,QAAQ,EAAE,aAAa,EAAE,QAAQ,GAAE,MAAY,iBAgCpF;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,GAAE,oBAAyB,GAAG,aAAa,CAWlF;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,wBAAgB,sBAAsB,CAAC,QAAQ,EAAE,aAAa,GAAG,mBAAmB,CAGnF;AAED;;;;;;GAMG;AACH,wBAAgB,mBAAmB,CAAC,OAAO,GAAE,oBAAyB,GAAG,aAAa,CAIrF;AAED;;;;;;;GAOG;AACH,wBAAgB,iBAAiB,CAC/B,MAAM,GAAE,WAAW,GAAG,MAAM,GAAG,MAAM,GAAG,IAAW,EACnD,OAAO,GAAE,kBAAuB,GAC/B,WAAW,CAsBb;AAED;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,wBAAsB,qBAAqB,CAAC,MAAM,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC,CAE1E;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,wBAAsB,iBAAiB,CAAC,IAAI,EAAE,OAAO,CAAC,SAAS,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CAW/E;AAED;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,wBAAsB,gCAAgC,IAAI,OAAO,CAAC,kBAAkB,CAAC,CAEpF;AAED;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,wBAAsB,mCAAmC,IAAI,OAAO,CAAC,kBAAkB,CAAC,CAOvF;AAED;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,wBAAsB,4BAA4B,IAAI,OAAO,CAAC,kBAAkB,CAAC,CAEhF;AAED,OAAO,EAAE,WAAW,EAAE,CAAC"}
\ No newline at end of file
+{"version":3,"file":"ExpoAudio.d.ts","sourceRoot":"","sources":["../src/ExpoAudio.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,kBAAkB,EAA4B,MAAM,mBAAmB,CAAC;AAIjF,OAAO,EACL,SAAS,EACT,kBAAkB,EAClB,oBAAoB,EACpB,mBAAmB,EACnB,WAAW,EACX,WAAW,EAEX,cAAc,EACd,aAAa,EACb,gBAAgB,EAEhB,eAAe,EAChB,MAAM,eAAe,CAAC;AAOvB,OAAO,WAAW,MAAM,eAAe,CAAC;AACxC,OAAO,EAAE,WAAW,EAAE,aAAa,EAAE,aAAa,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAoC7F;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsCG;AACH,wBAAgB,cAAc,CAC5B,MAAM,GAAE,WAAkB,EAC1B,OAAO,GAAE,kBAAuB,GAC/B,WAAW,CAkEb;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,WAAW,GAAG,WAAW,CAGrE;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuCG;AACH,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,WAAW,EAAE,QAAQ,EAAE,CAAC,IAAI,EAAE,WAAW,KAAK,IAAI,QAShG;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AACH,wBAAgB,gBAAgB,CAC9B,OAAO,EAAE,gBAAgB,EACzB,cAAc,CAAC,EAAE,CAAC,MAAM,EAAE,eAAe,KAAK,IAAI,GACjD,aAAa,CAcf;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,wBAAgB,qBAAqB,CAAC,QAAQ,EAAE,aAAa,EAAE,QAAQ,GAAE,MAAY,iBAgCpF;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,GAAE,oBAAyB,GAAG,aAAa,CAWlF;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,wBAAgB,sBAAsB,CAAC,QAAQ,EAAE,aAAa,GAAG,mBAAmB,CAGnF;AAED;;;;;;GAMG;AACH,wBAAgB,mBAAmB,CAAC,OAAO,GAAE,oBAAyB,GAAG,aAAa,CAIrF;AAED;;;;;;;GAOG;AACH,wBAAgB,iBAAiB,CAC/B,MAAM,GAAE,WAAW,GAAG,MAAM,GAAG,MAAM,GAAG,IAAW,EACnD,OAAO,GAAE,kBAAuB,GAC/B,WAAW,CAgCb;AAED;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,wBAAsB,qBAAqB,CAAC,MAAM,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC,CAE1E;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,wBAAsB,iBAAiB,CAAC,IAAI,EAAE,OAAO,CAAC,SAAS,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CAW/E;AAED;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,wBAAsB,gCAAgC,IAAI,OAAO,CAAC,kBAAkB,CAAC,CAEpF;AAED;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,wBAAsB,mCAAmC,IAAI,OAAO,CAAC,kBAAkB,CAAC,CAOvF;AAED;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,wBAAsB,4BAA4B,IAAI,OAAO,CAAC,kBAAkB,CAAC,CAEhF;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,wBAAsB,OAAO,CAAC,MAAM,EAAE,WAAW,EAAE,OAAO,GAAE,cAAmB,GAAG,OAAO,CAAC,IAAI,CAAC,CAK9F;AAED;;;;GAIG;AACH,wBAAsB,oBAAoB,CAAC,MAAM,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC,CAI7E;AAED;;GAEG;AACH,wBAAsB,wBAAwB,IAAI,OAAO,CAAC,IAAI,CAAC,CAE9D;AAED;;;;;;;GAOG;AACH,wBAAsB,mBAAmB,IAAI,OAAO,CAAC,MAAM,EAAE,CAAC,CAE7D;AAED,OAAO,EAAE,WAAW,EAAE,CAAC"}
\ No newline at end of file
diff --git a/packages/expo-audio/build/ExpoAudio.js b/packages/expo-audio/build/ExpoAudio.js
index 6b54e9674b8586..7840d5363d0ce9 100644
--- a/packages/expo-audio/build/ExpoAudio.js
+++ b/packages/expo-audio/build/ExpoAudio.js
@@ -72,14 +72,19 @@ if (!Platform.isTV || Platform.OS !== 'ios') {
* ```
*/
export function useAudioPlayer(source = null, options = {}) {
- const { updateInterval = 500, downloadFirst = false, keepAudioSessionActive = false } = options;
+ const { updateInterval = 500, downloadFirst = false, keepAudioSessionActive = false, preferredForwardBufferDuration = 0, } = options;
// If downloadFirst is true, we don't need to resolve the source, because it will be resolved in the useEffect below.
// If downloadFirst is false, we resolve the source here.
// we call .replace() in the useEffect below to replace the source with the downloaded one.
const initialSource = useMemo(() => {
return downloadFirst ? null : resolveSource(source);
}, [JSON.stringify(source), downloadFirst]);
- const player = useReleasingSharedObject(() => new AudioModule.AudioPlayer(initialSource, updateInterval, keepAudioSessionActive), [JSON.stringify(initialSource), updateInterval, keepAudioSessionActive]);
+ const player = useReleasingSharedObject(() => new AudioModule.AudioPlayer(initialSource, updateInterval, keepAudioSessionActive, preferredForwardBufferDuration), [
+ JSON.stringify(initialSource),
+ updateInterval,
+ keepAudioSessionActive,
+ preferredForwardBufferDuration,
+ ]);
// Handle async source resolution for downloadFirst
useEffect(() => {
if (!downloadFirst || source === null) {
@@ -379,9 +384,9 @@ export function createAudioPlaylist(options = {}) {
* @param options Audio player configuration options.
*/
export function createAudioPlayer(source = null, options = {}) {
- const { updateInterval = 500, downloadFirst = false, keepAudioSessionActive = false } = options;
+ const { updateInterval = 500, downloadFirst = false, keepAudioSessionActive = false, preferredForwardBufferDuration = 0, } = options;
const initialSource = downloadFirst ? null : resolveSource(source);
- const player = new AudioModule.AudioPlayer(initialSource, updateInterval, keepAudioSessionActive);
+ const player = new AudioModule.AudioPlayer(initialSource, updateInterval, keepAudioSessionActive, preferredForwardBufferDuration);
if (downloadFirst && source) {
resolveSourceWithDownload(source)
.then((resolved) => {
@@ -549,5 +554,68 @@ export async function requestNotificationPermissionsAsync() {
export async function getRecordingPermissionsAsync() {
return await AudioModule.getRecordingPermissionsAsync();
}
+/**
+ * Preloads an audio source for near-instant playback later.
+ *
+ * This should be called in module scope, before any React components render.
+ * When the source is later used with `useAudioPlayer()`, `createAudioPlayer()`, or `player.replace()`,
+ * playback begins with minimal delay.
+ *
+ * @param source The audio source to preload. Can be a URL string, a local asset via `require()`, or an audio source object.
+ * @param options Optional configuration for preloading behavior.
+ *
+ * @example
+ * ```tsx
+ * import { preload, useAudioPlayer } from 'expo-audio';
+ *
+ * const track1 = 'https://example.com/track1.mp3';
+ * const track2 = 'https://example.com/track2.mp3';
+ *
+ * // Preload at module scope — starts buffering immediately
+ * preload(track1);
+ * preload(track2, { preferredForwardBufferDuration: 20 });
+ *
+ * export default function App() {
+ * const player = useAudioPlayer(track1);
+ * // Playback starts near-instantly because the source was preloaded
+ * return player.play()} />;
+ * }
+ * ```
+ */
+export async function preload(source, options = {}) {
+ const resolved = resolveSource(source);
+ if (!resolved)
+ return;
+ const { preferredForwardBufferDuration = 10 } = options;
+ return AudioModule.preload(resolved, preferredForwardBufferDuration);
+}
+/**
+ * Releases a specific preloaded audio source to free memory.
+ *
+ * @param source The audio source to release. Must match the source previously passed to `preload()`.
+ */
+export async function clearPreloadedSource(source) {
+ const resolved = resolveSource(source);
+ if (!resolved)
+ return;
+ return AudioModule.clearPreloadedSource(resolved);
+}
+/**
+ * Releases all preloaded audio sources to free memory.
+ */
+export async function clearAllPreloadedSources() {
+ return AudioModule.clearAllPreloadedSources();
+}
+/**
+ * Returns the URIs of all currently preloaded audio sources.
+ *
+ * Sources are removed from this list when consumed by `useAudioPlayer()` or `createAudioPlayer()`,
+ * or when explicitly cleared with `clearPreloadedSource()` / `clearAllPreloadedSources()`.
+ *
+ * @returns An array of URI strings for sources currently in the preload cache.
+ */
+export async function getPreloadedSources() {
+ return AudioModule.getPreloadedSources();
+}
export { AudioModule };
//# sourceMappingURL=ExpoAudio.js.map
\ No newline at end of file
diff --git a/packages/expo-audio/build/ExpoAudio.js.map b/packages/expo-audio/build/ExpoAudio.js.map
index ac734ff501febb..907fcc2df17bc0 100644
--- a/packages/expo-audio/build/ExpoAudio.js.map
+++ b/packages/expo-audio/build/ExpoAudio.js.map
@@ -1 +1 @@
-{"version":3,"file":"ExpoAudio.js","sourceRoot":"","sources":["../src/ExpoAudio.ts"],"names":[],"mappings":"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","sourcesContent":["import { useEvent } from 'expo';\nimport { PermissionResponse, useReleasingSharedObject } from 'expo-modules-core';\nimport { useEffect, useState, useMemo } from 'react';\nimport { Platform } from 'react-native';\n\nimport {\n AudioMode,\n AudioPlayerOptions,\n AudioPlaylistOptions,\n AudioPlaylistStatus,\n AudioSource,\n AudioStatus,\n PitchCorrectionQuality,\n RecorderState,\n RecordingOptions,\n RecordingStartOptions,\n RecordingStatus,\n} from './Audio.types';\nimport {\n AUDIO_SAMPLE_UPDATE,\n PLAYBACK_STATUS_UPDATE,\n PLAYLIST_STATUS_UPDATE,\n RECORDING_STATUS_UPDATE,\n} from './AudioEventKeys';\nimport AudioModule from './AudioModule';\nimport { AudioPlayer, AudioPlaylist, AudioRecorder, AudioSample } from './AudioModule.types';\nimport { createRecordingOptions } from './utils/options';\nimport { resolveSource, resolveSources, resolveSourceWithDownload } from './utils/resolveSource';\n\n// TODO: Temporary solution until we develop a way of overriding prototypes that won't break the lazy loading of the module.\nconst replace = AudioModule.AudioPlayer.prototype.replace;\nAudioModule.AudioPlayer.prototype.replace = function (source: AudioSource) {\n return replace.call(this, resolveSource(source));\n};\n\nconst setPlaybackRate = AudioModule.AudioPlayer.prototype.setPlaybackRate;\nAudioModule.AudioPlayer.prototype.setPlaybackRate = function (\n rate: number,\n pitchCorrectionQuality?: PitchCorrectionQuality\n) {\n if (Platform.OS === 'android') {\n return setPlaybackRate.call(this, rate);\n } else {\n return setPlaybackRate.call(this, rate, pitchCorrectionQuality);\n }\n};\n\n// Audio recording prototypes should not be shimmed on tvOS, where they do not exist\nif (!Platform.isTV || Platform.OS !== 'ios') {\n const prepareToRecordAsync = AudioModule.AudioRecorder.prototype.prepareToRecordAsync;\n AudioModule.AudioRecorder.prototype.prepareToRecordAsync = function (options?: RecordingOptions) {\n const processedOptions = options ? createRecordingOptions(options) : undefined;\n return prepareToRecordAsync.call(this, processedOptions);\n };\n\n const record = AudioModule.AudioRecorder.prototype.record;\n AudioModule.AudioRecorder.prototype.record = function (options?: RecordingStartOptions) {\n return record.call(this, options);\n };\n}\n\n/**\n * Creates an `AudioPlayer` instance that automatically releases when the component unmounts.\n *\n * This hook manages the player's lifecycle and ensures it's properly disposed when no longer needed.\n * The player will start loading the audio source immediately upon creation.\n *\n * @param source The audio source to load. Can be a local asset via `require()`, a remote URL, or null for no initial source.\n * @param options Audio player configuration options.\n * @returns An `AudioPlayer` instance that's automatically managed by the component lifecycle.\n *\n * @example\n * ```tsx\n * import { useAudioPlayer } from 'expo-audio';\n *\n * function MyComponent() {\n * const player = useAudioPlayer(require('./sound.mp3'));\n *\n * return (\n * player.play()} />\n * );\n * }\n * ```\n *\n * @example Using downloadFirst\n * ```tsx\n * import { useAudioPlayer } from 'expo-audio';\n *\n * function MyComponent() {\n * const player = useAudioPlayer('https://example.com/audio.mp3', {\n * updateInterval: 1000,\n * downloadFirst: true,\n * });\n *\n * return (\n * player.play()} />\n * );\n * }\n * ```\n */\nexport function useAudioPlayer(\n source: AudioSource = null,\n options: AudioPlayerOptions = {}\n): AudioPlayer {\n const { updateInterval = 500, downloadFirst = false, keepAudioSessionActive = false } = options;\n\n // If downloadFirst is true, we don't need to resolve the source, because it will be resolved in the useEffect below.\n // If downloadFirst is false, we resolve the source here.\n // we call .replace() in the useEffect below to replace the source with the downloaded one.\n const initialSource = useMemo(() => {\n return downloadFirst ? null : resolveSource(source);\n }, [JSON.stringify(source), downloadFirst]);\n\n const player = useReleasingSharedObject(\n () => new AudioModule.AudioPlayer(initialSource, updateInterval, keepAudioSessionActive),\n [JSON.stringify(initialSource), updateInterval, keepAudioSessionActive]\n );\n\n // Handle async source resolution for downloadFirst\n useEffect(() => {\n if (!downloadFirst || source === null) {\n return;\n }\n\n let isCancelled = false;\n\n // We resolve the source with expo-asset and replace the player's source with the downloaded one.\n async function resolveAndReplaceSource() {\n try {\n const resolved = await resolveSourceWithDownload(source);\n\n if (\n !isCancelled &&\n resolved &&\n JSON.stringify(resolved) !== JSON.stringify(initialSource)\n ) {\n player.replace(resolved);\n }\n } catch (error) {\n if (!isCancelled) {\n console.warn('expo-audio: Failed to download source, using original:', error);\n }\n }\n }\n\n resolveAndReplaceSource();\n\n return () => {\n isCancelled = true;\n };\n }, [player, JSON.stringify(source), downloadFirst]);\n\n return player;\n}\n\n/**\n * Hook that provides real-time playback status updates for an `AudioPlayer`.\n *\n * This hook automatically subscribes to playback status changes and returns the current status.\n * The status includes information about playback state, current time, duration, loading state, and more.\n *\n * @param player The `AudioPlayer` instance to monitor.\n * @returns The current `AudioStatus` object containing playback information.\n *\n * @example\n * ```tsx\n * import { useAudioPlayer, useAudioPlayerStatus } from 'expo-audio';\n *\n * function PlayerComponent() {\n * const player = useAudioPlayer(require('./sound.mp3'));\n * const status = useAudioPlayerStatus(player);\n *\n * return (\n * \n * Playing: {status.playing ? 'Yes' : 'No'} \n * Current Time: {status.currentTime}s \n * Duration: {status.duration}s \n * \n * );\n * }\n * ```\n */\nexport function useAudioPlayerStatus(player: AudioPlayer): AudioStatus {\n const currentStatus = useMemo(() => player.currentStatus, [player.id]);\n return useEvent(player, PLAYBACK_STATUS_UPDATE, currentStatus);\n}\n\n/**\n * Hook that sets up audio sampling for an `AudioPlayer` and calls a listener with audio data.\n *\n * This hook enables audio sampling on the player (if supported) and subscribes to audio sample updates.\n * Audio sampling provides real-time access to audio waveform data for visualization or analysis.\n *\n * > **Note:** Audio sampling requires `RECORD_AUDIO` permission on Android and is not supported on all platforms.\n *\n * @param player The `AudioPlayer` instance to sample audio from.\n * @param listener Function called with each audio sample containing waveform data.\n *\n * @example\n * ```tsx\n * import { useEffect } from 'react';\n * import { useAudioPlayer, useAudioSampleListener, requestRecordingPermissionsAsync } from 'expo-audio';\n *\n * function AudioVisualizerComponent() {\n * const player = useAudioPlayer(require('./music.mp3'));\n *\n * // if required on Android, request recording permissions\n * useEffect(() => {\n * async function requestPermission() {\n * const { granted } = await requestRecordingPermissionsAsync();\n * if (granted) {\n * console.log(\"Permission granted\");\n * }\n * }\n *\n * requestPermission();\n * }, []);\n *\n * useAudioSampleListener(player, (sample) => {\n * // Use sample.channels array for audio visualization\n * console.log('Audio sample:', sample.channels[0].frames);\n * });\n *\n * return ;\n * }\n * ```\n */\nexport function useAudioSampleListener(player: AudioPlayer, listener: (data: AudioSample) => void) {\n useEffect(() => {\n if (!player.isAudioSamplingSupported) {\n return;\n }\n player.setAudioSamplingEnabled(true);\n const subscription = player.addListener(AUDIO_SAMPLE_UPDATE, listener);\n return () => subscription.remove();\n }, [player.id]);\n}\n\n/**\n * Hook that creates an `AudioRecorder` instance for recording audio.\n *\n * This hook manages the recorder's lifecycle and ensures it's properly disposed when no longer needed.\n * The recorder is automatically prepared with the provided options and can be used to record audio.\n *\n * @param options Recording configuration options including format, quality, sample rate, etc.\n * @param statusListener Optional callback function that receives recording status updates.\n * @returns An `AudioRecorder` instance that's automatically managed by the component lifecycle.\n *\n * @example\n * ```tsx\n * import { useAudioRecorder, RecordingPresets } from 'expo-audio';\n *\n * function RecorderComponent() {\n * const recorder = useAudioRecorder(\n * RecordingPresets.HIGH_QUALITY,\n * (status) => console.log('Recording status:', status)\n * );\n *\n * const startRecording = async () => {\n * await recorder.prepareToRecordAsync();\n * recorder.record();\n * };\n *\n * return (\n * \n * );\n * }\n * ```\n */\nexport function useAudioRecorder(\n options: RecordingOptions,\n statusListener?: (status: RecordingStatus) => void\n): AudioRecorder {\n const platformOptions = createRecordingOptions(options);\n const recorder = useReleasingSharedObject(() => {\n return new AudioModule.AudioRecorder(platformOptions);\n }, [JSON.stringify(platformOptions)]);\n\n useEffect(() => {\n const subscription = recorder.addListener(RECORDING_STATUS_UPDATE, (status) => {\n statusListener?.(status);\n });\n return () => subscription.remove();\n }, [recorder.id]);\n\n return recorder;\n}\n\n/**\n * Hook that provides real-time recording state updates for an `AudioRecorder`.\n *\n * This hook polls the recorder's status at regular intervals and returns the current recording state.\n * Use this when you need to monitor the recording status without setting up a status listener.\n *\n * @param recorder The `AudioRecorder` instance to monitor.\n * @param interval How often (in milliseconds) to poll the recorder's status. Defaults to 500ms.\n * @returns The current `RecorderState` containing recording information.\n *\n * @example\n * ```tsx\n * import { useAudioRecorder, useAudioRecorderState, RecordingPresets } from 'expo-audio';\n *\n * function RecorderStatusComponent() {\n * const recorder = useAudioRecorder(RecordingPresets.HIGH_QUALITY);\n * const state = useAudioRecorderState(recorder);\n *\n * return (\n * \n * Recording: {state.isRecording ? 'Yes' : 'No'} \n * Duration: {Math.round(state.durationMillis / 1000)}s \n * Can Record: {state.canRecord ? 'Yes' : 'No'} \n * \n * );\n * }\n * ```\n */\nexport function useAudioRecorderState(recorder: AudioRecorder, interval: number = 500) {\n const [state, setState] = useState(recorder.getStatus());\n\n useEffect(() => {\n const int = setInterval(() => {\n const newState = recorder.getStatus();\n\n setState((prevState) => {\n const meteringChanged =\n (prevState.metering === undefined) !== (newState.metering === undefined) ||\n (prevState.metering !== undefined &&\n newState.metering !== undefined &&\n Math.abs(prevState.metering - newState.metering) > 0.1);\n\n if (\n prevState.canRecord !== newState.canRecord ||\n prevState.isRecording !== newState.isRecording ||\n prevState.mediaServicesDidReset !== newState.mediaServicesDidReset ||\n prevState.url !== newState.url ||\n Math.abs(prevState.durationMillis - newState.durationMillis) > 50 ||\n meteringChanged\n ) {\n return newState;\n }\n return prevState;\n });\n }, interval);\n\n return () => clearInterval(int);\n }, [recorder.id]);\n\n return state;\n}\n\n/**\n * Creates an `AudioPlaylist` instance that automatically releases when the component unmounts.\n *\n * This hook manages the playlist's lifecycle and ensures it's properly disposed when no longer needed.\n * An audio playlist allows you to manage a collection of audio sources with gapless playback support.\n *\n * @param options Audio playlist configuration options including initial sources and loop mode.\n * @returns An `AudioPlaylist` instance that's automatically managed by the component lifecycle.\n *\n * @example\n * ```tsx\n * import { useAudioPlaylist } from 'expo-audio';\n *\n * function PlaylistPlayer() {\n * const playlist = useAudioPlaylist({\n * sources: [\n * require('./track1.mp3'),\n * require('./track2.mp3'),\n * 'https://example.com/track3.mp3',\n * ],\n * loop: 'all',\n * });\n *\n * return (\n * \n * Track {playlist.currentIndex + 1} of {playlist.trackCount} \n * playlist.previous()} />\n * playlist.playing ? playlist.pause() : playlist.play()} />\n * playlist.next()} />\n * \n * );\n * }\n * ```\n */\nexport function useAudioPlaylist(options: AudioPlaylistOptions = {}): AudioPlaylist {\n const { sources = [], updateInterval = 500, loop = 'none' } = options;\n\n const resolvedSources = useMemo(() => resolveSources(sources), [JSON.stringify(sources)]);\n\n const playlist = useReleasingSharedObject(\n () => new AudioModule.AudioPlaylist(resolvedSources, updateInterval, loop),\n [JSON.stringify(resolvedSources), updateInterval, loop]\n );\n\n return playlist;\n}\n\n/**\n * Hook that provides real-time status updates for an `AudioPlaylist`.\n *\n * This hook automatically subscribes to playlist status changes and returns the current status.\n * The status includes information about the current track, playback state, and playlist position.\n *\n * @param playlist The `AudioPlaylist` instance to monitor.\n * @returns The current `AudioPlaylistStatus` object containing playlist and playback information.\n *\n * @example\n * ```tsx\n * import { useAudioPlaylist, useAudioPlaylistStatus } from 'expo-audio';\n *\n * function PlaylistStatusDisplay() {\n * const playlist = useAudioPlaylist({ sources: [require('./track1.mp3')] });\n * const status = useAudioPlaylistStatus(playlist);\n *\n * return (\n * \n * Track: {status.currentIndex + 1} / {status.trackCount} \n * Time: {status.currentTime}s / {status.duration}s \n * Playing: {status.playing ? 'Yes' : 'No'} \n * \n * );\n * }\n * ```\n */\nexport function useAudioPlaylistStatus(playlist: AudioPlaylist): AudioPlaylistStatus {\n const currentStatus = useMemo(() => playlist.currentStatus, [playlist.id]);\n return useEvent(playlist, PLAYLIST_STATUS_UPDATE, currentStatus);\n}\n\n/**\n * Creates an instance of an `AudioPlaylist` that doesn't release automatically.\n *\n * > **info** For most use cases you should use the [`useAudioPlaylist`](#useaudioplaylistoptions) hook instead.\n *\n * @param options Audio playlist configuration options.\n */\nexport function createAudioPlaylist(options: AudioPlaylistOptions = {}): AudioPlaylist {\n const { sources = [], updateInterval = 500, loop = 'none' } = options;\n const resolvedSources = resolveSources(sources);\n return new AudioModule.AudioPlaylist(resolvedSources, updateInterval, loop);\n}\n\n/**\n * Creates an instance of an `AudioPlayer` that doesn't release automatically.\n *\n * > **info** For most use cases you should use the [`useAudioPlayer`](#useaudioplayersource-options) hook instead.\n * > See the [Using the `AudioPlayer` directly](#using-the-audioplayer-directly) section for more details.\n * @param source The audio source to load.\n * @param options Audio player configuration options.\n */\nexport function createAudioPlayer(\n source: AudioSource | string | number | null = null,\n options: AudioPlayerOptions = {}\n): AudioPlayer {\n const { updateInterval = 500, downloadFirst = false, keepAudioSessionActive = false } = options;\n const initialSource = downloadFirst ? null : resolveSource(source);\n const player = new AudioModule.AudioPlayer(initialSource, updateInterval, keepAudioSessionActive);\n\n if (downloadFirst && source) {\n resolveSourceWithDownload(source)\n .then((resolved) => {\n if (resolved) {\n player.replace(resolved);\n }\n })\n .catch((error) => {\n console.warn('expo-audio: Failed to download source, using fallback:', error);\n const fallback = resolveSource(source);\n if (fallback) {\n player.replace(fallback);\n }\n });\n }\n\n return player;\n}\n\n/**\n * Enables or disables the audio subsystem globally.\n *\n * When set to `false`, this will pause all audio playback and prevent new audio from playing.\n * This is useful for implementing app-wide audio controls or responding to system events.\n *\n * @param active Whether audio should be active (`true`) or disabled (`false`).\n * @returns A Promise that resolves when the audio state has been updated.\n *\n * @example\n * ```tsx\n * import { setIsAudioActiveAsync } from 'expo-audio';\n *\n * // Disable all audio when app goes to background\n * const handleAppStateChange = async (nextAppState) => {\n * if (nextAppState === 'background') {\n * await setIsAudioActiveAsync(false);\n * } else if (nextAppState === 'active') {\n * await setIsAudioActiveAsync(true);\n * }\n * };\n * ```\n */\nexport async function setIsAudioActiveAsync(active: boolean): Promise {\n return await AudioModule.setIsAudioActiveAsync(active);\n}\n\n/**\n * Configures the global audio behavior and session settings.\n *\n * This function allows you to control how your app's audio interacts with other apps,\n * background playback behavior, audio routing, and interruption handling.\n *\n * @param mode Partial audio mode configuration object. Only specified properties will be updated.\n * @returns A Promise that resolves when the audio mode has been applied.\n *\n * @example\n * ```tsx\n * import { setAudioModeAsync } from 'expo-audio';\n *\n * // Configure audio for background playback with mixing\n * await setAudioModeAsync({\n * playsInSilentMode: true,\n * shouldPlayInBackground: true,\n * interruptionMode: 'mixWithOthers'\n * });\n *\n * // Configure audio for recording\n * await setAudioModeAsync({\n * allowsRecording: true,\n * playsInSilentMode: true\n * });\n * ```\n */\nexport async function setAudioModeAsync(mode: Partial): Promise {\n const audioMode: Partial =\n Platform.OS === 'ios'\n ? mode\n : {\n shouldPlayInBackground: mode.shouldPlayInBackground,\n shouldRouteThroughEarpiece: mode.shouldRouteThroughEarpiece,\n interruptionMode: mode.interruptionMode ?? mode.interruptionModeAndroid,\n allowsBackgroundRecording: mode.allowsBackgroundRecording,\n };\n return await AudioModule.setAudioModeAsync(audioMode);\n}\n\n/**\n * Requests permission to record audio from the microphone.\n *\n * This function prompts the user for microphone access permission, which is required\n * for audio recording functionality. On iOS, this will show the system permission dialog.\n * On Android, this requests the `RECORD_AUDIO` permission.\n *\n * @returns A Promise that resolves to a `PermissionResponse` object containing the permission status.\n *\n * @example\n * ```tsx\n * import { requestRecordingPermissionsAsync } from 'expo-audio';\n *\n * const checkPermissions = async () => {\n * const { status, granted } = await requestRecordingPermissionsAsync();\n *\n * if (granted) {\n * console.log('Recording permission granted');\n * } else {\n * console.log('Recording permission denied:', status);\n * }\n * };\n * ```\n */\nexport async function requestRecordingPermissionsAsync(): Promise {\n return await AudioModule.requestRecordingPermissionsAsync();\n}\n\n/**\n * Requests permission to record audio from the microphone.\n *\n * This function prompts the user for microphone access permission, which is required\n * for audio recording functionality. On iOS, this will show the system permission dialog.\n * On Android, this requests the `RECORD_AUDIO` permission.\n *\n * @returns A Promise that resolves to a `PermissionResponse` object containing the permission status.\n *\n * @example\n * ```tsx\n * import { requestRecordingPermissionsAsync } from 'expo-audio';\n *\n * const checkPermissions = async () => {\n * const { status, granted } = await requestRecordingPermissionsAsync();\n *\n * if (granted) {\n * console.log('Recording permission granted');\n * } else {\n * console.log('Recording permission denied:', status);\n * }\n * };\n * ```\n */\nexport async function requestNotificationPermissionsAsync(): Promise {\n if (Platform.OS !== 'android') {\n throw new Error(\n 'expo-audio: `requestNotificationPermissionsAsync` is only available on Android.'\n );\n }\n return await AudioModule.requestNotificationPermissionsAsync();\n}\n\n/**\n * Checks the current status of recording permissions without requesting them.\n *\n * This function returns the current permission status for microphone access\n * without triggering a permission request dialog. Use this to check permissions\n * before deciding whether to call `requestRecordingPermissionsAsync()`.\n *\n * @returns A Promise that resolves to a `PermissionResponse` object containing the current permission status.\n *\n * @example\n * ```tsx\n * import { getRecordingPermissionsAsync, requestRecordingPermissionsAsync } from 'expo-audio';\n *\n * const ensureRecordingPermissions = async () => {\n * const { status } = await getRecordingPermissionsAsync();\n *\n * if (status !== 'granted') {\n * // Permission not granted, request it\n * const { granted } = await requestRecordingPermissionsAsync();\n * return granted;\n * }\n *\n * return true; // Already granted\n * };\n * ```\n */\nexport async function getRecordingPermissionsAsync(): Promise {\n return await AudioModule.getRecordingPermissionsAsync();\n}\n\nexport { AudioModule };\n"]}
\ No newline at end of file
+{"version":3,"file":"ExpoAudio.js","sourceRoot":"","sources":["../src/ExpoAudio.ts"],"names":[],"mappings":"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","sourcesContent":["import { useEvent } from 'expo';\nimport { PermissionResponse, useReleasingSharedObject } from 'expo-modules-core';\nimport { useEffect, useState, useMemo } from 'react';\nimport { Platform } from 'react-native';\n\nimport {\n AudioMode,\n AudioPlayerOptions,\n AudioPlaylistOptions,\n AudioPlaylistStatus,\n AudioSource,\n AudioStatus,\n PitchCorrectionQuality,\n PreloadOptions,\n RecorderState,\n RecordingOptions,\n RecordingStartOptions,\n RecordingStatus,\n} from './Audio.types';\nimport {\n AUDIO_SAMPLE_UPDATE,\n PLAYBACK_STATUS_UPDATE,\n PLAYLIST_STATUS_UPDATE,\n RECORDING_STATUS_UPDATE,\n} from './AudioEventKeys';\nimport AudioModule from './AudioModule';\nimport { AudioPlayer, AudioPlaylist, AudioRecorder, AudioSample } from './AudioModule.types';\nimport { createRecordingOptions } from './utils/options';\nimport { resolveSource, resolveSources, resolveSourceWithDownload } from './utils/resolveSource';\n\n// TODO: Temporary solution until we develop a way of overriding prototypes that won't break the lazy loading of the module.\nconst replace = AudioModule.AudioPlayer.prototype.replace;\nAudioModule.AudioPlayer.prototype.replace = function (source: AudioSource) {\n return replace.call(this, resolveSource(source));\n};\n\nconst setPlaybackRate = AudioModule.AudioPlayer.prototype.setPlaybackRate;\nAudioModule.AudioPlayer.prototype.setPlaybackRate = function (\n rate: number,\n pitchCorrectionQuality?: PitchCorrectionQuality\n) {\n if (Platform.OS === 'android') {\n return setPlaybackRate.call(this, rate);\n } else {\n return setPlaybackRate.call(this, rate, pitchCorrectionQuality);\n }\n};\n\n// Audio recording prototypes should not be shimmed on tvOS, where they do not exist\nif (!Platform.isTV || Platform.OS !== 'ios') {\n const prepareToRecordAsync = AudioModule.AudioRecorder.prototype.prepareToRecordAsync;\n AudioModule.AudioRecorder.prototype.prepareToRecordAsync = function (options?: RecordingOptions) {\n const processedOptions = options ? createRecordingOptions(options) : undefined;\n return prepareToRecordAsync.call(this, processedOptions);\n };\n\n const record = AudioModule.AudioRecorder.prototype.record;\n AudioModule.AudioRecorder.prototype.record = function (options?: RecordingStartOptions) {\n return record.call(this, options);\n };\n}\n\n/**\n * Creates an `AudioPlayer` instance that automatically releases when the component unmounts.\n *\n * This hook manages the player's lifecycle and ensures it's properly disposed when no longer needed.\n * The player will start loading the audio source immediately upon creation.\n *\n * @param source The audio source to load. Can be a local asset via `require()`, a remote URL, or null for no initial source.\n * @param options Audio player configuration options.\n * @returns An `AudioPlayer` instance that's automatically managed by the component lifecycle.\n *\n * @example\n * ```tsx\n * import { useAudioPlayer } from 'expo-audio';\n *\n * function MyComponent() {\n * const player = useAudioPlayer(require('./sound.mp3'));\n *\n * return (\n * player.play()} />\n * );\n * }\n * ```\n *\n * @example Using downloadFirst\n * ```tsx\n * import { useAudioPlayer } from 'expo-audio';\n *\n * function MyComponent() {\n * const player = useAudioPlayer('https://example.com/audio.mp3', {\n * updateInterval: 1000,\n * downloadFirst: true,\n * });\n *\n * return (\n * player.play()} />\n * );\n * }\n * ```\n */\nexport function useAudioPlayer(\n source: AudioSource = null,\n options: AudioPlayerOptions = {}\n): AudioPlayer {\n const {\n updateInterval = 500,\n downloadFirst = false,\n keepAudioSessionActive = false,\n preferredForwardBufferDuration = 0,\n } = options;\n\n // If downloadFirst is true, we don't need to resolve the source, because it will be resolved in the useEffect below.\n // If downloadFirst is false, we resolve the source here.\n // we call .replace() in the useEffect below to replace the source with the downloaded one.\n const initialSource = useMemo(() => {\n return downloadFirst ? null : resolveSource(source);\n }, [JSON.stringify(source), downloadFirst]);\n\n const player = useReleasingSharedObject(\n () =>\n new AudioModule.AudioPlayer(\n initialSource,\n updateInterval,\n keepAudioSessionActive,\n preferredForwardBufferDuration\n ),\n [\n JSON.stringify(initialSource),\n updateInterval,\n keepAudioSessionActive,\n preferredForwardBufferDuration,\n ]\n );\n\n // Handle async source resolution for downloadFirst\n useEffect(() => {\n if (!downloadFirst || source === null) {\n return;\n }\n\n let isCancelled = false;\n\n // We resolve the source with expo-asset and replace the player's source with the downloaded one.\n async function resolveAndReplaceSource() {\n try {\n const resolved = await resolveSourceWithDownload(source);\n\n if (\n !isCancelled &&\n resolved &&\n JSON.stringify(resolved) !== JSON.stringify(initialSource)\n ) {\n player.replace(resolved);\n }\n } catch (error) {\n if (!isCancelled) {\n console.warn('expo-audio: Failed to download source, using original:', error);\n }\n }\n }\n\n resolveAndReplaceSource();\n\n return () => {\n isCancelled = true;\n };\n }, [player, JSON.stringify(source), downloadFirst]);\n\n return player;\n}\n\n/**\n * Hook that provides real-time playback status updates for an `AudioPlayer`.\n *\n * This hook automatically subscribes to playback status changes and returns the current status.\n * The status includes information about playback state, current time, duration, loading state, and more.\n *\n * @param player The `AudioPlayer` instance to monitor.\n * @returns The current `AudioStatus` object containing playback information.\n *\n * @example\n * ```tsx\n * import { useAudioPlayer, useAudioPlayerStatus } from 'expo-audio';\n *\n * function PlayerComponent() {\n * const player = useAudioPlayer(require('./sound.mp3'));\n * const status = useAudioPlayerStatus(player);\n *\n * return (\n * \n * Playing: {status.playing ? 'Yes' : 'No'} \n * Current Time: {status.currentTime}s \n * Duration: {status.duration}s \n * \n * );\n * }\n * ```\n */\nexport function useAudioPlayerStatus(player: AudioPlayer): AudioStatus {\n const currentStatus = useMemo(() => player.currentStatus, [player.id]);\n return useEvent(player, PLAYBACK_STATUS_UPDATE, currentStatus);\n}\n\n/**\n * Hook that sets up audio sampling for an `AudioPlayer` and calls a listener with audio data.\n *\n * This hook enables audio sampling on the player (if supported) and subscribes to audio sample updates.\n * Audio sampling provides real-time access to audio waveform data for visualization or analysis.\n *\n * > **Note:** Audio sampling requires `RECORD_AUDIO` permission on Android and is not supported on all platforms.\n *\n * @param player The `AudioPlayer` instance to sample audio from.\n * @param listener Function called with each audio sample containing waveform data.\n *\n * @example\n * ```tsx\n * import { useEffect } from 'react';\n * import { useAudioPlayer, useAudioSampleListener, requestRecordingPermissionsAsync } from 'expo-audio';\n *\n * function AudioVisualizerComponent() {\n * const player = useAudioPlayer(require('./music.mp3'));\n *\n * // if required on Android, request recording permissions\n * useEffect(() => {\n * async function requestPermission() {\n * const { granted } = await requestRecordingPermissionsAsync();\n * if (granted) {\n * console.log(\"Permission granted\");\n * }\n * }\n *\n * requestPermission();\n * }, []);\n *\n * useAudioSampleListener(player, (sample) => {\n * // Use sample.channels array for audio visualization\n * console.log('Audio sample:', sample.channels[0].frames);\n * });\n *\n * return ;\n * }\n * ```\n */\nexport function useAudioSampleListener(player: AudioPlayer, listener: (data: AudioSample) => void) {\n useEffect(() => {\n if (!player.isAudioSamplingSupported) {\n return;\n }\n player.setAudioSamplingEnabled(true);\n const subscription = player.addListener(AUDIO_SAMPLE_UPDATE, listener);\n return () => subscription.remove();\n }, [player.id]);\n}\n\n/**\n * Hook that creates an `AudioRecorder` instance for recording audio.\n *\n * This hook manages the recorder's lifecycle and ensures it's properly disposed when no longer needed.\n * The recorder is automatically prepared with the provided options and can be used to record audio.\n *\n * @param options Recording configuration options including format, quality, sample rate, etc.\n * @param statusListener Optional callback function that receives recording status updates.\n * @returns An `AudioRecorder` instance that's automatically managed by the component lifecycle.\n *\n * @example\n * ```tsx\n * import { useAudioRecorder, RecordingPresets } from 'expo-audio';\n *\n * function RecorderComponent() {\n * const recorder = useAudioRecorder(\n * RecordingPresets.HIGH_QUALITY,\n * (status) => console.log('Recording status:', status)\n * );\n *\n * const startRecording = async () => {\n * await recorder.prepareToRecordAsync();\n * recorder.record();\n * };\n *\n * return (\n * \n * );\n * }\n * ```\n */\nexport function useAudioRecorder(\n options: RecordingOptions,\n statusListener?: (status: RecordingStatus) => void\n): AudioRecorder {\n const platformOptions = createRecordingOptions(options);\n const recorder = useReleasingSharedObject(() => {\n return new AudioModule.AudioRecorder(platformOptions);\n }, [JSON.stringify(platformOptions)]);\n\n useEffect(() => {\n const subscription = recorder.addListener(RECORDING_STATUS_UPDATE, (status) => {\n statusListener?.(status);\n });\n return () => subscription.remove();\n }, [recorder.id]);\n\n return recorder;\n}\n\n/**\n * Hook that provides real-time recording state updates for an `AudioRecorder`.\n *\n * This hook polls the recorder's status at regular intervals and returns the current recording state.\n * Use this when you need to monitor the recording status without setting up a status listener.\n *\n * @param recorder The `AudioRecorder` instance to monitor.\n * @param interval How often (in milliseconds) to poll the recorder's status. Defaults to 500ms.\n * @returns The current `RecorderState` containing recording information.\n *\n * @example\n * ```tsx\n * import { useAudioRecorder, useAudioRecorderState, RecordingPresets } from 'expo-audio';\n *\n * function RecorderStatusComponent() {\n * const recorder = useAudioRecorder(RecordingPresets.HIGH_QUALITY);\n * const state = useAudioRecorderState(recorder);\n *\n * return (\n * \n * Recording: {state.isRecording ? 'Yes' : 'No'} \n * Duration: {Math.round(state.durationMillis / 1000)}s \n * Can Record: {state.canRecord ? 'Yes' : 'No'} \n * \n * );\n * }\n * ```\n */\nexport function useAudioRecorderState(recorder: AudioRecorder, interval: number = 500) {\n const [state, setState] = useState(recorder.getStatus());\n\n useEffect(() => {\n const int = setInterval(() => {\n const newState = recorder.getStatus();\n\n setState((prevState) => {\n const meteringChanged =\n (prevState.metering === undefined) !== (newState.metering === undefined) ||\n (prevState.metering !== undefined &&\n newState.metering !== undefined &&\n Math.abs(prevState.metering - newState.metering) > 0.1);\n\n if (\n prevState.canRecord !== newState.canRecord ||\n prevState.isRecording !== newState.isRecording ||\n prevState.mediaServicesDidReset !== newState.mediaServicesDidReset ||\n prevState.url !== newState.url ||\n Math.abs(prevState.durationMillis - newState.durationMillis) > 50 ||\n meteringChanged\n ) {\n return newState;\n }\n return prevState;\n });\n }, interval);\n\n return () => clearInterval(int);\n }, [recorder.id]);\n\n return state;\n}\n\n/**\n * Creates an `AudioPlaylist` instance that automatically releases when the component unmounts.\n *\n * This hook manages the playlist's lifecycle and ensures it's properly disposed when no longer needed.\n * An audio playlist allows you to manage a collection of audio sources with gapless playback support.\n *\n * @param options Audio playlist configuration options including initial sources and loop mode.\n * @returns An `AudioPlaylist` instance that's automatically managed by the component lifecycle.\n *\n * @example\n * ```tsx\n * import { useAudioPlaylist } from 'expo-audio';\n *\n * function PlaylistPlayer() {\n * const playlist = useAudioPlaylist({\n * sources: [\n * require('./track1.mp3'),\n * require('./track2.mp3'),\n * 'https://example.com/track3.mp3',\n * ],\n * loop: 'all',\n * });\n *\n * return (\n * \n * Track {playlist.currentIndex + 1} of {playlist.trackCount} \n * playlist.previous()} />\n * playlist.playing ? playlist.pause() : playlist.play()} />\n * playlist.next()} />\n * \n * );\n * }\n * ```\n */\nexport function useAudioPlaylist(options: AudioPlaylistOptions = {}): AudioPlaylist {\n const { sources = [], updateInterval = 500, loop = 'none' } = options;\n\n const resolvedSources = useMemo(() => resolveSources(sources), [JSON.stringify(sources)]);\n\n const playlist = useReleasingSharedObject(\n () => new AudioModule.AudioPlaylist(resolvedSources, updateInterval, loop),\n [JSON.stringify(resolvedSources), updateInterval, loop]\n );\n\n return playlist;\n}\n\n/**\n * Hook that provides real-time status updates for an `AudioPlaylist`.\n *\n * This hook automatically subscribes to playlist status changes and returns the current status.\n * The status includes information about the current track, playback state, and playlist position.\n *\n * @param playlist The `AudioPlaylist` instance to monitor.\n * @returns The current `AudioPlaylistStatus` object containing playlist and playback information.\n *\n * @example\n * ```tsx\n * import { useAudioPlaylist, useAudioPlaylistStatus } from 'expo-audio';\n *\n * function PlaylistStatusDisplay() {\n * const playlist = useAudioPlaylist({ sources: [require('./track1.mp3')] });\n * const status = useAudioPlaylistStatus(playlist);\n *\n * return (\n * \n * Track: {status.currentIndex + 1} / {status.trackCount} \n * Time: {status.currentTime}s / {status.duration}s \n * Playing: {status.playing ? 'Yes' : 'No'} \n * \n * );\n * }\n * ```\n */\nexport function useAudioPlaylistStatus(playlist: AudioPlaylist): AudioPlaylistStatus {\n const currentStatus = useMemo(() => playlist.currentStatus, [playlist.id]);\n return useEvent(playlist, PLAYLIST_STATUS_UPDATE, currentStatus);\n}\n\n/**\n * Creates an instance of an `AudioPlaylist` that doesn't release automatically.\n *\n * > **info** For most use cases you should use the [`useAudioPlaylist`](#useaudioplaylistoptions) hook instead.\n *\n * @param options Audio playlist configuration options.\n */\nexport function createAudioPlaylist(options: AudioPlaylistOptions = {}): AudioPlaylist {\n const { sources = [], updateInterval = 500, loop = 'none' } = options;\n const resolvedSources = resolveSources(sources);\n return new AudioModule.AudioPlaylist(resolvedSources, updateInterval, loop);\n}\n\n/**\n * Creates an instance of an `AudioPlayer` that doesn't release automatically.\n *\n * > **info** For most use cases you should use the [`useAudioPlayer`](#useaudioplayersource-options) hook instead.\n * > See the [Using the `AudioPlayer` directly](#using-the-audioplayer-directly) section for more details.\n * @param source The audio source to load.\n * @param options Audio player configuration options.\n */\nexport function createAudioPlayer(\n source: AudioSource | string | number | null = null,\n options: AudioPlayerOptions = {}\n): AudioPlayer {\n const {\n updateInterval = 500,\n downloadFirst = false,\n keepAudioSessionActive = false,\n preferredForwardBufferDuration = 0,\n } = options;\n const initialSource = downloadFirst ? null : resolveSource(source);\n const player = new AudioModule.AudioPlayer(\n initialSource,\n updateInterval,\n keepAudioSessionActive,\n preferredForwardBufferDuration\n );\n\n if (downloadFirst && source) {\n resolveSourceWithDownload(source)\n .then((resolved) => {\n if (resolved) {\n player.replace(resolved);\n }\n })\n .catch((error) => {\n console.warn('expo-audio: Failed to download source, using fallback:', error);\n const fallback = resolveSource(source);\n if (fallback) {\n player.replace(fallback);\n }\n });\n }\n\n return player;\n}\n\n/**\n * Enables or disables the audio subsystem globally.\n *\n * When set to `false`, this will pause all audio playback and prevent new audio from playing.\n * This is useful for implementing app-wide audio controls or responding to system events.\n *\n * @param active Whether audio should be active (`true`) or disabled (`false`).\n * @returns A Promise that resolves when the audio state has been updated.\n *\n * @example\n * ```tsx\n * import { setIsAudioActiveAsync } from 'expo-audio';\n *\n * // Disable all audio when app goes to background\n * const handleAppStateChange = async (nextAppState) => {\n * if (nextAppState === 'background') {\n * await setIsAudioActiveAsync(false);\n * } else if (nextAppState === 'active') {\n * await setIsAudioActiveAsync(true);\n * }\n * };\n * ```\n */\nexport async function setIsAudioActiveAsync(active: boolean): Promise {\n return await AudioModule.setIsAudioActiveAsync(active);\n}\n\n/**\n * Configures the global audio behavior and session settings.\n *\n * This function allows you to control how your app's audio interacts with other apps,\n * background playback behavior, audio routing, and interruption handling.\n *\n * @param mode Partial audio mode configuration object. Only specified properties will be updated.\n * @returns A Promise that resolves when the audio mode has been applied.\n *\n * @example\n * ```tsx\n * import { setAudioModeAsync } from 'expo-audio';\n *\n * // Configure audio for background playback with mixing\n * await setAudioModeAsync({\n * playsInSilentMode: true,\n * shouldPlayInBackground: true,\n * interruptionMode: 'mixWithOthers'\n * });\n *\n * // Configure audio for recording\n * await setAudioModeAsync({\n * allowsRecording: true,\n * playsInSilentMode: true\n * });\n * ```\n */\nexport async function setAudioModeAsync(mode: Partial): Promise {\n const audioMode: Partial =\n Platform.OS === 'ios'\n ? mode\n : {\n shouldPlayInBackground: mode.shouldPlayInBackground,\n shouldRouteThroughEarpiece: mode.shouldRouteThroughEarpiece,\n interruptionMode: mode.interruptionMode ?? mode.interruptionModeAndroid,\n allowsBackgroundRecording: mode.allowsBackgroundRecording,\n };\n return await AudioModule.setAudioModeAsync(audioMode);\n}\n\n/**\n * Requests permission to record audio from the microphone.\n *\n * This function prompts the user for microphone access permission, which is required\n * for audio recording functionality. On iOS, this will show the system permission dialog.\n * On Android, this requests the `RECORD_AUDIO` permission.\n *\n * @returns A Promise that resolves to a `PermissionResponse` object containing the permission status.\n *\n * @example\n * ```tsx\n * import { requestRecordingPermissionsAsync } from 'expo-audio';\n *\n * const checkPermissions = async () => {\n * const { status, granted } = await requestRecordingPermissionsAsync();\n *\n * if (granted) {\n * console.log('Recording permission granted');\n * } else {\n * console.log('Recording permission denied:', status);\n * }\n * };\n * ```\n */\nexport async function requestRecordingPermissionsAsync(): Promise {\n return await AudioModule.requestRecordingPermissionsAsync();\n}\n\n/**\n * Requests permission to record audio from the microphone.\n *\n * This function prompts the user for microphone access permission, which is required\n * for audio recording functionality. On iOS, this will show the system permission dialog.\n * On Android, this requests the `RECORD_AUDIO` permission.\n *\n * @returns A Promise that resolves to a `PermissionResponse` object containing the permission status.\n *\n * @example\n * ```tsx\n * import { requestRecordingPermissionsAsync } from 'expo-audio';\n *\n * const checkPermissions = async () => {\n * const { status, granted } = await requestRecordingPermissionsAsync();\n *\n * if (granted) {\n * console.log('Recording permission granted');\n * } else {\n * console.log('Recording permission denied:', status);\n * }\n * };\n * ```\n */\nexport async function requestNotificationPermissionsAsync(): Promise {\n if (Platform.OS !== 'android') {\n throw new Error(\n 'expo-audio: `requestNotificationPermissionsAsync` is only available on Android.'\n );\n }\n return await AudioModule.requestNotificationPermissionsAsync();\n}\n\n/**\n * Checks the current status of recording permissions without requesting them.\n *\n * This function returns the current permission status for microphone access\n * without triggering a permission request dialog. Use this to check permissions\n * before deciding whether to call `requestRecordingPermissionsAsync()`.\n *\n * @returns A Promise that resolves to a `PermissionResponse` object containing the current permission status.\n *\n * @example\n * ```tsx\n * import { getRecordingPermissionsAsync, requestRecordingPermissionsAsync } from 'expo-audio';\n *\n * const ensureRecordingPermissions = async () => {\n * const { status } = await getRecordingPermissionsAsync();\n *\n * if (status !== 'granted') {\n * // Permission not granted, request it\n * const { granted } = await requestRecordingPermissionsAsync();\n * return granted;\n * }\n *\n * return true; // Already granted\n * };\n * ```\n */\nexport async function getRecordingPermissionsAsync(): Promise {\n return await AudioModule.getRecordingPermissionsAsync();\n}\n\n/**\n * Preloads an audio source for near-instant playback later.\n *\n * This should be called in module scope, before any React components render.\n * When the source is later used with `useAudioPlayer()`, `createAudioPlayer()`, or `player.replace()`,\n * playback begins with minimal delay.\n *\n * @param source The audio source to preload. Can be a URL string, a local asset via `require()`, or an audio source object.\n * @param options Optional configuration for preloading behavior.\n *\n * @example\n * ```tsx\n * import { preload, useAudioPlayer } from 'expo-audio';\n *\n * const track1 = 'https://example.com/track1.mp3';\n * const track2 = 'https://example.com/track2.mp3';\n *\n * // Preload at module scope — starts buffering immediately\n * preload(track1);\n * preload(track2, { preferredForwardBufferDuration: 20 });\n *\n * export default function App() {\n * const player = useAudioPlayer(track1);\n * // Playback starts near-instantly because the source was preloaded\n * return player.play()} />;\n * }\n * ```\n */\nexport async function preload(source: AudioSource, options: PreloadOptions = {}): Promise {\n const resolved = resolveSource(source);\n if (!resolved) return;\n const { preferredForwardBufferDuration = 10 } = options;\n return AudioModule.preload(resolved, preferredForwardBufferDuration);\n}\n\n/**\n * Releases a specific preloaded audio source to free memory.\n *\n * @param source The audio source to release. Must match the source previously passed to `preload()`.\n */\nexport async function clearPreloadedSource(source: AudioSource): Promise {\n const resolved = resolveSource(source);\n if (!resolved) return;\n return AudioModule.clearPreloadedSource(resolved);\n}\n\n/**\n * Releases all preloaded audio sources to free memory.\n */\nexport async function clearAllPreloadedSources(): Promise {\n return AudioModule.clearAllPreloadedSources();\n}\n\n/**\n * Returns the URIs of all currently preloaded audio sources.\n *\n * Sources are removed from this list when consumed by `useAudioPlayer()` or `createAudioPlayer()`,\n * or when explicitly cleared with `clearPreloadedSource()` / `clearAllPreloadedSources()`.\n *\n * @returns An array of URI strings for sources currently in the preload cache.\n */\nexport async function getPreloadedSources(): Promise {\n return AudioModule.getPreloadedSources();\n}\n\nexport { AudioModule };\n"]}
\ No newline at end of file
diff --git a/packages/expo-audio/build/ExpoAudio.web.d.ts b/packages/expo-audio/build/ExpoAudio.web.d.ts
index 09fb3f29d7e48a..642d05f0bc8548 100644
--- a/packages/expo-audio/build/ExpoAudio.web.d.ts
+++ b/packages/expo-audio/build/ExpoAudio.web.d.ts
@@ -1,5 +1,5 @@
import { PermissionResponse } from 'expo-modules-core';
-import { AudioMode, AudioPlayerOptions, AudioPlaylistOptions, AudioPlaylistStatus, AudioSource, AudioStatus, RecorderState, RecordingOptions, RecordingStatus } from './Audio.types';
+import { AudioMode, AudioPlayerOptions, AudioPlaylistOptions, AudioPlaylistStatus, AudioSource, AudioStatus, PreloadOptions, RecorderState, RecordingOptions, RecordingStatus } from './Audio.types';
import { AudioPlayer, AudioRecorder, AudioSample } from './AudioModule.types';
import * as AudioModule from './AudioModule.web';
export declare function createAudioPlayer(source?: AudioSource | string | number | null, options?: AudioPlayerOptions): AudioPlayer;
@@ -15,5 +15,9 @@ export declare function getRecordingPermissionsAsync(): Promise {
- URL.revokeObjectURL(objectUrl);
-});
+import { resolveSource, resolveSources } from './utils/resolveSource';
export function createAudioPlayer(source = null, options = {}) {
const { downloadFirst = false } = options;
// If downloadFirst is true, we don't need to resolve the source, because it will be replaced once the source is downloaded.
// If downloadFirst is false, we resolve the source here.
const initialSource = downloadFirst ? null : resolveSource(source);
const player = new AudioModule.AudioPlayerWeb(initialSource, options);
- // we call .replace() on the player to replace the source with the downloaded one
- // only relevant if downloadFirst is true and source is not null
+ // Preload the source and replace the player's source with the cached blob URL.
+ // Only relevant if downloadFirst is true and source is not null.
if (downloadFirst && source) {
- resolveSourceWithDownload(source)
- .then((resolved) => {
- if (resolved) {
- // Register object URL for automatic cleanup when player is garbage collected
- if (resolved &&
- typeof resolved === 'object' &&
- resolved.uri &&
- resolved.uri.startsWith('blob:')) {
- objectUrlRegistry.register(player, resolved.uri);
- }
+ const resolved = resolveSource(source);
+ if (resolved) {
+ AudioModule.preloadAsync(resolved).finally(() => {
player.replace(resolved);
- }
- })
- .catch((error) => {
- console.warn('expo-audio: Failed to download source, using fallback:', error);
- const fallback = resolveSource(source);
- if (fallback) {
- player.replace(fallback);
- }
- });
+ });
+ }
}
return player;
}
@@ -50,45 +31,23 @@ export function useAudioPlayer(source = null, options = {}) {
const initialSource = useMemo(() => {
return downloadFirst ? null : resolveSource(source);
}, [JSON.stringify(source), downloadFirst]);
- const player = useMemo(() => new AudioModule.AudioPlayerWeb(initialSource, options), [JSON.stringify(initialSource), JSON.stringify(options)]);
+ const player = useReleasingSharedObject(() => new AudioModule.AudioPlayerWeb(initialSource, options), [JSON.stringify(initialSource), JSON.stringify(options)]);
// Handle async source resolution for downloadFirst
useEffect(() => {
if (!downloadFirst || source === null) {
return;
}
let isCancelled = false;
- let objectUrl = null;
- // We resolve the source with expo-asset and replace the player's source with the downloaded one.
- async function resolveAndReplaceSource() {
- try {
- const resolved = await resolveSourceWithDownload(source);
- if (!isCancelled &&
- resolved &&
- JSON.stringify(resolved) !== JSON.stringify(initialSource)) {
- // Track the object URL for cleanup
- if (resolved &&
- typeof resolved === 'object' &&
- resolved.uri &&
- resolved.uri.startsWith('blob:')) {
- objectUrl = resolved.uri;
- }
- player.replace(resolved);
- }
- }
- catch (error) {
+ const resolved = resolveSource(source);
+ if (resolved) {
+ AudioModule.preloadAsync(resolved).finally(() => {
if (!isCancelled) {
- console.warn('expo-audio: Failed to download source, using original:', error);
+ player.replace(resolved);
}
- }
+ });
}
- resolveAndReplaceSource();
return () => {
isCancelled = true;
- player.remove();
- // Revoke the object URL created by this hook instance
- if (objectUrl) {
- URL.revokeObjectURL(objectUrl);
- }
};
}, [player, JSON.stringify(source), downloadFirst]);
return player;
@@ -178,5 +137,23 @@ export function createAudioPlaylist(options = {}) {
const resolvedSources = resolveSources(sources);
return new AudioModule.AudioPlaylistWeb(resolvedSources, updateInterval, loop, crossOrigin);
}
+export function preload(source, _options = {}) {
+ const resolved = resolveSource(source);
+ if (!resolved)
+ return;
+ AudioModule.preload(resolved);
+}
+export function clearPreloadedSource(source) {
+ const resolved = resolveSource(source);
+ if (!resolved)
+ return;
+ AudioModule.clearPreloadedSource(resolved);
+}
+export function clearAllPreloadedSources() {
+ AudioModule.clearAllPreloadedSources();
+}
+export function getPreloadedSources() {
+ return AudioModule.getPreloadedSources();
+}
export { AudioModule };
//# sourceMappingURL=ExpoAudio.web.js.map
\ No newline at end of file
diff --git a/packages/expo-audio/build/ExpoAudio.web.js.map b/packages/expo-audio/build/ExpoAudio.web.js.map
index eb10e5ef1f6f75..0a97491ed2dd31 100644
--- a/packages/expo-audio/build/ExpoAudio.web.js.map
+++ b/packages/expo-audio/build/ExpoAudio.web.js.map
@@ -1 +1 @@
-{"version":3,"file":"ExpoAudio.web.js","sourceRoot":"","sources":["../src/ExpoAudio.web.ts"],"names":[],"mappings":"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","sourcesContent":["import { useEvent } from 'expo';\nimport { PermissionResponse } from 'expo-modules-core';\nimport { useEffect, useState, useMemo } from 'react';\n\nimport {\n AudioMode,\n AudioPlayerOptions,\n AudioPlaylistOptions,\n AudioPlaylistStatus,\n AudioSource,\n AudioStatus,\n RecorderState,\n RecordingOptions,\n RecordingStatus,\n} from './Audio.types';\nimport {\n AUDIO_SAMPLE_UPDATE,\n PLAYBACK_STATUS_UPDATE,\n PLAYLIST_STATUS_UPDATE,\n RECORDING_STATUS_UPDATE,\n} from './AudioEventKeys';\nimport { AudioPlayer, AudioRecorder, AudioSample } from './AudioModule.types';\nimport * as AudioModule from './AudioModule.web';\nimport { createRecordingOptions } from './utils/options';\nimport { resolveSource, resolveSources, resolveSourceWithDownload } from './utils/resolveSource';\n\n// Global registry for cleaning up object URLs when players are garbage collected\n// Since we are using blob urls, we need to clean them up when the player is garbage collected\n// this is only used for createAudioPlayer, as we have lifecycle management in useAudioPlayer\nconst objectUrlRegistry = new FinalizationRegistry((objectUrl: string) => {\n URL.revokeObjectURL(objectUrl);\n});\n\nexport function createAudioPlayer(\n source: AudioSource | string | number | null = null,\n options: AudioPlayerOptions = {}\n): AudioPlayer {\n const { downloadFirst = false } = options;\n\n // If downloadFirst is true, we don't need to resolve the source, because it will be replaced once the source is downloaded.\n // If downloadFirst is false, we resolve the source here.\n const initialSource = downloadFirst ? null : resolveSource(source);\n const player = new AudioModule.AudioPlayerWeb(initialSource, options);\n\n // we call .replace() on the player to replace the source with the downloaded one\n // only relevant if downloadFirst is true and source is not null\n if (downloadFirst && source) {\n resolveSourceWithDownload(source)\n .then((resolved) => {\n if (resolved) {\n // Register object URL for automatic cleanup when player is garbage collected\n if (\n resolved &&\n typeof resolved === 'object' &&\n resolved.uri &&\n resolved.uri.startsWith('blob:')\n ) {\n objectUrlRegistry.register(player, resolved.uri);\n }\n player.replace(resolved);\n }\n })\n .catch((error) => {\n console.warn('expo-audio: Failed to download source, using fallback:', error);\n const fallback = resolveSource(source);\n if (fallback) {\n player.replace(fallback);\n }\n });\n }\n\n return player;\n}\n\nexport function useAudioPlayer(\n source: AudioSource | string | number | null = null,\n options: AudioPlayerOptions = {}\n): AudioModule.AudioPlayerWeb {\n const { downloadFirst = false } = options;\n\n // If downloadFirst is true, we don't need to resolve the source, because it will be resolved in the useEffect below.\n // If downloadFirst is false, we resolve the source here.\n // we call .replace() in the useEffect below to replace the source with the downloaded one.\n const initialSource = useMemo(() => {\n return downloadFirst ? null : resolveSource(source);\n }, [JSON.stringify(source), downloadFirst]);\n\n const player = useMemo(\n () => new AudioModule.AudioPlayerWeb(initialSource, options),\n [JSON.stringify(initialSource), JSON.stringify(options)]\n );\n\n // Handle async source resolution for downloadFirst\n useEffect(() => {\n if (!downloadFirst || source === null) {\n return;\n }\n\n let isCancelled = false;\n let objectUrl: string | null = null;\n\n // We resolve the source with expo-asset and replace the player's source with the downloaded one.\n async function resolveAndReplaceSource() {\n try {\n const resolved = await resolveSourceWithDownload(source);\n if (\n !isCancelled &&\n resolved &&\n JSON.stringify(resolved) !== JSON.stringify(initialSource)\n ) {\n // Track the object URL for cleanup\n if (\n resolved &&\n typeof resolved === 'object' &&\n resolved.uri &&\n resolved.uri.startsWith('blob:')\n ) {\n objectUrl = resolved.uri;\n }\n player.replace(resolved);\n }\n } catch (error) {\n if (!isCancelled) {\n console.warn('expo-audio: Failed to download source, using original:', error);\n }\n }\n }\n\n resolveAndReplaceSource();\n\n return () => {\n isCancelled = true;\n player.remove();\n // Revoke the object URL created by this hook instance\n if (objectUrl) {\n URL.revokeObjectURL(objectUrl);\n }\n };\n }, [player, JSON.stringify(source), downloadFirst]);\n\n return player;\n}\n\nexport function useAudioPlayerStatus(player: AudioModule.AudioPlayerWeb): AudioStatus {\n const currentStatus = useMemo(() => player.currentStatus, [player.id]);\n return useEvent(player, PLAYBACK_STATUS_UPDATE, currentStatus);\n}\n\nexport function useAudioSampleListener(\n player: AudioModule.AudioPlayerWeb,\n listener: (data: AudioSample) => void\n) {\n player.setAudioSamplingEnabled(true);\n useEffect(() => {\n const subscription = player.addListener(AUDIO_SAMPLE_UPDATE, listener);\n return () => {\n player.setAudioSamplingEnabled(false);\n subscription.remove();\n };\n }, [player.id]);\n}\n\nexport function useAudioRecorder(\n options: RecordingOptions,\n statusListener?: (status: RecordingStatus) => void\n): AudioModule.AudioRecorderWeb {\n const platformOptions = createRecordingOptions(options);\n const recorder = useMemo(() => {\n return new AudioModule.AudioRecorderWeb(platformOptions);\n }, [JSON.stringify(platformOptions)]);\n\n useEffect(() => {\n const subscription = recorder.addListener(RECORDING_STATUS_UPDATE, (status) => {\n statusListener?.(status);\n });\n return () => {\n recorder.clearTimeouts();\n subscription.remove();\n };\n }, [recorder.id]);\n\n return recorder;\n}\n\nexport function useAudioRecorderState(recorder: AudioRecorder, interval: number = 500) {\n const [state, setState] = useState(recorder.getStatus());\n\n useEffect(() => {\n const int = setInterval(() => {\n const newState = recorder.getStatus();\n\n setState((prevState) => {\n const meteringChanged =\n (prevState.metering === undefined) !== (newState.metering === undefined) ||\n (prevState.metering !== undefined &&\n newState.metering !== undefined &&\n Math.abs(prevState.metering - newState.metering) > 0.1);\n\n if (\n prevState.canRecord !== newState.canRecord ||\n prevState.isRecording !== newState.isRecording ||\n prevState.mediaServicesDidReset !== newState.mediaServicesDidReset ||\n prevState.url !== newState.url ||\n Math.abs(prevState.durationMillis - newState.durationMillis) > 50 ||\n meteringChanged\n ) {\n return newState;\n }\n return prevState;\n });\n }, interval);\n\n return () => clearInterval(int);\n }, [recorder.id]);\n\n return state;\n}\n\nexport async function setIsAudioActiveAsync(active: boolean): Promise {\n return await AudioModule.setIsAudioActiveAsync(active);\n}\n\nexport async function setAudioModeAsync(mode: AudioMode): Promise {\n return await AudioModule.setAudioModeAsync(mode);\n}\n\nexport async function requestRecordingPermissionsAsync(): Promise {\n return await AudioModule.requestRecordingPermissionsAsync();\n}\n\nexport async function getRecordingPermissionsAsync(): Promise {\n return await AudioModule.getRecordingPermissionsAsync();\n}\n\nexport function useAudioPlaylist(options: AudioPlaylistOptions = {}): AudioModule.AudioPlaylistWeb {\n const { sources = [], updateInterval = 500, loop = 'none', crossOrigin } = options;\n\n const resolvedSources = useMemo(() => resolveSources(sources), [JSON.stringify(sources)]);\n\n const playlist = useMemo(\n () => new AudioModule.AudioPlaylistWeb(resolvedSources, updateInterval, loop, crossOrigin),\n [JSON.stringify(resolvedSources), updateInterval, loop, crossOrigin]\n );\n\n useEffect(() => {\n return () => playlist.destroy();\n }, [playlist]);\n\n return playlist;\n}\n\nexport function useAudioPlaylistStatus(\n playlist: AudioModule.AudioPlaylistWeb\n): AudioPlaylistStatus {\n const currentStatus = useMemo(() => playlist.currentStatus, [playlist.id]);\n return useEvent(playlist, PLAYLIST_STATUS_UPDATE, currentStatus);\n}\n\nexport function createAudioPlaylist(\n options: AudioPlaylistOptions = {}\n): AudioModule.AudioPlaylistWeb {\n const { sources = [], updateInterval = 500, loop = 'none', crossOrigin } = options;\n const resolvedSources = resolveSources(sources);\n return new AudioModule.AudioPlaylistWeb(resolvedSources, updateInterval, loop, crossOrigin);\n}\n\nexport { AudioModule };\n"]}
\ No newline at end of file
+{"version":3,"file":"ExpoAudio.web.js","sourceRoot":"","sources":["../src/ExpoAudio.web.ts"],"names":[],"mappings":"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","sourcesContent":["import { useEvent } from 'expo';\nimport { PermissionResponse, useReleasingSharedObject } from 'expo-modules-core';\nimport { useEffect, useState, useMemo } from 'react';\n\nimport {\n AudioMode,\n AudioPlayerOptions,\n AudioPlaylistOptions,\n AudioPlaylistStatus,\n AudioSource,\n AudioStatus,\n PreloadOptions,\n RecorderState,\n RecordingOptions,\n RecordingStatus,\n} from './Audio.types';\nimport {\n AUDIO_SAMPLE_UPDATE,\n PLAYBACK_STATUS_UPDATE,\n PLAYLIST_STATUS_UPDATE,\n RECORDING_STATUS_UPDATE,\n} from './AudioEventKeys';\nimport { AudioPlayer, AudioRecorder, AudioSample } from './AudioModule.types';\nimport * as AudioModule from './AudioModule.web';\nimport { createRecordingOptions } from './utils/options';\nimport { resolveSource, resolveSources } from './utils/resolveSource';\n\nexport function createAudioPlayer(\n source: AudioSource | string | number | null = null,\n options: AudioPlayerOptions = {}\n): AudioPlayer {\n const { downloadFirst = false } = options;\n\n // If downloadFirst is true, we don't need to resolve the source, because it will be replaced once the source is downloaded.\n // If downloadFirst is false, we resolve the source here.\n const initialSource = downloadFirst ? null : resolveSource(source);\n const player = new AudioModule.AudioPlayerWeb(initialSource, options);\n\n // Preload the source and replace the player's source with the cached blob URL.\n // Only relevant if downloadFirst is true and source is not null.\n if (downloadFirst && source) {\n const resolved = resolveSource(source);\n if (resolved) {\n AudioModule.preloadAsync(resolved).finally(() => {\n player.replace(resolved);\n });\n }\n }\n\n return player;\n}\n\nexport function useAudioPlayer(\n source: AudioSource | string | number | null = null,\n options: AudioPlayerOptions = {}\n): AudioModule.AudioPlayerWeb {\n const { downloadFirst = false } = options;\n\n // If downloadFirst is true, we don't need to resolve the source, because it will be resolved in the useEffect below.\n // If downloadFirst is false, we resolve the source here.\n // we call .replace() in the useEffect below to replace the source with the downloaded one.\n const initialSource = useMemo(() => {\n return downloadFirst ? null : resolveSource(source);\n }, [JSON.stringify(source), downloadFirst]);\n\n const player = useReleasingSharedObject(\n () => new AudioModule.AudioPlayerWeb(initialSource, options),\n [JSON.stringify(initialSource), JSON.stringify(options)]\n );\n\n // Handle async source resolution for downloadFirst\n useEffect(() => {\n if (!downloadFirst || source === null) {\n return;\n }\n\n let isCancelled = false;\n const resolved = resolveSource(source);\n\n if (resolved) {\n AudioModule.preloadAsync(resolved).finally(() => {\n if (!isCancelled) {\n player.replace(resolved);\n }\n });\n }\n\n return () => {\n isCancelled = true;\n };\n }, [player, JSON.stringify(source), downloadFirst]);\n\n return player;\n}\n\nexport function useAudioPlayerStatus(player: AudioModule.AudioPlayerWeb): AudioStatus {\n const currentStatus = useMemo(() => player.currentStatus, [player.id]);\n return useEvent(player, PLAYBACK_STATUS_UPDATE, currentStatus);\n}\n\nexport function useAudioSampleListener(\n player: AudioModule.AudioPlayerWeb,\n listener: (data: AudioSample) => void\n) {\n player.setAudioSamplingEnabled(true);\n useEffect(() => {\n const subscription = player.addListener(AUDIO_SAMPLE_UPDATE, listener);\n return () => {\n player.setAudioSamplingEnabled(false);\n subscription.remove();\n };\n }, [player.id]);\n}\n\nexport function useAudioRecorder(\n options: RecordingOptions,\n statusListener?: (status: RecordingStatus) => void\n): AudioModule.AudioRecorderWeb {\n const platformOptions = createRecordingOptions(options);\n const recorder = useMemo(() => {\n return new AudioModule.AudioRecorderWeb(platformOptions);\n }, [JSON.stringify(platformOptions)]);\n\n useEffect(() => {\n const subscription = recorder.addListener(RECORDING_STATUS_UPDATE, (status) => {\n statusListener?.(status);\n });\n return () => {\n recorder.clearTimeouts();\n subscription.remove();\n };\n }, [recorder.id]);\n\n return recorder;\n}\n\nexport function useAudioRecorderState(recorder: AudioRecorder, interval: number = 500) {\n const [state, setState] = useState(recorder.getStatus());\n\n useEffect(() => {\n const int = setInterval(() => {\n const newState = recorder.getStatus();\n\n setState((prevState) => {\n const meteringChanged =\n (prevState.metering === undefined) !== (newState.metering === undefined) ||\n (prevState.metering !== undefined &&\n newState.metering !== undefined &&\n Math.abs(prevState.metering - newState.metering) > 0.1);\n\n if (\n prevState.canRecord !== newState.canRecord ||\n prevState.isRecording !== newState.isRecording ||\n prevState.mediaServicesDidReset !== newState.mediaServicesDidReset ||\n prevState.url !== newState.url ||\n Math.abs(prevState.durationMillis - newState.durationMillis) > 50 ||\n meteringChanged\n ) {\n return newState;\n }\n return prevState;\n });\n }, interval);\n\n return () => clearInterval(int);\n }, [recorder.id]);\n\n return state;\n}\n\nexport async function setIsAudioActiveAsync(active: boolean): Promise {\n return await AudioModule.setIsAudioActiveAsync(active);\n}\n\nexport async function setAudioModeAsync(mode: AudioMode): Promise {\n return await AudioModule.setAudioModeAsync(mode);\n}\n\nexport async function requestRecordingPermissionsAsync(): Promise {\n return await AudioModule.requestRecordingPermissionsAsync();\n}\n\nexport async function getRecordingPermissionsAsync(): Promise {\n return await AudioModule.getRecordingPermissionsAsync();\n}\n\nexport function useAudioPlaylist(options: AudioPlaylistOptions = {}): AudioModule.AudioPlaylistWeb {\n const { sources = [], updateInterval = 500, loop = 'none', crossOrigin } = options;\n\n const resolvedSources = useMemo(() => resolveSources(sources), [JSON.stringify(sources)]);\n\n const playlist = useMemo(\n () => new AudioModule.AudioPlaylistWeb(resolvedSources, updateInterval, loop, crossOrigin),\n [JSON.stringify(resolvedSources), updateInterval, loop, crossOrigin]\n );\n\n useEffect(() => {\n return () => playlist.destroy();\n }, [playlist]);\n\n return playlist;\n}\n\nexport function useAudioPlaylistStatus(\n playlist: AudioModule.AudioPlaylistWeb\n): AudioPlaylistStatus {\n const currentStatus = useMemo(() => playlist.currentStatus, [playlist.id]);\n return useEvent(playlist, PLAYLIST_STATUS_UPDATE, currentStatus);\n}\n\nexport function createAudioPlaylist(\n options: AudioPlaylistOptions = {}\n): AudioModule.AudioPlaylistWeb {\n const { sources = [], updateInterval = 500, loop = 'none', crossOrigin } = options;\n const resolvedSources = resolveSources(sources);\n return new AudioModule.AudioPlaylistWeb(resolvedSources, updateInterval, loop, crossOrigin);\n}\n\nexport function preload(source: AudioSource, _options: PreloadOptions = {}): void {\n const resolved = resolveSource(source);\n if (!resolved) return;\n AudioModule.preload(resolved);\n}\n\nexport function clearPreloadedSource(source: AudioSource): void {\n const resolved = resolveSource(source);\n if (!resolved) return;\n AudioModule.clearPreloadedSource(resolved);\n}\n\nexport function clearAllPreloadedSources(): void {\n AudioModule.clearAllPreloadedSources();\n}\n\nexport function getPreloadedSources(): string[] {\n return AudioModule.getPreloadedSources();\n}\n\nexport { AudioModule };\n"]}
\ No newline at end of file
diff --git a/packages/expo-audio/build/utils/resolveSource.d.ts.map b/packages/expo-audio/build/utils/resolveSource.d.ts.map
index 68f6cca926e2e3..f692f332f7ee06 100644
--- a/packages/expo-audio/build/utils/resolveSource.d.ts.map
+++ b/packages/expo-audio/build/utils/resolveSource.d.ts.map
@@ -1 +1 @@
-{"version":3,"file":"resolveSource.d.ts","sourceRoot":"","sources":["../../src/utils/resolveSource.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAqD7C,wBAAgB,cAAc,CAAC,OAAO,EAAE,WAAW,EAAE,GAAG,WAAW,CAAC,WAAW,CAAC,EAAE,CAIjF;AAED,wBAAgB,aAAa,CAAC,MAAM,CAAC,EAAE,WAAW,GAAG,MAAM,GAAG,MAAM,GAAG,IAAI,GAAG,WAAW,GAAG,IAAI,CA+B/F;AAED;;;GAGG;AACH,wBAAsB,yBAAyB,CAC7C,MAAM,EAAE,WAAW,GAAG,MAAM,GAAG,MAAM,GAAG,IAAI,GAC3C,OAAO,CAAC,WAAW,GAAG,IAAI,CAAC,CA0D7B"}
\ No newline at end of file
+{"version":3,"file":"resolveSource.d.ts","sourceRoot":"","sources":["../../src/utils/resolveSource.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAqD7C,wBAAgB,cAAc,CAAC,OAAO,EAAE,WAAW,EAAE,GAAG,WAAW,CAAC,WAAW,CAAC,EAAE,CAIjF;AAED,wBAAgB,aAAa,CAAC,MAAM,CAAC,EAAE,WAAW,GAAG,MAAM,GAAG,MAAM,GAAG,IAAI,GAAG,WAAW,GAAG,IAAI,CA+B/F;AAED;;;GAGG;AACH,wBAAsB,yBAAyB,CAC7C,MAAM,EAAE,WAAW,GAAG,MAAM,GAAG,MAAM,GAAG,IAAI,GAC3C,OAAO,CAAC,WAAW,GAAG,IAAI,CAAC,CAgD7B"}
\ No newline at end of file
diff --git a/packages/expo-audio/build/utils/resolveSource.js b/packages/expo-audio/build/utils/resolveSource.js
index fd52b10c9afa74..4781a8a92e2dda 100644
--- a/packages/expo-audio/build/utils/resolveSource.js
+++ b/packages/expo-audio/build/utils/resolveSource.js
@@ -1,5 +1,4 @@
import { Asset } from 'expo-asset';
-import { Platform } from 'expo-modules-core';
function getAssetFromSource(source) {
if (!source) {
return null;
@@ -96,16 +95,7 @@ export async function resolveSourceWithDownload(source) {
await assetToDownload.downloadAsync();
// Use the local URI if available after download
if (assetToDownload.localUri) {
- let finalUri = assetToDownload.localUri;
- // On web, we need to fetch the audio file and create a blob URL
- // this fully downloads the file to the user's device memory and makes it available for the user to play
- // fetch() is subject to CORS restrictions, so we need to document this for the users on web
- // TODO(@hirbod): evaluate if we should implement a downloadAsync for web instead of using fetch here
- if (Platform.OS === 'web') {
- const response = await fetch(assetToDownload.localUri);
- const blob = await response.blob();
- finalUri = URL.createObjectURL(blob);
- }
+ const finalUri = assetToDownload.localUri;
if (fallbackSource && typeof fallbackSource === 'object') {
return {
...fallbackSource,
diff --git a/packages/expo-audio/build/utils/resolveSource.js.map b/packages/expo-audio/build/utils/resolveSource.js.map
index 1bec21298a7be1..83b97a224e2ade 100644
--- a/packages/expo-audio/build/utils/resolveSource.js.map
+++ b/packages/expo-audio/build/utils/resolveSource.js.map
@@ -1 +1 @@
-{"version":3,"file":"resolveSource.js","sourceRoot":"","sources":["../../src/utils/resolveSource.ts"],"names":[],"mappings":"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","sourcesContent":["import { Asset } from 'expo-asset';\nimport { Platform } from 'expo-modules-core';\n\nimport { AudioSource } from '../Audio.types';\n\ntype AudioSourceObject = Exclude;\n\nfunction getAssetFromSource(source?: AudioSource | string | number | Asset | null): Asset | null {\n if (!source) {\n return null;\n }\n\n if (source instanceof Asset) {\n return source;\n }\n\n if (typeof source === 'number') {\n return Asset.fromModule(source);\n }\n\n if (typeof source === 'object') {\n if ('assetId' in source && typeof source.assetId === 'number') {\n return Asset.fromModule(source.assetId);\n }\n if ('uri' in source && typeof source.uri === 'string') {\n return Asset.fromURI(source.uri);\n }\n }\n\n if (typeof source === 'string') {\n return Asset.fromURI(source);\n }\n\n return null;\n}\n\nfunction createSourceFromAsset(\n asset: Asset,\n extras: { assetId?: number; headers?: Record } = {}\n): AudioSourceObject {\n const uri = asset.localUri ?? asset.uri;\n const result: AudioSourceObject = { uri };\n\n if (asset.name) {\n result.name = asset.name;\n }\n if (extras.assetId != null) {\n result.assetId = extras.assetId;\n }\n if (extras.headers) {\n result.headers = extras.headers;\n }\n\n return result;\n}\n\nexport function resolveSources(sources: AudioSource[]): NonNullable[] {\n return sources\n .map((source) => resolveSource(source))\n .filter((source): source is NonNullable => source != null);\n}\n\nexport function resolveSource(source?: AudioSource | string | number | null): AudioSource | null {\n if (source == null) {\n return null;\n }\n\n if (source instanceof Asset) {\n return createSourceFromAsset(source);\n }\n\n if (typeof source === 'string') {\n return { uri: source };\n }\n if (typeof source === 'number') {\n const asset = Asset.fromModule(source);\n return createSourceFromAsset(asset, { assetId: source });\n }\n\n if (typeof source === 'object') {\n if ('assetId' in source && typeof source.assetId === 'number') {\n const asset = Asset.fromModule(source.assetId);\n return {\n ...source,\n uri: asset.localUri ?? asset.uri,\n };\n }\n if ('uri' in source && typeof source.uri === 'string') {\n return source;\n }\n }\n\n return source ?? null;\n}\n\n/**\n * Resolves and optionally downloads an audio source before loading.\n * Similar to expo-av's getNativeSourceAndFullInitialStatusForLoadAsync but simplified for expo-audio.\n */\nexport async function resolveSourceWithDownload(\n source: AudioSource | string | number | null\n): Promise {\n const asset = getAssetFromSource(source);\n const fallbackSource = resolveSource(source);\n\n if (asset) {\n let assetToDownload: Asset = asset;\n try {\n // iOS AVPlayer fails to load the asset if the type is not set or can't be inferred\n // since this is an audio asset, we can safely set the type to mp3 or any other audio type\n // and iOS will be able to download and play the asset\n // Since expo-asset caches, this will only run once per asset, as long as the asset is not deleted from the cache\n if (!assetToDownload.type) {\n assetToDownload = new Asset({\n name: asset.name,\n type: 'mp3',\n uri: asset.uri,\n });\n }\n\n // FYI: downloadAsync is a no-op on web and immediately returns a promise that resolves to the original url\n // TODO(@hirbod): evaluate if we should implement downloadAsync for web instead\n await assetToDownload.downloadAsync();\n\n // Use the local URI if available after download\n if (assetToDownload.localUri) {\n let finalUri = assetToDownload.localUri;\n\n // On web, we need to fetch the audio file and create a blob URL\n // this fully downloads the file to the user's device memory and makes it available for the user to play\n // fetch() is subject to CORS restrictions, so we need to document this for the users on web\n // TODO(@hirbod): evaluate if we should implement a downloadAsync for web instead of using fetch here\n if (Platform.OS === 'web') {\n const response = await fetch(assetToDownload.localUri);\n const blob = await response.blob();\n finalUri = URL.createObjectURL(blob);\n }\n\n if (fallbackSource && typeof fallbackSource === 'object') {\n return {\n ...fallbackSource,\n uri: finalUri,\n };\n }\n\n return { uri: finalUri };\n } else {\n console.warn(\n 'No localUri found, asset may not have downloaded properly, returning the original source'\n );\n }\n } catch (error) {\n // If download fails, fall back to original source\n console.warn('expo-audio: Failed to download asset, falling back to original source:', error);\n }\n }\n\n // Fallback to normal resolution\n return fallbackSource;\n}\n"]}
\ No newline at end of file
+{"version":3,"file":"resolveSource.js","sourceRoot":"","sources":["../../src/utils/resolveSource.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AAMnC,SAAS,kBAAkB,CAAC,MAAqD;IAC/E,IAAI,CAAC,MAAM,EAAE,CAAC;QACZ,OAAO,IAAI,CAAC;IACd,CAAC;IAED,IAAI,MAAM,YAAY,KAAK,EAAE,CAAC;QAC5B,OAAO,MAAM,CAAC;IAChB,CAAC;IAED,IAAI,OAAO,MAAM,KAAK,QAAQ,EAAE,CAAC;QAC/B,OAAO,KAAK,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC;IAClC,CAAC;IAED,IAAI,OAAO,MAAM,KAAK,QAAQ,EAAE,CAAC;QAC/B,IAAI,SAAS,IAAI,MAAM,IAAI,OAAO,MAAM,CAAC,OAAO,KAAK,QAAQ,EAAE,CAAC;YAC9D,OAAO,KAAK,CAAC,UAAU,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;QAC1C,CAAC;QACD,IAAI,KAAK,IAAI,MAAM,IAAI,OAAO,MAAM,CAAC,GAAG,KAAK,QAAQ,EAAE,CAAC;YACtD,OAAO,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;QACnC,CAAC;IACH,CAAC;IAED,IAAI,OAAO,MAAM,KAAK,QAAQ,EAAE,CAAC;QAC/B,OAAO,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;IAC/B,CAAC;IAED,OAAO,IAAI,CAAC;AACd,CAAC;AAED,SAAS,qBAAqB,CAC5B,KAAY,EACZ,SAAiE,EAAE;IAEnE,MAAM,GAAG,GAAG,KAAK,CAAC,QAAQ,IAAI,KAAK,CAAC,GAAG,CAAC;IACxC,MAAM,MAAM,GAAsB,EAAE,GAAG,EAAE,CAAC;IAE1C,IAAI,KAAK,CAAC,IAAI,EAAE,CAAC;QACf,MAAM,CAAC,IAAI,GAAG,KAAK,CAAC,IAAI,CAAC;IAC3B,CAAC;IACD,IAAI,MAAM,CAAC,OAAO,IAAI,IAAI,EAAE,CAAC;QAC3B,MAAM,CAAC,OAAO,GAAG,MAAM,CAAC,OAAO,CAAC;IAClC,CAAC;IACD,IAAI,MAAM,CAAC,OAAO,EAAE,CAAC;QACnB,MAAM,CAAC,OAAO,GAAG,MAAM,CAAC,OAAO,CAAC;IAClC,CAAC;IAED,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,MAAM,UAAU,cAAc,CAAC,OAAsB;IACnD,OAAO,OAAO;SACX,GAAG,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,aAAa,CAAC,MAAM,CAAC,CAAC;SACtC,MAAM,CAAC,CAAC,MAAM,EAAsC,EAAE,CAAC,MAAM,IAAI,IAAI,CAAC,CAAC;AAC5E,CAAC;AAED,MAAM,UAAU,aAAa,CAAC,MAA6C;IACzE,IAAI,MAAM,IAAI,IAAI,EAAE,CAAC;QACnB,OAAO,IAAI,CAAC;IACd,CAAC;IAED,IAAI,MAAM,YAAY,KAAK,EAAE,CAAC;QAC5B,OAAO,qBAAqB,CAAC,MAAM,CAAC,CAAC;IACvC,CAAC;IAED,IAAI,OAAO,MAAM,KAAK,QAAQ,EAAE,CAAC;QAC/B,OAAO,EAAE,GAAG,EAAE,MAAM,EAAE,CAAC;IACzB,CAAC;IACD,IAAI,OAAO,MAAM,KAAK,QAAQ,EAAE,CAAC;QAC/B,MAAM,KAAK,GAAG,KAAK,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC;QACvC,OAAO,qBAAqB,CAAC,KAAK,EAAE,EAAE,OAAO,EAAE,MAAM,EAAE,CAAC,CAAC;IAC3D,CAAC;IAED,IAAI,OAAO,MAAM,KAAK,QAAQ,EAAE,CAAC;QAC/B,IAAI,SAAS,IAAI,MAAM,IAAI,OAAO,MAAM,CAAC,OAAO,KAAK,QAAQ,EAAE,CAAC;YAC9D,MAAM,KAAK,GAAG,KAAK,CAAC,UAAU,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;YAC/C,OAAO;gBACL,GAAG,MAAM;gBACT,GAAG,EAAE,KAAK,CAAC,QAAQ,IAAI,KAAK,CAAC,GAAG;aACjC,CAAC;QACJ,CAAC;QACD,IAAI,KAAK,IAAI,MAAM,IAAI,OAAO,MAAM,CAAC,GAAG,KAAK,QAAQ,EAAE,CAAC;YACtD,OAAO,MAAM,CAAC;QAChB,CAAC;IACH,CAAC;IAED,OAAO,MAAM,IAAI,IAAI,CAAC;AACxB,CAAC;AAED;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,yBAAyB,CAC7C,MAA4C;IAE5C,MAAM,KAAK,GAAG,kBAAkB,CAAC,MAAM,CAAC,CAAC;IACzC,MAAM,cAAc,GAAG,aAAa,CAAC,MAAM,CAAC,CAAC;IAE7C,IAAI,KAAK,EAAE,CAAC;QACV,IAAI,eAAe,GAAU,KAAK,CAAC;QACnC,IAAI,CAAC;YACH,mFAAmF;YACnF,0FAA0F;YAC1F,sDAAsD;YACtD,iHAAiH;YACjH,IAAI,CAAC,eAAe,CAAC,IAAI,EAAE,CAAC;gBAC1B,eAAe,GAAG,IAAI,KAAK,CAAC;oBAC1B,IAAI,EAAE,KAAK,CAAC,IAAI;oBAChB,IAAI,EAAE,KAAK;oBACX,GAAG,EAAE,KAAK,CAAC,GAAG;iBACf,CAAC,CAAC;YACL,CAAC;YAED,2GAA2G;YAC3G,+EAA+E;YAC/E,MAAM,eAAe,CAAC,aAAa,EAAE,CAAC;YAEtC,gDAAgD;YAChD,IAAI,eAAe,CAAC,QAAQ,EAAE,CAAC;gBAC7B,MAAM,QAAQ,GAAG,eAAe,CAAC,QAAQ,CAAC;gBAE1C,IAAI,cAAc,IAAI,OAAO,cAAc,KAAK,QAAQ,EAAE,CAAC;oBACzD,OAAO;wBACL,GAAG,cAAc;wBACjB,GAAG,EAAE,QAAQ;qBACd,CAAC;gBACJ,CAAC;gBAED,OAAO,EAAE,GAAG,EAAE,QAAQ,EAAE,CAAC;YAC3B,CAAC;iBAAM,CAAC;gBACN,OAAO,CAAC,IAAI,CACV,0FAA0F,CAC3F,CAAC;YACJ,CAAC;QACH,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,kDAAkD;YAClD,OAAO,CAAC,IAAI,CAAC,wEAAwE,EAAE,KAAK,CAAC,CAAC;QAChG,CAAC;IACH,CAAC;IAED,gCAAgC;IAChC,OAAO,cAAc,CAAC;AACxB,CAAC","sourcesContent":["import { Asset } from 'expo-asset';\n\nimport { AudioSource } from '../Audio.types';\n\ntype AudioSourceObject = Exclude;\n\nfunction getAssetFromSource(source?: AudioSource | string | number | Asset | null): Asset | null {\n if (!source) {\n return null;\n }\n\n if (source instanceof Asset) {\n return source;\n }\n\n if (typeof source === 'number') {\n return Asset.fromModule(source);\n }\n\n if (typeof source === 'object') {\n if ('assetId' in source && typeof source.assetId === 'number') {\n return Asset.fromModule(source.assetId);\n }\n if ('uri' in source && typeof source.uri === 'string') {\n return Asset.fromURI(source.uri);\n }\n }\n\n if (typeof source === 'string') {\n return Asset.fromURI(source);\n }\n\n return null;\n}\n\nfunction createSourceFromAsset(\n asset: Asset,\n extras: { assetId?: number; headers?: Record } = {}\n): AudioSourceObject {\n const uri = asset.localUri ?? asset.uri;\n const result: AudioSourceObject = { uri };\n\n if (asset.name) {\n result.name = asset.name;\n }\n if (extras.assetId != null) {\n result.assetId = extras.assetId;\n }\n if (extras.headers) {\n result.headers = extras.headers;\n }\n\n return result;\n}\n\nexport function resolveSources(sources: AudioSource[]): NonNullable[] {\n return sources\n .map((source) => resolveSource(source))\n .filter((source): source is NonNullable => source != null);\n}\n\nexport function resolveSource(source?: AudioSource | string | number | null): AudioSource | null {\n if (source == null) {\n return null;\n }\n\n if (source instanceof Asset) {\n return createSourceFromAsset(source);\n }\n\n if (typeof source === 'string') {\n return { uri: source };\n }\n if (typeof source === 'number') {\n const asset = Asset.fromModule(source);\n return createSourceFromAsset(asset, { assetId: source });\n }\n\n if (typeof source === 'object') {\n if ('assetId' in source && typeof source.assetId === 'number') {\n const asset = Asset.fromModule(source.assetId);\n return {\n ...source,\n uri: asset.localUri ?? asset.uri,\n };\n }\n if ('uri' in source && typeof source.uri === 'string') {\n return source;\n }\n }\n\n return source ?? null;\n}\n\n/**\n * Resolves and optionally downloads an audio source before loading.\n * Similar to expo-av's getNativeSourceAndFullInitialStatusForLoadAsync but simplified for expo-audio.\n */\nexport async function resolveSourceWithDownload(\n source: AudioSource | string | number | null\n): Promise {\n const asset = getAssetFromSource(source);\n const fallbackSource = resolveSource(source);\n\n if (asset) {\n let assetToDownload: Asset = asset;\n try {\n // iOS AVPlayer fails to load the asset if the type is not set or can't be inferred\n // since this is an audio asset, we can safely set the type to mp3 or any other audio type\n // and iOS will be able to download and play the asset\n // Since expo-asset caches, this will only run once per asset, as long as the asset is not deleted from the cache\n if (!assetToDownload.type) {\n assetToDownload = new Asset({\n name: asset.name,\n type: 'mp3',\n uri: asset.uri,\n });\n }\n\n // FYI: downloadAsync is a no-op on web and immediately returns a promise that resolves to the original url\n // TODO(@hirbod): evaluate if we should implement downloadAsync for web instead\n await assetToDownload.downloadAsync();\n\n // Use the local URI if available after download\n if (assetToDownload.localUri) {\n const finalUri = assetToDownload.localUri;\n\n if (fallbackSource && typeof fallbackSource === 'object') {\n return {\n ...fallbackSource,\n uri: finalUri,\n };\n }\n\n return { uri: finalUri };\n } else {\n console.warn(\n 'No localUri found, asset may not have downloaded properly, returning the original source'\n );\n }\n } catch (error) {\n // If download fails, fall back to original source\n console.warn('expo-audio: Failed to download asset, falling back to original source:', error);\n }\n }\n\n // Fallback to normal resolution\n return fallbackSource;\n}\n"]}
\ No newline at end of file
diff --git a/packages/expo-audio/ios/AudioComponentRegistry.swift b/packages/expo-audio/ios/AudioComponentRegistry.swift
index 43e5adf2601257..0a3b94caebf8bd 100644
--- a/packages/expo-audio/ios/AudioComponentRegistry.swift
+++ b/packages/expo-audio/ios/AudioComponentRegistry.swift
@@ -1,8 +1,10 @@
+import AVFoundation
import Foundation
class AudioComponentRegistry {
private var players = [String: AudioPlayer]()
private var playlists = [String: AudioPlaylist]()
+ private var preloadedPlayers = [String: AVPlayer]()
#if os(iOS)
private var recorders = [String: AudioRecorder]()
#endif
@@ -105,4 +107,35 @@ class AudioComponentRegistry {
}
}
#endif
+
+ func addPreloadedPlayer(_ player: AVPlayer, forKey key: String) {
+ registryQueue.async(flags: .barrier) {
+ self.preloadedPlayers[key] = player
+ }
+ }
+
+ func hasPreloadedPlayer(forKey key: String) -> Bool {
+ return registryQueue.sync {
+ return preloadedPlayers[key] != nil
+ }
+ }
+
+ func removePreloadedPlayer(forKey key: String) -> AVPlayer? {
+ return registryQueue.sync(flags: .barrier) {
+ return preloadedPlayers.removeValue(forKey: key)
+ }
+ }
+
+ func removeAllPreloadedPlayers() {
+ registryQueue.async(flags: .barrier) {
+ self.preloadedPlayers.values.forEach { $0.replaceCurrentItem(with: nil) }
+ self.preloadedPlayers.removeAll()
+ }
+ }
+
+ func preloadedPlayerKeys() -> [String] {
+ return registryQueue.sync {
+ return Array(preloadedPlayers.keys)
+ }
+ }
}
diff --git a/packages/expo-audio/ios/AudioModule.swift b/packages/expo-audio/ios/AudioModule.swift
index 6c12073c398546..d2046b5185ff05 100644
--- a/packages/expo-audio/ios/AudioModule.swift
+++ b/packages/expo-audio/ios/AudioModule.swift
@@ -60,7 +60,37 @@ public class AudioModule: Module {
#endif
}
+ AsyncFunction("preload") { (source: AudioSource, preferredForwardBufferDuration: Double) in
+ guard let uri = source.uri else {
+ return
+ }
+ let key = uri.absoluteString
+ if self.registry.hasPreloadedPlayer(forKey: key) {
+ return
+ }
+ let player = AudioUtils.createAVPlayer(from: source)
+ player.currentItem?.preferredForwardBufferDuration = preferredForwardBufferDuration
+ self.registry.addPreloadedPlayer(player, forKey: key)
+ }
+
+ AsyncFunction("clearPreloadedSource") { (source: AudioSource) in
+ guard let uri = source.uri else {
+ return
+ }
+ let key = uri.absoluteString
+ _ = self.registry.removePreloadedPlayer(forKey: key)
+ }
+
+ AsyncFunction("clearAllPreloadedSources") {
+ self.registry.removeAllPreloadedPlayers()
+ }
+
+ AsyncFunction("getPreloadedSources") {
+ self.registry.preloadedPlayerKeys()
+ }
+
OnDestroy {
+ registry.removeAllPreloadedPlayers()
registry.removeAll()
NotificationCenter.default.removeObserver(self)
}
@@ -89,8 +119,16 @@ public class AudioModule: Module {
// swiftlint:disable:next closure_body_length
Class(AudioPlayer.self) {
- Constructor { (source: AudioSource?, updateInterval: Double, keepAudioSessionActive: Bool) -> AudioPlayer in
- let avPlayer = AudioUtils.createAVPlayer(from: source)
+ Constructor { (source: AudioSource?, updateInterval: Double, keepAudioSessionActive: Bool, preferredForwardBufferDuration: Double) -> AudioPlayer in
+ let avPlayer: AVPlayer
+ if let uri = source?.uri?.absoluteString, let cachedPlayer = self.registry.removePreloadedPlayer(forKey: uri) {
+ avPlayer = cachedPlayer
+ } else {
+ avPlayer = AudioUtils.createAVPlayer(from: source)
+ if preferredForwardBufferDuration > 0 {
+ avPlayer.currentItem?.preferredForwardBufferDuration = preferredForwardBufferDuration
+ }
+ }
let player = AudioPlayer(avPlayer, interval: updateInterval, source: source)
player.owningRegistry = self.registry
player.keepAudioSessionActive = keepAudioSessionActive
@@ -194,7 +232,13 @@ public class AudioModule: Module {
}
Function("replace") { (player, source: AudioSource) in
- player.replaceCurrentSource(source: source)
+ if let uri = source.uri?.absoluteString, let cachedPlayer = self.registry.removePreloadedPlayer(forKey: uri) {
+ let cachedItem = cachedPlayer.currentItem
+ cachedPlayer.replaceCurrentItem(with: nil)
+ player.replaceWithPreloadedItem(cachedItem)
+ } else {
+ player.replaceCurrentSource(source: source)
+ }
}
Function("pause") { player in
diff --git a/packages/expo-audio/ios/AudioPlayer.swift b/packages/expo-audio/ios/AudioPlayer.swift
index da439b1d507e02..368504335d3fcc 100644
--- a/packages/expo-audio/ios/AudioPlayer.swift
+++ b/packages/expo-audio/ios/AudioPlayer.swift
@@ -179,6 +179,25 @@ public class AudioPlayer: SharedRef {
.store(in: &cancellables)
}
+ func replaceWithPreloadedItem(_ item: AVPlayerItem?) {
+ let wasPlaying = ref.timeControlStatus == .playing
+ let wasSamplingEnabled = samplingEnabled
+ ref.pause()
+
+ if samplingEnabled {
+ uninstallTap()
+ }
+ ref.replaceCurrentItem(with: item)
+
+ if wasSamplingEnabled {
+ shouldInstallAudioTap = true
+ }
+
+ if wasPlaying {
+ ref.play()
+ }
+ }
+
func replaceCurrentSource(source: AudioSource) {
self.source = source
let wasPlaying = ref.timeControlStatus == .playing
@@ -188,8 +207,8 @@ public class AudioPlayer: SharedRef {
if samplingEnabled {
uninstallTap()
}
-
- ref.replaceCurrentItem(with: AudioUtils.createAVPlayerItem(from: source))
+ let item = AudioUtils.createAVPlayerItem(from: source)
+ ref.replaceCurrentItem(with: item)
if wasSamplingEnabled {
shouldInstallAudioTap = true
diff --git a/packages/expo-audio/ios/AudioUtils.swift b/packages/expo-audio/ios/AudioUtils.swift
index 995484c605576f..66916fb77c5f24 100644
--- a/packages/expo-audio/ios/AudioUtils.swift
+++ b/packages/expo-audio/ios/AudioUtils.swift
@@ -86,20 +86,7 @@ struct AudioUtils {
#endif
static func createAVPlayer(from source: AudioSource?) -> AVPlayer {
- if let source, let url = source.uri {
- let finalUrl = if url.isBase64Audio {
- handleBase64Asset(base64String: url.absoluteString) ?? url
- } else {
- url
- }
-
- var options: [String: Any]?
- if let headers = source.headers {
- options = ["AVURLAssetHTTPHeaderFieldsKey": headers]
- }
-
- let asset = AVURLAsset(url: finalUrl, options: options)
- let item = AVPlayerItem(asset: asset)
+ if let item = createAVPlayerItem(from: source) {
return AVPlayer(playerItem: item)
}
return AVPlayer()
@@ -121,7 +108,7 @@ struct AudioUtils {
}
let asset = AVURLAsset(url: finalUrl, options: options)
- return AVPlayerItem(asset: asset)
+ return AVPlayerItem(asset: asset, automaticallyLoadedAssetKeys: [.tracks, .duration])
}
static func createRecordingOptions(_ options: RecordingOptions) -> [String: Any] {
diff --git a/packages/expo-audio/ios/MediaController.swift b/packages/expo-audio/ios/MediaController.swift
index 2dd3fb2e5f2fe7..b13872f121a73c 100644
--- a/packages/expo-audio/ios/MediaController.swift
+++ b/packages/expo-audio/ios/MediaController.swift
@@ -11,7 +11,7 @@ class MediaController {
private var currentArtworkUrl: URL?
private var cachedArtwork: MPMediaItemArtwork?
-
+
func setActivePlayer(_ player: AudioPlayer?, options: LockScreenOptions? = nil) {
if let previous = activePlayer, previous.id != player?.id {
previous.isActiveForLockScreen = false
@@ -200,7 +200,7 @@ class MediaController {
return .success
}
-
+
remoteCommandCenter.playCommand.isEnabled = true
remoteCommandCenter.pauseCommand.isEnabled = true
remoteCommandCenter.togglePlayPauseCommand.isEnabled = true
@@ -216,13 +216,13 @@ class MediaController {
remoteCommandCenter.changePlaybackPositionCommand.isEnabled = false
remoteCommandCenter.skipForwardCommand.isEnabled = false
remoteCommandCenter.skipBackwardCommand.isEnabled = false
-
+
// Remove event targets
- remoteCommandCenter.playCommand.removeTarget(self);
- remoteCommandCenter.pauseCommand.removeTarget(self);
- remoteCommandCenter.togglePlayPauseCommand.removeTarget(self);
- remoteCommandCenter.changePlaybackPositionCommand.removeTarget(self);
- remoteCommandCenter.skipForwardCommand.removeTarget(self);
- remoteCommandCenter.skipBackwardCommand.removeTarget(self);
+ remoteCommandCenter.playCommand.removeTarget(self)
+ remoteCommandCenter.pauseCommand.removeTarget(self)
+ remoteCommandCenter.togglePlayPauseCommand.removeTarget(self)
+ remoteCommandCenter.changePlaybackPositionCommand.removeTarget(self)
+ remoteCommandCenter.skipForwardCommand.removeTarget(self)
+ remoteCommandCenter.skipBackwardCommand.removeTarget(self)
}
}
diff --git a/packages/expo-audio/src/Audio.types.ts b/packages/expo-audio/src/Audio.types.ts
index 7f522a9aa6d7e9..642269453660b0 100644
--- a/packages/expo-audio/src/Audio.types.ts
+++ b/packages/expo-audio/src/Audio.types.ts
@@ -124,6 +124,40 @@ export type AudioPlayerOptions = {
* @default false
*/
keepAudioSessionActive?: boolean;
+ /**
+ * The duration in seconds the player should buffer ahead of the current playback position.
+ * A higher value improves playback stability at the cost of more memory/network usage.
+ *
+ * - **iOS**: Maps to `AVPlayerItem.preferredForwardBufferDuration`. A value of `0` lets the system decide.
+ * - **Android**: Configures ExoPlayer's `DefaultLoadControl` max buffer duration.
+ * - **Web**: Not applicable (browser manages buffering).
+ *
+ * @default 0 (system default)
+ *
+ * @platform ios
+ * @platform android
+ */
+ preferredForwardBufferDuration?: number;
+};
+
+/**
+ * Options for configuring audio preloading behavior.
+ */
+export type PreloadOptions = {
+ /**
+ * The duration in seconds the player should buffer ahead of the current playback position.
+ * A higher value improves playback stability at the cost of more memory/network usage.
+ *
+ * - **iOS**: Maps to `AVPlayerItem.preferredForwardBufferDuration`. A value of `0` lets the system decide.
+ * - **Android**: Configures ExoPlayer's buffer duration.
+ * - **Web**: Not applicable (browser manages buffering).
+ *
+ * @default 10
+ *
+ * @platform ios
+ * @platform android
+ */
+ preferredForwardBufferDuration?: number;
};
/**
diff --git a/packages/expo-audio/src/AudioModule.types.ts b/packages/expo-audio/src/AudioModule.types.ts
index a0dae1d1427f48..f5b3deed4d15cf 100644
--- a/packages/expo-audio/src/AudioModule.types.ts
+++ b/packages/expo-audio/src/AudioModule.types.ts
@@ -26,6 +26,10 @@ export declare class NativeAudioModule extends NativeModule {
requestRecordingPermissionsAsync(): Promise;
requestNotificationPermissionsAsync(): Promise;
getRecordingPermissionsAsync(): Promise;
+ preload(source: AudioSource, preferredForwardBufferDuration: number): Promise;
+ clearPreloadedSource(source: AudioSource): Promise;
+ clearAllPreloadedSources(): Promise;
+ getPreloadedSources(): Promise;
readonly AudioPlayer: typeof AudioPlayer;
readonly AudioRecorder: typeof AudioRecorder;
@@ -37,7 +41,12 @@ export declare class AudioPlayer extends SharedObject {
* Initializes a new audio player instance with the given source.
* @hidden
*/
- constructor(source: AudioSource, updateInterval: number, keepAudioSessionActive: boolean);
+ constructor(
+ source: AudioSource,
+ updateInterval: number,
+ keepAudioSessionActive: boolean,
+ preferredForwardBufferDuration: number
+ );
/**
* Unique identifier for the player object.
diff --git a/packages/expo-audio/src/AudioModule.web.ts b/packages/expo-audio/src/AudioModule.web.ts
index 7bdfe1e91bff40..a1d8b8ce34edce 100644
--- a/packages/expo-audio/src/AudioModule.web.ts
+++ b/packages/expo-audio/src/AudioModule.web.ts
@@ -1,8 +1,8 @@
import { PermissionResponse, PermissionStatus } from 'expo-modules-core';
-import { AudioMode } from './Audio.types';
+import { AudioMode, AudioSource } from './Audio.types';
import { activePlayers } from './AudioPlayer.web';
-import { getUserMedia } from './AudioUtils.web';
+import { getUserMedia, getSourceUri, preloadCache } from './AudioUtils.web';
export { AudioPlayerWeb } from './AudioPlayer.web';
export { AudioPlaylistWeb } from './AudioPlaylist.web';
@@ -43,6 +43,47 @@ export async function setIsAudioActiveAsync(active: boolean) {
}
}
+export async function preloadAsync(source: AudioSource): Promise {
+ const uri = getSourceUri(source);
+ if (!uri || preloadCache.has(uri)) return;
+
+ const headers =
+ source && typeof source === 'object' && !Array.isArray(source) ? source.headers : undefined;
+
+ const response = await fetch(uri, headers ? { headers } : undefined);
+ const blob = await response.blob();
+ const blobUrl = URL.createObjectURL(blob);
+ const audio = new Audio(blobUrl);
+ audio.preload = 'auto';
+ preloadCache.set(uri, { blobUrl, audio });
+}
+
+export function preload(source: AudioSource): void {
+ preloadAsync(source).catch(() => {});
+}
+
+export function clearPreloadedSource(source: AudioSource): void {
+ const uri = getSourceUri(source);
+ if (!uri) return;
+
+ const cached = preloadCache.get(uri);
+ if (cached) {
+ URL.revokeObjectURL(cached.blobUrl);
+ preloadCache.delete(uri);
+ }
+}
+
+export function clearAllPreloadedSources(): void {
+ for (const cached of preloadCache.values()) {
+ URL.revokeObjectURL(cached.blobUrl);
+ }
+ preloadCache.clear();
+}
+
+export function getPreloadedSources(): string[] {
+ return Array.from(preloadCache.keys());
+}
+
export async function getRecordingPermissionsAsync(): Promise {
const maybeStatus = await getPermissionWithQueryAsync('microphone');
switch (maybeStatus) {
diff --git a/packages/expo-audio/src/AudioPlayer.web.ts b/packages/expo-audio/src/AudioPlayer.web.ts
index 38a72d37db11b9..1e651c9adb1818 100644
--- a/packages/expo-audio/src/AudioPlayer.web.ts
+++ b/packages/expo-audio/src/AudioPlayer.web.ts
@@ -9,7 +9,14 @@ import { AudioLockScreenOptions } from './AudioConstants';
import { AUDIO_SAMPLE_UPDATE, PLAYBACK_STATUS_UPDATE } from './AudioEventKeys';
import { AudioPlayer, AudioEvents } from './AudioModule.types';
import { isAudioActive } from './AudioModule.web';
-import { getAudioContext, getSourceUri, getStatusFromMedia, nextId } from './AudioUtils.web';
+import {
+ getAudioContext,
+ getSourceUri,
+ getStatusFromMedia,
+ nextId,
+ preloadCache,
+ safeDuration,
+} from './AudioUtils.web';
import { mediaSessionController } from './MediaSessionController.web';
export const activePlayers = new Set();
@@ -66,7 +73,7 @@ export class AudioPlayerWeb
}
get duration(): number {
- return this.media.duration;
+ return safeDuration(this.media.duration);
}
get currentTime(): number {
@@ -269,6 +276,10 @@ export class AudioPlayerWeb
activePlayers.delete(this);
}
+ release(): void {
+ this.remove();
+ }
+
setActiveForLockScreen(
active: boolean,
metadata?: AudioMetadata,
@@ -299,7 +310,9 @@ export class AudioPlayerWeb
_createMediaElement(): HTMLAudioElement {
const newSource = getSourceUri(this.src);
- const media = new Audio(newSource);
+ const cachedUri =
+ newSource && preloadCache.has(newSource) ? preloadCache.get(newSource)!.blobUrl : newSource;
+ const media = new Audio(cachedUri);
if (this.crossOrigin !== undefined) {
media.crossOrigin = this.crossOrigin;
}
diff --git a/packages/expo-audio/src/AudioUtils.web.ts b/packages/expo-audio/src/AudioUtils.web.ts
index c8fa526ad77bc5..4e2d28669363ad 100644
--- a/packages/expo-audio/src/AudioUtils.web.ts
+++ b/packages/expo-audio/src/AudioUtils.web.ts
@@ -46,6 +46,10 @@ export function getUserMedia(constraints: MediaStreamConstraints): Promise 0 &&
@@ -57,7 +61,7 @@ export function getStatusFromMedia(media: HTMLMediaElement, id: string): AudioSt
const status: AudioStatus = {
id,
isLoaded: true,
- duration: media.duration,
+ duration: safeDuration(media.duration),
currentTime: media.currentTime,
playbackState: '',
timeControlStatus: isPlaying ? 'playing' : 'paused',
@@ -74,6 +78,9 @@ export function getStatusFromMedia(media: HTMLMediaElement, id: string): AudioSt
return status;
}
+// Preload cache: maps original source URIs to pre-fetched blob URLs
+export const preloadCache = new Map();
+
export function getSourceUri(source: AudioSource): string | undefined {
if (typeof source === 'string') {
return source;
diff --git a/packages/expo-audio/src/ExpoAudio.ts b/packages/expo-audio/src/ExpoAudio.ts
index 07e058cf62e2c1..606dc799290f09 100644
--- a/packages/expo-audio/src/ExpoAudio.ts
+++ b/packages/expo-audio/src/ExpoAudio.ts
@@ -11,6 +11,7 @@ import {
AudioSource,
AudioStatus,
PitchCorrectionQuality,
+ PreloadOptions,
RecorderState,
RecordingOptions,
RecordingStartOptions,
@@ -102,7 +103,12 @@ export function useAudioPlayer(
source: AudioSource = null,
options: AudioPlayerOptions = {}
): AudioPlayer {
- const { updateInterval = 500, downloadFirst = false, keepAudioSessionActive = false } = options;
+ const {
+ updateInterval = 500,
+ downloadFirst = false,
+ keepAudioSessionActive = false,
+ preferredForwardBufferDuration = 0,
+ } = options;
// If downloadFirst is true, we don't need to resolve the source, because it will be resolved in the useEffect below.
// If downloadFirst is false, we resolve the source here.
@@ -112,8 +118,19 @@ export function useAudioPlayer(
}, [JSON.stringify(source), downloadFirst]);
const player = useReleasingSharedObject(
- () => new AudioModule.AudioPlayer(initialSource, updateInterval, keepAudioSessionActive),
- [JSON.stringify(initialSource), updateInterval, keepAudioSessionActive]
+ () =>
+ new AudioModule.AudioPlayer(
+ initialSource,
+ updateInterval,
+ keepAudioSessionActive,
+ preferredForwardBufferDuration
+ ),
+ [
+ JSON.stringify(initialSource),
+ updateInterval,
+ keepAudioSessionActive,
+ preferredForwardBufferDuration,
+ ]
);
// Handle async source resolution for downloadFirst
@@ -452,9 +469,19 @@ export function createAudioPlayer(
source: AudioSource | string | number | null = null,
options: AudioPlayerOptions = {}
): AudioPlayer {
- const { updateInterval = 500, downloadFirst = false, keepAudioSessionActive = false } = options;
+ const {
+ updateInterval = 500,
+ downloadFirst = false,
+ keepAudioSessionActive = false,
+ preferredForwardBufferDuration = 0,
+ } = options;
const initialSource = downloadFirst ? null : resolveSource(source);
- const player = new AudioModule.AudioPlayer(initialSource, updateInterval, keepAudioSessionActive);
+ const player = new AudioModule.AudioPlayer(
+ initialSource,
+ updateInterval,
+ keepAudioSessionActive,
+ preferredForwardBufferDuration
+ );
if (downloadFirst && source) {
resolveSourceWithDownload(source)
@@ -633,4 +660,69 @@ export async function getRecordingPermissionsAsync(): Promise player.play()} />;
+ * }
+ * ```
+ */
+export async function preload(source: AudioSource, options: PreloadOptions = {}): Promise {
+ const resolved = resolveSource(source);
+ if (!resolved) return;
+ const { preferredForwardBufferDuration = 10 } = options;
+ return AudioModule.preload(resolved, preferredForwardBufferDuration);
+}
+
+/**
+ * Releases a specific preloaded audio source to free memory.
+ *
+ * @param source The audio source to release. Must match the source previously passed to `preload()`.
+ */
+export async function clearPreloadedSource(source: AudioSource): Promise {
+ const resolved = resolveSource(source);
+ if (!resolved) return;
+ return AudioModule.clearPreloadedSource(resolved);
+}
+
+/**
+ * Releases all preloaded audio sources to free memory.
+ */
+export async function clearAllPreloadedSources(): Promise {
+ return AudioModule.clearAllPreloadedSources();
+}
+
+/**
+ * Returns the URIs of all currently preloaded audio sources.
+ *
+ * Sources are removed from this list when consumed by `useAudioPlayer()` or `createAudioPlayer()`,
+ * or when explicitly cleared with `clearPreloadedSource()` / `clearAllPreloadedSources()`.
+ *
+ * @returns An array of URI strings for sources currently in the preload cache.
+ */
+export async function getPreloadedSources(): Promise {
+ return AudioModule.getPreloadedSources();
+}
+
export { AudioModule };
diff --git a/packages/expo-audio/src/ExpoAudio.web.ts b/packages/expo-audio/src/ExpoAudio.web.ts
index 0f4bbca94025e3..c927b7b6ad4ca7 100644
--- a/packages/expo-audio/src/ExpoAudio.web.ts
+++ b/packages/expo-audio/src/ExpoAudio.web.ts
@@ -1,5 +1,5 @@
import { useEvent } from 'expo';
-import { PermissionResponse } from 'expo-modules-core';
+import { PermissionResponse, useReleasingSharedObject } from 'expo-modules-core';
import { useEffect, useState, useMemo } from 'react';
import {
@@ -9,6 +9,7 @@ import {
AudioPlaylistStatus,
AudioSource,
AudioStatus,
+ PreloadOptions,
RecorderState,
RecordingOptions,
RecordingStatus,
@@ -22,14 +23,7 @@ import {
import { AudioPlayer, AudioRecorder, AudioSample } from './AudioModule.types';
import * as AudioModule from './AudioModule.web';
import { createRecordingOptions } from './utils/options';
-import { resolveSource, resolveSources, resolveSourceWithDownload } from './utils/resolveSource';
-
-// Global registry for cleaning up object URLs when players are garbage collected
-// Since we are using blob urls, we need to clean them up when the player is garbage collected
-// this is only used for createAudioPlayer, as we have lifecycle management in useAudioPlayer
-const objectUrlRegistry = new FinalizationRegistry((objectUrl: string) => {
- URL.revokeObjectURL(objectUrl);
-});
+import { resolveSource, resolveSources } from './utils/resolveSource';
export function createAudioPlayer(
source: AudioSource | string | number | null = null,
@@ -42,31 +36,15 @@ export function createAudioPlayer(
const initialSource = downloadFirst ? null : resolveSource(source);
const player = new AudioModule.AudioPlayerWeb(initialSource, options);
- // we call .replace() on the player to replace the source with the downloaded one
- // only relevant if downloadFirst is true and source is not null
+ // Preload the source and replace the player's source with the cached blob URL.
+ // Only relevant if downloadFirst is true and source is not null.
if (downloadFirst && source) {
- resolveSourceWithDownload(source)
- .then((resolved) => {
- if (resolved) {
- // Register object URL for automatic cleanup when player is garbage collected
- if (
- resolved &&
- typeof resolved === 'object' &&
- resolved.uri &&
- resolved.uri.startsWith('blob:')
- ) {
- objectUrlRegistry.register(player, resolved.uri);
- }
- player.replace(resolved);
- }
- })
- .catch((error) => {
- console.warn('expo-audio: Failed to download source, using fallback:', error);
- const fallback = resolveSource(source);
- if (fallback) {
- player.replace(fallback);
- }
+ const resolved = resolveSource(source);
+ if (resolved) {
+ AudioModule.preloadAsync(resolved).finally(() => {
+ player.replace(resolved);
});
+ }
}
return player;
@@ -85,7 +63,7 @@ export function useAudioPlayer(
return downloadFirst ? null : resolveSource(source);
}, [JSON.stringify(source), downloadFirst]);
- const player = useMemo(
+ const player = useReleasingSharedObject(
() => new AudioModule.AudioPlayerWeb(initialSource, options),
[JSON.stringify(initialSource), JSON.stringify(options)]
);
@@ -97,44 +75,18 @@ export function useAudioPlayer(
}
let isCancelled = false;
- let objectUrl: string | null = null;
+ const resolved = resolveSource(source);
- // We resolve the source with expo-asset and replace the player's source with the downloaded one.
- async function resolveAndReplaceSource() {
- try {
- const resolved = await resolveSourceWithDownload(source);
- if (
- !isCancelled &&
- resolved &&
- JSON.stringify(resolved) !== JSON.stringify(initialSource)
- ) {
- // Track the object URL for cleanup
- if (
- resolved &&
- typeof resolved === 'object' &&
- resolved.uri &&
- resolved.uri.startsWith('blob:')
- ) {
- objectUrl = resolved.uri;
- }
- player.replace(resolved);
- }
- } catch (error) {
+ if (resolved) {
+ AudioModule.preloadAsync(resolved).finally(() => {
if (!isCancelled) {
- console.warn('expo-audio: Failed to download source, using original:', error);
+ player.replace(resolved);
}
- }
+ });
}
- resolveAndReplaceSource();
-
return () => {
isCancelled = true;
- player.remove();
- // Revoke the object URL created by this hook instance
- if (objectUrl) {
- URL.revokeObjectURL(objectUrl);
- }
};
}, [player, JSON.stringify(source), downloadFirst]);
@@ -264,4 +216,24 @@ export function createAudioPlaylist(
return new AudioModule.AudioPlaylistWeb(resolvedSources, updateInterval, loop, crossOrigin);
}
+export function preload(source: AudioSource, _options: PreloadOptions = {}): void {
+ const resolved = resolveSource(source);
+ if (!resolved) return;
+ AudioModule.preload(resolved);
+}
+
+export function clearPreloadedSource(source: AudioSource): void {
+ const resolved = resolveSource(source);
+ if (!resolved) return;
+ AudioModule.clearPreloadedSource(resolved);
+}
+
+export function clearAllPreloadedSources(): void {
+ AudioModule.clearAllPreloadedSources();
+}
+
+export function getPreloadedSources(): string[] {
+ return AudioModule.getPreloadedSources();
+}
+
export { AudioModule };
diff --git a/packages/expo-audio/src/utils/resolveSource.ts b/packages/expo-audio/src/utils/resolveSource.ts
index 7cf076ab364eac..176f6f411fb38d 100644
--- a/packages/expo-audio/src/utils/resolveSource.ts
+++ b/packages/expo-audio/src/utils/resolveSource.ts
@@ -1,5 +1,4 @@
import { Asset } from 'expo-asset';
-import { Platform } from 'expo-modules-core';
import { AudioSource } from '../Audio.types';
@@ -124,17 +123,7 @@ export async function resolveSourceWithDownload(
// Use the local URI if available after download
if (assetToDownload.localUri) {
- let finalUri = assetToDownload.localUri;
-
- // On web, we need to fetch the audio file and create a blob URL
- // this fully downloads the file to the user's device memory and makes it available for the user to play
- // fetch() is subject to CORS restrictions, so we need to document this for the users on web
- // TODO(@hirbod): evaluate if we should implement a downloadAsync for web instead of using fetch here
- if (Platform.OS === 'web') {
- const response = await fetch(assetToDownload.localUri);
- const blob = await response.blob();
- finalUri = URL.createObjectURL(blob);
- }
+ const finalUri = assetToDownload.localUri;
if (fallbackSource && typeof fallbackSource === 'object') {
return {
diff --git a/packages/expo-dev-launcher/ios/Assets.xcassets/radar-icon.imageset/Contents.json b/packages/expo-dev-launcher/ios/Assets.xcassets/radar-icon.imageset/Contents.json
new file mode 100644
index 00000000000000..38f83cf75926b5
--- /dev/null
+++ b/packages/expo-dev-launcher/ios/Assets.xcassets/radar-icon.imageset/Contents.json
@@ -0,0 +1,21 @@
+{
+ "images" : [
+ {
+ "filename" : "radar-icon.png",
+ "idiom" : "universal",
+ "scale" : "1x"
+ },
+ {
+ "idiom" : "universal",
+ "scale" : "2x"
+ },
+ {
+ "idiom" : "universal",
+ "scale" : "3x"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/packages/expo-dev-launcher/ios/Assets.xcassets/radar-icon.imageset/radar-icon.png b/packages/expo-dev-launcher/ios/Assets.xcassets/radar-icon.imageset/radar-icon.png
new file mode 100644
index 00000000000000..6f6a9c6469e9af
Binary files /dev/null and b/packages/expo-dev-launcher/ios/Assets.xcassets/radar-icon.imageset/radar-icon.png differ
diff --git a/packages/expo-dev-launcher/ios/SwiftUI/DevLauncherViewModel.swift b/packages/expo-dev-launcher/ios/SwiftUI/DevLauncherViewModel.swift
index 26400e30654014..ccdd546fe22de8 100644
--- a/packages/expo-dev-launcher/ios/SwiftUI/DevLauncherViewModel.swift
+++ b/packages/expo-dev-launcher/ios/SwiftUI/DevLauncherViewModel.swift
@@ -14,6 +14,8 @@ private let networkPermissionGrantedKey = "expo.devlauncher.hasGrantedNetworkPer
enum LocalNetworkPermissionStatus: Equatable, Sendable {
case unknown
+ case checking
+ case granted
case denied
}
@@ -217,17 +219,69 @@ class DevLauncherViewModel: ObservableObject {
func markNetworkPermissionGranted() {
UserDefaults.standard.set(true, forKey: networkPermissionGrantedKey)
- }
-
- func resetPermissionFlowState() {
- UserDefaults.standard.removeObject(forKey: networkPermissionGrantedKey)
- permissionStatus = .unknown
+ permissionStatus = .granted
}
var hasGrantedNetworkPermission: Bool {
UserDefaults.standard.bool(forKey: networkPermissionGrantedKey)
}
+ func refreshPermissionStatus() {
+ permissionStatus = .checking
+ Task {
+ let hasAccess = await checkLocalNetworkAccess()
+ permissionStatus = hasAccess ? .granted : .denied
+ }
+ }
+
+ func checkLocalNetworkAccess() async -> Bool {
+ let serviceType = BONJOUR_TYPE
+ let queue = DispatchQueue(label: "expo.devlauncher.permissioncheck")
+
+ return await withCheckedContinuation { continuation in
+ var done = false
+
+ let listener = try? NWListener(using: .tcp, on: .any)
+ listener?.service = NWListener.Service(type: serviceType)
+ listener?.stateUpdateHandler = { _ in }
+ listener?.newConnectionHandler = { $0.cancel() }
+ listener?.start(queue: queue)
+
+ let browser = NWBrowser(for: .bonjour(type: serviceType, domain: nil), using: .tcp)
+ browser.browseResultsChangedHandler = { results, _ in
+ guard !done else { return }
+ if !results.isEmpty {
+ done = true
+ continuation.resume(returning: true)
+ browser.cancel()
+ listener?.cancel()
+ }
+ }
+
+ browser.stateUpdateHandler = { state in
+ guard !done else { return }
+ if case .waiting(let error) = state,
+ case .dns(let dnsError) = error,
+ dnsError == kDNSServiceErr_PolicyDenied {
+ done = true
+ continuation.resume(returning: false)
+ browser.cancel()
+ listener?.cancel()
+ }
+ }
+
+ browser.start(queue: queue)
+
+ queue.asyncAfter(deadline: .now() + 2) {
+ guard !done else { return }
+ done = true
+ continuation.resume(returning: false)
+ browser.cancel()
+ listener?.cancel()
+ }
+ }
+ }
+
func stopServerDiscovery() {
pingTask?.cancel()
scanTask?.cancel()
@@ -268,8 +322,6 @@ class DevLauncherViewModel: ObservableObject {
Task { @MainActor [weak self] in
guard let self else { return }
switch state {
- case .ready:
- self.markNetworkPermissionGranted()
case .waiting(let error):
if case .dns(let dnsError) = error, dnsError == kDNSServiceErr_PolicyDenied {
self.permissionStatus = .denied
@@ -288,6 +340,7 @@ class DevLauncherViewModel: ObservableObject {
guard let self = self else { return }
Task { @MainActor [weak self, results] in
guard let self else { return }
+ self.markNetworkPermissionGranted()
self.pingTask?.cancel()
self.pingTask = Task {
defer { self.pingTask = nil }
diff --git a/packages/expo-dev-launcher/ios/SwiftUI/DevLauncherViews.swift b/packages/expo-dev-launcher/ios/SwiftUI/DevLauncherViews.swift
index 2d677a27d66a0c..f7e8d3cf0fe69f 100644
--- a/packages/expo-dev-launcher/ios/SwiftUI/DevLauncherViews.swift
+++ b/packages/expo-dev-launcher/ios/SwiftUI/DevLauncherViews.swift
@@ -24,7 +24,7 @@ public struct DevLauncherRootView: View {
public var body: some View {
if !hasCompletedPermissionFlow {
- LocalNetworkPermissionView {
+ LocalNetworkPermissionView(viewModel: viewModel) {
hasCompletedPermissionFlow = true
}
} else {
diff --git a/packages/expo-dev-launcher/ios/SwiftUI/HomeTabView.swift b/packages/expo-dev-launcher/ios/SwiftUI/HomeTabView.swift
index 837f98f8131390..7ed647351ef944 100644
--- a/packages/expo-dev-launcher/ios/SwiftUI/HomeTabView.swift
+++ b/packages/expo-dev-launcher/ios/SwiftUI/HomeTabView.swift
@@ -17,6 +17,12 @@ struct HomeTabView: View {
crashReportBanner
}
+ #if !targetEnvironment(simulator)
+ if viewModel.permissionStatus == .denied {
+ NetworkPermissionsBanner()
+ }
+ #endif
+
DevServersView(showingInfoDialog: $showingInfoDialog)
if !viewModel.recentlyOpenedApps.isEmpty {
@@ -76,6 +82,41 @@ struct HomeTabView: View {
}
}
+struct NetworkPermissionsBanner: View {
+ var body: some View {
+ Button {
+#if os(iOS)
+ if let url = URL(string: UIApplication.openSettingsURLString) {
+ UIApplication.shared.open(url)
+ }
+#endif
+ } label: {
+ HStack {
+ Image(systemName: "wifi.exclamationmark")
+ .font(.title2)
+ .foregroundColor(.orange)
+ VStack(alignment: .leading, spacing: 4) {
+ Text("Local Network Access Disabled")
+ .font(.subheadline)
+ .fontWeight(.semibold)
+ .foregroundColor(.primary)
+ Text("Dev servers can't be discovered. Tap to open Settings and enable Local Network access.")
+ .font(.footnote)
+ .foregroundColor(.secondary)
+ .multilineTextAlignment(.leading)
+ }
+ Spacer()
+ Image(systemName: "gear")
+ .foregroundColor(.secondary)
+ }
+ .padding()
+ }
+ .buttonStyle(PlainButtonStyle())
+ .background(Color.expoSecondarySystemGroupedBackground)
+ .cornerRadius(18)
+ }
+}
+
#Preview {
HomeTabView()
}
diff --git a/packages/expo-dev-launcher/ios/SwiftUI/LocalNetworkPermissionView.swift b/packages/expo-dev-launcher/ios/SwiftUI/LocalNetworkPermissionView.swift
index d83f5f515fa12d..032c71442e8b74 100644
--- a/packages/expo-dev-launcher/ios/SwiftUI/LocalNetworkPermissionView.swift
+++ b/packages/expo-dev-launcher/ios/SwiftUI/LocalNetworkPermissionView.swift
@@ -3,75 +3,207 @@
import SwiftUI
struct LocalNetworkPermissionView: View {
+ @ObservedObject var viewModel: DevLauncherViewModel
let onContinue: () -> Void
+ @State private var hasRequestedPermission = false
+ @State private var isCheckingAccess = false
+ @State private var showNoAccessMessage = false
+ @State private var showTryAgainFailedAlert = false
+ @State private var showAlreadyGrantedAlert = false
+
+ private var isDenied: Bool {
+ viewModel.permissionStatus == .denied
+ }
+
var body: some View {
VStack(spacing: 0) {
Spacer()
VStack(spacing: 24) {
- Image(systemName: "wifi")
- .font(.system(size: 64))
- .foregroundColor(.accentColor)
+ Image("radar-icon", bundle: getDevLauncherBundle())
+ .resizable()
+ .scaledToFit()
+ .frame(width: 80, height: 80)
+ Text("Finding Dev Servers")
+ .font(.title)
+ .fontWeight(.bold)
+
+ Text("Expo Dev Launcher needs to access your local network to discover development servers running on your computer.")
+ .font(.body)
+ .foregroundColor(.secondary)
+ .multilineTextAlignment(.center)
+
VStack(spacing: 12) {
- Text("Find Dev Servers")
- .font(.title)
- .fontWeight(.bold)
-
- Text("Expo Dev Launcher needs to access your local network to discover development servers running on your computer.")
- .font(.body)
- .foregroundColor(.secondary)
- .multilineTextAlignment(.center)
+ if !hasRequestedPermission {
+ continueButton
+ } else if isDenied || showNoAccessMessage {
+ noAccessButtons
+ } else {
+ postRequestButtons
+ }
}
+ }
- HStack(alignment: .center, spacing: 8) {
- Image(systemName: "info.circle")
- Text("You'll see a system prompt asking for local network access.\nTap \"Allow\" to continue.")
- .multilineTextAlignment(.center)
+ Spacer()
+
+ HStack(alignment: .firstTextBaseline) {
+ Image(systemName: "info.circle")
+ Text("Dev servers advertise themselves on your local network using Bonjour. This permission allows the development client to discover them automatically.")
+ .fontWeight(.semibold)
+ }
+ .foregroundColor(.secondary)
+ }
+ .padding()
+ .background(Color.expoSystemBackground)
+ .alert("Permission Not Granted", isPresented: $showTryAgainFailedAlert) {
+ Button("Open Settings") {
+ #if os(iOS)
+ if let url = URL(string: UIApplication.openSettingsURLString) {
+ UIApplication.shared.open(url)
}
- .frame(maxWidth: .infinity)
- .padding()
- .background(Color.expoSecondarySystemBackground)
- .cornerRadius(12)
- .font(.callout)
+ #endif
+ }
+ Button("OK", role: .cancel) {}
+ } message: {
+ Text("Local network access is still disabled. To discover dev servers, enable it in Settings \u{2192} Privacy & Security \u{2192} Local Network.")
+ }
+ .alert("Permission Already Granted", isPresented: $showAlreadyGrantedAlert) {
+ Button("OK") {
+ onContinue()
+ }
+ } message: {
+ Text("Local network access is already enabled. You're all set!")
+ .multilineTextAlignment(.center)
+ }
+ }
+
+ private var continueButton: some View {
+ Group {
+ Button {
+ viewModel.startServerDiscovery()
+ hasRequestedPermission = true
+ } label: {
+ Text("Next")
+ .fontWeight(.semibold)
+ .frame(maxWidth: .infinity)
+ .padding()
+ }
+ .background(Color.accentColor)
+ .foregroundColor(.white)
+ .cornerRadius(12)
+
+ Text("When the system prompt pops up, tap \"Allow\" to continue.")
+ .font(.footnote)
.foregroundColor(.secondary)
+ .multilineTextAlignment(.center)
+ }
+ }
- Button {
+ private var postRequestButtons: some View {
+ Group {
+ Button {
+ onContinue()
+ } label: {
+ Text("Done")
+ .fontWeight(.semibold)
+ .frame(maxWidth: .infinity)
+ .padding()
+ }
+ .background(Color.accentColor)
+ .foregroundColor(.white)
+ .cornerRadius(12)
+
+ checkAccessButton(label: "I was not prompted") { hasAccess in
+ if hasAccess {
+ showAlreadyGrantedAlert = true
+ } else {
+ showNoAccessMessage = true
+ showTryAgainFailedAlert = true
+ }
+ }
+ }
+ }
+
+ private var noAccessButtons: some View {
+ Group {
+ Text("Local network permission was not granted. Please enable it in Settings \u{2192} Privacy & Security \u{2192} Local Network.")
+ .font(.footnote)
+ .foregroundColor(.secondary)
+ .multilineTextAlignment(.center)
+
+ Button {
+ #if os(iOS)
+ if let url = URL(string: UIApplication.openSettingsURLString) {
+ UIApplication.shared.open(url)
+ }
+ #endif
+ } label: {
+ Text("Open Settings")
+ .fontWeight(.semibold)
+ .frame(maxWidth: .infinity)
+ .padding()
+ }
+ .background(Color.expoSecondarySystemBackground)
+ .foregroundColor(.primary)
+ .cornerRadius(12)
+
+ checkAccessButton(label: "Try Again") { hasAccess in
+ if hasAccess {
+ viewModel.startServerDiscovery()
onContinue()
- } label: {
- Text("Continue")
- .fontWeight(.semibold)
- .frame(maxWidth: .infinity)
- .padding()
+ } else {
+ showTryAgainFailedAlert = true
}
- .background(Color.accentColor)
- .foregroundColor(.white)
- .cornerRadius(12)
}
- Spacer()
+ Button {
+ onContinue()
+ } label: {
+ Text("Continue Anyway")
+ .fontWeight(.semibold)
+ .frame(maxWidth: .infinity)
+ .padding()
+ }
+ .background(Color.expoSecondarySystemBackground)
+ .foregroundColor(.secondary)
+ .cornerRadius(12)
+ }
+ }
- VStack(spacing: 4) {
- Text("Why is this needed?")
- .font(.footnote)
- .fontWeight(.medium)
- Text("Dev servers advertise themselves on your local network using Bonjour. This permission allows the app to discover them automatically.")
- .font(.caption)
- .foregroundColor(.secondary)
- .multilineTextAlignment(.center)
+ private func checkAccessButton(label: String, onResult: @escaping (Bool) -> Void) -> some View {
+ Button {
+ isCheckingAccess = true
+ viewModel.stopServerDiscovery()
+ Task {
+ let hasAccess = await viewModel.checkLocalNetworkAccess()
+ isCheckingAccess = false
+ onResult(hasAccess)
+ }
+ } label: {
+ if isCheckingAccess {
+ ProgressView()
+ .frame(maxWidth: .infinity)
+ .padding()
+ } else {
+ Text(label)
+ .fontWeight(.semibold)
+ .frame(maxWidth: .infinity)
+ .padding()
}
}
- .padding(.horizontal, 24)
- .padding(.vertical, 32)
- .background(Color.expoSystemBackground)
+ .disabled(isCheckingAccess)
+ .background(Color.expoSecondarySystemBackground)
+ .foregroundColor(.secondary)
+ .cornerRadius(12)
}
}
#if DEBUG
struct LocalNetworkPermissionView_Previews: PreviewProvider {
static var previews: some View {
- LocalNetworkPermissionView(onContinue: {})
+ LocalNetworkPermissionView(viewModel: DevLauncherViewModel(), onContinue: {})
}
}
#endif
diff --git a/packages/expo-dev-launcher/ios/SwiftUI/SettingsTabView.swift b/packages/expo-dev-launcher/ios/SwiftUI/SettingsTabView.swift
index ede6a5bc3bf39c..7899349c026e0d 100644
--- a/packages/expo-dev-launcher/ios/SwiftUI/SettingsTabView.swift
+++ b/packages/expo-dev-launcher/ios/SwiftUI/SettingsTabView.swift
@@ -11,8 +11,6 @@ struct SettingsTabView: View {
@State private var showCopiedMessage = false
@State private var defaultPageSize: Int = 10
@State private var showCacheClearedMessage = false
- @State private var permissionCheckResult: String = "Not checked"
- @State private var isCheckingPermission = false
private func createBuildInfoJSON() -> String {
let buildInfoDict: [String: Any] = [
@@ -41,6 +39,10 @@ struct SettingsTabView: View {
.font(.system(size: 13))
.foregroundStyle(.secondary)
+ #if !targetEnvironment(simulator)
+ localNetworkDebugSettings
+ #endif
+
VStack(alignment: .leading, spacing: 8) {
Text("system".uppercased())
.font(.caption)
@@ -51,10 +53,6 @@ struct SettingsTabView: View {
copyToClipboardButton
}
- #if DEBUG
- localNetworkDebugSettings
- #endif
-
if isAdminUser {
debugSettings
easUpdateConfig
@@ -69,11 +67,12 @@ struct SettingsTabView: View {
#if !os(macOS)
.navigationBarHidden(true)
#endif
- #if DEBUG
- .onChange(of: viewModel.permissionStatus) { _ in
- if isCheckingPermission {
- updatePermissionResultFromStatus()
- }
+ #if os(iOS) && !targetEnvironment(simulator)
+ .task {
+ viewModel.refreshPermissionStatus()
+ }
+ .onReceive(NotificationCenter.default.publisher(for: UIApplication.willEnterForegroundNotification)) { _ in
+ viewModel.refreshPermissionStatus()
}
#endif
}
@@ -238,114 +237,38 @@ struct SettingsTabView: View {
"""
}
- #if DEBUG
+ #if !targetEnvironment(simulator)
private var localNetworkDebugSettings: some View {
VStack(alignment: .leading, spacing: 8) {
- Text("Local Network Permission".uppercased())
- .font(.caption)
- .foregroundColor(.primary.opacity(0.6))
-
VStack(spacing: 0) {
- HStack {
- Text("Permission Status")
- Spacer()
- Text(permissionCheckResult)
- .foregroundColor(.secondary)
- }
- .padding()
-
- Divider()
+ Toggle("Local Network", isOn: .constant(viewModel.permissionStatus == .granted))
+ .disabled(true)
+ .padding()
- HStack {
- Text("First Launch Check")
- Spacer()
- Text(viewModel.hasGrantedNetworkPermission ? "Granted" : "Pending")
- .foregroundColor(.secondary)
- }
- .padding()
-
- Divider()
+ if viewModel.permissionStatus == .denied {
+ Divider()
- Button {
- checkNetworkPermission()
- } label: {
- HStack {
- if isCheckingPermission {
- ProgressView()
- .scaleEffect(0.8)
+ #if os(iOS)
+ Button {
+ if let url = URL(string: UIApplication.openSettingsURLString) {
+ UIApplication.shared.open(url)
+ }
+ } label: {
+ HStack {
+ Text("Open App Settings")
+ Spacer()
+ Image(systemName: "gear")
+ .foregroundColor(.blue)
}
- Text(isCheckingPermission ? "Checking..." : "Check Permission Now")
- Spacer()
- Image(systemName: "wifi")
- .foregroundColor(.blue)
- }
- }
- .disabled(isCheckingPermission)
- .padding()
-
- Divider()
-
- Button {
- resetPermissionFlow()
- } label: {
- HStack {
- Text("Reset Permission Flow")
- Spacer()
- Image(systemName: "arrow.counterclockwise")
- .foregroundColor(.orange)
- }
- }
- .padding()
-
- Divider()
-
- #if os(iOS)
- Button {
- if let url = URL(string: UIApplication.openSettingsURLString) {
- UIApplication.shared.open(url)
- }
- } label: {
- HStack {
- Text("Open App Settings")
- Spacer()
- Image(systemName: "gear")
- .foregroundColor(.blue)
}
+ .padding()
+ #endif
}
- .padding()
- #endif
}
.background(Color.expoSecondarySystemBackground)
.cornerRadius(12)
-
- Text("Use these tools to debug local network permission flow. 'Reset Permission Flow' will show the pre-flight screen again on next launch.")
- .font(.system(size: 13))
- .foregroundStyle(.secondary)
}
}
-
- private func checkNetworkPermission() {
- isCheckingPermission = true
- permissionCheckResult = "Checking..."
- viewModel.stopServerDiscovery()
- viewModel.startServerDiscovery()
- }
-
- private func updatePermissionResultFromStatus() {
- isCheckingPermission = false
- if viewModel.hasGrantedNetworkPermission {
- permissionCheckResult = "✅ Granted"
- } else if viewModel.permissionStatus == .denied {
- permissionCheckResult = "❌ Denied"
- } else {
- permissionCheckResult = "⚠️ Unknown"
- }
- }
-
- private func resetPermissionFlow() {
- viewModel.resetPermissionFlowState()
- permissionCheckResult = "Not checked"
- }
#endif
}
diff --git a/packages/expo-image-picker/android/src/main/java/expo/modules/imagepicker/ImagePickerModule.kt b/packages/expo-image-picker/android/src/main/java/expo/modules/imagepicker/ImagePickerModule.kt
index 03dee49bb6b4c7..2accee30be33b9 100644
--- a/packages/expo-image-picker/android/src/main/java/expo/modules/imagepicker/ImagePickerModule.kt
+++ b/packages/expo-image-picker/android/src/main/java/expo/modules/imagepicker/ImagePickerModule.kt
@@ -8,7 +8,6 @@ import android.os.Build
import android.os.Bundle
import android.os.OperationCanceledException
import androidx.core.content.ContextCompat
-import expo.modules.core.errors.ModuleNotFoundException
import expo.modules.imagepicker.contracts.CameraContract
import expo.modules.imagepicker.contracts.CameraContractOptions
import expo.modules.imagepicker.contracts.CropImageContract
@@ -269,7 +268,10 @@ class ImagePickerModule : Module() {
}
private suspend fun ensureCameraPermissionsAreGranted(): Unit = suspendCancellableCoroutine { continuation ->
- val permissions = appContext.permissions ?: throw ModuleNotFoundException("Permissions")
+ val permissions = appContext.permissions ?: run {
+ continuation.resumeWithException(Exceptions.ModuleNotFound(Permissions::class))
+ return@suspendCancellableCoroutine
+ }
permissions.askForPermissions(
{ permissionsResponse ->
diff --git a/packages/expo-image/CHANGELOG.md b/packages/expo-image/CHANGELOG.md
index e7a7d3a788f953..2bb85a4fae829e 100644
--- a/packages/expo-image/CHANGELOG.md
+++ b/packages/expo-image/CHANGELOG.md
@@ -8,6 +8,8 @@
### 🐛 Bug fixes
+- [Android] Uses shared cookie jar for image requests. ([#43257](https://github.com/expo/expo/pull/43257) by [@alanjhughes](https://github.com/alanjhughes))
+
### 💡 Others
## 55.0.3 — 2026-01-27
diff --git a/packages/expo-image/android/src/main/java/expo/modules/image/okhttp/ExpoImageOkHttpClientGlideModule.kt b/packages/expo-image/android/src/main/java/expo/modules/image/okhttp/ExpoImageOkHttpClientGlideModule.kt
index 071907c97adcd8..74c6a3da9f7de2 100644
--- a/packages/expo-image/android/src/main/java/expo/modules/image/okhttp/ExpoImageOkHttpClientGlideModule.kt
+++ b/packages/expo-image/android/src/main/java/expo/modules/image/okhttp/ExpoImageOkHttpClientGlideModule.kt
@@ -1,6 +1,7 @@
package expo.modules.image.okhttp
import android.content.Context
+import android.webkit.CookieManager
import com.bumptech.glide.Glide
import com.bumptech.glide.Registry
import com.bumptech.glide.annotation.GlideModule
@@ -9,6 +10,9 @@ import com.bumptech.glide.load.model.GlideUrl
import com.bumptech.glide.load.model.Headers
import com.bumptech.glide.module.LibraryGlideModule
import expo.modules.image.events.OkHttpProgressListener
+import okhttp3.Cookie
+import okhttp3.CookieJar
+import okhttp3.HttpUrl
import okhttp3.OkHttpClient
import java.io.InputStream
@@ -68,10 +72,33 @@ data class GlideUrlWrapper(val glideUrl: GlideUrl) {
}
}
+private object SharedCookieJar : CookieJar {
+ override fun saveFromResponse(url: HttpUrl, cookies: List) {
+ val cookieManager = getCookieManager() ?: return
+ val urlString = url.toString()
+ for (cookie in cookies) {
+ cookieManager.setCookie(urlString, cookie.toString())
+ }
+ cookieManager.flush()
+ }
+
+ override fun loadForRequest(url: HttpUrl): List {
+ val cookieManager = getCookieManager() ?: return emptyList()
+ val cookieString = cookieManager.getCookie(url.toString()) ?: return emptyList()
+ return cookieString.split(";").mapNotNull { Cookie.parse(url, it.trim()) }
+ }
+
+ private fun getCookieManager(): CookieManager? = runCatching {
+ CookieManager.getInstance()
+ }.getOrNull()
+}
+
@GlideModule
class ExpoImageOkHttpClientGlideModule : LibraryGlideModule() {
override fun registerComponents(context: Context, glide: Glide, registry: Registry) {
- val client = OkHttpClient()
+ val client = OkHttpClient.Builder()
+ .cookieJar(SharedCookieJar)
+ .build()
// We don't use the `GlideUrl` directly but we want to replace the default okhttp loader anyway
// to make sure that the app will use only one client.
registry.replace(GlideUrl::class.java, InputStream::class.java, OkHttpUrlLoader.Factory(client))
diff --git a/packages/expo-location/android/src/main/java/expo/modules/location/taskConsumers/LocationTaskConsumer.kt b/packages/expo-location/android/src/main/java/expo/modules/location/taskConsumers/LocationTaskConsumer.kt
index 2ea62db1ef10f7..b4f6c06ab28e48 100644
--- a/packages/expo-location/android/src/main/java/expo/modules/location/taskConsumers/LocationTaskConsumer.kt
+++ b/packages/expo-location/android/src/main/java/expo/modules/location/taskConsumers/LocationTaskConsumer.kt
@@ -17,10 +17,8 @@ import com.google.android.gms.location.FusedLocationProviderClient
import com.google.android.gms.location.LocationRequest
import com.google.android.gms.location.LocationResult
import com.google.android.gms.location.LocationServices
-import expo.modules.core.MapHelper
import expo.modules.core.arguments.MapArguments
import expo.modules.core.arguments.ReadableArguments
-import expo.modules.core.interfaces.Arguments
import expo.modules.core.interfaces.LifecycleEventListener
import expo.modules.interfaces.taskManager.TaskConsumer
import expo.modules.interfaces.taskManager.TaskConsumerInterface
@@ -297,7 +295,7 @@ class LocationTaskConsumer(context: Context, taskManagerUtils: TaskManagerUtilsI
sLastTimestamp = timestamp
}
}
- if (data.size > 0) {
+ if (data.isNotEmpty()) {
// Save last reported location, reset the distance and clear a list of locations.
mLastReportedLocation = mDeferredLocations[mDeferredLocations.size - 1]
mDeferredDistance = 0.0
@@ -310,7 +308,7 @@ class LocationTaskConsumer(context: Context, taskManagerUtils: TaskManagerUtilsI
private fun shouldReportDeferredLocations(): Boolean {
val task = mTask ?: return false
- if (mDeferredLocations.size == 0) {
+ if (mDeferredLocations.isEmpty()) {
return false
}
if (!mIsHostPaused) {
@@ -319,9 +317,8 @@ class LocationTaskConsumer(context: Context, taskManagerUtils: TaskManagerUtilsI
}
val oldestLocation = mLastReportedLocation ?: mDeferredLocations[0]
val newestLocation = mDeferredLocations[mDeferredLocations.size - 1]
- val options: Arguments = MapHelper(task.options)
- val distance = options.getDouble("deferredUpdatesDistance")
- val interval = options.getLong("deferredUpdatesInterval")
+ val distance = (task.options["deferredUpdatesDistance"] as? Number)?.toDouble() ?: 0.0
+ val interval = (task.options["deferredUpdatesInterval"] as? Number)?.toLong() ?: 0L
return newestLocation.time - oldestLocation.time >= interval && mDeferredDistance >= distance
}
@@ -330,7 +327,7 @@ class LocationTaskConsumer(context: Context, taskManagerUtils: TaskManagerUtilsI
}
private fun executeTaskWithLocationBundles(locationBundles: ArrayList, callback: TaskExecutionCallback) {
- if (locationBundles.size > 0 && mTask != null) {
+ if (locationBundles.isNotEmpty() && mTask != null) {
val data = Bundle()
data.putParcelableArrayList("locations", locationBundles)
mTask?.execute(data, null, callback)
diff --git a/packages/expo-maps/android/src/main/java/expo/modules/maps/GoogleStreetView.kt b/packages/expo-maps/android/src/main/java/expo/modules/maps/GoogleStreetView.kt
index c07e7081e1d079..7d72e01ebbac52 100644
--- a/packages/expo-maps/android/src/main/java/expo/modules/maps/GoogleStreetView.kt
+++ b/packages/expo-maps/android/src/main/java/expo/modules/maps/GoogleStreetView.kt
@@ -5,7 +5,6 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.key
import androidx.compose.runtime.mutableStateOf
-import androidx.compose.ui.Modifier
import com.google.android.gms.maps.StreetViewPanoramaOptions
import com.google.android.gms.maps.model.StreetViewPanoramaCamera
import com.google.maps.android.compose.streetview.StreetView
diff --git a/packages/expo-modules-core/android/src/main/java/expo/modules/adapters/react/apploader/RNHeadlessAppLoader.kt b/packages/expo-modules-core/android/src/main/java/expo/modules/adapters/react/apploader/RNHeadlessAppLoader.kt
index 254f85c69e8a11..9ba0bfba4bf6f2 100644
--- a/packages/expo-modules-core/android/src/main/java/expo/modules/adapters/react/apploader/RNHeadlessAppLoader.kt
+++ b/packages/expo-modules-core/android/src/main/java/expo/modules/adapters/react/apploader/RNHeadlessAppLoader.kt
@@ -12,7 +12,7 @@ import expo.modules.core.interfaces.DoNotStrip
private val appRecords: MutableMap = mutableMapOf()
-class RNHeadlessAppLoader @DoNotStrip constructor(private val context: Context) : HeadlessAppLoader {
+class RNHeadlessAppLoader @DoNotStrip constructor() : HeadlessAppLoader {
//region HeadlessAppLoader
diff --git a/packages/expo-modules-core/android/src/main/java/expo/modules/apploader/AppLoaderPackagesProviderInterface.java b/packages/expo-modules-core/android/src/main/java/expo/modules/apploader/AppLoaderPackagesProviderInterface.java
deleted file mode 100644
index 606c77d8e27834..00000000000000
--- a/packages/expo-modules-core/android/src/main/java/expo/modules/apploader/AppLoaderPackagesProviderInterface.java
+++ /dev/null
@@ -1,17 +0,0 @@
-package expo.modules.apploader;
-
-import java.util.List;
-
-import expo.modules.core.interfaces.Package;
-
-public interface AppLoaderPackagesProviderInterface {
- /**
- * Returns a list of React Native packages to load.
- */
- List getPackages();
-
- /**
- * Returns a list of Expo packages to load.
- */
- List getExpoPackages();
-}
diff --git a/packages/expo-modules-core/android/src/main/java/expo/modules/apploader/AppLoaderProvider.kt b/packages/expo-modules-core/android/src/main/java/expo/modules/apploader/AppLoaderProvider.kt
index bfb2689771d205..60cc512d6a3cb6 100644
--- a/packages/expo-modules-core/android/src/main/java/expo/modules/apploader/AppLoaderProvider.kt
+++ b/packages/expo-modules-core/android/src/main/java/expo/modules/apploader/AppLoaderProvider.kt
@@ -33,8 +33,8 @@ object AppLoaderProvider {
loaderClass = Class.forName(loaderClassName) as Class
loaders[name] = loaderClass
- .getDeclaredConstructor(Context::class.java)
- .newInstance(context) as HeadlessAppLoader
+ .getDeclaredConstructor()
+ .newInstance() as HeadlessAppLoader
} catch (e: PackageManager.NameNotFoundException) {
throw IllegalStateException("Unable to instantiate AppLoader!", e)
}
diff --git a/packages/expo-modules-core/android/src/main/java/expo/modules/apploader/HeadlessAppLoader.java b/packages/expo-modules-core/android/src/main/java/expo/modules/apploader/HeadlessAppLoader.java
index e0cbb39c0b6513..7054067330c4d0 100644
--- a/packages/expo-modules-core/android/src/main/java/expo/modules/apploader/HeadlessAppLoader.java
+++ b/packages/expo-modules-core/android/src/main/java/expo/modules/apploader/HeadlessAppLoader.java
@@ -5,19 +5,7 @@
import expo.modules.core.interfaces.Consumer;
public interface HeadlessAppLoader {
-
- class AppConfigurationError extends Exception {
-
- public AppConfigurationError(String message) {
- super(message);
- }
-
- public AppConfigurationError(String message, Exception cause) {
- super(message, cause);
- }
- }
-
- void loadApp(Context context, Params params, Runnable alreadyRunning, Consumer callback) throws AppConfigurationError;
+ void loadApp(Context context, Params params, Runnable alreadyRunning, Consumer callback);
boolean invalidateApp(String appScopeKey);
@@ -39,7 +27,5 @@ public String getAppScopeKey() {
public String getAppUrl() {
return appUrl;
}
-
}
-
}
diff --git a/packages/expo-modules-core/android/src/main/java/expo/modules/core/MapHelper.java b/packages/expo-modules-core/android/src/main/java/expo/modules/core/MapHelper.java
deleted file mode 100644
index 364bcd058131f0..00000000000000
--- a/packages/expo-modules-core/android/src/main/java/expo/modules/core/MapHelper.java
+++ /dev/null
@@ -1,142 +0,0 @@
-package expo.modules.core;
-
-import java.util.List;
-import java.util.Map;
-
-import expo.modules.core.interfaces.Arguments;
-
-public class MapHelper implements Arguments {
- private Map mMap;
-
- public MapHelper(Map map) {
- super();
- mMap = map;
- }
-
- @Override
- public boolean containsKey(String key) {
- return mMap.containsKey(key);
- }
-
- @Override
- public Object get(String key) {
- return mMap.get(key);
- }
-
- @Override
- public boolean getBoolean(String key) {
- return getBoolean(key, false);
- }
-
- @Override
- public boolean getBoolean(String key, boolean defaultValue) {
- Object value = mMap.get(key);
- if (value instanceof Boolean) {
- return (Boolean) value;
- }
- return defaultValue;
- }
-
- @Override
- public double getDouble(String key) {
- return getDouble(key, 0);
- }
-
- @Override
- public double getDouble(String key, double defaultValue) {
- Object value = mMap.get(key);
- if (value instanceof Number) {
- return ((Number) value).doubleValue();
- }
- return defaultValue;
- }
-
- @Override
- public int getInt(String key) {
- return getInt(key, 0);
- }
-
- @Override
- public int getInt(String key, int defaultValue) {
- Object value = mMap.get(key);
- if (value instanceof Number) {
- return ((Number) value).intValue();
- }
- return defaultValue;
- }
-
- @Override
- public long getLong(String key) {
- return getLong(key, 0);
- }
-
- @Override
- public long getLong(String key, long defaultValue) {
- Object value = mMap.get(key);
- if (value instanceof Number) {
- return ((Number) value).longValue();
- }
- return defaultValue;
- }
-
- @Override
- public String getString(String key) {
- return getString(key, null);
- }
-
- @Override
- public String getString(String key, String defaultValue) {
- Object value = mMap.get(key);
- if (value instanceof String) {
- return (String) value;
- }
- return defaultValue;
- }
-
- @Override
- public List getList(String key) {
- return getList(key, null);
- }
-
- @Override
- public List getList(String key, List defaultValue) {
- Object value = mMap.get(key);
- if (value instanceof List) {
- return (List) value;
- }
- return defaultValue;
- }
-
- @Override
- public Map getMap(String key) {
- return getMap(key, null);
- }
-
- @Override
- public Map getMap(String key, Map defaultValue) {
- Object value = mMap.get(key);
- if (value instanceof Map) {
- return (Map) value;
- }
- return defaultValue;
- }
-
- @Override
- public boolean isEmpty() {
- return mMap.isEmpty();
- }
-
- @Override
- public int size() {
- return mMap.size();
- }
-
- @Override
- public Arguments getArguments(String key) {
- Map value = getMap(key);
- if (value != null) {
- return new MapHelper(value);
- }
- return null;
- }
-}
diff --git a/packages/expo-modules-core/android/src/main/java/expo/modules/core/errors/CurrentActivityNotFoundException.java b/packages/expo-modules-core/android/src/main/java/expo/modules/core/errors/CurrentActivityNotFoundException.java
deleted file mode 100644
index 01f26d0ceebb91..00000000000000
--- a/packages/expo-modules-core/android/src/main/java/expo/modules/core/errors/CurrentActivityNotFoundException.java
+++ /dev/null
@@ -1,14 +0,0 @@
-package expo.modules.core.errors;
-
-import expo.modules.core.interfaces.CodedThrowable;
-
-public class CurrentActivityNotFoundException extends CodedException implements CodedThrowable {
- public CurrentActivityNotFoundException() {
- super("Current activity not found. Make sure to call this method while your application is in foreground.");
- }
-
- @Override
- public String getCode() {
- return "E_CURRENT_ACTIVITY_NOT_FOUND";
- }
-}
diff --git a/packages/expo-modules-core/android/src/main/java/expo/modules/core/errors/ModuleNotFoundException.java b/packages/expo-modules-core/android/src/main/java/expo/modules/core/errors/ModuleNotFoundException.java
deleted file mode 100644
index 785b3058d23dd8..00000000000000
--- a/packages/expo-modules-core/android/src/main/java/expo/modules/core/errors/ModuleNotFoundException.java
+++ /dev/null
@@ -1,14 +0,0 @@
-package expo.modules.core.errors;
-
-import expo.modules.core.interfaces.CodedThrowable;
-
-public class ModuleNotFoundException extends CodedException implements CodedThrowable {
- public ModuleNotFoundException(String moduleName) {
- super("Module '" + moduleName + "' not found. Are you sure all modules are linked correctly?");
- }
-
- @Override
- public String getCode() {
- return "E_MODULE_NOT_FOUND";
- }
-}
diff --git a/packages/expo-task-manager/android/src/main/java/expo/modules/taskManager/TaskManagerModule.kt b/packages/expo-task-manager/android/src/main/java/expo/modules/taskManager/TaskManagerModule.kt
index 294cd712b35273..fc42f02a2752b4 100644
--- a/packages/expo-task-manager/android/src/main/java/expo/modules/taskManager/TaskManagerModule.kt
+++ b/packages/expo-task-manager/android/src/main/java/expo/modules/taskManager/TaskManagerModule.kt
@@ -4,9 +4,9 @@ import android.os.Bundle
import android.os.Handler
import android.os.Looper
import android.util.Log
-import expo.modules.core.errors.ModuleNotFoundException
import expo.modules.interfaces.taskManager.TaskManagerInterface
import expo.modules.interfaces.taskManager.TaskServiceInterface
+import expo.modules.kotlin.exception.Exceptions
import expo.modules.kotlin.modules.Module
import expo.modules.kotlin.modules.ModuleDefinition
import java.lang.ref.WeakReference
@@ -17,7 +17,7 @@ class TaskManagerModule : Module() {
}
private val taskService: TaskServiceInterface
get() = _taskService
- ?: throw ModuleNotFoundException(TaskServiceInterface::class.java.toString())
+ ?: throw Exceptions.ModuleNotFound(TaskManagerInterface::class)
private val taskManagerInternal: TaskManagerInterface? by lazy {
appContext.legacyModule()
@@ -94,6 +94,6 @@ class TaskManagerModule : Module() {
private val appScopeKey: String
get() = (
taskManagerInternal
- ?: throw ModuleNotFoundException(TaskManagerInterface::class.java.toString())
+ ?: throw Exceptions.ModuleNotFound(TaskManagerInterface::class)
).appScopeKey
}
diff --git a/packages/expo-task-manager/android/src/main/java/expo/modules/taskManager/TaskService.java b/packages/expo-task-manager/android/src/main/java/expo/modules/taskManager/TaskService.java
index 124ba1ead6777a..ab09b212eca68f 100644
--- a/packages/expo-task-manager/android/src/main/java/expo/modules/taskManager/TaskService.java
+++ b/packages/expo-task-manager/android/src/main/java/expo/modules/taskManager/TaskService.java
@@ -12,9 +12,6 @@
import android.util.Log;
import org.json.JSONObject;
-import expo.modules.apploader.AppLoaderProvider;
-import expo.modules.apploader.HeadlessAppLoader;
-import expo.modules.core.interfaces.SingletonModule;
import java.lang.ref.WeakReference;
import java.lang.reflect.Constructor;
@@ -25,7 +22,9 @@
import java.util.UUID;
import androidx.annotation.Nullable;
-
+import expo.modules.apploader.AppLoaderProvider;
+import expo.modules.apploader.HeadlessAppLoader;
+import expo.modules.core.interfaces.SingletonModule;
import expo.modules.interfaces.taskManager.TaskConsumerInterface;
import expo.modules.interfaces.taskManager.TaskExecutionCallback;
import expo.modules.interfaces.taskManager.TaskInterface;
@@ -203,17 +202,14 @@ public void notifyTaskFinished(String taskName, final String appScopeKey, Map {
+ if (!sEvents.containsKey(appScopeKey)) {
+ invalidateAppRecord(appScopeKey);
}
}, 2000);
}
@@ -404,9 +400,11 @@ public void executeTask(TaskInterface task, Bundle data, Error error, TaskExecut
}
mTasksAndEventsRepository.putEventForAppScopeKey(appScopeKey, body);
- try {
- getAppLoader().loadApp(mContextRef.get(), new HeadlessAppLoader.Params(appScopeKey, task.getAppUrl()), () -> {
- }, success -> {
+ getAppLoader().loadApp(mContextRef.get(),
+ new HeadlessAppLoader.Params(appScopeKey, task.getAppUrl()),
+ () -> {
+ },
+ success -> {
if (!success) {
sEvents.remove(appScopeKey);
mTasksAndEventsRepository.removeEvents(appScopeKey);
@@ -414,17 +412,8 @@ public void executeTask(TaskInterface task, Bundle data, Error error, TaskExecut
// Host unreachable? Unregister all tasks for that app.
unregisterAllTasksForAppScopeKey(appScopeKey);
}
- });
- } catch (HeadlessAppLoader.AppConfigurationError ignored) {
- try {
- unregisterTask(task.getName(), appScopeKey, null);
- } catch (Exception e) {
- Log.e(TAG, "Error occurred while unregistering invalid task.", e);
}
-
- appEvents.remove(eventId);
- mTasksAndEventsRepository.removeEvents(appScopeKey);
- }
+ );
}
//endregion
@@ -519,7 +508,7 @@ private void maybeUpdateAppUrlForAppScopeKey(String appUrl, String appScopeKey)
SharedPreferences preferences = getSharedPreferences();
Map appConfig = preferences != null ? jsonToMap(preferences.getString(appScopeKey, "")) : null;
- if (appConfig != null && appConfig.size() > 0) {
+ if (appConfig != null && !appConfig.isEmpty()) {
String oldAppUrl = (String) appConfig.get("appUrl");
if (oldAppUrl == null || !oldAppUrl.equals(appUrl)) {
@@ -542,7 +531,7 @@ private void restoreTasks() {
String appUrl = entry.getValue().appUrl;
Map tasksConfig = entry.getValue().tasks;
- if (appUrl != null && tasksConfig != null && tasksConfig.size() > 0) {
+ if (appUrl != null && tasksConfig != null && !tasksConfig.isEmpty()) {
for (String taskName : tasksConfig.keySet()) {
Map taskConfig = (HashMap) tasksConfig.get(taskName);
String consumerClassString = (String) taskConfig.get("consumerClass");
@@ -607,12 +596,6 @@ private void invalidateAppRecord(String appScopeKey) {
private void finishJobAfterTimeout(final JobService jobService, final JobParameters params, long timeout) {
Handler handler = new Handler();
- handler.postDelayed(new Runnable() {
- @Override
- public void run() {
- jobService.jobFinished(params, false);
- }
- }, timeout);
+ handler.postDelayed(() -> jobService.jobFinished(params, false), timeout);
}
-
}
diff --git a/packages/expo-video/CHANGELOG.md b/packages/expo-video/CHANGELOG.md
index 760de9a677a2f6..9cc11c60d3bfbb 100644
--- a/packages/expo-video/CHANGELOG.md
+++ b/packages/expo-video/CHANGELOG.md
@@ -7,6 +7,7 @@
### 🎉 New features
- [Android][iOS] Add `url` field to HLS video tracks. ([#41681](https://github.com/expo/expo/pull/41681) by [@behenate](https://github.com/behenate))
+- [Android][iOS] Add `name`, `isDefault` and `autoSelect` fields to `AudioTrack` and `SubtitleTrack`. ([#43250](https://github.com/expo/expo/pull/43250) by [@behenate](https://github.com/behenate))
### 🐛 Bug fixes
@@ -36,6 +37,7 @@
- [iOS] Prevents blocking main thread when loading asset tracks for non-HSL tracks ([#42037](https://github.com/expo/expo/pull/42037) by [@santitopo](https://github.com/santitopo))
- [Android] Fix crash due to `SimpleCache` directory lock conflicts. ([#42723](https://github.com/expo/expo/pull/42723) by [@santitopo](https://github.com/santitopo))
+- [Android] Avoid crash when FullscreenPlayerActivity init fails. ([#42943](https://github.com/expo/expo/pull/42943) by [@amyu](https://github.com/amyu))
## 55.0.4 — 2026-02-03
diff --git a/packages/expo-video/android/src/main/java/expo/modules/video/FullscreenPlayerActivity.kt b/packages/expo-video/android/src/main/java/expo/modules/video/FullscreenPlayerActivity.kt
index cf92500d68dc2e..c35bb7006cb346 100644
--- a/packages/expo-video/android/src/main/java/expo/modules/video/FullscreenPlayerActivity.kt
+++ b/packages/expo-video/android/src/main/java/expo/modules/video/FullscreenPlayerActivity.kt
@@ -27,14 +27,14 @@ import expo.modules.video.managers.VideoManager
@androidx.annotation.OptIn(androidx.media3.common.util.UnstableApi::class)
class FullscreenPlayerActivity : Activity() {
private lateinit var mContentView: View
- private lateinit var videoViewId: String
+ private var videoViewId: String? = null
private var videoPlayer: VideoPlayer? = null
private lateinit var playerView: PlayerView
private lateinit var videoView: VideoView
private var didFinish = false
private var wasAutoPaused = false
private lateinit var options: FullscreenOptions
- private lateinit var orientationHelper: FullscreenActivityOrientationHelper
+ private var orientationHelper: FullscreenActivityOrientationHelper? = null
private var captioningChangeListener: CaptioningManager.CaptioningChangeListener? = null
override fun onCreate(savedInstanceState: Bundle?) {
@@ -53,7 +53,8 @@ class FullscreenPlayerActivity : Activity() {
?: throw FullScreenOptionsNotFoundException()
}
- videoView = VideoManager.getVideoView(videoViewId)
+ videoView = videoViewId?.let { VideoManager.getVideoView(it) }
+ ?: throw FullScreenVideoViewNotFoundException()
orientationHelper = FullscreenActivityOrientationHelper(
this,
@@ -65,7 +66,7 @@ class FullscreenPlayerActivity : Activity() {
requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED
}
)
- orientationHelper.startOrientationEventListener()
+ orientationHelper?.startOrientationEventListener()
} catch (e: CodedException) {
Log.e("ExpoVideo", "${e.message}", e)
finish()
@@ -119,7 +120,7 @@ class FullscreenPlayerActivity : Activity() {
override fun finish() {
super.finish()
didFinish = true
- VideoManager.getVideoView(videoViewId).attachPlayer()
+ videoViewId?.let { VideoManager.getVideoView(it).attachPlayer() }
// Disable the exit transition
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
@@ -131,7 +132,7 @@ class FullscreenPlayerActivity : Activity() {
}
override fun onResume() {
- orientationHelper.startOrientationEventListener()
+ orientationHelper?.startOrientationEventListener()
playerView.useController = true
// Reconfigure subtitles when resuming (handles returning from settings)
SubtitleUtils.configureSubtitleView(playerView, this)
@@ -146,7 +147,7 @@ class FullscreenPlayerActivity : Activity() {
videoPlayer?.player?.pause()
}
}
- orientationHelper.stopOrientationEventListener()
+ orientationHelper?.stopOrientationEventListener()
super.onPause()
}
@@ -162,7 +163,7 @@ class FullscreenPlayerActivity : Activity() {
videoView.exitFullscreen()
VideoManager.unregisterFullscreenPlayerActivity(hashCode().toString())
- orientationHelper.stopOrientationEventListener()
+ orientationHelper?.stopOrientationEventListener()
}
private fun setupFullscreenButton() {
@@ -216,7 +217,7 @@ class FullscreenPlayerActivity : Activity() {
override fun onConfigurationChanged(newConfig: Configuration) {
super.onConfigurationChanged(newConfig)
- orientationHelper.onConfigurationChanged(newConfig)
+ orientationHelper?.onConfigurationChanged(newConfig)
}
companion object {
diff --git a/packages/expo-video/android/src/main/java/expo/modules/video/records/Tracks.kt b/packages/expo-video/android/src/main/java/expo/modules/video/records/Tracks.kt
index f39e7afc2fc022..93d3ef1205e4a2 100644
--- a/packages/expo-video/android/src/main/java/expo/modules/video/records/Tracks.kt
+++ b/packages/expo-video/android/src/main/java/expo/modules/video/records/Tracks.kt
@@ -12,7 +12,10 @@ import java.util.Locale
class SubtitleTrack(
@Field val id: String,
@Field val language: String?,
- @Field val label: String?
+ @Field val label: String?,
+ @Field val name: String?,
+ @Field val isDefault: Boolean,
+ @Field val autoSelect: Boolean
) : Record, Serializable {
companion object {
fun fromFormat(format: Format?): SubtitleTrack? {
@@ -20,11 +23,17 @@ class SubtitleTrack(
val id = format.id ?: return null
val language = format.language ?: return null
val label = Locale(language).displayLanguage
+ val name = format.label
+ val isDefault = (format.selectionFlags and androidx.media3.common.C.SELECTION_FLAG_DEFAULT) != 0
+ val autoSelect = (format.selectionFlags and androidx.media3.common.C.SELECTION_FLAG_AUTOSELECT) != 0
return SubtitleTrack(
id = id,
language = language,
- label = label
+ label = label,
+ name = name,
+ isDefault = isDefault,
+ autoSelect = autoSelect
)
}
}
@@ -33,7 +42,10 @@ class SubtitleTrack(
class AudioTrack(
@Field val id: String,
@Field val language: String?,
- @Field val label: String?
+ @Field val label: String?,
+ @Field val name: String?,
+ @Field val isDefault: Boolean,
+ @Field val autoSelect: Boolean
) : Record, Serializable {
companion object {
fun fromFormat(format: Format?): AudioTrack? {
@@ -41,11 +53,17 @@ class AudioTrack(
val id = format.id ?: return null
val language = format.language
val label = language?.let { Locale(it).displayLanguage } ?: "Unknown"
+ val name = format.label
+ val isDefault = (format.selectionFlags and androidx.media3.common.C.SELECTION_FLAG_DEFAULT) != 0
+ val autoSelect = (format.selectionFlags and androidx.media3.common.C.SELECTION_FLAG_AUTOSELECT) != 0
return AudioTrack(
id = id,
language = language,
- label = label
+ label = label,
+ name = name,
+ isDefault = isDefault,
+ autoSelect = autoSelect
)
}
}
diff --git a/packages/expo-video/build/VideoPlayer.types.d.ts b/packages/expo-video/build/VideoPlayer.types.d.ts
index 48cfe68e3f71b8..458dd2387915d2 100644
--- a/packages/expo-video/build/VideoPlayer.types.d.ts
+++ b/packages/expo-video/build/VideoPlayer.types.d.ts
@@ -508,6 +508,24 @@ export type SubtitleTrack = {
* Label of the subtitle track in the language of the device.
*/
label: string;
+ /**
+ * Name of the subtitle track as specified in the media source.
+ * @platform android
+ * @platform ios
+ */
+ name?: string;
+ /**
+ * Indicates whether this is the default subtitle track.
+ * @platform android
+ * @platform ios
+ */
+ isDefault?: boolean;
+ /**
+ * Indicates whether this track should be auto-selected based on user preferences.
+ * @platform android
+ * @platform ios
+ */
+ autoSelect?: boolean;
};
/**
* Specifies a VideoTrack loaded from a [`VideoSource`](#videosource).
@@ -584,6 +602,24 @@ export type AudioTrack = {
* Label of the audio track in the language of the device.
*/
label: string;
+ /**
+ * Name of the audio track as specified in the media source.
+ * @platform android
+ * @platform ios
+ */
+ name?: string;
+ /**
+ * Indicates whether this is the default audio track.
+ * @platform android
+ * @platform ios
+ */
+ isDefault?: boolean;
+ /**
+ * Indicates whether this track should be auto-selected based on user preferences.
+ * @platform android
+ * @platform ios
+ */
+ autoSelect?: boolean;
};
/**
* Determines the time that the actual position seeked to may precede or exceed the requested seek position.
diff --git a/packages/expo-video/build/VideoPlayer.types.d.ts.map b/packages/expo-video/build/VideoPlayer.types.d.ts.map
index 47357a586f1108..fdf2d0d0fe520f 100644
--- a/packages/expo-video/build/VideoPlayer.types.d.ts.map
+++ b/packages/expo-video/build/VideoPlayer.types.d.ts.map
@@ -1 +1 @@
-{"version":3,"file":"VideoPlayer.types.d.ts","sourceRoot":"","sources":["../src/VideoPlayer.types.ts"],"names":[],"mappings":"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"}
\ No newline at end of file
+{"version":3,"file":"VideoPlayer.types.d.ts","sourceRoot":"","sources":["../src/VideoPlayer.types.ts"],"names":[],"mappings":"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"}
\ No newline at end of file
diff --git a/packages/expo-video/build/VideoPlayer.types.js.map b/packages/expo-video/build/VideoPlayer.types.js.map
index 54a1c17d84ecef..4f596535d081ae 100644
--- a/packages/expo-video/build/VideoPlayer.types.js.map
+++ b/packages/expo-video/build/VideoPlayer.types.js.map
@@ -1 +1 @@
-{"version":3,"file":"VideoPlayer.types.js","sourceRoot":"","sources":["../src/VideoPlayer.types.ts"],"names":[],"mappings":"","sourcesContent":["import { SharedObject } from 'expo';\n\nimport { VideoPlayerEvents } from './VideoPlayerEvents.types';\nimport { VideoThumbnail } from './VideoThumbnail';\n\n/**\n * A class that represents an instance of the video player.\n */\nexport declare class VideoPlayer extends SharedObject {\n /**\n * Boolean value whether the player is currently playing.\n * > Use `play` and `pause` methods to control the playback.\n */\n readonly playing: boolean;\n\n /**\n * Determines whether the player should automatically replay after reaching the end of the video.\n * @default false\n */\n loop: boolean;\n\n /**\n * Determines whether the player should allow external playback.\n * @default true\n * @platform ios\n */\n allowsExternalPlayback: boolean;\n\n /**\n * Determines how the player will interact with other audio playing in the system.\n *\n * @default 'auto'\n * @platform android\n * @platform ios\n */\n audioMixingMode: AudioMixingMode;\n\n /**\n * Boolean value whether the player is currently muted.\n * Setting this property to `true`/`false` will mute/unmute the player.\n * @default false\n */\n muted: boolean;\n\n /**\n * Float value indicating the current playback time in seconds.\n *\n * If the player is not yet playing, this value indicates the time position\n * at which playback will begin once the `play()` method is called.\n *\n * Setting `currentTime` to a new value seeks the player to the given time.\n * Check out the [`seekTolerance`](#seektolerance) property to configure the seeking precision.\n */\n currentTime: number;\n\n /**\n * The exact timestamp when the currently displayed video frame was sent from the server,\n * based on the `EXT-X-PROGRAM-DATE-TIME` tag in the livestream metadata.\n * If this metadata is missing, this property will return `null`.\n * @platform android\n * @platform ios\n */\n readonly currentLiveTimestamp: number | null;\n\n /**\n * Float value indicating the latency of the live stream in seconds.\n * If a livestream doesn't have the required metadata, this will return `null`.\n * @platform android\n * @platform ios\n */\n readonly currentOffsetFromLive: number | null;\n\n /**\n * Float value indicating the time offset from the live in seconds.\n * @platform ios\n */\n targetOffsetFromLive: number;\n\n /**\n * Float value indicating the duration of the current video in seconds.\n */\n readonly duration: number;\n\n /**\n * Float value between `0` and `1.0` representing the current volume.\n * Muting the player doesn't affect the volume. In other words, when the player is muted, the volume is the same as\n * when unmuted. Similarly, setting the volume doesn't unmute the player.\n * @default 1.0\n */\n volume: number;\n\n /**\n * Boolean value indicating if the player should correct audio pitch when the playback speed changes.\n * @default true\n */\n preservesPitch: boolean;\n\n /**\n * Float value indicating the interval in seconds at which the player will emit the [`timeUpdate`](#videoplayerevents) event.\n * When the value is equal to `0`, the event will not be emitted.\n *\n * @default 0\n */\n timeUpdateEventInterval: number;\n\n /**\n * Float value between `0` and `16.0` indicating the current playback speed of the player.\n * @default 1.0\n */\n playbackRate: number;\n\n /**\n * Boolean indicating if the player should keep the screen on while playing.\n *\n * > On Android, this property has an effect only when a [`VideoView`](#videoview) is visible. If you want to keep the screen awake at all times use [`expo-keep-awake`](./keep-awake/).\n *\n * @default true\n * @platform android\n * @platform ios\n */\n keepScreenOnWhilePlaying: boolean;\n\n /**\n * Boolean value indicating whether the player is currently playing a live stream.\n */\n readonly isLive: boolean;\n\n /**\n * Indicates the current status of the player.\n */\n readonly status: VideoPlayerStatus;\n\n /**\n * Boolean value determining whether the player should show the now playing notification.\n *\n * > **Note**: On Android, `supportsBackgroundPlayback` property of the [config plugin](#configuration-in-app-config)\n * > has to be `true` for the now playing notification to work.\n * @default false\n * @platform android\n * @platform ios\n */\n showNowPlayingNotification: boolean;\n\n /**\n * Determines whether the player should continue playing after the app enters the background.\n *\n * > **Note**: The `supportsBackgroundPlayback` property of the [config plugin](#configuration-in-app-config)\n * > has to be `true` for the background playback to work.\n * @default false\n * @platform ios\n * @platform android\n */\n staysActiveInBackground: boolean;\n\n /**\n * Float value indicating how far the player has buffered the video in seconds.\n *\n * This value is 0 when the player has not buffered up to the current playback time.\n * When it's impossible to determine the buffer state (for example, when the player isn't playing any media), this value is -1.\n */\n readonly bufferedPosition: number;\n\n /**\n * Specifies buffer options which will be used by the player when buffering the video.\n *\n * > You should provide a `BufferOptions` object when setting this property. Setting individual buffer properties is not supported.\n * @platform android\n * @platform ios\n */\n bufferOptions: BufferOptions;\n\n /**\n * Specifies the subtitle track which is currently displayed by the player. `null` when no subtitles are displayed.\n *\n * > To ensure a valid subtitle track, always assign one of the subtitle tracks from the [`availableSubtitleTracks`](#availablesubtitletracks) array.\n *\n * @default null\n * @platform android\n * @platform ios\n */\n subtitleTrack: SubtitleTrack | null;\n\n /**\n * Specifies the audio track currently played by the player. `null` when no audio is played.\n *\n * @default null\n * @platform android\n * @platform ios\n */\n audioTrack: AudioTrack | null;\n\n /**\n * An array of audio tracks available for the current video.\n *\n * @platform android\n * @platform ios\n */\n readonly availableAudioTracks: AudioTrack[];\n\n /**\n * An array of subtitle tracks available for the current video.\n *\n * @platform android\n * @platform ios\n */\n readonly availableSubtitleTracks: SubtitleTrack[];\n\n /**\n * Specifies the video track currently played by the player. `null` when no video is displayed.\n *\n * @default null\n * @platform android\n * @platform ios\n */\n readonly videoTrack: VideoTrack | null;\n\n /**\n * An array of video tracks available for the current video.\n *\n * > On iOS, when using a HLS source, make sure that the uri contains `.m3u8` extension or that the [`contentType`](#contenttype) property of the [`VideoSource`](#videosource) has been set to `'hls'`. Otherwise, the video tracks will not be available.\n *\n * @platform android\n * @platform ios\n */\n readonly availableVideoTracks: VideoTrack[];\n\n /**\n * Indicates whether the player is currently playing back the media to an external device via AirPlay.\n *\n * @platform ios\n */\n readonly isExternalPlaybackActive: boolean;\n\n /**\n * Determines the time that the actual position seeked to may precede or exceed the requested seek position.\n *\n * This property affects the precision of setting the [`currentTime`](#currenttime) property and the [`seekBy`](#seekbyseconds) method, and on Android, it also affects the accuracy of the scrubber from the default native controls.\n *\n * By default, the player seeks to the exact requested time.\n *\n * > If you are trying to optimize for scrubbing (many frequent seeks), also see [`ScrubbingModeOptions`](#scrubbingmodeoptions-1).\n */\n seekTolerance: SeekTolerance;\n\n /**\n * Determines whether the scrubbing mode is enabled and what scrubbing optimizations should be enabled.\n *\n * > See [`SeekTolerance`](#seektolerance) to set the seeking tolerance, which can also affect the scrubbing performance.\n *\n */\n scrubbingModeOptions: ScrubbingModeOptions;\n\n /**\n * Initializes a new video player instance with the given source.\n *\n * @param source The source of the video to be played.\n * @param useSynchronousReplace Optional parameter, when `true` `source` from the first parameter will be loaded on the main thread.\n * @param playerBuilderOptions Options to apply to the player builder before the native constructor is invoked.\n * @hidden\n */\n constructor(\n source: VideoSource,\n useSynchronousReplace?: boolean,\n playerBuilderOptions?: PlayerBuilderOptions\n );\n\n /**\n * Resumes the player.\n */\n play(): void;\n\n /**\n * Pauses the player.\n */\n pause(): void;\n\n /**\n * Replaces the current source with a new one.\n *\n * > On iOS, this method loads the asset data synchronously on the UI thread and can block it for extended periods of time.\n * > Use `replaceAsync` to load the asset asynchronously and avoid UI lags.\n *\n * > This method will be deprecated in the future.\n */\n replace(source: VideoSource, disableWarning?: boolean): void;\n\n /**\n * Replaces the current source with a new one, while offloading loading of the asset to a different thread.\n *\n * > On Android and Web, this method is equivalent to `replace`.\n */\n replaceAsync(source: VideoSource): Promise;\n\n /**\n * Seeks the playback by the given number of seconds. The time to which the player seeks may differ from the specified requested time for efficiency,\n * depending on the encoding and what is currently buffered by the player. Use this function to implement playback controls that seek by specific amount of time,\n * in which case, the actual time usually does not have to be precise. For frame accurate seeking, use the [`currentTime`](#currenttime) property.\n */\n seekBy(seconds: number): void;\n\n /**\n * Seeks the playback to the beginning.\n */\n replay(): void;\n\n /**\n * Generates thumbnails from the currently played asset. The thumbnails are references to native images,\n * thus they can be used as a source of the `Image` component from `expo-image`.\n * @platform android\n * @platform ios\n */\n generateThumbnailsAsync(\n times: number | number[],\n options?: VideoThumbnailOptions\n ): Promise;\n}\n\n/**\n * Additional options for video thumbnails generation.\n */\nexport type VideoThumbnailOptions = {\n /**\n * If provided, the generated thumbnail will not exceed this width in pixels, preserving its aspect ratio.\n * @platform android\n * @platform ios\n */\n maxWidth?: number;\n\n /**\n * If provided, the generated thumbnail will not exceed this height in pixels, preserving its aspect ratio.\n * @platform android\n * @platform ios\n */\n maxHeight?: number;\n};\n\n/**\n * Describes the current status of the player.\n * - `idle`: The player is not playing or loading any videos.\n * - `loading`: The player is loading video data from the provided source\n * - `readyToPlay`: The player has loaded enough data to start playing or to continue playback.\n * - `error`: The player has encountered an error while loading or playing the video.\n */\nexport type VideoPlayerStatus = 'idle' | 'loading' | 'readyToPlay' | 'error';\n\nexport type VideoSource = string | number | null | VideoSourceObject;\n\nexport type VideoSourceObject = {\n /**\n * The URI of the video.\n *\n * On iOS, `PHAsset` URIs are supported, but can only be loaded using the [`replaceAsync`](#replaceasyncsource) method or the default [`VideoPlayer`](#videoplayer) constructor.\n *\n * This property is exclusive with the `assetId` property. When both are present, the `assetId` will be ignored.\n */\n uri?: string;\n\n /**\n * The asset ID of a local video asset, acquired with the `require` function.\n * This property is exclusive with the `uri` property. When both are present, the `assetId` will be ignored.\n */\n assetId?: number;\n\n /**\n * Specifies the DRM options which will be used by the player while loading the video.\n */\n drm?: DRMOptions;\n\n /**\n * Specifies information which will be displayed in the now playing notification.\n * When undefined the player will display information contained in the video metadata.\n * @platform android\n * @platform ios\n */\n metadata?: VideoMetadata;\n\n /**\n * Specifies headers sent with the video request.\n * > For DRM license headers use the `headers` field of [`DRMOptions`](#drmoptions).\n * @platform android\n * @platform ios\n */\n headers?: Record;\n\n /**\n * Specifies whether the player should use caching for the video.\n * > Due to platform limitations, the cache cannot be used with HLS video sources on iOS. Caching DRM-protected videos is not supported on Android and iOS.\n * @default false\n * @platform android\n * @platform ios\n */\n useCaching?: boolean;\n\n /**\n * Specifies the content type of the video source. When set to `'auto'`, the player will try to automatically determine the content type.\n *\n * You should use this property when playing HLS, SmoothStreaming or DASH videos from an uri, which does not contain a standardized extension for the corresponding media type.\n * @default 'auto'\n * @platform android\n * @platform ios\n */\n contentType?: ContentType;\n};\n\n/**\n * Contains information about any errors that the player encountered during the playback\n */\nexport type PlayerError = {\n message: string;\n};\n\n/**\n * Contains information that will be displayed in the now playing notification when the video is playing.\n * @platform android\n * @platform ios\n */\nexport type VideoMetadata = {\n /**\n * The title of the video.\n * @platform android\n * @platform ios\n */\n title?: string;\n /**\n * Secondary text that will be displayed under the title.\n * @platform android\n * @platform ios\n */\n artist?: string;\n /**\n * The uri of the video artwork.\n * @platform android\n * @platform ios\n */\n artwork?: string;\n};\n\n/**\n * Specifies which type of DRM to use:\n * - Android supports ClearKey, PlayReady and Widevine.\n * - iOS supports FairPlay.\n */\nexport type DRMType = 'clearkey' | 'fairplay' | 'playready' | 'widevine';\n\n/**\n * Specifies DRM options which will be used by the player while loading the video.\n */\nexport type DRMOptions = {\n /**\n * Determines which type of DRM to use.\n */\n type: DRMType;\n\n /**\n * Determines the license server URL.\n */\n licenseServer: string;\n\n /**\n * Determines headers sent to the license server on license requests.\n */\n headers?: Record;\n\n /**\n * Specifies whether the DRM is a multi-key DRM.\n * @platform android\n */\n multiKey?: boolean;\n\n /**\n * Specifies the content ID of the stream.\n * @platform ios\n */\n contentId?: string;\n\n /**\n * Specifies the certificate URL for the FairPlay DRM.\n * @platform ios\n */\n certificateUrl?: string;\n\n /**\n * Specifies the base64 encoded certificate data for the FairPlay DRM.\n * When this property is set, the `certificateUrl` property is ignored.\n * @platform ios\n */\n base64CertificateData?: string;\n};\n\n/**\n * Specifies buffer options which will be used by the player when buffering the video.\n *\n * @platform android\n * @platform ios\n */\nexport type BufferOptions = {\n /**\n * The duration in seconds which determines how much media the player should buffer ahead of the current playback time.\n *\n * On iOS when set to `0` the player will automatically decide appropriate buffer duration.\n *\n * Equivalent to [`AVPlayerItem.preferredForwardBufferDuration`](https://developer.apple.com/documentation/avfoundation/avplayeritem/1643630-preferredforwardbufferduration).\n * @default Android: 20, iOS: 0\n * @platform android\n * @platform ios\n */\n readonly preferredForwardBufferDuration?: number;\n\n /**\n * A Boolean value that indicates whether the player should automatically delay playback in order to minimize stalling.\n *\n * Equivalent to [`AVPlayer.automaticallyWaitsToMinimizeStalling`](https://developer.apple.com/documentation/avfoundation/avplayer/1643482-automaticallywaitstominimizestal).\n * @default true\n * @platform ios\n */\n readonly waitsToMinimizeStalling?: boolean;\n\n /**\n * Minimum duration of the buffer in seconds required to continue playing after the player has been paused or started buffering.\n *\n * > This property will be ignored if `preferredForwardBufferDuration` is lower.\n * @default 2\n * @platform android\n */\n readonly minBufferForPlayback?: number;\n\n /**\n * The maximum number of bytes that the player can buffer from the network.\n * When 0 the player will automatically decide appropriate buffer size.\n *\n * @default 0\n * @platform android\n */\n readonly maxBufferBytes?: number | null;\n\n /**\n * A Boolean value which determines whether the player should prioritize time over size when buffering media.\n *\n * @default false\n * @platform android\n */\n readonly prioritizeTimeOverSizeThreshold?: boolean;\n};\n\n/**\n * Specifies the content type of the source.\n *\n * - `auto`: The player will automatically determine the content type of the video.\n * - `progressive`: The player will use progressive download content type. This is the default `ContentType` when the uri does not contain an extension.\n * - `hls`: The player will use HLS content type.\n * - `dash`: The player will use DASH content type (Android-only).\n * - `smoothStreaming`: The player will use SmoothStreaming content type (Android-only).\n *\n * @default `auto`\n */\nexport type ContentType = 'auto' | 'progressive' | 'hls' | 'dash' | 'smoothStreaming';\n\n/**\n * Specifies the audio mode that the player should use. Audio mode is set on per-app basis, if there are multiple players playing and\n * have different a `AudioMode` specified, the highest priority mode will be used. Priority order: 'doNotMix' > 'auto' > 'duckOthers' > 'mixWithOthers'.\n *\n * - `mixWithOthers`: The player will mix its audio output with other apps.\n * - `duckOthers`: The player will lower the volume of other apps if any of the active players is outputting audio.\n * - `auto`: The player will allow other apps to keep playing audio only when it is muted. On iOS it will always interrupt other apps when `showNowPlayingNotification` is `true` due to system requirements.\n * - `doNotMix`: The player will pause playback in other apps, even when it's muted.\n *\n * > On iOS, the Now Playing notification is dependent on the audio mode. If the audio mode is different from `doNotMix` or `auto` this feature will not work.\n */\nexport type AudioMixingMode = 'mixWithOthers' | 'duckOthers' | 'auto' | 'doNotMix';\n\nexport type SubtitleTrack = {\n /**\n * A string used by `expo-video` to identify the subtitle track.\n *\n * @platform android\n */\n id?: string;\n\n /**\n * Language of the subtitle track. For example, `en`, `pl`, `de`.\n */\n language: string;\n\n /**\n * Label of the subtitle track in the language of the device.\n */\n label: string;\n};\n\n/**\n * Specifies a VideoTrack loaded from a [`VideoSource`](#videosource).\n */\nexport type VideoTrack = {\n /**\n * The id of the video track.\n *\n * > This field is platform-specific and may return different depending on the operating system.\n */\n id: string;\n\n /**\n * The URL of the `VideoTrack` for HLS video sources. `null` for other source types.\n */\n url: string | null;\n\n /**\n * Size of the video track.\n */\n size: VideoSize;\n\n /**\n * MimeType of the video track or null if unknown.\n */\n mimeType: string | null;\n\n /**\n * Indicates whether the video track format is supported by the device.\n *\n * @platform android\n */\n isSupported: boolean;\n\n /**\n * Specifies the bitrate in bits per second. This is the peak bitrate if known, or else the average bitrate if known, or else null.\n *\n * @deprecated Use `peakBitrate` or `averageBitrate` instead.\n */\n bitrate: number | null;\n\n /**\n * Specifies the average bitrate in bits per second or null if the value is unknown.\n *\n */\n averageBitrate: number | null;\n\n /**\n * Specifies the average bitrate in bits per second or null if the value is unknown.\n */\n peakBitrate: number | null;\n\n /**\n * Specifies the frame rate of the video track in frames per second.\n */\n frameRate: number | null;\n};\n\n/**\n * Specifies the size of a video track.\n */\nexport type VideoSize = {\n /**\n * Width of the video track in pixels.\n */\n width: number;\n /**\n * Height of the video track in pixels.\n */\n height: number;\n};\n\nexport type AudioTrack = {\n /**\n * A string used by expo-video to identify the audio track.\n * @platform android\n */\n id?: string;\n\n /**\n * Language of the audio track. For example, 'en', 'pl', 'de'.\n */\n language: string;\n\n /**\n * Label of the audio track in the language of the device.\n */\n label: string;\n};\n\n/**\n * Determines the time that the actual position seeked to may precede or exceed the requested seek position.\n * Larger tolerance will usually result in faster seeking.\n * This property affects the precision of setting the [`currentTime`](#currenttime) property and the [`seekBy`](#seekbyseconds) method, and on Android, it also affects the accuracy of the scrubber from the default native controls.\n *\n * > If you are trying to optimize for scrubbing (many frequent seeks), also see [`ScrubbingModeOptions`](#scrubbingmodeoptions-1).\n *\n * @platform android\n * @platform ios\n */\nexport type SeekTolerance = {\n /**\n * The maximum time that the actual position seeked to may precede the requested seek position, in seconds. Must be non-negative.\n * @default 0\n */\n toleranceBefore?: number;\n\n /**\n * The maximum time that the actual position seeked to may exceed the requested seek position, in seconds. Must be non-negative.\n * @default 0\n */\n toleranceAfter?: number;\n};\n\n/**\n * Defines scrubbing mode options used by a [`VideoPlayer`](#videoplayer).\n */\nexport type ScrubbingModeOptions = {\n /**\n * Whether the codec operating rate should be increased in scrubbing mode.\n *\n * You should only enable this when the player is receiving a large number of seeks in a short period of time. For less frequent seeks, fine-tuning the [`SeekTolerance`](#seektolerance-1) may be sufficient.\n *\n * On Android, the player may consume more resources in this mode, so it should only be used for short periods of time in response to user interaction (for example, dragging on a progress bar UI element).\n *\n * On Android, when `scrubbingModeEnabled` is `true`, the playback is suppressed. You should set this property back to `false` when the user interaction ends to allow the playback to resume.\n * For best results, on iOS you should pause the playback when scrubbing.\n *\n * > For best scrubbing performance, consider also increasing the seeking tolerance using the [`SeekTolerance`](#seektolerance-1) property.\n *\n * > Other scrubbing mode options will have no effect when this is `false`.\n * @default false\n * @platform android\n * @platform ios\n */\n scrubbingModeEnabled?: boolean;\n\n /**\n * Whether the codec operating rate should be increased in scrubbing mode.\n *\n * @platform android\n * @default true\n */\n increaseCodecOperatingRate?: boolean;\n\n /**\n * Sets whether ExoPlayer's dynamic scheduling should be enabled in scrubbing mode.\n * This can result in available output buffers being handled more quickly when seeking.\n *\n * @platform android\n * @default true\n */\n enableDynamicScheduling?: boolean;\n /**\n * Sets whether to use `MediaCodec.BUFFER_FLAG_DECODE_ONLY` in scrubbing mode.\n * When playback is using MediaCodec on API 34+, this flag can speed up seeking by signalling that the decoded output of buffers between the previous keyframe and the target frame is not needed by the player.\n *\n * @platform android\n * @default true\n */\n useDecodeOnlyFlag?: boolean;\n\n /**\n * Sets whether to avoid flushing the decoder (where possible) in scrubbing mode.\n * When `true`, avoids flushing the decoder when a new seek starts decoding from a key-frame in compatible content.\n *\n * @platform android\n * @default true\n */\n allowSkippingMediaCodecFlush?: boolean;\n};\n\n/**\n * Options to apply to the player builder before the native constructor is invoked\n * @platform android\n */\nexport type PlayerBuilderOptions = {\n /**\n * Seek backward increment in seconds.\n * Values will be clamped between 0.001 and 999 seconds.\n * @platform android\n */\n seekBackwardIncrement?: number;\n\n /**\n * Seek forward increment in seconds.\n * Values will be clamped between 0.001 and 999 seconds.\n * @platform android\n */\n seekForwardIncrement?: number;\n};\n"]}
\ No newline at end of file
+{"version":3,"file":"VideoPlayer.types.js","sourceRoot":"","sources":["../src/VideoPlayer.types.ts"],"names":[],"mappings":"","sourcesContent":["import { SharedObject } from 'expo';\n\nimport { VideoPlayerEvents } from './VideoPlayerEvents.types';\nimport { VideoThumbnail } from './VideoThumbnail';\n\n/**\n * A class that represents an instance of the video player.\n */\nexport declare class VideoPlayer extends SharedObject {\n /**\n * Boolean value whether the player is currently playing.\n * > Use `play` and `pause` methods to control the playback.\n */\n readonly playing: boolean;\n\n /**\n * Determines whether the player should automatically replay after reaching the end of the video.\n * @default false\n */\n loop: boolean;\n\n /**\n * Determines whether the player should allow external playback.\n * @default true\n * @platform ios\n */\n allowsExternalPlayback: boolean;\n\n /**\n * Determines how the player will interact with other audio playing in the system.\n *\n * @default 'auto'\n * @platform android\n * @platform ios\n */\n audioMixingMode: AudioMixingMode;\n\n /**\n * Boolean value whether the player is currently muted.\n * Setting this property to `true`/`false` will mute/unmute the player.\n * @default false\n */\n muted: boolean;\n\n /**\n * Float value indicating the current playback time in seconds.\n *\n * If the player is not yet playing, this value indicates the time position\n * at which playback will begin once the `play()` method is called.\n *\n * Setting `currentTime` to a new value seeks the player to the given time.\n * Check out the [`seekTolerance`](#seektolerance) property to configure the seeking precision.\n */\n currentTime: number;\n\n /**\n * The exact timestamp when the currently displayed video frame was sent from the server,\n * based on the `EXT-X-PROGRAM-DATE-TIME` tag in the livestream metadata.\n * If this metadata is missing, this property will return `null`.\n * @platform android\n * @platform ios\n */\n readonly currentLiveTimestamp: number | null;\n\n /**\n * Float value indicating the latency of the live stream in seconds.\n * If a livestream doesn't have the required metadata, this will return `null`.\n * @platform android\n * @platform ios\n */\n readonly currentOffsetFromLive: number | null;\n\n /**\n * Float value indicating the time offset from the live in seconds.\n * @platform ios\n */\n targetOffsetFromLive: number;\n\n /**\n * Float value indicating the duration of the current video in seconds.\n */\n readonly duration: number;\n\n /**\n * Float value between `0` and `1.0` representing the current volume.\n * Muting the player doesn't affect the volume. In other words, when the player is muted, the volume is the same as\n * when unmuted. Similarly, setting the volume doesn't unmute the player.\n * @default 1.0\n */\n volume: number;\n\n /**\n * Boolean value indicating if the player should correct audio pitch when the playback speed changes.\n * @default true\n */\n preservesPitch: boolean;\n\n /**\n * Float value indicating the interval in seconds at which the player will emit the [`timeUpdate`](#videoplayerevents) event.\n * When the value is equal to `0`, the event will not be emitted.\n *\n * @default 0\n */\n timeUpdateEventInterval: number;\n\n /**\n * Float value between `0` and `16.0` indicating the current playback speed of the player.\n * @default 1.0\n */\n playbackRate: number;\n\n /**\n * Boolean indicating if the player should keep the screen on while playing.\n *\n * > On Android, this property has an effect only when a [`VideoView`](#videoview) is visible. If you want to keep the screen awake at all times use [`expo-keep-awake`](./keep-awake/).\n *\n * @default true\n * @platform android\n * @platform ios\n */\n keepScreenOnWhilePlaying: boolean;\n\n /**\n * Boolean value indicating whether the player is currently playing a live stream.\n */\n readonly isLive: boolean;\n\n /**\n * Indicates the current status of the player.\n */\n readonly status: VideoPlayerStatus;\n\n /**\n * Boolean value determining whether the player should show the now playing notification.\n *\n * > **Note**: On Android, `supportsBackgroundPlayback` property of the [config plugin](#configuration-in-app-config)\n * > has to be `true` for the now playing notification to work.\n * @default false\n * @platform android\n * @platform ios\n */\n showNowPlayingNotification: boolean;\n\n /**\n * Determines whether the player should continue playing after the app enters the background.\n *\n * > **Note**: The `supportsBackgroundPlayback` property of the [config plugin](#configuration-in-app-config)\n * > has to be `true` for the background playback to work.\n * @default false\n * @platform ios\n * @platform android\n */\n staysActiveInBackground: boolean;\n\n /**\n * Float value indicating how far the player has buffered the video in seconds.\n *\n * This value is 0 when the player has not buffered up to the current playback time.\n * When it's impossible to determine the buffer state (for example, when the player isn't playing any media), this value is -1.\n */\n readonly bufferedPosition: number;\n\n /**\n * Specifies buffer options which will be used by the player when buffering the video.\n *\n * > You should provide a `BufferOptions` object when setting this property. Setting individual buffer properties is not supported.\n * @platform android\n * @platform ios\n */\n bufferOptions: BufferOptions;\n\n /**\n * Specifies the subtitle track which is currently displayed by the player. `null` when no subtitles are displayed.\n *\n * > To ensure a valid subtitle track, always assign one of the subtitle tracks from the [`availableSubtitleTracks`](#availablesubtitletracks) array.\n *\n * @default null\n * @platform android\n * @platform ios\n */\n subtitleTrack: SubtitleTrack | null;\n\n /**\n * Specifies the audio track currently played by the player. `null` when no audio is played.\n *\n * @default null\n * @platform android\n * @platform ios\n */\n audioTrack: AudioTrack | null;\n\n /**\n * An array of audio tracks available for the current video.\n *\n * @platform android\n * @platform ios\n */\n readonly availableAudioTracks: AudioTrack[];\n\n /**\n * An array of subtitle tracks available for the current video.\n *\n * @platform android\n * @platform ios\n */\n readonly availableSubtitleTracks: SubtitleTrack[];\n\n /**\n * Specifies the video track currently played by the player. `null` when no video is displayed.\n *\n * @default null\n * @platform android\n * @platform ios\n */\n readonly videoTrack: VideoTrack | null;\n\n /**\n * An array of video tracks available for the current video.\n *\n * > On iOS, when using a HLS source, make sure that the uri contains `.m3u8` extension or that the [`contentType`](#contenttype) property of the [`VideoSource`](#videosource) has been set to `'hls'`. Otherwise, the video tracks will not be available.\n *\n * @platform android\n * @platform ios\n */\n readonly availableVideoTracks: VideoTrack[];\n\n /**\n * Indicates whether the player is currently playing back the media to an external device via AirPlay.\n *\n * @platform ios\n */\n readonly isExternalPlaybackActive: boolean;\n\n /**\n * Determines the time that the actual position seeked to may precede or exceed the requested seek position.\n *\n * This property affects the precision of setting the [`currentTime`](#currenttime) property and the [`seekBy`](#seekbyseconds) method, and on Android, it also affects the accuracy of the scrubber from the default native controls.\n *\n * By default, the player seeks to the exact requested time.\n *\n * > If you are trying to optimize for scrubbing (many frequent seeks), also see [`ScrubbingModeOptions`](#scrubbingmodeoptions-1).\n */\n seekTolerance: SeekTolerance;\n\n /**\n * Determines whether the scrubbing mode is enabled and what scrubbing optimizations should be enabled.\n *\n * > See [`SeekTolerance`](#seektolerance) to set the seeking tolerance, which can also affect the scrubbing performance.\n *\n */\n scrubbingModeOptions: ScrubbingModeOptions;\n\n /**\n * Initializes a new video player instance with the given source.\n *\n * @param source The source of the video to be played.\n * @param useSynchronousReplace Optional parameter, when `true` `source` from the first parameter will be loaded on the main thread.\n * @param playerBuilderOptions Options to apply to the player builder before the native constructor is invoked.\n * @hidden\n */\n constructor(\n source: VideoSource,\n useSynchronousReplace?: boolean,\n playerBuilderOptions?: PlayerBuilderOptions\n );\n\n /**\n * Resumes the player.\n */\n play(): void;\n\n /**\n * Pauses the player.\n */\n pause(): void;\n\n /**\n * Replaces the current source with a new one.\n *\n * > On iOS, this method loads the asset data synchronously on the UI thread and can block it for extended periods of time.\n * > Use `replaceAsync` to load the asset asynchronously and avoid UI lags.\n *\n * > This method will be deprecated in the future.\n */\n replace(source: VideoSource, disableWarning?: boolean): void;\n\n /**\n * Replaces the current source with a new one, while offloading loading of the asset to a different thread.\n *\n * > On Android and Web, this method is equivalent to `replace`.\n */\n replaceAsync(source: VideoSource): Promise;\n\n /**\n * Seeks the playback by the given number of seconds. The time to which the player seeks may differ from the specified requested time for efficiency,\n * depending on the encoding and what is currently buffered by the player. Use this function to implement playback controls that seek by specific amount of time,\n * in which case, the actual time usually does not have to be precise. For frame accurate seeking, use the [`currentTime`](#currenttime) property.\n */\n seekBy(seconds: number): void;\n\n /**\n * Seeks the playback to the beginning.\n */\n replay(): void;\n\n /**\n * Generates thumbnails from the currently played asset. The thumbnails are references to native images,\n * thus they can be used as a source of the `Image` component from `expo-image`.\n * @platform android\n * @platform ios\n */\n generateThumbnailsAsync(\n times: number | number[],\n options?: VideoThumbnailOptions\n ): Promise;\n}\n\n/**\n * Additional options for video thumbnails generation.\n */\nexport type VideoThumbnailOptions = {\n /**\n * If provided, the generated thumbnail will not exceed this width in pixels, preserving its aspect ratio.\n * @platform android\n * @platform ios\n */\n maxWidth?: number;\n\n /**\n * If provided, the generated thumbnail will not exceed this height in pixels, preserving its aspect ratio.\n * @platform android\n * @platform ios\n */\n maxHeight?: number;\n};\n\n/**\n * Describes the current status of the player.\n * - `idle`: The player is not playing or loading any videos.\n * - `loading`: The player is loading video data from the provided source\n * - `readyToPlay`: The player has loaded enough data to start playing or to continue playback.\n * - `error`: The player has encountered an error while loading or playing the video.\n */\nexport type VideoPlayerStatus = 'idle' | 'loading' | 'readyToPlay' | 'error';\n\nexport type VideoSource = string | number | null | VideoSourceObject;\n\nexport type VideoSourceObject = {\n /**\n * The URI of the video.\n *\n * On iOS, `PHAsset` URIs are supported, but can only be loaded using the [`replaceAsync`](#replaceasyncsource) method or the default [`VideoPlayer`](#videoplayer) constructor.\n *\n * This property is exclusive with the `assetId` property. When both are present, the `assetId` will be ignored.\n */\n uri?: string;\n\n /**\n * The asset ID of a local video asset, acquired with the `require` function.\n * This property is exclusive with the `uri` property. When both are present, the `assetId` will be ignored.\n */\n assetId?: number;\n\n /**\n * Specifies the DRM options which will be used by the player while loading the video.\n */\n drm?: DRMOptions;\n\n /**\n * Specifies information which will be displayed in the now playing notification.\n * When undefined the player will display information contained in the video metadata.\n * @platform android\n * @platform ios\n */\n metadata?: VideoMetadata;\n\n /**\n * Specifies headers sent with the video request.\n * > For DRM license headers use the `headers` field of [`DRMOptions`](#drmoptions).\n * @platform android\n * @platform ios\n */\n headers?: Record;\n\n /**\n * Specifies whether the player should use caching for the video.\n * > Due to platform limitations, the cache cannot be used with HLS video sources on iOS. Caching DRM-protected videos is not supported on Android and iOS.\n * @default false\n * @platform android\n * @platform ios\n */\n useCaching?: boolean;\n\n /**\n * Specifies the content type of the video source. When set to `'auto'`, the player will try to automatically determine the content type.\n *\n * You should use this property when playing HLS, SmoothStreaming or DASH videos from an uri, which does not contain a standardized extension for the corresponding media type.\n * @default 'auto'\n * @platform android\n * @platform ios\n */\n contentType?: ContentType;\n};\n\n/**\n * Contains information about any errors that the player encountered during the playback\n */\nexport type PlayerError = {\n message: string;\n};\n\n/**\n * Contains information that will be displayed in the now playing notification when the video is playing.\n * @platform android\n * @platform ios\n */\nexport type VideoMetadata = {\n /**\n * The title of the video.\n * @platform android\n * @platform ios\n */\n title?: string;\n /**\n * Secondary text that will be displayed under the title.\n * @platform android\n * @platform ios\n */\n artist?: string;\n /**\n * The uri of the video artwork.\n * @platform android\n * @platform ios\n */\n artwork?: string;\n};\n\n/**\n * Specifies which type of DRM to use:\n * - Android supports ClearKey, PlayReady and Widevine.\n * - iOS supports FairPlay.\n */\nexport type DRMType = 'clearkey' | 'fairplay' | 'playready' | 'widevine';\n\n/**\n * Specifies DRM options which will be used by the player while loading the video.\n */\nexport type DRMOptions = {\n /**\n * Determines which type of DRM to use.\n */\n type: DRMType;\n\n /**\n * Determines the license server URL.\n */\n licenseServer: string;\n\n /**\n * Determines headers sent to the license server on license requests.\n */\n headers?: Record;\n\n /**\n * Specifies whether the DRM is a multi-key DRM.\n * @platform android\n */\n multiKey?: boolean;\n\n /**\n * Specifies the content ID of the stream.\n * @platform ios\n */\n contentId?: string;\n\n /**\n * Specifies the certificate URL for the FairPlay DRM.\n * @platform ios\n */\n certificateUrl?: string;\n\n /**\n * Specifies the base64 encoded certificate data for the FairPlay DRM.\n * When this property is set, the `certificateUrl` property is ignored.\n * @platform ios\n */\n base64CertificateData?: string;\n};\n\n/**\n * Specifies buffer options which will be used by the player when buffering the video.\n *\n * @platform android\n * @platform ios\n */\nexport type BufferOptions = {\n /**\n * The duration in seconds which determines how much media the player should buffer ahead of the current playback time.\n *\n * On iOS when set to `0` the player will automatically decide appropriate buffer duration.\n *\n * Equivalent to [`AVPlayerItem.preferredForwardBufferDuration`](https://developer.apple.com/documentation/avfoundation/avplayeritem/1643630-preferredforwardbufferduration).\n * @default Android: 20, iOS: 0\n * @platform android\n * @platform ios\n */\n readonly preferredForwardBufferDuration?: number;\n\n /**\n * A Boolean value that indicates whether the player should automatically delay playback in order to minimize stalling.\n *\n * Equivalent to [`AVPlayer.automaticallyWaitsToMinimizeStalling`](https://developer.apple.com/documentation/avfoundation/avplayer/1643482-automaticallywaitstominimizestal).\n * @default true\n * @platform ios\n */\n readonly waitsToMinimizeStalling?: boolean;\n\n /**\n * Minimum duration of the buffer in seconds required to continue playing after the player has been paused or started buffering.\n *\n * > This property will be ignored if `preferredForwardBufferDuration` is lower.\n * @default 2\n * @platform android\n */\n readonly minBufferForPlayback?: number;\n\n /**\n * The maximum number of bytes that the player can buffer from the network.\n * When 0 the player will automatically decide appropriate buffer size.\n *\n * @default 0\n * @platform android\n */\n readonly maxBufferBytes?: number | null;\n\n /**\n * A Boolean value which determines whether the player should prioritize time over size when buffering media.\n *\n * @default false\n * @platform android\n */\n readonly prioritizeTimeOverSizeThreshold?: boolean;\n};\n\n/**\n * Specifies the content type of the source.\n *\n * - `auto`: The player will automatically determine the content type of the video.\n * - `progressive`: The player will use progressive download content type. This is the default `ContentType` when the uri does not contain an extension.\n * - `hls`: The player will use HLS content type.\n * - `dash`: The player will use DASH content type (Android-only).\n * - `smoothStreaming`: The player will use SmoothStreaming content type (Android-only).\n *\n * @default `auto`\n */\nexport type ContentType = 'auto' | 'progressive' | 'hls' | 'dash' | 'smoothStreaming';\n\n/**\n * Specifies the audio mode that the player should use. Audio mode is set on per-app basis, if there are multiple players playing and\n * have different a `AudioMode` specified, the highest priority mode will be used. Priority order: 'doNotMix' > 'auto' > 'duckOthers' > 'mixWithOthers'.\n *\n * - `mixWithOthers`: The player will mix its audio output with other apps.\n * - `duckOthers`: The player will lower the volume of other apps if any of the active players is outputting audio.\n * - `auto`: The player will allow other apps to keep playing audio only when it is muted. On iOS it will always interrupt other apps when `showNowPlayingNotification` is `true` due to system requirements.\n * - `doNotMix`: The player will pause playback in other apps, even when it's muted.\n *\n * > On iOS, the Now Playing notification is dependent on the audio mode. If the audio mode is different from `doNotMix` or `auto` this feature will not work.\n */\nexport type AudioMixingMode = 'mixWithOthers' | 'duckOthers' | 'auto' | 'doNotMix';\n\nexport type SubtitleTrack = {\n /**\n * A string used by `expo-video` to identify the subtitle track.\n *\n * @platform android\n */\n id?: string;\n\n /**\n * Language of the subtitle track. For example, `en`, `pl`, `de`.\n */\n language: string;\n\n /**\n * Label of the subtitle track in the language of the device.\n */\n label: string;\n\n /**\n * Name of the subtitle track as specified in the media source.\n * @platform android\n * @platform ios\n */\n name?: string;\n\n /**\n * Indicates whether this is the default subtitle track.\n * @platform android\n * @platform ios\n */\n isDefault?: boolean;\n\n /**\n * Indicates whether this track should be auto-selected based on user preferences.\n * @platform android\n * @platform ios\n */\n autoSelect?: boolean;\n};\n\n/**\n * Specifies a VideoTrack loaded from a [`VideoSource`](#videosource).\n */\nexport type VideoTrack = {\n /**\n * The id of the video track.\n *\n * > This field is platform-specific and may return different depending on the operating system.\n */\n id: string;\n\n /**\n * The URL of the `VideoTrack` for HLS video sources. `null` for other source types.\n */\n url: string | null;\n\n /**\n * Size of the video track.\n */\n size: VideoSize;\n\n /**\n * MimeType of the video track or null if unknown.\n */\n mimeType: string | null;\n\n /**\n * Indicates whether the video track format is supported by the device.\n *\n * @platform android\n */\n isSupported: boolean;\n\n /**\n * Specifies the bitrate in bits per second. This is the peak bitrate if known, or else the average bitrate if known, or else null.\n *\n * @deprecated Use `peakBitrate` or `averageBitrate` instead.\n */\n bitrate: number | null;\n\n /**\n * Specifies the average bitrate in bits per second or null if the value is unknown.\n *\n */\n averageBitrate: number | null;\n\n /**\n * Specifies the average bitrate in bits per second or null if the value is unknown.\n */\n peakBitrate: number | null;\n\n /**\n * Specifies the frame rate of the video track in frames per second.\n */\n frameRate: number | null;\n};\n\n/**\n * Specifies the size of a video track.\n */\nexport type VideoSize = {\n /**\n * Width of the video track in pixels.\n */\n width: number;\n /**\n * Height of the video track in pixels.\n */\n height: number;\n};\n\nexport type AudioTrack = {\n /**\n * A string used by expo-video to identify the audio track.\n * @platform android\n */\n id?: string;\n\n /**\n * Language of the audio track. For example, 'en', 'pl', 'de'.\n */\n language: string;\n\n /**\n * Label of the audio track in the language of the device.\n */\n label: string;\n\n /**\n * Name of the audio track as specified in the media source.\n * @platform android\n * @platform ios\n */\n name?: string;\n\n /**\n * Indicates whether this is the default audio track.\n * @platform android\n * @platform ios\n */\n isDefault?: boolean;\n\n /**\n * Indicates whether this track should be auto-selected based on user preferences.\n * @platform android\n * @platform ios\n */\n autoSelect?: boolean;\n};\n\n/**\n * Determines the time that the actual position seeked to may precede or exceed the requested seek position.\n * Larger tolerance will usually result in faster seeking.\n * This property affects the precision of setting the [`currentTime`](#currenttime) property and the [`seekBy`](#seekbyseconds) method, and on Android, it also affects the accuracy of the scrubber from the default native controls.\n *\n * > If you are trying to optimize for scrubbing (many frequent seeks), also see [`ScrubbingModeOptions`](#scrubbingmodeoptions-1).\n *\n * @platform android\n * @platform ios\n */\nexport type SeekTolerance = {\n /**\n * The maximum time that the actual position seeked to may precede the requested seek position, in seconds. Must be non-negative.\n * @default 0\n */\n toleranceBefore?: number;\n\n /**\n * The maximum time that the actual position seeked to may exceed the requested seek position, in seconds. Must be non-negative.\n * @default 0\n */\n toleranceAfter?: number;\n};\n\n/**\n * Defines scrubbing mode options used by a [`VideoPlayer`](#videoplayer).\n */\nexport type ScrubbingModeOptions = {\n /**\n * Whether the codec operating rate should be increased in scrubbing mode.\n *\n * You should only enable this when the player is receiving a large number of seeks in a short period of time. For less frequent seeks, fine-tuning the [`SeekTolerance`](#seektolerance-1) may be sufficient.\n *\n * On Android, the player may consume more resources in this mode, so it should only be used for short periods of time in response to user interaction (for example, dragging on a progress bar UI element).\n *\n * On Android, when `scrubbingModeEnabled` is `true`, the playback is suppressed. You should set this property back to `false` when the user interaction ends to allow the playback to resume.\n * For best results, on iOS you should pause the playback when scrubbing.\n *\n * > For best scrubbing performance, consider also increasing the seeking tolerance using the [`SeekTolerance`](#seektolerance-1) property.\n *\n * > Other scrubbing mode options will have no effect when this is `false`.\n * @default false\n * @platform android\n * @platform ios\n */\n scrubbingModeEnabled?: boolean;\n\n /**\n * Whether the codec operating rate should be increased in scrubbing mode.\n *\n * @platform android\n * @default true\n */\n increaseCodecOperatingRate?: boolean;\n\n /**\n * Sets whether ExoPlayer's dynamic scheduling should be enabled in scrubbing mode.\n * This can result in available output buffers being handled more quickly when seeking.\n *\n * @platform android\n * @default true\n */\n enableDynamicScheduling?: boolean;\n /**\n * Sets whether to use `MediaCodec.BUFFER_FLAG_DECODE_ONLY` in scrubbing mode.\n * When playback is using MediaCodec on API 34+, this flag can speed up seeking by signalling that the decoded output of buffers between the previous keyframe and the target frame is not needed by the player.\n *\n * @platform android\n * @default true\n */\n useDecodeOnlyFlag?: boolean;\n\n /**\n * Sets whether to avoid flushing the decoder (where possible) in scrubbing mode.\n * When `true`, avoids flushing the decoder when a new seek starts decoding from a key-frame in compatible content.\n *\n * @platform android\n * @default true\n */\n allowSkippingMediaCodecFlush?: boolean;\n};\n\n/**\n * Options to apply to the player builder before the native constructor is invoked\n * @platform android\n */\nexport type PlayerBuilderOptions = {\n /**\n * Seek backward increment in seconds.\n * Values will be clamped between 0.001 and 999 seconds.\n * @platform android\n */\n seekBackwardIncrement?: number;\n\n /**\n * Seek forward increment in seconds.\n * Values will be clamped between 0.001 and 999 seconds.\n * @platform android\n */\n seekForwardIncrement?: number;\n};\n"]}
\ No newline at end of file
diff --git a/packages/expo-video/ios/Records/Tracks.swift b/packages/expo-video/ios/Records/Tracks.swift
index 13c067f390a407..8488fc277feb76 100644
--- a/packages/expo-video/ios/Records/Tracks.swift
+++ b/packages/expo-video/ios/Records/Tracks.swift
@@ -11,25 +11,55 @@ internal struct SubtitleTrack: Record {
@Field
var label: String? = nil
- static func from(mediaSelectionOption option: AVMediaSelectionOption) -> SubtitleTrack? {
+ @Field
+ var name: String? = nil
+
+ @Field
+ var isDefault: Bool = false
+
+ @Field
+ var autoSelect: Bool = false
+
+ static func from(mediaSelectionOption option: AVMediaSelectionOption, in group: AVMediaSelectionGroup? = nil) -> SubtitleTrack? {
guard let identifier = option.locale?.identifier else {
return nil
}
- return SubtitleTrack(language: identifier, label: option.displayName)
+ let isDefault = group?.defaultOption == option
+ let autoSelect = option.hasMediaCharacteristic(.isAuxiliaryContent) == false
+
+ return SubtitleTrack(
+ language: identifier,
+ label: option.displayName,
+ name: option.commonMetadata.first(where: { $0.commonKey == .commonKeyTitle })?.stringValue,
+ isDefault: isDefault,
+ autoSelect: autoSelect
+ )
}
}
internal struct AudioTrack: Record {
@Field var language: String? = nil
@Field var label: String? = nil
+ @Field var name: String? = nil
+ @Field var isDefault: Bool = false
+ @Field var autoSelect: Bool = false
- static func from(mediaSelectionOption option: AVMediaSelectionOption) -> AudioTrack? {
+ static func from(mediaSelectionOption option: AVMediaSelectionOption, in group: AVMediaSelectionGroup? = nil) -> AudioTrack? {
guard let identifier = option.locale?.identifier else {
return nil
}
- return AudioTrack(language: identifier, label: option.displayName)
+ let isDefault = group?.defaultOption == option
+ let autoSelect = option.hasMediaCharacteristic(.isAuxiliaryContent) == false
+
+ return AudioTrack(
+ language: identifier,
+ label: option.displayName,
+ name: option.commonMetadata.first(where: { $0.commonKey == .commonKeyTitle })?.stringValue,
+ isDefault: isDefault,
+ autoSelect: autoSelect
+ )
}
}
diff --git a/packages/expo-video/ios/VideoPlayerAudioTracks.swift b/packages/expo-video/ios/VideoPlayerAudioTracks.swift
index 0166c8d252fdb4..461e3cb3336a00 100644
--- a/packages/expo-video/ios/VideoPlayerAudioTracks.swift
+++ b/packages/expo-video/ios/VideoPlayerAudioTracks.swift
@@ -64,7 +64,7 @@ class VideoPlayerAudioTracks {
if let group = try await asset.loadMediaSelectionGroup(for: characteristic) {
for option in group.options {
- guard let audioTrack = AudioTrack.from(mediaSelectionOption: option) else {
+ guard let audioTrack = AudioTrack.from(mediaSelectionOption: option, in: group) else {
continue
}
@@ -84,6 +84,6 @@ class VideoPlayerAudioTracks {
return nil
}
- return AudioTrack.from(mediaSelectionOption: selectedMediaOption)
+ return AudioTrack.from(mediaSelectionOption: selectedMediaOption, in: mediaSelectionGroup)
}
}
diff --git a/packages/expo-video/ios/VideoPlayerSubtitles.swift b/packages/expo-video/ios/VideoPlayerSubtitles.swift
index 59c84fbbb39d01..ab8464c918cf43 100644
--- a/packages/expo-video/ios/VideoPlayerSubtitles.swift
+++ b/packages/expo-video/ios/VideoPlayerSubtitles.swift
@@ -63,7 +63,7 @@ class VideoPlayerSubtitles {
if let group = try await asset.loadMediaSelectionGroup(for: characteristic) {
for option in group.options {
- guard let subtitleTrack = SubtitleTrack.from(mediaSelectionOption: option) else {
+ guard let subtitleTrack = SubtitleTrack.from(mediaSelectionOption: option, in: group) else {
continue
}
@@ -83,6 +83,6 @@ class VideoPlayerSubtitles {
return nil
}
- return SubtitleTrack.from(mediaSelectionOption: selectedMediaOption)
+ return SubtitleTrack.from(mediaSelectionOption: selectedMediaOption, in: mediaSelectionGroup)
}
}
diff --git a/packages/expo-video/src/VideoPlayer.types.ts b/packages/expo-video/src/VideoPlayer.types.ts
index d0240354f3be5f..385de4467d2b04 100644
--- a/packages/expo-video/src/VideoPlayer.types.ts
+++ b/packages/expo-video/src/VideoPlayer.types.ts
@@ -585,6 +585,27 @@ export type SubtitleTrack = {
* Label of the subtitle track in the language of the device.
*/
label: string;
+
+ /**
+ * Name of the subtitle track as specified in the media source.
+ * @platform android
+ * @platform ios
+ */
+ name?: string;
+
+ /**
+ * Indicates whether this is the default subtitle track.
+ * @platform android
+ * @platform ios
+ */
+ isDefault?: boolean;
+
+ /**
+ * Indicates whether this track should be auto-selected based on user preferences.
+ * @platform android
+ * @platform ios
+ */
+ autoSelect?: boolean;
};
/**
@@ -674,6 +695,27 @@ export type AudioTrack = {
* Label of the audio track in the language of the device.
*/
label: string;
+
+ /**
+ * Name of the audio track as specified in the media source.
+ * @platform android
+ * @platform ios
+ */
+ name?: string;
+
+ /**
+ * Indicates whether this is the default audio track.
+ * @platform android
+ * @platform ios
+ */
+ isDefault?: boolean;
+
+ /**
+ * Indicates whether this track should be auto-selected based on user preferences.
+ * @platform android
+ * @platform ios
+ */
+ autoSelect?: boolean;
};
/**
diff --git a/packages/expo-web-browser/android/src/main/java/expo/modules/webbrowser/CustomTabsActivitiesHelper.kt b/packages/expo-web-browser/android/src/main/java/expo/modules/webbrowser/CustomTabsActivitiesHelper.kt
index 9d0e490a6e1b17..c1169b2ed7d558 100644
--- a/packages/expo-web-browser/android/src/main/java/expo/modules/webbrowser/CustomTabsActivitiesHelper.kt
+++ b/packages/expo-web-browser/android/src/main/java/expo/modules/webbrowser/CustomTabsActivitiesHelper.kt
@@ -8,7 +8,6 @@ import androidx.browser.customtabs.CustomTabsClient
import androidx.browser.customtabs.CustomTabsIntent
import androidx.browser.customtabs.CustomTabsService
import androidx.core.net.toUri
-import expo.modules.core.errors.CurrentActivityNotFoundException
import expo.modules.kotlin.AppContext
private const val DUMMY_URL = "https://expo.dev"
@@ -20,7 +19,6 @@ internal class CustomTabsActivitiesHelper(
// region Actual custom tabs activities helper methods
/**
- * @throws CurrentActivityNotFoundException
* @throws PackageManagerNotFoundException
*/
fun canResolveIntent(customTabsIntent: CustomTabsIntent): Boolean =
@@ -28,7 +26,6 @@ internal class CustomTabsActivitiesHelper(
/**
* @throws PackageManagerNotFoundException
- * @throws CurrentActivityNotFoundException
*/
val customTabsResolvingActivities: ArrayList
get() = getResolvingActivities(createDefaultCustomTabsIntent())
@@ -38,7 +35,6 @@ internal class CustomTabsActivitiesHelper(
/**
* @throws PackageManagerNotFoundException
- * @throws CurrentActivityNotFoundException
*/
val customTabsResolvingServices: ArrayList
get() = packageManager.queryIntentServices(createDefaultCustomTabsServiceIntent(), 0)
@@ -48,7 +44,6 @@ internal class CustomTabsActivitiesHelper(
/**
* @throws PackageManagerNotFoundException
- * @throws CurrentActivityNotFoundException
*/
fun getPreferredCustomTabsResolvingActivity(packages: List?): String? {
val resolvedPackages = packages ?: customTabsResolvingActivities
@@ -57,7 +52,6 @@ internal class CustomTabsActivitiesHelper(
/**
* @throws PackageManagerNotFoundException
- * @throws CurrentActivityNotFoundException
*/
val defaultCustomTabsResolvingActivity: String?
get() {
@@ -65,9 +59,6 @@ internal class CustomTabsActivitiesHelper(
return info?.activityInfo?.packageName
}
- /**
- * @throws CurrentActivityNotFoundException
- */
fun startCustomTabs(tabsIntent: CustomTabsIntent, options: OpenBrowserOptions) {
val url = tabsIntent.intent.data ?: throw NoUrlProvidedException()
@@ -107,7 +98,6 @@ internal class CustomTabsActivitiesHelper(
// region Private helpers
/**
- * @throws CurrentActivityNotFoundException
* @throws PackageManagerNotFoundException
*/
private fun getResolvingActivities(customTabsIntent: CustomTabsIntent): List {
@@ -115,15 +105,11 @@ internal class CustomTabsActivitiesHelper(
}
/**
- * @throws CurrentActivityNotFoundException
* @throws PackageManagerNotFoundException
*/
private val packageManager: PackageManager
get() = currentActivity.packageManager ?: throw PackageManagerNotFoundException()
- /**
- * @throws CurrentActivityNotFoundException
- */
private val currentActivity: Activity
get() = appContext.throwingActivity
diff --git a/packages/expo-web-browser/android/src/main/java/expo/modules/webbrowser/WebBrowserModule.kt b/packages/expo-web-browser/android/src/main/java/expo/modules/webbrowser/WebBrowserModule.kt
index a7b8a65704d1f8..1193713e031932 100644
--- a/packages/expo-web-browser/android/src/main/java/expo/modules/webbrowser/WebBrowserModule.kt
+++ b/packages/expo-web-browser/android/src/main/java/expo/modules/webbrowser/WebBrowserModule.kt
@@ -1,6 +1,5 @@
package expo.modules.webbrowser
-import expo.modules.core.errors.CurrentActivityNotFoundException
import android.os.Bundle
import android.text.TextUtils
import androidx.browser.customtabs.CustomTabColorSchemeParams
@@ -64,7 +63,6 @@ class WebBrowserModule : Module() {
)
}
- // throws CurrentActivityNotFoundException
AsyncFunction("getCustomTabsSupportingBrowsersAsync") {
val activities = customTabsResolver.customTabsResolvingActivities
val services = customTabsResolver.customTabsResolvingServices
@@ -82,7 +80,6 @@ class WebBrowserModule : Module() {
}
}
- // throws CurrentActivityNotFoundException
AsyncFunction("openBrowserAsync") { url: String, options: OpenBrowserOptions ->
val tabsIntent = createCustomTabsIntent(options).apply {
intent.data = url.toUri()
@@ -147,8 +144,6 @@ class WebBrowserModule : Module() {
packageName?.takeIf { it.isNotEmpty() }.ifNull {
customTabsResolver.getPreferredCustomTabsResolvingActivity(null)
}
- } catch (_: CurrentActivityNotFoundException) {
- throw NoPreferredPackageFound()
} catch (_: PackageManagerNotFoundException) {
throw NoPreferredPackageFound()
}