# 子页面播放音频返回首页后，仍会短暂听到音频声音

#### 问题现象

应用中子页面播放音频，点击返回首页后，仍然可以短暂地听到音频声音，声音没有立刻结束。

#### 背景知识

*   AVPlayer：支持将Audio/Video媒体资源（比如mp4/mp3/mkv/mpeg-ts等）转码为可供渲染的图像和可听见的音频模拟信号，并通过输出设备进行播放。
*   onPageShow：router路由页面（即@Entry装饰的自定义组件）每次显示时触发一次，包括路由跳转、应用进入前台等场景。

#### 问题定位

检查应用中AVPlayer实例调用stop接口停止音频播放的调用时机，是否在首页的onPageShow函数中停止的音频播放，问题代码如下：

```
onPageShow(): void {
  // 应用首页停止子页面的音频播放
  this.avStop();
}
avStop(): void {
  if (this.avPlayer) {
    try {
      this.avPlayer.stop();
      console.info(`avPlayer Stop success`);
    } catch (e) {
      console.error(`avPlayer Stop failed`);
    }
  }
}
```

#### 分析结论

应用在首页的onPageShow函数中，调用的AVPlayer实例的stop接口停止的音频播放；而子页面从销毁到执行首页onPageShow函数中stop接口停止音频播放，有一定代码运行时间，导致返回首页后短暂的音频播放问题。

#### 修改建议

应用在子页面的aboutToDisappear函数中，调用AVPlayer实例的stop及release接口，停止音频播放并销毁AVPlayer实例，避免返回首页后仍然有音频播放的问题，示例如下：

```
aboutToDisappear() {
  if (this.avPlayer == null) {
    console.info(`${this.tag}: avPlayer has not init aboutToDisappear`);
    return;
  }

  try {
    this.avPlayer.stop();
    console.info(`${this.tag}: avStop==`);
  } catch (e) {
    console.error(`${this.tag}: avStop== ${JSON.stringify(e)}`);
  }

  this.avPlayer.release((err) => {
    if (err == null) {
      console.info(`${this.tag}: videoRelease release success`);
    } else {
      console.error(`${this.tag}: videoRelease release failed, error message is = ${JSON.stringify(err.message)}`);
    }
  });
  if (this.subFile) {
    try {
      fileIo.closeSync(this.subFile);
    } catch (err) {
      console.error(`failed to close subtitle file, ${JSON.stringify(err)}`);
    }
  }
}
```

完整示例如下：

1.  应用首页，点击Next会跳转到音频播放子页面。
    
    ```
    @Entry
    @Component
    struct Index {
      pathStack: NavPathStack = new NavPathStack();
    
    
      build() {
        Navigation(this.pathStack) {
          Row() {
            Column() {
              Text('首页')
                .fontSize(50)
                .fontWeight(FontWeight.Bold);
              // 添加按钮，以响应用户onClick事件
              Button() {
                Text('子页面音频')
                  .fontSize(20)
                  .fontWeight(FontWeight.Bold);
              }
              .type(ButtonType.Capsule)
              .margin({
                top: 50
              })
              .backgroundColor('#0D9FFB')
              .width('40%')
              .height('5%')
              .onClick(() => {
                this.pathStack.pushPathByName('PageOne', null);
              });
            }
            .width('100%');
          }
          .height('100%');
        }
        .title('Navigation')
        .mode(NavigationMode.Stack);
      }
    }
    ```
    
2.  音频播放子页面，自动播放音频，点击Back会返回首页，并停止音乐播放。
    
    ```
    import display from '@ohos.display';
    import { common } from '@kit.AbilityKit';
    import media from '@ohos.multimedia.media';
    import { fileIo } from '@kit.CoreFileKit';
    
    
    const PROPORTION = 0.99; // 占屏幕比例
    const SURFACE_W = 0.9; // 表面宽比例
    const SURFACE_H = 1.78; // 表面高比例
    
    
    @Builder
    export function PageOneBuilder() {
      PageOne();
    }
    
    
    @Component
    struct PageOne {
      pathStack: NavPathStack = new NavPathStack();
      tag: string = 'AVPlayManager';
      private xComponentController: XComponentController = new XComponentController();
      private avPlayer: media.AVPlayer | null = null;
      private subFile: fileIo.File | null = null;
      private surfaceId: string = '';
      private intervalID: number = -1;
      private context: common.UIAbilityContext | undefined = undefined;
      private fileName: string = '3463094780.mp3'; // 资源需替换成用户自己的资源，否则无法成功运行
      private isSwiping: boolean = false; // 用户滑动过程中
      private xComponentFlag: boolean = false;
      @State surfaceW: number | null = null;
      @State surfaceH: number | null = null;
      private percent: number = 0;
      private windowWidth: number = 300;
      private windowHeight: number = 200;
    
    
      async msleepAsync(ms: number): Promise<boolean> {
        return new Promise((resolve) => {
          setTimeout(() => {
            resolve(true);
          }, ms);
        });
      }
    
    
      async avSetupVideoAndSubtitle() {
        // 通过UIAbilityContext的resourceManager成员的getRawFd接口获取媒体资源播放地址。
        if (this.context === undefined) {
          return;
        }
        // this.fileName需根据应用实际情况配置对应的资源，否则会导致程序无法正常运行
        let fileDescriptorVideo = await this.context.resourceManager.getRawFd(this.fileName);
        let avFileDescriptor: media.AVFileDescriptor =
          { fd: fileDescriptorVideo.fd, offset: fileDescriptorVideo.offset, length: fileDescriptorVideo.length };
    
    
        if (this.avPlayer) {
          console.info(`${this.tag}: init avPlayer release2createNew`);
          this.avPlayer.release();
          await this.msleepAsync(1500);
        }
        // 创建avPlayer实例对象
        this.avPlayer = await media.createAVPlayer();
        // 创建状态机变化回调函数
        await this.setAVPlayerCallback((avPlayer: media.AVPlayer) => {
          this.percent = avPlayer.width / avPlayer.height;
          this.setVideoWH();
        });
        // 为fdSrc赋值触发initialized状态机上报
        this.avPlayer.fdSrc = avFileDescriptor;
      }
    
    
      avPlay(): void {
        if (this.avPlayer) {
          try {
            this.avPlayer.play();
          } catch (e) {
            console.error(`${this.tag}: avPlay = ${JSON.stringify(e)}`);
          }
        }
      }
    
    
      // 注册avplayer回调函数
      async setAVPlayerCallback(callback: (avPlayer: media.AVPlayer) => void): Promise<void> {
        // seek操作结果回调函数
        if (this.avPlayer == null) {
          console.error(`${this.tag}: avPlayer has not init!`);
          return;
        }
        this.avPlayer.on('seekDone', (seekDoneTime) => {
          console.info(`${this.tag}: setAVPlayerCallback AVPlayer seek succeeded, seek time is ${seekDoneTime}`);
        });
        this.avPlayer.on('speedDone', (speed) => {
          console.info(`${this.tag}: setAVPlayerCallback AVPlayer speedDone, speed is ${speed}`);
        });
        // error回调监听函数,当avPlayer在操作过程中出现错误时调用reset接口触发重置流程
        this.avPlayer.on('error', (err) => {
          console.error(`${this.tag}: setAVPlayerCallback Invoke avPlayer failed ${JSON.stringify(err)}`);
          if (this.avPlayer == null) {
            console.error(`${this.tag}: avPlayer has not init on error`);
            return;
          }
          this.avPlayer.reset();
        });
        // 状态机变化回调函数
        this.avPlayer.on('stateChange', async (state) => {
          if (this.avPlayer == null) {
            console.info(`${this.tag}: avPlayer has not init on state change`);
            return;
          }
          switch (state) {
            case 'idle': // 成功调用reset接口后触发该状态机上报
              console.info(`${this.tag}: setAVPlayerCallback AVPlayer state idle called.`);
              break;
            case 'initialized': // avplayer设置播放源后触发该状态上报
              console.info(`${this.tag}: setAVPlayerCallback AVPlayer state initialized called.`);
              if (this.surfaceId) {
                this.avPlayer.surfaceId = this.surfaceId; // 设置显示画面，当播放的资源为纯音频时无需设置
                console.info(`${this.tag}: setAVPlayerCallback this.avPlayer.surfaceId = ${this.avPlayer.surfaceId}`);
                this.avPlayer.prepare();
              }
              break;
            case 'prepared': // prepare调用成功后上报该状态机
              console.info(`${this.tag}: setAVPlayerCallback AVPlayer state prepared called.`);
              this.avPlayer.on('bufferingUpdate', (infoType: media.BufferingInfoType, value: number) => {
                console.info(`${this.tag}: bufferingUpdate called, infoType value: ${infoType}, value:${value}}`);
              });
              this.avPlayer.play(); // 调用播放接口开始播放
              callback(this.avPlayer);
              break;
            case 'playing': // play成功调用后触发该状态机上报
              console.info(`${this.tag}: setAVPlayerCallback AVPlayer state playing called.`);
              if (this.intervalID !== -1) {
                clearInterval(this.intervalID);
              }
              break;
            case 'completed': // 播放结束后触发该状态机上报
              console.info(`${this.tag}: setAVPlayerCallback AVPlayer state completed called.`);
              if (this.intervalID !== -1) {
                clearInterval(this.intervalID);
              }
              this.avPlayer.off('bufferingUpdate');
              break;
            case 'released':
              console.info(`${this.tag}: setAVPlayerCallback released called.`);
              break;
            case 'stopped':
              console.info(`${this.tag}: setAVPlayerCallback AVPlayer state stopped called.`);
              break;
            case 'error':
              console.error(`${this.tag}: setAVPlayerCallback AVPlayer state error called.`);
              break;
            case 'paused':
              console.info(`${this.tag}: setAVPlayerCallback AVPlayer state paused called.`);
              break;
            default:
              console.info(`${this.tag}: setAVPlayerCallback AVPlayer state unknown called.`);
              break;
          }
        });
      }
    
    
      async aboutToAppear() {
        this.windowWidth = display.getDefaultDisplaySync().width;
        this.windowHeight = display.getDefaultDisplaySync().height;
        this.surfaceW = this.windowWidth * SURFACE_W;
        this.surfaceH = this.surfaceW / SURFACE_H;
        this.context = this.getUIContext().getHostContext() as common.UIAbilityContext;
        // 通过UIAbilityContext的resourceManager成员的getRawFd接口获取媒体资源播放地址。
        if (this.context === undefined) {
          return;
        }
        // this.fileName需根据应用实际情况配置对应的资源，否则会导致程序无法正常运行
        let fileDescriptorVideo = await this.context.resourceManager.getRawFd(this.fileName);
        let avFileDescriptor: media.AVFileDescriptor =
          { fd: fileDescriptorVideo.fd, offset: fileDescriptorVideo.offset, length: fileDescriptorVideo.length };
    
    
        if (this.avPlayer) {
          console.info(`${this.tag}: init avPlayer release2createNew`);
          this.avPlayer.release();
          await this.msleepAsync(1500);
        }
        // 创建avPlayer实例对象
        this.avPlayer = await media.createAVPlayer();
        // 创建状态机变化回调函数
        await this.setAVPlayerCallback((avPlayer: media.AVPlayer) => {
          this.percent = avPlayer.width / avPlayer.height;
          this.setVideoWH();
        });
        // 为fdSrc赋值触发initialized状态机上报
        this.avPlayer.fdSrc = avFileDescriptor;
      }
    
    
      aboutToDisappear() {
        if (this.avPlayer == null) {
          console.info(`${this.tag}: avPlayer has not init aboutToDisappear`);
          return;
        }
    
    
        try {
          this.avPlayer.stop();
          console.info(`${this.tag}: avStop==`);
        } catch (e) {
          console.error(`${this.tag}: avStop== ${JSON.stringify(e)}`);
        }
    
    
        this.avPlayer.release((err) => {
          if (err == null) {
            console.info(`${this.tag}: videoRelease release success`);
          } else {
            console.error(`${this.tag}: videoRelease release failed, error message is = ${JSON.stringify(err.message)}`);
          }
        });
        if (this.subFile) {
          try {
            fileIo.closeSync(this.subFile);
          } catch (err) {
            console.error(`failed to close subtitle file, ${JSON.stringify(err)}`);
          }
        }
      }
    
    
      setVideoWH(): void {
        if (this.percent >= 1) { // 横向视频
          this.surfaceW = Math.round(this.windowWidth * PROPORTION);
          this.surfaceH = Math.round(this.surfaceW / this.percent);
        } else { // 纵向视频
          this.surfaceH = Math.round(this.windowHeight * PROPORTION);
          this.surfaceW = Math.round(this.surfaceH * this.percent);
        }
      }
    
    
      @Builder
      CoverXComponent() {
        XComponent({
          // 装载视频容器
          id: 'xComponent',
          type: XComponentType.SURFACE,
          controller: this.xComponentController
        })
          .id('VideoView')
          .visibility(this.xComponentFlag ? Visibility.Visible : Visibility.Hidden)
          .onLoad(() => {
            this.surfaceId = this.xComponentController.getXComponentSurfaceId();
          })
          .height(`${this.surfaceH}px`)
          .width(`${this.surfaceW}px`);
      }
    
    
      build() {
        NavDestination() {
          Column() {
            Row() {
              Button() {
                Text('返回首页')
                  .fontSize(30)
                  .fontWeight(FontWeight.Bold);
              }
              .type(ButtonType.Capsule)
              .margin({
                top: 20
              })
              .backgroundColor('#0D9FFB')
              .width('40%')
              .height('5%')
              .onClick(() => {
                this.pathStack.clear();
              });
            };
    
    
            Stack() {
              Column() {
                this.CoverXComponent();
              }
              .align(Alignment.TopStart)
              .margin({ top: 80 })
              .id('VideoView')
              .justifyContent(FlexAlign.Center);
    
    
              Text()
                .height(`${this.surfaceH}px`)
                .width(`${this.surfaceW}px`)
                .margin({ top: 80 })
                .backgroundColor(Color.Black)
                .visibility(this.isSwiping ? Visibility.Visible : Visibility.Hidden);
            }
            .backgroundColor(Color.Black)
            .height('90%')
            .width('100%');
    
    
          }.backgroundColor(Color.Black)
          .height('100%')
          .width('100%');
        }.title('PageOne')
        .onReady((context: NavDestinationContext) => {
          this.pathStack = context.pathStack;
        });
      }
    }
    ```