跳到主要内容

2026年Expo开发声音播放App的最新方法,以及如何实现后台播放

1. 技术栈与依赖

本指南基于 Expo SDK 54 + React Native 0.81,使用以下核心库:

用途
expo-audio音频文件播放(mp3等)
expo-speech语音合成播放(TTS)

安装方式:

npx expo install expo-audio expo-speech

2. 最简单的实现(非Hook方式)

如果不使用React Hook,可以直接创建Player实例:

import { Audio } from 'expo-audio';

const player = new Audio.Player();
await player.loadAsync(require('./sound.mp3'));
player.setIsLoopingAsync(true);
await player.playAsync();

核心API

  • loadAsync(source) - 加载音频
  • playAsync() / pauseAsync() / stopAsync() - 播放控制
  • setIsLoopingAsync(true) - 循环播放
  • setVolumeAsync(0.5) - 设置音量

注意:非Hook方式需要手动管理生命周期,在组件卸载时调用 player.unloadAsync() 释放资源。


3. 推荐方式:Hook实现

Expo提供了两个Hook,简化了状态管理和自动清理:

import { useAudioPlayer, useAudioPlayerStatus } from 'expo-audio';

const player = useAudioPlayer(require('./sound.mp3'));
const status = useAudioPlayerStatus(player);

优势

  • 自动订阅播放状态
  • 组件卸载时自动清理
  • 状态变化触发组件重渲染

4. 音频文件播放

4.1 加载音频

支持多种音频源格式:

// 本地文件
const player = useAudioPlayer(require('./audio.mp3'));

// URL远程音频
const player = useAudioPlayer({ uri: 'https://example.com/audio.mp3' });

4.2 播放控制

// 播放
player.play();

// 暂停
player.pause();

// 停止(回到开头)
player.stop();

// 跳转进度
player.seekTo(60000); // 毫秒,60秒

4.3 循环播放

player.loop = true; // 开启循环

4.4 音量控制

player.volume = 0.5; // 0.0 ~ 1.0

4.5 获取播放状态

const status = useAudioPlayerStatus(player);

if (status?.isPlaying) { /* 正在播放 */ }
if (status?.isBuffering) { /* 缓冲中 */ }
const position = status?.positionMillis; // 当前进度(毫秒)
const duration = status?.durationMillis; // 总时长

5. 语音合成播放

使用 expo-speech 实现TTS:

import * as Speech from 'expo-speech';

Speech.speak('你好,这是语音合成');

5.1 参数配置

Speech.speak('语音内容', {
language: 'zh-CN', // 语言:zh-CN, en-US, ja-JP等
rate: 0.5, // 语速:0.0 ~ 1.0
pitch: 1.0, // 音调:0.0 ~ 2.0
volume: 0.5, // 音量:0.0 ~ 1.0
});

5.2 回调控制

Speech.speak('内容', {
onDone: () => console.log('播放完成'),
onStopped: () => console.log('被停止'),
onError: (e) => console.error('错误', e),
});

5.3 停止播放

Speech.stop(); // 立即停止当前所有语音

6. 后台播放配置(重点)

实现后台播放需要在平台配置运行时设置两个层面进行配置。

6.1 平台配置

iOS配置(关键)

app.json 中添加:

{
"expo": {
"ios": {
"infoPlist": {
"UIBackgroundModes": ["audio"]
}
}
}
}

作用:声明App需要后台音频能力,使系统允许音频在App进入后台时继续播放。

Android配置(简要)

{
"expo": {
"android": {
"permissions": [
"android.permission.FOREGROUND_SERVICE",
"android.permission.FOREGROUND_SERVICE_MEDIA_PLAYBACK",
"android.permission.WAKE_LOCK"
]
}
}
}

6.2 运行时Audio Mode设置

在App启动时(通常在根Layout或专门初始化文件中)调用:

import { setAudioModeAsync } from 'expo-audio';

await setAudioModeAsync({
allowsRecording: false,
playsInSilentMode: true,
shouldPlayInBackground: true, // 关键:允许后台播放
interruptionMode: 'doNotMix', // 不与其他音频混音
});

参数说明

参数说明
shouldPlayInBackground必须设为 true,否则App进入后台时音频会停止
playsInSilentMode静音模式下是否继续播放(iOS)
interruptionMode来电等中断时的处理策略

6.3 锁屏Controls与通知栏

让音频在锁屏界面显示播放控制,并支持快捷操作。

6.3.1 启用锁屏控件

player.setActiveForLockScreen(true, {
title: '歌曲标题',
artist: '艺术家',
artwork: require('./cover.png'), // 可选:专辑封面
});

参数说明

  • title - 显示标题
  • artist - 艺术家名称
  • artwork - 封面图片(本地require或URL)

6.3.2 更新播放信息

播放过程中可动态更新:

player.updatePlaybackUI({
title: '新标题',
artist: '新艺术家',
});

6.3.3 清除锁屏控件

player.clearLockScreenControls();

6.3.4 锁屏控制事件监听

监听用户在锁屏界面的操作:

player.addOnPlaybackStatusUpdate((status) => {
if (status?.isLoaded) {
// 可根据需要处理
}
});

注意:锁屏控件的底层实现依赖于iOS的 MPNowPlayingInfoCenterMPRemoteCommandCenter

6.3.5 锁屏控件的限制与注意事项

expo-audio 的锁屏控件存在以下限制:

1. 播放/暂停按钮无法隐藏

  • 系统默认显示播放/暂停按钮,无法通过 API 隐藏
  • 点击按钮会改变 native player 状态,但不会触发 App 状态更新

2. 播放状态不同步问题

// 问题示例
player.setActiveForLockScreen(true, {
title: '标题',
artist: '艺术家',
});
// 用户在锁屏点击暂停 → native player 暂停了
// 但 App 内的播放状态仍然是 true
// 需要手动监听 player 状态或使用其他机制同步

3. 快进/快退按钮

  • 可以通过 AudioLockScreenOptions 配置显示/隐藏:
player.setActiveForLockScreen(
true,
{ title: '标题', artist: '艺术家' },
{ showSeekForward: false, showSeekBackward: false } // 隐藏快进/快退
);
  • 但按钮本身也是无效的(不触发 App 回调)

4. 音量控制

  • 音量滑块有效,是系统原生支持的功能

5. 如需完整控件功能

如需锁屏控件点击后能正确回调 App(实现真正的播放/暂停/快进/快退功能),需要使用 react-native-track-player

6.4 完整后台播放示例

// 初始化(在App入口调用一次)
await setAudioModeAsync({
allowsRecording: false,
playsInSilentMode: true,
shouldPlayInBackground: true,
interruptionMode: 'doNotMix',
});

// 播放器使用
const player = useAudioPlayer(require('./bgm.mp3'));
player.loop = true;

const play = () => {
player.play();

// 设置锁屏信息
setTimeout(() => {
player.setActiveForLockScreen(true, {
title: '标题',
artist: '艺术家',
});
}, 500);
};

const stop = () => {
player.pause();
player.clearLockScreenControls();
};

7. 测试方法

7.1 iOS真机测试要点

  1. Build版本测试

    • 必须使用 expo run:ioseas build 生成的ipa包
    • Expo Go的某些后台功能可能受限
  2. 锁屏测试

    • 播放音频后按电源键锁屏
    • 验证音频是否继续播放
    • 验证锁屏界面是否显示播放控件
  3. 后台切换测试

    • 播放音频时按Home键切到后台
    • 切换回前台,验证播放状态正常
  4. 中断测试

    • 播放时接听电话,验证音频正确暂停/恢复
    • 播放时启动其他音频App,验证行为符合预期

7.2 Android测试要点

  1. 后台服务测试

    • 锁屏或切后台后,观察通知栏是否有常驻通知
    • 验证点击通知是否能控制播放
  2. 厂商定制系统

    • 某些Android厂商有后台管理白名单,需手动添加到白名单

7.3 常见问题排查

问题可能原因解决方案
后台播放无效未配置 UIBackgroundModes检查app.json配置
锁屏控件不显示未调用 setActiveForLockScreen在play后调用
切后台后停止shouldPlayInBackground 未设true检查setAudioModeAsync
iOS模拟器正常但真机无效模拟器与真机行为差异必须用真机测试

8. 总结

实现后台音频播放的核心要点:

  1. app.json 声明 UIBackgroundModes: ["audio"](iOS)
  2. setAudioModeAsync 设置 shouldPlayInBackground: true
  3. 使用 useAudioPlayer Hook管理播放器
  4. 调用 setActiveForLockScreen 显示锁屏控件
  5. 真机测试是验证后台播放的唯一标准

掌握以上内容,即可在Expo项目中实现稳定的音频后台播放功能。