uniapp切换扬声器和听筒播放

1,082 阅读4分钟

市面上其实已经有很多成熟的产品实现了语音功能,以微信语音功能为例:即使手机处于静音模式,仍然能播放语音;语音播放能在扬声器和听筒模式之间来回切换,在播放时监测到用户耳朵靠近会熄灭屏幕,如果此时的播放模式为扬声器播放,还会将播放模式切换至听筒播放。

由此,我们整理出以下需求点:

需求点

  1. 语音播放不跟随系统铃声模式。
  2. 语音播放支持扬声器/听筒播放。
  3. 扬声器模式下播放检测到距离接近需要息屏(防误触)并实时转换为听筒播放,距离远离需亮屏并转回扬声器播放。
  4. 听筒模式下播放检测距离变化不需要切换播放声道,但距离接近需要息屏(防误触),距离远离需要亮屏。

\

分析需求

首先,需要明确一点,语音播放支持扬声器/听筒播放,那就只能使用 plus.audio.createPlayer 来实现。

而切换播放声道,则可以使用 plus.audio.createPlayer 创建出来实例的 setRoute() 方法。

基于3、4点需求,明确在播放语音时需要添加距离监听器,可以使用 plus.proximity.watchProximity 来实时监听设备接近距离。

最后一个接近息屏,远离亮屏的需求,先把前面的做了,再看看如何实现吧。

\

实现

this.playMode:播放声道(扬声器 0,听筒 1)

this.playState:play回调中播放参数

\

1、距离监听器

this.watchProximity = plus.proximity.watchProximity((distance) => {
 /**
  * iOS端接近为0,远离为Infinity
  * Android端接近为0,远离为5
  */
 if (this.playMode === 0) {
     // 扬声器模式下,需要对声道进行实时修改
     this.voicePlayer.pause();
     if (distance !== 0) {
         // 扬声器
         this.voicePlayer.setRoute(0);
     } else {
         // 听筒
         this.voicePlayer.setRoute(1);
     }
     this.voicePlayer.resume();
 }
});

2、初始化播放器

this.voicePlayer = plus.audio.createPlayer({ src: 'xxx' });
this.voicePlayer.setRoute(this.playMode);
// 监听自然播放完成
this.voicePlayer.addEventListener('ended', () => {
 // 销毁正在监听设备距离的监听器
 if (this.watchProximity) {
     plus.proximity.clearWatch(this.watchProximity);
     this.watchProximity = null;
 }
 console.log('播放完毕了');
});
// 监听音频可以开始播放事件
this.voicePlayer.addEventListener('play', () => {
 // 首次播放时会执行两次回调
 if (this.playState !== 2) {
     // plus.audio.ROUTE_SPEAKER:扬声器 0
     // plus.audio.ROUTE_EARPIECE:听筒 1
     this.voicePlayer.setRoute(this.playMode);
     this.playState++;
 }
});
// 监听音频播放错误事件
this.voicePlayer.addEventListener('error', (err) => {
 console.log('报错err', err);
 // 销毁正在监听设备距离的监听器
 if (this.watchProximity) {
     plus.proximity.clearWatch(this.watchProximity);
     this.watchProximity = null;
 }
});

3、播放

// 在正确的地方调用播放
this.voicePlayer.play();

播放后,发现语音在 iOS 端,是默认存在接近息屏,远离亮屏的现象(默认行为)。但 Android 端在播放时,始终是亮屏的,这样看来 Android 端还需要自己实现。

播放时,初始播放模式为扬声器模式,接近切换为听筒,远离切换回扬声器,在 iOS 端是能完美表现的,切换无延迟,而Android 端切换是存在延迟的,好在延迟不影响播放。

播放时,初始播放模式为听筒模式,Android 端第一次播放音频会存在几秒钟的卡顿延迟,且该卡顿是占播放时间的。这也就是说第一次播放是不完整的,目前也没有什么比较好的方案解决(暂时搁置)。

\

Android端实现播放时接近息屏,远离亮屏

这该如何下手呢?

博主查阅了 plus 几乎所有的 API,都没有能找到相关的内容,看来只能上 Android 官网找了。

直接上 Android 官网找跟电源相关的 API,发现了 PowerManager,在其中找到了一个电平模式 PROXIMITY_SCREEN_OFF_WAKE_LOCK。

具体如下:


这不就是我想要的答案吗?

事不宜迟,说干就干!

由于 PowerManager 电源管理类是属于全局唯一的,所以使用也不能像播放音频那样随随便便 new 一个实例出来,需要借助 Android 的主实例对象plus.android.runtimeMainActivity() 来调用获取。

\

this.platform:当前手机平台

this.wakeLock:唤醒锁

// Android端需要设置唤醒模式才能在接近传感器激活时关闭屏幕,iOS端自带了
if (this.platform === 'android') {
    let main = plus.android.runtimeMainActivity();
    let Context = plus.android.importClass('android.content.Context');
    let PowerManager = plus.android.importClass('android.os.PowerManager');
    let pm = main.getSystemService(Context.POWER_SERVICE);
    // 32代表PROXIMITY_SCREEN_OFF_WAKE_LOCK,唤醒锁定电平:当接近传感器激活时关闭屏幕
    let status = pm.isWakeLockLevelSupported(PowerManager.PROXIMITY_SCREEN_OFF_WAKE_LOCK);
    // 系统支持该唤醒模式
    if (status) {
        this.wakeLock = pm.newWakeLock(PowerManager.PROXIMITY_SCREEN_OFF_WAKE_LOCK, 'TAG');
        this.wakeLock.acquire();
    }
}

在刚刚初始化播放器时添加的监听事件中,增加对唤醒模式资源的释放。

// 监听自然播放完成
this.voicePlayer.addEventListener('ended', () => {
    // 销毁正在监听设备距离的监听器
    if (this.watchProximity) {
        plus.proximity.clearWatch(this.watchProximity);
        this.watchProximity = null;
    }
    // 释放唤醒锁
    if (this.wakeLock) {
        this.wakeLock.release();
        this.wakeLock = null;
    }
    console.log('播放完毕了');
});
// 监听音频播放错误事件
this.voicePlayer.addEventListener('error', (err) => {
    console.log('报错err', err);
    // 释放唤醒锁
    if (this.wakeLock) {
        this.wakeLock.release();
        this.wakeLock = null;
    }
    // 销毁正在监听设备距离的监听器
    if (this.watchProximity) {
        plus.proximity.clearWatch(this.watchProximity);
        this.watchProximity = null;
    }
});

至此,所有的需求都圆满完成了!

\

总结

随着对 uniapp 的深入了解,越来越多功能点的实现,开发人员不止需要把视角放在前端,还需要转战到 Android、iOS 官方查阅相关的 API 文档才能实现。