鸿蒙开发音频播放支持多种数据源和播放模式

实现了一个音频播放器类 AVPlayerClass,提供了丰富的音频播放功能和管理能力。以下是该文件的主要功能总结:


1. 播放器核心功能

  • 播放器初始化与状态管理

    • 初始化播放器(initPlayer)。
    • 管理播放器的状态(如 idle, playing, paused 等)。
    • 提供获取当前播放状态的方法(getPlayState)。
  • 播放控制

    • 播放(play)、暂停(pause)、停止(stop)。
    • 上一首(previous)、下一首(next)。
    • 支持单曲循环、顺序播放、随机播放等多种播放模式(setPlayMode)。
  • 播放进度管理

    • 设置播放进度(seekTo)。
    • 获取当前播放进度(getCurrentTime)。
    • 获取音频总时长(getDuration)。

2. 播放源支持

  • 多种播放源类型

    • URL 播放(playUrl)。
    • 文件描述符播放(playFdSrc)。
    • 数据源播放(支持 seek 和不支持 seek 的两种模式:playDataSrcSeekplayDataSrcNoSeek)。
    • 直播流播放(playLive)。
  • 播放列表管理

    • 添加播放源到播放列表(addToPlaylist)。
    • 清空播放列表(clearPlaylist)。
    • 获取当前播放列表(getPlaylist)。
    • 播放指定索引的曲目(playTrackByIndex)。

3. 后台运行支持

  • 后台任务管理

    • 申请后台运行权限(startBackgroundRunning),确保音频在后台继续播放。
    • 停止后台任务(stopBackgroundRunning)。
  • AVSession 支持

    • 创建并激活 AVSession,用于后台音频播放约束。

4. 回调与事件监听

  • 播放器事件监听

    • 监听播放器状态变化(如 stateChange)。
    • 监听播放完成事件(自动切换到下一首或循环播放)。
    • 监听错误事件(error)并处理异常。
  • 进度回调

    • 提供播放进度更新的回调接口(onProgressChange)。

5. 异常处理与资源释放

  • 异常处理

    • 在播放、设置播放源等操作中捕获异常并进行日志记录。
    • 播放失败时尝试重置播放器状态。
  • 资源释放

    • 提供释放播放器资源的方法(release),包括停止播放和销毁播放器实例。

6. 日志记录

  • 使用 LogUtils 记录播放器的操作日志,便于调试和问题排查。

7. 其他辅助功能

  • 循环播放设置

    • 支持设置是否循环播放(setLooping)。
  • 便捷方法

    • 提供直接播放指定路径或 URL 的便捷方法(如 playUrl, playFdSrc 等)。

完整代码 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')

总结

该文件实现了一个功能全面的音频播放器类,涵盖了从播放器初始化、播放控制、播放源管理到后台运行支持等多个方面。它具有以下特点:

  1. 灵活性:支持多种播放源类型和播放模式。
  2. 健壮性:通过状态管理和异常处理确保播放器的稳定性。
  3. 扩展性:提供丰富的接口和回调机制,便于集成到更大的系统中。
  4. 后台支持:通过 AVSession 和后台任务管理,确保音频在后台正常播放。

适用于需要复杂音频播放功能的应用场景,如音乐播放器、直播应用等。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值