从"后台静音"到"无缝续播":一次完整的音频播放状态管理优化

在HarmonyOS 6应用开发中,我最近负责优化一个音乐播放器应用。这个应用功能很完善——支持本地音乐播放、在线流媒体、播放列表管理,看起来一切都很好。但上线后,用户反馈来了一个让人头疼的问题:"你们的播放器切到后台再回来,音乐就放不出来了!我开车时切导航再切回来,音乐就哑巴了!"

更让人困惑的是,这个问题不是每次都会出现:有时候切后台再回来能正常播放,有时候就完全没声音。查看日志,发现了一行关键错误信息:"当前AVPlayer组件状态不处于工作状态(prepared、playing、paused或completed),却执行了更改播放速度的操作,当前状态不允许执行该操作,导致播放失败。"

有用户开玩笑说:"你们这个播放器是害羞吗?切到后台就不敢出声了?"

今天,我就把这次完整的AVPlayer状态管理优化经历记录下来,从后台切换的诡异现象到生命周期管理的深层原理,帮你彻底解决HarmonyOS音频播放中的状态同步问题。

问题现象:后台切换后的"静音陷阱"

实际测试场景

在我们的音乐播放器中,音频播放需要完美适配各种场景:

  1. 前台播放:应用在前台,用户直接操作播放

  2. 后台播放:应用切到后台,音乐继续播放(如开车时)

  3. 前后台切换:应用从后台切回前台,恢复播放控制

  4. 锁屏播放:手机锁屏后,通过通知栏控制播放

预期效果

  • 应用切到后台,音乐继续播放

  • 应用从后台切回前台,播放控制立即恢复

  • 锁屏状态下,通知栏控制正常响应

  • 各种切换场景下,播放状态保持同步

实际效果

  • 应用切到后台再切回前台,点击播放按钮无声音

  • 控制台日志显示状态错误:"当前AVPlayer组件状态不处于工作状态"

  • 有时能正常恢复,有时完全失败,随机性很强

  • 锁屏后通过通知栏控制,应用内状态不同步

问题代码示例

以下是存在问题的简化实现代码,这也是很多开发者容易犯的错误:

// ❌ 错误示例:简单的AVPlayer管理
@Component
struct FaultyMusicPlayer {
  @State avPlayer: avPlayer.AVPlayer | null = null;
  @State isPlaying: boolean = false;
  
  aboutToAppear() {
    // 初始化播放器
    this.initAVPlayer();
  }
  
  initAVPlayer() {
    // 创建播放器实例
    this.avPlayer = avPlayer.createAVPlayer();
    
    // 设置音频源
    let fdPath = 'file:///data/storage/el2/base/haps/entry/files/music.mp3';
    this.avPlayer.src = fdPath;
    
    // 准备播放器
    this.avPlayer.prepare((err) => {
      if (err) {
        console.error('AVPlayer prepare failed: ' + JSON.stringify(err));
        return;
      }
      console.log('AVPlayer prepared successfully');
    });
  }
  
  // 播放/暂停控制
  togglePlay() {
    if (!this.avPlayer) {
      console.error('AVPlayer is null');
      return;
    }
    
    if (this.isPlaying) {
      // ❌ 问题:直接调用pause,没有检查状态
      this.avPlayer.pause();
      this.isPlaying = false;
    } else {
      // ❌ 问题:直接调用play,没有检查状态
      this.avPlayer.play();
      this.isPlaying = true;
    }
  }
  
  // 切换播放速度
  changeSpeed(speed: number) {
    if (!this.avPlayer) {
      console.error('AVPlayer is null');
      return;
    }
    
    // ❌ 致命问题:直接设置速度,没有检查状态
    // 这就是日志中报错的地方
    this.avPlayer.speed = speed;
  }
  
  aboutToDisappear() {
    // 清理播放器
    if (this.avPlayer) {
      this.avPlayer.release();
      this.avPlayer = null;
    }
  }
  
  build() {
    Column() {
      // 播放控制按钮
      Button(this.isPlaying ? '暂停' : '播放')
        .onClick(() => this.togglePlay())
        .margin({ bottom: 20 })
      
      // 速度控制
      Row() {
        Button('0.5x')
          .onClick(() => this.changeSpeed(0.5))
        Button('1.0x')
          .onClick(() => this.changeSpeed(1.0))
        Button('1.5x')
          .onClick(() => this.changeSpeed(1.5))
        Button('2.0x')
          .onClick(() => this.changeSpeed(2.0))
      }
    }
  }
}

这段代码看起来没什么问题:初始化播放器、控制播放、清理资源。但实际运行后,当应用切到后台再切回前台,点击播放按钮就会失败,控制台会输出状态错误。

问题根因:生命周期与状态管理的"脱节"

AVPlayer状态机详解

要理解问题根源,首先要明白AVPlayer有一个完整的状态机。根据华为开发者文档,AVPlayer的状态流转如下:

IDLE → INITIALIZED → PREPARED → PLAYING/PAUSED → COMPLETED → STOPPED/RELEASED

关键状态说明

  • IDLE:初始状态,播放器刚创建

  • INITIALIZED:设置数据源后的状态

  • PREPARED:调用prepare()成功后的状态,可以开始播放

  • PLAYING:正在播放状态

  • PAUSED:暂停状态

  • COMPLETED:播放完成状态

  • STOPPED:停止状态

  • RELEASED:资源释放状态

允许操作的状态条件

  • play():只能在PREPARED、PAUSED、COMPLETED状态下调用

  • pause():只能在PLAYING状态下调用

  • speed设置:只能在PREPARED、PLAYING、PAUSED、COMPLETED状态下调用

  • seek():只能在PREPARED、PLAYING、PAUSED、COMPLETED状态下调用

为什么后台切换会失败?

问题的本质是生命周期管理与播放器状态不同步。具体来说:

  1. 后台销毁机制:当应用切到后台时,系统可能会为了节省资源而销毁一些组件。如果AVPlayer被销毁,状态会变为RELEASED。

  2. 前台重建缺失:当应用切回前台时,如果没有重新初始化AVPlayer,播放器实例为null或处于RELEASED状态。

  3. 状态检查缺失:直接调用播放控制方法,没有检查当前状态是否允许该操作。

  4. 异步操作竞争:prepare()是异步操作,可能在状态检查之后才完成,导致状态判断错误。

在我们的音乐播放器中,当应用切到后台时,AVPlayer可能被系统销毁。切回前台后,点击播放按钮时:

  • this.avPlayer可能为null(已被释放)

  • 或者this.avPlayer存在但状态为RELEASED

  • 直接调用this.avPlayer.play()或设置speed,违反了状态机规则

  • 系统抛出错误:"当前AVPlayer组件状态不处于工作状态"

解决方案:完整的生命周期状态管理

第一步:理解应用生命周期

在HarmonyOS中,应用的生命周期状态包括:

  • CREATE:Ability创建

  • FOREGROUND:Ability处于前台

  • BACKGROUND:Ability处于后台

  • DESTROY:Ability销毁

对于UIAbility,我们需要监听这些状态变化:

// ✅ 正确:监听Ability生命周期
import UIAbility from '@ohos.app.ability.UIAbility';
import window from '@ohos.window';

export default class MusicAbility extends UIAbility {
  // 当前窗口实例
  private currentWindow: window.Window | null = null;
  
  // 播放器管理器
  private playerManager: PlayerManager | null = null;
  
  onCreate(want, launchParam) {
    console.log('MusicAbility onCreate');
    // 初始化播放器管理器
    this.playerManager = new PlayerManager();
  }
  
  onWindowStageCreate(windowStage: window.WindowStage) {
    console.log('MusicAbility onWindowStageCreate');
    
    // 获取窗口实例
    windowStage.getMainWindow((err, window) => {
      if (err) {
        console.error('Failed to get main window: ' + JSON.stringify(err));
        return;
      }
      this.currentWindow = window;
      
      // 监听窗口焦点变化
      this.currentWindow.on('windowFocus', (isFocus) => {
        console.log('Window focus changed: ' + isFocus);
        if (isFocus) {
          // 窗口获得焦点(切回前台)
          this.onForeground();
        } else {
          // 窗口失去焦点(切到后台)
          this.onBackground();
        }
      });
    });
    
    // 加载页面
    windowStage.loadContent('pages/Index', (err) => {
      if (err) {
        console.error('Failed to load content: ' + JSON.stringify(err));
      }
    });
  }
  
  // 切到前台时的处理
  onForeground() {
    console.log('Application moved to foreground');
    if (this.playerManager) {
      // 恢复播放器状态
      this.playerManager.restorePlayer();
    }
  }
  
  // 切到后台时的处理
  onBackground() {
    console.log('Application moved to background');
    if (this.playerManager) {
      // 保存播放器状态
      this.playerManager.savePlayerState();
      
      // 根据需求决定是否释放播放器
      // 如果需要在后台播放,不要释放
      // 如果不需要后台播放,可以暂停并释放
      if (!this.playerManager.needBackgroundPlay()) {
        this.playerManager.pauseAndRelease();
      }
    }
  }
  
  onWindowStageDestroy() {
    console.log('MusicAbility onWindowStageDestroy');
    // 清理资源
    if (this.playerManager) {
      this.playerManager.release();
      this.playerManager = null;
    }
  }
  
  onDestroy() {
    console.log('MusicAbility onDestroy');
    // 最终清理
    if (this.playerManager) {
      this.playerManager.release();
      this.playerManager = null;
    }
  }
}

第二步:实现健壮的播放器管理器

// ✅ 正确示例:完整的播放器状态管理器
import avPlayer from '@ohos.multimedia.avPlayer';
import media from '@ohos.multimedia.media';

// 播放器状态枚举
enum PlayerState {
  IDLE = 'idle',           // 初始状态
  INITIALIZED = 'initialized', // 已初始化
  PREPARED = 'prepared',   // 已准备
  PLAYING = 'playing',     // 播放中
  PAUSED = 'paused',       // 暂停
  COMPLETED = 'completed', // 播放完成
  STOPPED = 'stopped',     // 停止
  RELEASED = 'released',   // 已释放
  ERROR = 'error'          // 错误
}

// 播放器配置
interface PlayerConfig {
  autoPlay: boolean;        // 是否自动播放
  loop: boolean;            // 是否循环播放
  backgroundPlay: boolean;  // 是否允许后台播放
  saveState: boolean;       // 是否保存状态
}

// 播放状态保存
interface PlaybackState {
  currentTime: number;      // 当前播放位置(毫秒)
  isPlaying: boolean;       // 是否正在播放
  speed: number;            // 播放速度
  volume: number;           // 音量
}

export class PlayerManager {
  // 播放器实例
  private avPlayer: avPlayer.AVPlayer | null = null;
  
  // 当前状态
  private currentState: PlayerState = PlayerState.IDLE;
  
  // 播放器配置
  private config: PlayerConfig = {
    autoPlay: false,
    loop: false,
    backgroundPlay: true,
    saveState: true
  };
  
  // 保存的播放状态
  private savedState: PlaybackState | null = null;
  
  // 当前音频源
  private currentSource: string = '';
  
  // 状态监听器
  private stateListeners: Array<(state: PlayerState) => void> = [];
  
  // 错误监听器
  private errorListeners: Array<(error: string) => void> = [];
  
  constructor(config?: Partial<PlayerConfig>) {
    // 合并配置
    if (config) {
      this.config = { ...this.config, ...config };
    }
    
    // 初始化播放器
    this.initPlayer();
  }
  
  // 初始化播放器
  private initPlayer(): void {
    try {
      // 创建播放器实例
      this.avPlayer = avPlayer.createAVPlayer();
      this.currentState = PlayerState.INITIALIZED;
      
      // 设置播放器回调
      this.setupCallbacks();
      
      console.log('Player initialized, state: ' + this.currentState);
    } catch (error) {
      console.error('Failed to initialize player: ' + JSON.stringify(error));
      this.currentState = PlayerState.ERROR;
      this.notifyError('初始化播放器失败: ' + JSON.stringify(error));
    }
  }
  
  // 设置播放器回调
  private setupCallbacks(): void {
    if (!this.avPlayer) {
      return;
    }
    
    // 状态变化回调
    this.avPlayer.on('stateChange', (state: string) => {
      console.log('Player state changed: ' + state);
      
      // 更新内部状态
      switch (state) {
        case 'idle':
          this.currentState = PlayerState.IDLE;
          break;
        case 'initialized':
          this.currentState = PlayerState.INITIALIZED;
          break;
        case 'prepared':
          this.currentState = PlayerState.PREPARED;
          // 如果配置了自动播放,开始播放
          if (this.config.autoPlay) {
            this.play();
          }
          break;
        case 'playing':
          this.currentState = PlayerState.PLAYING;
          break;
        case 'paused':
          this.currentState = PlayerState.PAUSED;
          break;
        case 'completed':
          this.currentState = PlayerState.COMPLETED;
          // 如果配置了循环播放,重新开始
          if (this.config.loop) {
            this.seek(0);
            this.play();
          }
          break;
        case 'stopped':
          this.currentState = PlayerState.STOPPED;
          break;
        case 'released':
          this.currentState = PlayerState.RELEASED;
          break;
        case 'error':
          this.currentState = PlayerState.ERROR;
          break;
      }
      
      // 通知状态变化
      this.notifyStateChange();
    });
    
    // 错误回调
    this.avPlayer.on('error', (error: BusinessError) => {
      console.error('Player error: ' + JSON.stringify(error));
      this.currentState = PlayerState.ERROR;
      this.notifyError('播放器错误: ' + JSON.stringify(error));
    });
    
    // 播放完成回调
    this.avPlayer.on('finish', () => {
      console.log('Playback finished');
      this.currentState = PlayerState.COMPLETED;
      this.notifyStateChange();
    });
  }
  
  // 设置音频源
  setSource(source: string): Promise<void> {
    return new Promise((resolve, reject) => {
      if (!this.avPlayer) {
        reject(new Error('播放器未初始化'));
        return;
      }
      
      if (this.currentState === PlayerState.RELEASED) {
        // 如果播放器已释放,重新初始化
        this.reinitPlayer();
      }
      
      // 保存当前源
      this.currentSource = source;
      
      // 设置数据源
      this.avPlayer.src = source;
      this.currentState = PlayerState.INITIALIZED;
      
      // 准备播放器
      this.avPlayer.prepare((err) => {
        if (err) {
          console.error('Prepare failed: ' + JSON.stringify(err));
          this.currentState = PlayerState.ERROR;
          this.notifyError('准备播放失败: ' + JSON.stringify(err));
          reject(err);
          return;
        }
        
        console.log('Prepare successful');
        this.currentState = PlayerState.PREPARED;
        this.notifyStateChange();
        resolve();
      });
    });
  }
  
  // 播放
  play(): void {
    if (!this.avPlayer) {
      this.notifyError('播放器未初始化');
      return;
    }
    
    // 检查状态是否允许播放
    if (!this.isStateAllowed('play')) {
      this.notifyError(`当前状态不允许播放: ${this.currentState}`);
      
      // 如果状态不对,尝试恢复
      if (this.currentState === PlayerState.RELEASED) {
        console.log('播放器已释放,尝试重新初始化');
        this.reinitPlayer().then(() => {
          // 重新设置源并播放
          if (this.currentSource) {
            this.setSource(this.currentSource).then(() => {
              this.internalPlay();
            });
          }
        });
      } else if (this.currentState === PlayerState.ERROR) {
        console.log('播放器错误,尝试重置');
        this.resetPlayer().then(() => {
          if (this.currentSource) {
            this.setSource(this.currentSource).then(() => {
              this.internalPlay();
            });
          }
        });
      }
      return;
    }
    
    this.internalPlay();
  }
  
  // 内部播放实现
  private internalPlay(): void {
    if (!this.avPlayer) {
      return;
    }
    
    this.avPlayer.play((err) => {
      if (err) {
        console.error('Play failed: ' + JSON.stringify(err));
        this.currentState = PlayerState.ERROR;
        this.notifyError('播放失败: ' + JSON.stringify(err));
        return;
      }
      
      console.log('Play started');
      this.currentState = PlayerState.PLAYING;
      this.notifyStateChange();
    });
  }
  
  // 暂停
  pause(): void {
    if (!this.avPlayer) {
      this.notifyError('播放器未初始化');
      return;
    }
    
    // 检查状态是否允许暂停
    if (!this.isStateAllowed('pause')) {
      this.notifyError(`当前状态不允许暂停: ${this.currentState}`);
      return;
    }
    
    this.avPlayer.pause((err) => {
      if (err) {
        console.error('Pause failed: ' + JSON.stringify(err));
        this.currentState = PlayerState.ERROR;
        this.notifyError('暂停失败: ' + JSON.stringify(err));
        return;
      }
      
      console.log('Playback paused');
      this.currentState = PlayerState.PAUSED;
      this.notifyStateChange();
    });
  }
  
  // 设置播放速度
  setSpeed(speed: number): void {
    if (!this.avPlayer) {
      this.notifyError('播放器未初始化');
      return;
    }
    
    // 检查状态是否允许设置速度
    if (!this.isStateAllowed('setSpeed')) {
      this.notifyError(`当前状态不允许设置速度: ${this.currentState}`);
      return;
    }
    
    // 检查速度范围
    if (speed < 0.5 || speed > 2.0) {
      this.notifyError('速度值超出范围 (0.5-2.0)');
      return;
    }
    
    this.avPlayer.speed = speed;
    console.log('Speed set to: ' + speed);
  }
  
  // 跳转到指定位置
  seek(position: number): void {
    if (!this.avPlayer) {
      this.notifyError('播放器未初始化');
      return;
    }
    
    // 检查状态是否允许跳转
    if (!this.isStateAllowed('seek')) {
      this.notifyError(`当前状态不允许跳转: ${this.currentState}`);
      return;
    }
    
    this.avPlayer.seek(position, media.SeekMode.SEEK_NEXT_SYNC, (err) => {
      if (err) {
        console.error('Seek failed: ' + JSON.stringify(err));
        this.notifyError('跳转失败: ' + JSON.stringify(err));
        return;
      }
      
      console.log('Seek to: ' + position);
    });
  }
  
  // 检查状态是否允许操作
  private isStateAllowed(operation: string): boolean {
    const allowedStates: Record<string, PlayerState[]> = {
      'play': [PlayerState.PREPARED, PlayerState.PAUSED, PlayerState.COMPLETED],
      'pause': [PlayerState.PLAYING],
      'setSpeed': [PlayerState.PREPARED, PlayerState.PLAYING, PlayerState.PAUSED, PlayerState.COMPLETED],
      'seek': [PlayerState.PREPARED, PlayerState.PLAYING, PlayerState.PAUSED, PlayerState.COMPLETED],
      'stop': [PlayerState.PLAYING, PlayerState.PAUSED, PlayerState.COMPLETED],
      'release': [PlayerState.IDLE, PlayerState.INITIALIZED, PlayerState.PREPARED, 
                  PlayerState.PLAYING, PlayerState.PAUSED, PlayerState.COMPLETED, PlayerState.STOPPED]
    };
    
    const states = allowedStates[operation];
    if (!states) {
      return true; // 没有限制的操作
    }
    
    return states.includes(this.currentState);
  }
  
  // 重新初始化播放器
  private reinitPlayer(): Promise<void> {
    return new Promise((resolve) => {
      // 释放旧播放器
      this.release();
      
      // 创建新播放器
      this.initPlayer();
      
      // 设置回调
      this.setupCallbacks();
      
      console.log('Player reinitialized');
      resolve();
    });
  }
  
  // 重置播放器
  private resetPlayer(): Promise<void> {
    return new Promise((resolve) => {
      if (!this.avPlayer) {
        this.initPlayer();
        resolve();
        return;
      }
      
      // 重置播放器
      this.avPlayer.reset((err) => {
        if (err) {
          console.error('Reset failed: ' + JSON.stringify(err));
          // 重置失败,重新初始化
          this.reinitPlayer();
        } else {
          this.currentState = PlayerState.IDLE;
          console.log('Player reset');
        }
        resolve();
      });
    });
  }
  
  // 保存播放状态
  savePlayerState(): void {
    if (!this.avPlayer || !this.config.saveState) {
      return;
    }
    
    // 获取当前播放位置
    this.avPlayer.getCurrentTime((err, currentTime) => {
      if (err) {
        console.error('Failed to get current time: ' + JSON.stringify(err));
        return;
      }
      
      this.savedState = {
        currentTime: currentTime,
        isPlaying: this.currentState === PlayerState.PLAYING,
        speed: this.avPlayer.speed,
        volume: this.avPlayer.volume
      };
      
      console.log('Player state saved: ', this.savedState);
    });
  }
  
  // 恢复播放状态
  restorePlayer(): void {
    if (!this.config.saveState || !this.savedState || !this.currentSource) {
      return;
    }
    
    console.log('Restoring player state: ', this.savedState);
    
    // 如果播放器已释放,重新初始化
    if (this.currentState === PlayerState.RELEASED || !this.avPlayer) {
      this.reinitPlayer().then(() => {
        this.restorePlayback();
      });
    } else {
      this.restorePlayback();
    }
  }
  
  // 恢复播放
  private restorePlayback(): void {
    if (!this.savedState || !this.currentSource) {
      return;
    }
    
    // 重新设置源
    this.setSource(this.currentSource).then(() => {
      // 跳转到保存的位置
      this.seek(this.savedState!.currentTime);
      
      // 恢复速度
      this.setSpeed(this.savedState!.speed);
      
      // 恢复音量
      if (this.avPlayer) {
        this.avPlayer.volume = this.savedState!.volume;
      }
      
      // 恢复播放状态
      if (this.savedState!.isPlaying) {
        setTimeout(() => {
          this.play();
        }, 100); // 稍微延迟,确保跳转完成
      }
      
      console.log('Player state restored');
    }).catch((err) => {
      console.error('Failed to restore player: ' + JSON.stringify(err));
    });
  }
  
  // 暂停并释放(用于后台处理)
  pauseAndRelease(): void {
    if (!this.avPlayer) {
      return;
    }
    
    // 如果正在播放,先暂停
    if (this.currentState === PlayerState.PLAYING) {
      this.pause();
    }
    
    // 保存状态
    this.savePlayerState();
    
    // 释放播放器
    this.release();
  }
  
  // 释放播放器
  release(): void {
    if (!this.avPlayer) {
      return;
    }
    
    // 检查状态是否允许释放
    if (!this.isStateAllowed('release')) {
      console.warn(`Cannot release player in state: ${this.currentState}`);
      return;
    }
    
    this.avPlayer.release((err) => {
      if (err) {
        console.error('Release failed: ' + JSON.stringify(err));
        return;
      }
      
      this.avPlayer = null;
      this.currentState = PlayerState.RELEASED;
      console.log('Player released');
      this.notifyStateChange();
    });
  }
  
  // 检查是否需要后台播放
  needBackgroundPlay(): boolean {
    return this.config.backgroundPlay;
  }
  
  // 添加状态监听器
  addStateListener(listener: (state: PlayerState) => void): void {
    this.stateListeners.push(listener);
  }
  
  // 移除状态监听器
  removeStateListener(listener: (state: PlayerState) => void): void {
    const index = this.stateListeners.indexOf(listener);
    if (index > -1) {
      this.stateListeners.splice(index, 1);
    }
  }
  
  // 添加错误监听器
  addErrorListener(listener: (error: string) => void): void {
    this.errorListeners.push(listener);
  }
  
  // 移除错误监听器
  removeErrorListener(listener: (error: string) => void): void {
    const index = this.errorListeners.indexOf(listener);
    if (index > -1) {
      this.errorListeners.splice(index, 1);
    }
  }
  
  // 通知状态变化
  private notifyStateChange(): void {
    this.stateListeners.forEach(listener => {
      listener(this.currentState);
    });
  }
  
  // 通知错误
  private notifyError(error: string): void {
    this.errorListeners.forEach(listener => {
      listener(error);
    });
  }
  
  // 获取当前状态
  getCurrentState(): PlayerState {
    return this.currentState;
  }
  
  // 获取播放器实例(谨慎使用)
  getPlayer(): avPlayer.AVPlayer | null {
    return this.avPlayer;
  }
  
  // 获取当前播放位置
  getCurrentPosition(): Promise<number> {
    return new Promise((resolve, reject) => {
      if (!this.avPlayer) {
        reject(new Error('播放器未初始化'));
        return;
      }
      
      this.avPlayer.getCurrentTime((err, currentTime) => {
        if (err) {
          reject(err);
          return;
        }
        
        resolve(currentTime);
      });
    });
  }
  
  // 获取总时长
  getDuration(): Promise<number> {
    return new Promise((resolve, reject) => {
      if (!this.avPlayer) {
        reject(new Error('播放器未初始化'));
        return;
      }
      
      this.avPlayer.getDuration((err, duration) => {
        if (err) {
          reject(err);
          return;
        }
        
        resolve(duration);
      });
    });
  }
}

第三步:完整的UI组件实现

// ✅ 最佳实践:完整的音乐播放器组件
@Component
export struct RobustMusicPlayer {
  // 播放器管理器
  private playerManager: PlayerManager = new PlayerManager({
    autoPlay: false,
    loop: false,
    backgroundPlay: true,
    saveState: true
  });
  
  // 播放状态
  @State currentState: PlayerState = PlayerState.IDLE;
  @State isPlaying: boolean = false;
  @State currentTime: number = 0;
  @State duration: number = 0;
  @State currentSpeed: number = 1.0;
  @State currentVolume: number = 1.0;
  
  // 错误信息
  @State errorMessage: string = '';
  @State showError: boolean = false;
  
  // 定时器用于更新进度
  private progressTimer: number = 0;
  
  aboutToAppear() {
    // 添加状态监听
    this.playerManager.addStateListener((state: PlayerState) => {
      this.currentState = state;
      this.isPlaying = state === PlayerState.PLAYING;
      
      // 更新UI状态
      this.updateUIState();
    });
    
    // 添加错误监听
    this.playerManager.addErrorListener((error: string) => {
      this.errorMessage = error;
      this.showError = true;
      
      // 3秒后自动隐藏错误
      setTimeout(() => {
        this.showError = false;
      }, 3000);
    });
    
    // 加载默认音频
    this.loadDefaultAudio();
    
    // 启动进度更新定时器
    this.startProgressTimer();
  }
  
  aboutToDisappear() {
    // 停止定时器
    this.stopProgressTimer();
    
    // 保存播放状态
    this.playerManager.savePlayerState();
    
    // 移除监听器
    this.playerManager.removeStateListener(this.stateListener);
    this.playerManager.removeErrorListener(this.errorListener);
    
    // 根据配置决定是否释放播放器
    // 如果页面只是暂时隐藏,不要释放
    // 如果页面销毁,应该释放
  }
  
  // 状态监听器
  private stateListener = (state: PlayerState) => {
    this.currentState = state;
    this.isPlaying = state === PlayerState.PLAYING;
    this.updateUIState();
  };
  
  // 错误监听器
  private errorListener = (error: string) => {
    this.errorMessage = error;
    this.showError = true;
    setTimeout(() => {
      this.showError = false;
    }, 3000);
  };
  
  // 加载默认音频
  private loadDefaultAudio(): void {
    const audioPath = 'file:///data/storage/el2/base/haps/entry/files/sample.mp3';
    
    this.playerManager.setSource(audioPath)
      .then(() => {
        console.log('Audio loaded successfully');
        
        // 获取音频时长
        this.playerManager.getDuration()
          .then((duration) => {
            this.duration = duration;
          })
          .catch((err) => {
            console.error('Failed to get duration: ' + JSON.stringify(err));
          });
      })
      .catch((err) => {
        console.error('Failed to load audio: ' + JSON.stringify(err));
        this.errorMessage = '加载音频失败: ' + JSON.stringify(err);
        this.showError = true;
      });
  }
  
  // 播放/暂停控制
  private togglePlay(): void {
    if (this.isPlaying) {
      this.playerManager.pause();
    } else {
      this.playerManager.play();
    }
  }
  
  // 切换播放速度
  private changeSpeed(speed: number): void {
    this.playerManager.setSpeed(speed);
    this.currentSpeed = speed;
  }
  
  // 跳转到指定位置
  private seekTo(position: number): void {
    this.playerManager.seek(position);
  }
  
  // 更新UI状态
  private updateUIState(): void {
    // 更新播放按钮状态
    // 更新进度条状态
    // 更新其他UI元素
  }
  
  // 启动进度更新定时器
  private startProgressTimer(): void {
    this.progressTimer = setInterval(() => {
      if (this.isPlaying) {
        this.playerManager.getCurrentPosition()
          .then((position) => {
            this.currentTime = position;
          })
          .catch((err) => {
            console.error('Failed to get current position: ' + JSON.stringify(err));
          });
      }
    }, 1000); // 每秒更新一次
  }
  
  // 停止进度更新定时器
  private stopProgressTimer(): void {
    if (this.progressTimer) {
      clearInterval(this.progressTimer);
      this.progressTimer = 0;
    }
  }
  
  // 处理应用生命周期事件
  onForeground(): void {
    console.log('Player component onForeground');
    
    // 恢复播放器状态
    this.playerManager.restorePlayer();
    
    // 重新启动进度定时器
    this.startProgressTimer();
  }
  
  onBackground(): void {
    console.log('Player component onBackground');
    
    // 停止进度定时器
    this.stopProgressTimer();
    
    // 保存播放状态
    this.playerManager.savePlayerState();
    
    // 如果不需要后台播放,暂停并释放
    if (!this.playerManager.needBackgroundPlay()) {
      this.playerManager.pauseAndRelease();
    }
  }
  
  build() {
    Column() {
      // 错误提示
      if (this.showError) {
        Text(this.errorMessage)
          .fontSize(14)
          .fontColor('#FF0000')
          .margin({ bottom: 10 })
      }
      
      // 播放控制区域
      Column() {
        // 歌曲信息
        Text('示例音乐')
          .fontSize(18)
          .fontWeight(FontWeight.Bold)
          .margin({ bottom: 5 })
        
        Text('艺术家 - 专辑')
          .fontSize(14)
          .fontColor('#666666')
          .margin({ bottom: 20 })
        
        // 进度条
        Row() {
          Text(this.formatTime(this.currentTime))
            .fontSize(12)
            .fontColor('#666666')
            .width('15%')
          
          Slider({
            value: this.currentTime,
            min: 0,
            max: this.duration,
            step: 1000
          })
            .width('70%')
            .onChange((value: number) => {
              // 拖动时更新显示,但不立即跳转
              this.currentTime = value;
            })
            .onChangeEnd((value: number) => {
              // 拖动结束时跳转
              this.seekTo(value);
            })
          
          Text(this.formatTime(this.duration))
            .fontSize(12)
            .fontColor('#666666')
            .width('15%')
        }
        .width('100%')
        .margin({ bottom: 20 })
        
        // 控制按钮
        Row() {
          // 上一首
          Button('⏮')
            .onClick(() => {
              // 上一首逻辑
            })
            .width(48)
            .height(48)
            .margin({ right: 20 })
          
          // 播放/暂停
          Button(this.isPlaying ? '⏸' : '▶')
            .onClick(() => this.togglePlay())
            .width(64)
            .height(64)
            .fontSize(24)
            .margin({ right: 20 })
          
          // 下一首
          Button('⏭')
            .onClick(() => {
              // 下一首逻辑
            })
            .width(48)
            .height(48)
        }
        .justifyContent(FlexAlign.Center)
        .margin({ bottom: 20 })
        
        // 速度控制
        Row() {
          Text('速度:')
            .fontSize(14)
            .margin({ right: 10 })
          
          ForEach([0.5, 0.75, 1.0, 1.25, 1.5, 2.0], (speed: number) => {
            Button(speed.toString() + 'x')
              .backgroundColor(this.currentSpeed === speed ? '#007DFF' : '#F0F0F0')
              .fontColor(this.currentSpeed === speed ? '#FFFFFF' : '#333333')
              .onClick(() => this.changeSpeed(speed))
              .margin({ right: 5 })
          })
        }
        .justifyContent(FlexAlign.Center)
        
        // 状态显示
        Text('状态: ' + this.currentState)
          .fontSize(12)
          .fontColor('#999999')
          .margin({ top: 20 })
      }
      .padding(20)
      .width('100%')
    }
  }
  
  // 格式化时间(毫秒转分:秒)
  private formatTime(milliseconds: number): string {
    const totalSeconds = Math.floor(milliseconds / 1000);
    const minutes = Math.floor(totalSeconds / 60);
    const seconds = totalSeconds % 60;
    return `${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}`;
  }
}

实战:修复后台切换问题的完整过程

问题定位步骤

当我收到用户反馈后,按照以下步骤进行问题定位:

  1. 复现问题:启动音乐播放器,播放音频,切到后台再切回前台,点击播放按钮,确实没有声音。

  2. 查看日志:控制台输出错误:"当前AVPlayer组件状态不处于工作状态(prepared、playing、paused或completed),却执行了更改播放速度的操作,当前状态不允许执行该操作,导致播放失败。"

  3. 分析代码:检查播放控制逻辑,发现直接调用avPlayer.play()和设置speed,没有检查状态。

  4. 理解生命周期:查阅华为开发者文档,了解应用前后台切换时组件的生命周期变化。

  5. 测试验证:添加状态检查逻辑和生命周期管理后,问题解决。

关键修改点

1. 添加状态检查机制

// 修改前:直接调用播放控制
this.avPlayer.play();

// 修改后:检查状态后再调用
if (this.isStateAllowed('play')) {
  this.avPlayer.play();
} else {
  // 状态不允许,尝试恢复
  this.handleInvalidState();
}

2. 实现生命周期监听

// 在UIAbility中监听窗口焦点变化
this.currentWindow.on('windowFocus', (isFocus) => {
  if (isFocus) {
    // 切回前台:恢复播放器
    this.playerManager.restorePlayer();
  } else {
    // 切到后台:保存状态
    this.playerManager.savePlayerState();
  }
});

3. 完善状态管理

// 定义完整的状态机
enum PlayerState {
  IDLE = 'idle',
  INITIALIZED = 'initialized',
  PREPARED = 'prepared',
  PLAYING = 'playing',
  PAUSED = 'paused',
  COMPLETED = 'completed',
  STOPPED = 'stopped',
  RELEASED = 'released',
  ERROR = 'error'
}

// 定义允许操作的状态映射
const allowedStates: Record<string, PlayerState[]> = {
  'play': [PlayerState.PREPARED, PlayerState.PAUSED, PlayerState.COMPLETED],
  'pause': [PlayerState.PLAYING],
  'setSpeed': [PlayerState.PREPARED, PlayerState.PLAYING, 
               PlayerState.PAUSED, PlayerState.COMPLETED],
  // ... 其他操作
};

其他可能导致播放失败的原因

除了状态管理问题,还有几个常见原因可能导致音频播放失败:

1. 权限问题

// 需要在module.json5中声明权限
{
  "module": {
    "requestPermissions": [
      {
        "name": "ohos.permission.MICROPHONE",
        "reason": "用于音频播放",
        "usedScene": {
          "abilities": [
            "MusicAbility"
          ],
          "when": "always"
        }
      },
      {
        "name": "ohos.permission.READ_MEDIA",
        "reason": "读取音频文件",
        "usedScene": {
          "abilities": [
            "MusicAbility"
          ],
          "when": "always"
        }
      }
    ]
  }
}

2. 音频格式不支持

// 检查音频格式
private checkAudioFormat(source: string): boolean {
  const supportedFormats = ['.mp3', '.wav', '.aac', '.ogg', '.flac'];
  const extension = source.substring(source.lastIndexOf('.')).toLowerCase();
  return supportedFormats.includes(extension);
}

// 使用前检查
if (!this.checkAudioFormat(audioPath)) {
  this.notifyError('不支持的音频格式: ' + audioPath);
  return;
}

3. 文件路径问题

// 正确的文件路径格式
const audioPath = 'file:///data/storage/el2/base/haps/entry/files/music.mp3';

// 从资源文件加载
const resourcePath = $rawfile('music.mp3');

// 从沙箱路径加载
const sandboxPath = this.context.filesDir + '/music.mp3';

4. 内存管理问题

// 及时释放不再使用的播放器
aboutToDisappear() {
  if (this.playerManager) {
    this.playerManager.release();
  }
}

// 避免内存泄漏
onDestroy() {
  this.stopProgressTimer();
  this.removeAllListeners();
  this.releaseResources();
}
Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐