实现了一个音频播放器类 AVPlayerClass
,提供了丰富的音频播放功能和管理能力。以下是该文件的主要功能总结:
1. 播放器核心功能
-
播放器初始化与状态管理:
- 初始化播放器(
initPlayer
)。 - 管理播放器的状态(如
idle
,playing
,paused
等)。 - 提供获取当前播放状态的方法(
getPlayState
)。
- 初始化播放器(
-
播放控制:
- 播放(
play
)、暂停(pause
)、停止(stop
)。 - 上一首(
previous
)、下一首(next
)。 - 支持单曲循环、顺序播放、随机播放等多种播放模式(
setPlayMode
)。
- 播放(
-
播放进度管理:
- 设置播放进度(
seekTo
)。 - 获取当前播放进度(
getCurrentTime
)。 - 获取音频总时长(
getDuration
)。
- 设置播放进度(
2. 播放源支持
-
多种播放源类型:
- URL 播放(
playUrl
)。 - 文件描述符播放(
playFdSrc
)。 - 数据源播放(支持 seek 和不支持 seek 的两种模式:
playDataSrcSeek
和playDataSrcNoSeek
)。 - 直播流播放(
playLive
)。
- URL 播放(
-
播放列表管理:
- 添加播放源到播放列表(
addToPlaylist
)。 - 清空播放列表(
clearPlaylist
)。 - 获取当前播放列表(
getPlaylist
)。 - 播放指定索引的曲目(
playTrackByIndex
)。
- 添加播放源到播放列表(
3. 后台运行支持
-
后台任务管理:
- 申请后台运行权限(
startBackgroundRunning
),确保音频在后台继续播放。 - 停止后台任务(
stopBackgroundRunning
)。
- 申请后台运行权限(
-
AVSession 支持:
- 创建并激活 AVSession,用于后台音频播放约束。
4. 回调与事件监听
-
播放器事件监听:
- 监听播放器状态变化(如
stateChange
)。 - 监听播放完成事件(自动切换到下一首或循环播放)。
- 监听错误事件(
error
)并处理异常。
- 监听播放器状态变化(如
-
进度回调:
- 提供播放进度更新的回调接口(
onProgressChange
)。
- 提供播放进度更新的回调接口(
5. 异常处理与资源释放
-
异常处理:
- 在播放、设置播放源等操作中捕获异常并进行日志记录。
- 播放失败时尝试重置播放器状态。
-
资源释放:
- 提供释放播放器资源的方法(
release
),包括停止播放和销毁播放器实例。
- 提供释放播放器资源的方法(
6. 日志记录
- 使用
LogUtils
记录播放器的操作日志,便于调试和问题排查。
7. 其他辅助功能
-
循环播放设置:
- 支持设置是否循环播放(
setLooping
)。
- 支持设置是否循环播放(
-
便捷方法:
- 提供直接播放指定路径或 URL 的便捷方法(如
playUrl
,playFdSrc
等)。
- 提供直接播放指定路径或 URL 的便捷方法(如
完整代码 AVPlayerClass.ets
import { media } from '@kit.MediaKit';
import { fileIo as fs } from '@kit.CoreFileKit';
import { bundleManager, common, WantAgent, wantAgent } from '@kit.AbilityKit';
import { BusinessError } from '@kit.BasicServicesKit';
import { audio } from '@kit.AudioKit';
import { avSession } from '@kit.AVSessionKit';
import { backgroundTaskManager } from '@kit.BackgroundTasksKit';
import { LogUtils } from '@wear/basic';
// 定义播放器状态类型
type AVPlayerState = 'idle' | 'initialized' | 'preparing' | 'prepared' | 'playing' | 'paused' | 'completed' | 'stopped' | 'released' | 'error';
/**
* 播放源类型枚举:
* - URL: 通过URL路径播放音频文件
* - FD_SRC: 通过文件描述符播放音频资源
* - DATA_SRC_SEEK: 通过数据源播放,支持seek操作
* - DATA_SRC_NO_SEEK: 通过数据源播放,不支持seek操作
* - LIVE: 播放直播流
*/
export enum PlaySourceType {
URL = 'url',
FD_SRC = 'fdSrc',
DATA_SRC_SEEK = 'dataSrcSeek',
DATA_SRC_NO_SEEK = 'dataSrcNoSeek',
LIVE = 'live'
}
/**
* 播放模式枚举:
* - SEQUENCE: 顺序播放,播放完当前曲目后播放下一首
* - RANDOM: 随机播放,播放完当前曲目后随机选择下一首
* - SINGLE: 单曲播放,只播放当前选中的曲目
*/
export enum PlaybackMode {
SEQUENCE = 'sequence',
RANDOM = 'random',
SINGLE = 'single'
}
/**
* 播放源接口定义
* @param type 播放源类型
* @param source 播放源地址/路径
*/
export interface PlaySource {
type: PlaySourceType;
source: string;
}
export interface AudioPlayListener {
/**F
* 播放进度改变
*/
onProgressChange: (progress: number) => void
onSingleChange: (progress: number) => void
}
const TAG = 'AVPlayerClass';
export default class AVPlayerClass {
private count: number = 0;
private isSeek: boolean = true;
private fileSize: number = -1;
private fd: number = 0;
private avPlayer: media.AVPlayer | null = null;
private currentIndex: number = 0;
private playlist: PlaySource[] = [];
private isLooping: boolean = false;
private playMode: PlaybackMode = PlaybackMode.SEQUENCE;
private context: common.Context;
public onProgressChange?:AudioPlayListener;
constructor(context: common.Context, playlist: PlaySource[] = []) {
this.context = context;
this.playlist = [...playlist];
this.initPlayer();
}
// 初始化播放器
private async initPlayer() {
try {
if (!this.avPlayer) {
LogUtils.i(TAG, '开始初始化播放器');
this.avPlayer = await media.createAVPlayer();
this.setAVPlayerCallback(this.avPlayer);
LogUtils.i(TAG, `播放器初始化完成,当前状态: ${this.avPlayer.state}`);
if (this.playlist.length > 0) {
LogUtils.i(TAG, '播放列表不为空,设置第一个播放源');
await this.setPlayerSource(this.playlist[0]);
}
}
} catch (error) {
LogUtils.e(TAG, `播放器初始化失败: ${error}`);
}
}
// 设置播放源
private async setPlayerSource(item: PlaySource) {
if (!this.avPlayer) {
LogUtils.e(TAG, '播放器未初始化');
return;
}
try {
LogUtils.i(TAG, `设置播放源前,当前状态: ${this.avPlayer.state}`);
// 确保播放器处于idle状态
if (this.avPlayer.state !== 'idle') {
LogUtils.i(TAG, '重置播放器到idle状态');
await this.avPlayer.reset();
// 等待状态变为idle
await new Promise<void>((resolve) => {
const checkState = () => {
if (this.avPlayer?.state === 'idle') {
resolve();
} else {
setTimeout(checkState, 50);
}
};
checkState();
});
}
LogUtils.i(TAG, `设置播放源,类型: ${item.type}`);
switch (item.type) {
case PlaySourceType.URL:
await this.setUrlSource(item.source);
break;
case PlaySourceType.FD_SRC:
await this.setFdSrcSource(item.source);
break;
case PlaySourceType.DATA_SRC_SEEK:
await this.setDataSrcSeekSource(item.source);
break;
case PlaySourceType.DATA_SRC_NO_SEEK:
await this.setDataSrcNoSeekSource(item.source);
break;
case PlaySourceType.LIVE:
this.setLiveSource(item.source);
break;
}
LogUtils.i(TAG, `播放源设置完成,当前状态: ${this.avPlayer.state}`);
} catch (error) {
LogUtils.e(TAG, `设置播放源失败: ${JSON.stringify(error)}`);
}
}
// 设置URL播放源
private async setUrlSource(path: string) {
let fdPath = 'fd://';
let file = await fs.open(path);
fdPath = fdPath + '' + file.fd;
this.isSeek = true;
this.avPlayer!.url = fdPath;
}
// 设置FdSrc播放源
private async setFdSrcSource(path: string) {
let fileDescriptor = await this.context.resourceManager.getRawFd(path);
let avFileDescriptor: media.AVFileDescriptor = {
fd: fileDescriptor.fd,
offset: fileDescriptor.offset,
length: fileDescriptor.length
};
this.isSeek = true;
this.avPlayer!.fdSrc = avFileDescriptor;
}
// 设置DataSrcSeek播放源
private async setDataSrcSeekSource(path: string) {
await fs.open(path).then((file: fs.File) => {
this.fd = file.fd;
});
this.fileSize = fs.statSync(path).size;
let src: media.AVDataSrcDescriptor = {
fileSize: this.fileSize,
callback: (buf: ArrayBuffer, length: number, pos: number | undefined) => {
let num = 0;
if (buf == undefined || length == undefined || pos == undefined) {
return -1;
}
num = fs.readSync(this.fd, buf, { offset: pos, length: length });
if (num > 0 && (this.fileSize >= pos)) {
return num;
}
return -1;
}
};
this.isSeek = true;
this.avPlayer!.dataSrc = src;
}
// 设置DataSrcNoSeek播放源
private async setDataSrcNoSeekSource(path: string) {
await fs.open(path).then((file: fs.File) => {
this.fd = file.fd;
});
let src: media.AVDataSrcDescriptor = {
fileSize: -1,
callback: (buf: ArrayBuffer, length: number) => {
let num = 0;
if (buf == undefined || length == undefined) {
return -1;
}
num = fs.readSync(this.fd, buf);
if (num > 0) {
return num;
}
return -1;
}
};
this.isSeek = false;
this.avPlayer!.dataSrc = src;
}
// 设置直播源
private setLiveSource(url: string) {
this.isSeek = true;
this.avPlayer!.url = url;
}
// 播放当前曲目
private async playCurrentTrack() {
if (!this.avPlayer) {
LogUtils.e(TAG, '播放器未初始化');
return;
}
if (this.playlist.length === 0) {
LogUtils.e(TAG, '播放列表为空');
return;
}
try {
LogUtils.i(TAG, `开始播放当前曲目,索引: ${this.currentIndex}`);
LogUtils.i(TAG, `播放前状态: ${this.avPlayer.state}`);
// 设置播放源(内部会确保idle状态)
await this.setPlayerSource(this.playlist[this.currentIndex]);
LogUtils.i(TAG, `播放源设置完成,当前状态: ${this.avPlayer.state}`);
// 等待状态变为initialized
await new Promise<void>((resolve) => {
const checkState = () => {
if (this.avPlayer?.state === 'initialized') {
resolve();
} else {
setTimeout(checkState, 50);
}
};
checkState();
});
// 准备播放
LogUtils.i(TAG, `准备播放,当前状态: ${this.avPlayer.state}`);
await this.avPlayer.prepare();
// 等待状态变为prepared
await new Promise<void>((resolve) => {
const checkState = () => {
if (this.avPlayer?.state === 'prepared') {
resolve();
} else {
setTimeout(checkState, 50);
}
};
checkState();
});
LogUtils.i(TAG, `准备完成,当前状态: ${this.avPlayer.state}`);
// 申请后台运行权限
try {
LogUtils.i(TAG, '申请后台运行权限');
await AVPlayerClass.startBackgroundRunning();
LogUtils.i(TAG, '后台运行权限申请成功');
} catch (error) {
LogUtils.e(TAG, `后台运行权限申请失败: ${error}`);
}
// 开始播放
LogUtils.i(TAG, '开始播放');
await this.avPlayer.play();
// 等待状态变为playing
await new Promise<void>((resolve) => {
const checkState = () => {
if (this.avPlayer?.state === 'playing') {
resolve();
} else {
setTimeout(checkState, 50);
}
};
checkState();
});
LogUtils.i(TAG, `播放命令已发送,当前状态: ${this.avPlayer.state}`);
} catch (error) {
LogUtils.e(TAG, `播放失败: ${error}`);
// 如果失败,尝试重置播放器状态
try {
await this.avPlayer.reset();
LogUtils.i(TAG, `播放器已重置,当前状态: ${this.avPlayer.state}`);
} catch (resetError) {
LogUtils.e(TAG, `重置播放器失败: ${resetError}`);
}
}
}
// 播放
async play() {
LogUtils.i(TAG, '调用play方法');
try {
if (!this.avPlayer) {
LogUtils.i(TAG, '播放器未初始化,开始初始化');
await this.initPlayer();
}
if (this.playlist.length === 0) {
LogUtils.e(TAG, '播放列表为空');
return;
}
const currentState = this.avPlayer?.state as AVPlayerState;
LogUtils.i(TAG, `当前播放器状态: ${currentState}`);
// 如果是暂停状态,继续播放
if (currentState === 'paused') {
LogUtils.i(TAG, '从暂停状态继续播放');
await this.avPlayer?.play();
// 等待状态变为playing
await new Promise<void>((resolve) => {
const checkState = () => {
if (this.avPlayer?.state === 'playing') {
resolve();
} else {
setTimeout(checkState, 50);
}
};
checkState();
});
return;
}
// 其他状态都重新播放
LogUtils.i(TAG, '重新开始播放');
await this.playCurrentTrack();
} catch (error) {
LogUtils.e(TAG, `播放操作失败: ${error}`);
// 如果播放失败,尝试重置播放器
if (this.avPlayer) {
try {
await this.avPlayer.reset();
LogUtils.i(TAG, `播放器已重置,当前状态: ${this.avPlayer.state}`);
await this.playCurrentTrack();
} catch (resetError) {
LogUtils.e(TAG, `重置播放器失败: ${resetError}`);
}
}
}
}
// 暂停
pause() {
const currentState = this.avPlayer?.state as AVPlayerState;
if (currentState === 'playing') {
this.avPlayer?.pause();
}
}
// 停止
async stop() {
if (!this.avPlayer) return;
const currentState = this.avPlayer.state as AVPlayerState;
if (currentState !== 'idle' && currentState !== 'stopped') {
LogUtils.i(TAG, `停止播放,当前状态: ${currentState}`);
await this.avPlayer.stop();
// 等待状态变更完成
await new Promise<void>((resolve) => setTimeout(resolve, 100));
LogUtils.i(TAG, `停止完成,当前状态: ${this.avPlayer.state}`);
// 停止后台运行
try {
LogUtils.i(TAG, '停止后台运行');
await AVPlayerClass.stopBackgroundRunning();
LogUtils.i(TAG, '后台运行已停止');
} catch (error) {
LogUtils.e(TAG, `停止后台运行失败: ${error}`);
}
}
}
// 上一首
async previous() {
if (this.playlist.length === 0) return;
if (this.playMode === PlaybackMode.RANDOM) {
this.currentIndex = Math.floor(Math.random() * this.playlist.length);
} else {
this.currentIndex--;
if (this.currentIndex < 0) {
this.currentIndex = this.playlist.length - 1;
}
}
await this.playCurrentTrack();
}
// 下一首
async next() {
if (this.playlist.length === 0) return;
if (this.playMode === PlaybackMode.RANDOM) {
this.currentIndex = Math.floor(Math.random() * this.playlist.length);
} else {
this.currentIndex++;
if (this.currentIndex >= this.playlist.length) {
this.currentIndex = 0;
}
}
this.onProgressChange?.onSingleChange(this.currentIndex)
await this.playCurrentTrack();
}
// 设置播放模式
setPlayMode(mode: PlaybackMode) {
this.playMode = mode;
}
// 设置是否循环播放
setLooping(isLooping: boolean) {
this.isLooping = isLooping;
}
// 获取当前播放状态
getPlayState(): string {
return this.avPlayer?.state || 'idle';
}
// 获取当前播放索引
getCurrentIndex(): number {
return this.currentIndex;
}
// 获取当前播放列表
getPlaylist(): PlaySource[] {
return [...this.playlist];
}
// 情况当前播放列表
clearPlaylist() {
this.playlist = []
}
// 获取当前播放模式
getPlayMode(): string {
return this.playMode;
}
// 释放资源
release() {
if (this.avPlayer) {
this.stop();
this.avPlayer.release();
this.avPlayer = null;
}
}
// 注册avplayer回调函数
private setAVPlayerCallback(avPlayer: media.AVPlayer) {
// seek操作结果回调函数
avPlayer.on('seekDone', (seekDoneTime: number) => {
LogUtils.i(TAG, `AVPlayer seek succeeded, seek time is ${seekDoneTime}`);
});
// error回调监听函数
avPlayer.on('error', (err: BusinessError) => {
LogUtils.e(TAG, `Invoke avPlayer failed, code is ${err.code}, message is ${err.message}`);
avPlayer.reset();
});
// 状态机变化回调函数
avPlayer.on('stateChange', async (state: string) => {
LogUtils.i(TAG, `AVPlayer state changed to ${state}`);
if (state === 'completed') {
if (this.playMode === PlaybackMode.SINGLE) {
if (this.isLooping) {
// 单曲循环模式
await this.playCurrentTrack();
}else{
// 停止播放
await this.stop()
}
} else {
// 其他模式,播放下一首
await this.next();
}
}
});
avPlayer.on('timeUpdate', (currentTime: number) => {
this.onProgressChange?.onProgressChange(currentTime)
})
}
// 添加新的便捷方法
async addToPlaylist(item: PlaySource) {
LogUtils.i(TAG, `添加播放源到列表: ${JSON.stringify(item)}`);
this.playlist.push(item);
}
async playUrl(path: string) {
LogUtils.i(TAG, `播放URL: ${path}`);
await this.addToPlaylist({ type: PlaySourceType.URL, source: path });
if (this.playlist.length === 1) {
await this.play();
}
}
async playFdSrc(path: string) {
LogUtils.i(TAG, `播放文件描述符: ${path}`);
await this.addToPlaylist({ type: PlaySourceType.FD_SRC, source: path });
if (this.playlist.length === 1) {
await this.play();
}
}
async playDataSrcSeek(path: string) {
LogUtils.i(TAG, `播放可seek数据源: ${path}`);
await this.addToPlaylist({ type: PlaySourceType.DATA_SRC_SEEK, source: path });
if (this.playlist.length === 1) {
await this.play();
}
}
async playDataSrcNoSeek(path: string) {
LogUtils.i(TAG, `播放不可seek数据源: ${path}`);
await this.addToPlaylist({ type: PlaySourceType.DATA_SRC_NO_SEEK, source: path });
if (this.playlist.length === 1) {
await this.play();
}
}
async playLive(url: string) {
LogUtils.i(TAG, `播放直播流: ${url}`);
await this.addToPlaylist({ type: PlaySourceType.LIVE, source: url });
if (this.playlist.length === 1) {
await this.play();
}
}
// 播放指定索引的曲目
async playTrackByIndex(index: number) {
if (index < 0 || index >= this.playlist.length) {
LogUtils.e(TAG, `无效的索引: ${index}, 播放列表长度: ${this.playlist.length}`);
return;
}
this.currentIndex = index;
await this.playCurrentTrack();
}
/**
* 设置播放进度
* @param position 目标进度,单位为毫秒
* @returns 是否设置成功
*/
async seekTo(position: number): Promise<boolean> {
if (!this.avPlayer || !this.isSeek) {
LogUtils.e(TAG, '播放器不支持seek操作或播放器未初始化');
return false;
}
try {
if (position < 0) position = 0;
const duration = await this.getDuration();
if (position > duration) position = duration;
await this.avPlayer.seek(position);
return true;
} catch (error) {
LogUtils.e(TAG, `设置播放进度失败: ${error}`);
return false;
}
}
/**
* 获取当前播放进度
* @returns 当前播放进度,单位为毫秒
*/
async getCurrentTime(): Promise<number> {
if (!this.avPlayer) return 0;
try {
return await this.avPlayer.currentTime;
} catch (error) {
LogUtils.e(TAG, `获取当前播放进度失败: ${error}`);
return 0;
}
}
/**
* 获取音频总时长
* @returns 音频总时长,单位为毫秒
*/
async getDuration(): Promise<number> {
if (!this.avPlayer) return 0;
try {
return await this.avPlayer.duration;
} catch (error) {
LogUtils.e(TAG, `获取音频总时长失败: ${error}`);
return 0;
}
}
private static session: avSession.AVSession; // 本次申请短时任务的剩余时间
// 申请长时任务
private static async startBackgroundRunning() {
const context = getContext()
// 重点1: 提供音频后台约束能力,音频接入AVSession后,可以进行后台音频播放
AVPlayerClass.session = await avSession.createAVSession(context, 'guardianSession', 'audio')
await AVPlayerClass.session.activate()
// 获取 bundle 应用信息
const bundleInfo = bundleManager.getBundleInfoForSelfSync(bundleManager.BundleFlag.GET_BUNDLE_INFO_WITH_APPLICATION)
// 通过wantAgent模块下getWantAgent方法获取WantAgent对象
let wantAgentInfo: wantAgent.WantAgentInfo = {
wants: [
{
bundleName: bundleInfo.name,
abilityName: "EntryAbility"
}
],
operationType: wantAgent.OperationType.START_ABILITY,
requestCode: 0,
wantAgentFlags: [wantAgent.WantAgentFlags.UPDATE_PRESENT_FLAG]
};
// 重点2: 创建后台任务
let bgModes = ['location','audioPlayback']
wantAgent.getWantAgent(wantAgentInfo).then((wantAgentObj: WantAgent) => {
backgroundTaskManager.startBackgroundRunning(context,bgModes, wantAgentObj).then(() => {
LogUtils.i( TAG , `Operation startBackgroundRunning success.`);
}).catch((err: BusinessError) => {
LogUtils.e( TAG , `Failed to operation startBackgroundRunning. Code is ${err.code}, message is ${err.message}`);
bgModes.push('bluetoothInteraction')
backgroundTaskManager.updateBackgroundRunning(context, bgModes).then(() => {
LogUtils.i( TAG , `Operation updateBackgroundRunning success.`);
}).catch((error: BusinessError) => {
LogUtils.e( TAG , `Operation updateBackgroundRunning failed. code is ${error.code} message is ${error.message}`);
});
});
});
}
// 停止后台任务
private static async stopBackgroundRunning() {
AVPlayerClass.session.destroy((err) => {
if (err) {
LogUtils.e(TAG, `Failed to destroy session. Code: ${err.code}, message: ${err.message}`)
} else {
LogUtils.i(TAG, `Destroy : SUCCESS `)
}
});
// backgroundTaskManager.cancelSuspendDelay(AVPlayerClass.id);
// backgroundTaskManager.stopBackgroundRunning(getContext())
}
}
使用方法:
// 语音播报控制器
private reportAudioPlayer = new AVPlayerClass(getContext())
// 设置音频为单曲播放
this.reportAudioPlayer.setPlayMode(PlaybackMode.SINGLE);
//播放
this.reportAudioPlayer.playUrl('music.mp3')
总结
该文件实现了一个功能全面的音频播放器类,涵盖了从播放器初始化、播放控制、播放源管理到后台运行支持等多个方面。它具有以下特点:
- 灵活性:支持多种播放源类型和播放模式。
- 健壮性:通过状态管理和异常处理确保播放器的稳定性。
- 扩展性:提供丰富的接口和回调机制,便于集成到更大的系统中。
- 后台支持:通过 AVSession 和后台任务管理,确保音频在后台正常播放。
适用于需要复杂音频播放功能的应用场景,如音乐播放器、直播应用等。