managarten/apps-archived/memoro/apps/mobile/EXPO_AUDIO_MIGRATION.md
Till-JS 61d181fbc2 chore: archive inactive projects to apps-archived/
Move inactive projects out of active workspace:
- bauntown (community website)
- maerchenzauber (AI story generation)
- memoro (voice memo app)
- news (news aggregation)
- nutriphi (nutrition tracking)
- reader (reading app)
- uload (URL shortener)
- wisekeep (AI wisdom extraction)

Update CLAUDE.md documentation:
- Add presi to active projects
- Document archived projects section
- Update workspace configuration

Archived apps can be re-activated by moving back to apps/

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-29 07:03:59 +01:00

7.4 KiB

expo-av to expo-audio Migration Guide

Overview

This guide documents the migration from expo-av to expo-audio completed as part of the Expo SDK 54 upgrade to address Android 16 compatibility issues and deprecation warnings.

Background

Why We Migrated

  1. Deprecation: expo-av was deprecated in Expo SDK 54
  2. Android 16 Issues: Recording functionality broken on Android 16 devices
  3. Performance: expo-audio offers better performance and smaller bundle size
  4. Future Support: expo-audio is actively maintained and developed

Timeline

  • Date: January 2025
  • Expo SDK: 53 → 54
  • React Native: 0.79.2 → 0.81.4
  • expo-av: 15.1.4 → removed
  • expo-audio: not used → 1.0.13

Migration Steps

1. Package Installation

# Remove expo-av
npm uninstall expo-av

# Install expo-audio
npm install expo-audio@~1.0.13

# Clean and reinstall
npx expo install --fix

2. Import Changes

Recording

// Before (expo-av)
import { Audio } from 'expo-av';

// After (expo-audio)
import {
	AudioRecorder,
	RecordingPresets,
	setAudioModeAsync,
	requestRecordingPermissionsAsync,
	getRecordingPermissionsAsync,
} from 'expo-audio';

Playback

// Before (expo-av)
import { Audio } from 'expo-av';

// After (expo-audio)
import { AudioPlayer, createAudioPlayer, setAudioModeAsync } from 'expo-audio';

3. API Changes

Recording API

Starting Recording
// Before (expo-av)
const recording = new Audio.Recording();
await recording.prepareToRecordAsync(Audio.RecordingOptionsPresets.HIGH_QUALITY);
await recording.startAsync();

// After (expo-audio)
const recorder = new AudioRecorder(RecordingPresets.HIGH_QUALITY);
await recorder.prepareToRecordAsync();
recorder.record(); // Note: synchronous, not async
Stopping Recording
// Before (expo-av)
await recording.stopAndUnloadAsync();
const uri = recording.getURI();

// After (expo-audio)
await recorder.stop();
const uri = recorder.uri; // Direct property access
Pause/Resume
// Before (expo-av)
await recording.pauseAsync();
await recording.startAsync(); // Resume

// After (expo-audio)
recorder.pause(); // Synchronous
recorder.record(); // Resume (same as start)

Playback API

Creating Player
// Before (expo-av)
const { sound } = await Audio.Sound.createAsync({ uri }, { progressUpdateIntervalMillis: 100 });

// After (expo-audio)
const player = createAudioPlayer(uri);
// Note: No progress update interval option; use polling
Playback Control
// Before (expo-av)
await sound.playAsync();
await sound.pauseAsync();
await sound.stopAsync();
await sound.setPositionAsync(positionMillis);
await sound.unloadAsync();

// After (expo-audio)
await player.play();
await player.pause();
await player.stop();
player.currentTime = positionSeconds; // Note: seconds, not milliseconds
player.release(); // Synchronous cleanup
Status Updates
// Before (expo-av)
sound.setOnPlaybackStatusUpdate((status) => {
	if (status.isLoaded) {
		console.log(status.positionMillis, status.durationMillis);
	}
});

// After (expo-audio)
// No built-in status updates; use polling
setInterval(() => {
	console.log(player.currentTime, player.duration); // In seconds
	console.log(player.playing); // Boolean
}, 100);

Audio Mode Configuration

// Before (expo-av)
await Audio.setAudioModeAsync({
	allowsRecordingIOS: true,
	playsInSilentModeIOS: true,
	staysActiveInBackground: true,
	interruptionModeIOS: InterruptionModeIOS.DoNotMix,
	interruptionModeAndroid: InterruptionModeAndroid.DoNotMix,
	shouldDuckAndroid: true,
	playThroughEarpieceAndroid: false,
});

// After (expo-audio)
await setAudioModeAsync({
	allowsRecording: true,
	playsInSilentMode: true,
	shouldPlayInBackground: true,
	interruptionMode: 'doNotMix', // String literals instead of enums
	// Note: Some options like shouldDuckAndroid are not available
});

Permissions

// Before (expo-av)
const { status } = await Audio.requestPermissionsAsync();
const { status } = await Audio.getPermissionsAsync();

// After (expo-audio)
const { granted } = await requestRecordingPermissionsAsync();
const { granted } = await getRecordingPermissionsAsync();

Files Modified

Core Recording Services

  • features/audioRecording/audioRecording.service.ts
  • features/audioRecording/audioRecording.service.android.ts
  • features/audioRecording/audioRecording.service.ios.ts
  • features/audioRecording/audioRecording.service.web.ts
  • features/audioRecording/audioRecording.types.ts

Audio Player

  • features/audioPlayer/useAudioPlayer.ts
  • features/audioPlayer/store/audioPlaybackStore.ts

Storage & Utilities

  • features/storage/fileStorage.service.ts
  • features/storage/fileStorage.service.web.ts
  • utils/mediaUtils.ts

Sound Effects

  • features/audioRecording/services/recordingSoundManager.ts

Configuration

  • package.json
  • app.json (plugin configuration)

Platform-Specific Considerations

Android 16

  • Added foreground state verification before recording
  • Implemented AppState monitoring for background restrictions
  • Added explicit error handling for permission denials

iOS

  • Maintained compatibility with existing iOS audio session configuration
  • No significant changes required for iOS implementation

Web

  • Updated to use Web Audio API compatible methods
  • Maintained fallback for permissions API

Known Issues & Workarounds

1. Zero-byte Audio Files (Expo SDK 54)

Issue: Some Android devices create zero-byte audio files Reference: GitHub issue #39646 Workaround: Added logging and validation after recording stops

2. Missing Status Updates

Issue: No built-in playback status updates like expo-av Solution: Implemented polling mechanism with setInterval

3. Time Units Difference

Issue: expo-audio uses seconds, expo-av used milliseconds Solution: Added conversion where necessary (÷ 1000 for ms → s)

Benefits Achieved

  1. Android 16 Compatibility: Recording works on latest Android devices
  2. Smaller Bundle: Reduced app size by ~200KB
  3. Better Performance: Faster audio initialization and lower memory usage
  4. Simpler API: More intuitive method names and patterns
  5. Future-Proof: Active development and support from Expo team

Testing Checklist

  • Audio recording starts successfully
  • Recording can be paused and resumed
  • Recording stops and saves properly
  • Audio playback works correctly
  • Seek/scrub functionality works
  • Volume controls function properly
  • Background recording (iOS)
  • Permissions are requested correctly
  • Sound effects play correctly
  • Multiple audio instances don't conflict

Rollback Plan

If issues arise, rollback by:

  1. Revert package.json changes
  2. Run npm install expo-av@~15.1.4
  3. Revert all file changes listed above
  4. Run npx expo install --fix
  5. Clean build folders and rebuild

Resources

Contact

For questions about this migration, please refer to the project maintainers or create an issue in the project repository.