通过监听距离来实现音频播放设备在扬声器和听筒之间切换
==========================

#### 问题现象

IM类软件聊天会话中发送或接收的语音类型消息（是短消息不是语音通话），在播放过程中通过监听距离传感器来切换听筒和扬声器播放。语音播放使用AVPlayer，如何实现；

#### 背景知识

*   [sensor模块](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/js-apis-sensor)：设备传感器模块，可用于订阅设备传感器信息，如重力传感器、计步器等，其中[接近光传感器](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/js-apis-sensor#proximity9)可用于监测设备前置相机距离障碍物的距离变化。
*   管理全局音频输出设备:从API 20开始，AudioSessionManager提供了部分输出设备管理的接口，支持通过[AudioSession管理全局音频输出](https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/audio-output-device-management#通过audiosession管理全局音频输出设备)，方便在使用AudioSession管理音频焦点的同时管理音频输出。
*   [setDefaultOutputDevice](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/arkts-apis-audio-audiosessionmanager#setdefaultoutputdevice20)：设置默认发声设备。仅适用于[StreamUsage](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/arkts-apis-audio-e#streamusage)为语音消息、VoIP语音通话或者VoIP视频通话的场景，支持听筒、扬声器和系统默认设备，使用前需要调用[activateAudioSession](https://developer.huawei.com/consumer/cn/doc/harmonyos-references/arkts-apis-audio-audiosessionmanager#activateaudiosession12)激活。

#### 解决方案

1.  设置管理全局音频输出设备的接口：
    
    let audioManager = audio.getAudioManager(); _// 需要先创建AudioManager实例。_
    let audioSessionManager = audioManager.getSessionManager(); _// 再调用AudioManager的方法创建AudioSessionManager实例。_
    let strategy: audio.AudioSessionStrategy = {
      concurrencyMode: audio.AudioConcurrencyMode.CONCURRENCY\_PAUSE\_OTHERS
    };
    
2.  设置传感器接口，当设备贴近耳朵时，将全局播放设备切换为听筒，当设备远离时，全局播放设备切换为扬声器：
    
    sensor.on(sensor.SensorId.PROXIMITY, (response) => {
      if (response.distance === 0) {
        this.sessionIsSpeaker = false;
        audioSessionManager.setDefaultOutputDevice(audio.DeviceType.EARPIECE)
          .then(() => {
            console.info('AudioSession Succeeded in setting default EARPIECE device.');
          }).catch((err: BusinessError) => {
          console.error(\`AudioSession setDefaultOutputDevice Fail: ${err}\`);
        });
      } else if (response.distance === 5) {
        this.sessionIsSpeaker = true;
        audioSessionManager.setDefaultOutputDevice(audio.DeviceType.SPEAKER)
          .then(() => {
            console.info('AudioSession Succeeded in setting SPEAKER output device.');
          }).catch((err: BusinessError) => {
          console.error(\`AudioSession setDefaultOutputDevice Fail: ${err}\`);
        });
      }
    }, { interval: 1000000000 }); _//采样周期1000000000ns，即1s_
    

完整示例如下：

import { audio } from '@kit.AudioKit';
import { BusinessError } from '@kit.BasicServicesKit';
import { sensor } from '@kit.SensorServiceKit';
import { common } from '@kit.AbilityKit';
import { media } from '@kit.MediaKit';


let audioManager = audio.getAudioManager(); _// 需要先创建AudioManager实例。_
let audioSessionManager = audioManager.getSessionManager(); _// 再调用AudioManager的方法创建AudioSessionManager实例。_
let strategy: audio.AudioSessionStrategy = {
  concurrencyMode: audio.AudioConcurrencyMode.CONCURRENCY\_PAUSE\_OTHERS
};

_// 请用户切换为自己的播放逻辑，此处仅做模拟演示。_
class AvPlayer {
  static avPlayer: media.AVPlayer | undefined;


  static async initAvplayer() {
    AvPlayer.avPlayer = await media.createAVPlayer();
    AvPlayer.avPlayer.on('stateChange', (state) => {
      if (!AvPlayer.avPlayer) {
        return;
      }
      switch (state) {
        case 'idle':
          console.info('触发了播放器idle状态');
          break;
        case 'initialized':
          AvPlayer.avPlayer.audioRendererInfo = {
            usage: audio.StreamUsage.STREAM\_USAGE\_VOICE\_COMMUNICATION,
            rendererFlags: 0
          };
          console.info('触发了播放器initialized状态');
          AvPlayer.avPlayer.prepare();
          break;
        case 'prepared':
          console.info('触发了播放器prepared状态');
          AvPlayer.avPlayer.play();
          break;
        case 'completed':
          console.info('触发了播放器completed状态');
          break;
      }
    });
  }


  static async playMusic(url: media.AVFileDescriptor) {
    if (!AvPlayer.avPlayer) {
      return;
    }
    AvPlayer.avPlayer.fdSrc = url;
  }
}




@Entry
@ComponentV2
struct Index {
  @Local sessionIsSpeaker: boolean = true;


  aboutToAppear(): void {
    AvPlayer.initAvplayer();
    audioSessionManager.activateAudioSession(strategy);
    try {
      sensor.on(sensor.SensorId.PROXIMITY, (response) => {
        if (response.distance === 0) {
          this.sessionIsSpeaker = false;
          audioSessionManager.setDefaultOutputDevice(audio.DeviceType.EARPIECE)
            .then(() => {
              console.info('AudioSession Succeeded in setting default EARPIECE device.');
            }).catch((err: BusinessError) => {
            console.error(\`AudioSession setDefaultOutputDevice Fail: ${err}\`);
          });
        } else if (response.distance === 5) {
          this.sessionIsSpeaker = true;
          audioSessionManager.setDefaultOutputDevice(audio.DeviceType.SPEAKER)
            .then(() => {
              console.info('AudioSession Succeeded in setting SPEAKER output device.');
            }).catch((err: BusinessError) => {
            console.error(\`AudioSession setDefaultOutputDevice Fail: ${err}\`);
          });
        }
      }, { interval: 1000000000 }); _//采样周期1000000000ns，即1s_
    } catch (error) {
      let e: BusinessError = error as BusinessError;
      console.error(\`Failed to invoke on. Code: ${e.code}, message: ${e.message}\`);
    }
  }


  aboutToDisappear(): void {
    try {
      sensor.off(sensor.SensorId.PROXIMITY, () => {
        console.info('Succeeded in invoking off. Distance');
      });
    } catch (error) {
      let e: BusinessError = error as BusinessError;
      console.error(\`Failed to invoke off. Code: ${e.code}, message: ${e.message}\`);
    }
  }


  build() {
    Column({ space: 10 }) {
      Button('播放音乐')
        .onClick(async () => {
          let context = this.getUIContext().getHostContext() as common.UIAbilityContext;
         _ // 请用户切换为自己的播放资源_
          let fileDescriptor = await context.resourceManager.getRawFd('test.mp3');
          let avFileDescriptor: media.AVFileDescriptor =
            { fd: fileDescriptor.fd, offset: fileDescriptor.offset, length: fileDescriptor.length };
          AvPlayer.playMusic(avFileDescriptor);
        });
      Row() {
        Text(this.sessionIsSpeaker ? 'Speaker' : 'Ear');
      };
    }.width('100%')
    .height('100%')
    .justifyContent(FlexAlign.Center)
    .alignItems(HorizontalAlign.Center);
  }
}