uniapp语音播放Android端细节进阶实现

Posted ㄏ、Forgetˊ

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了uniapp语音播放Android端细节进阶实现相关的知识,希望对你有一定的参考价值。

承接上回语音功能的实现《uniapp实现语音播放功能》,仍存在有瑕疵,问题如下:
1. 语音播放时,会直接打断后台正在播放的音乐,播放完毕无自动恢复音乐
2. 语音播放的输出(耳机/扬声器)全凭各种品牌手机的实现,不可控

基于此,博主又深入研究了一下,目前实现了android的优化方案,暂无ios端 o(╥﹏╥)o

背景

首先,语音播放打断音乐的解决方案,在上一篇博客《uniapp实现音频播放抢占系统音频焦点》中已解决,实现的细节均已清晰指明。
由于音频的初始化加载,由原来的plus.audio.createPlayer转为使用android.media.MediaPlayer类来实现,引出问题3. 音频输出频道(扬声器/听筒)的切换问题

初始化播放器

先声明相关变量

data() 
	return 
		voicePlayer: null, // 语音播放器
		watchProximity: null, // 设备距离监听器
		sessionPlayMode: 0, // 语音播放模式 0扬声器 1听筒
		wakeLock: null, // Android端唤醒锁
		audioManager: null, // Android音频管理器
		audioFocus: false // 音频聚焦状态
	

初始化播放器

async initPlayer(src) 
	return new Promise((resolve) => 
		let path = plus.io.convertLocalFileSystemURL(src); // 本地文件路径转为系统路径
		// 用于判断文件是否存在
		plus.io.resolveLocalFileSystemURL(
			path,
			() => 
				console.log('路径', path);
				let MediaPlayer = plus.android.importClass('android.media.MediaPlayer');
				let AudioAttributes = plus.android.importClass('android.media.AudioAttributes');
				this.voicePlayer = new MediaPlayer();
				let completionCB = plus.android.implements('android.media.MediaPlayer$OnCompletionListener', 
					onCompletion: () => 
						this.voicePlayEnded(); // 语音播放完毕
					
				);
				this.voicePlayer.setOnCompletionListener(completionCB);
				this.voicePlayer.setDataSource(path);
				this.voicePlayer.setAudioAttributes(AudioAttributes.CONTENT_TYPE_SPEECH); // 设置类型为语音
				resolve(true);
			,
			(err) => 
				// 文件获取失败
				console.log('文件获取失败,请重新获取', err);
				// 释放唤醒锁
				if (this.wakeLock) 
					this.wakeLock.release();
					this.wakeLock = null;
				
				// 销毁正在监听设备距离的监听器
				if (this.watchProximity) 
					plus.proximity.clearWatch(this.watchProximity);
					this.watchProximity = null;
				
				resolve(false);
			
		);
	);

播放语音

async playVoice(src) 
	console.log('播放地址', src);
	if (!(await this.initPlayer(src))) 
		return;
	
	let main = plus.android.runtimeMainActivity();
	let Context = plus.android.importClass('android.content.Context');

	// 判断是否有音乐播放,若存在音乐播放,把音频聚焦到语音上
	this.audioManager = main.getSystemService(Context.AUDIO_SERVICE);
	let isMusicActive = this.audioManager.isMusicActive();
	if (isMusicActive) 
		this.audioFocus = true;
		// 请求音频焦点,暂停音乐播放,待语音播放完毕后恢复
		this.audioManager.requestAudioFocus(null, this.audioManager.STREAM_MUSIC, this.audioManager.AUDIOFOCUS_GAIN_TRANSIENT);
	

	// 设置播放模式
	let headsetStatus = false;
	let AudioDeviceInfo = plus.android.importClass('android.media.AudioDeviceInfo');
	let audioDevices = this.audioManager.getDevices(this.audioManager.GET_DEVICES_OUTPUTS);
	// 判断设备类型
	for (let deviceInfo of audioDevices) 
		let status = plus.android.invoke(deviceInfo, 'getType');
		if (
			status == AudioDeviceInfo.TYPE_WIRED_HEADPHONES ||
			status == AudioDeviceInfo.TYPE_WIRED_HEADSET ||
			status == AudioDeviceInfo.TYPE_BLUETOOTH_A2DP ||
			status == AudioDeviceInfo.TYPE_BLUETOOTH_SCO
		) 
			headsetStatus = true;
		
	
	if (headsetStatus) 
		// 有耳机
		this.audioManager.setSpeakerphoneOn(false);
		this.audioManager.setMode(this.audioManager.MODE_NORMAL);
		this.voicePlayer.setAudioStreamType(this.audioManager.STREAM_MUSIC);
	 else 
		// 无耳机
		if (!this.sessionPlayMode) 
			// 扬声器
			this.audioManager.setSpeakerphoneOn(true);
			this.audioManager.setMode(this.audioManager.MODE_NORMAL);
			this.voicePlayer.setAudioStreamType(this.audioManager.STREAM_MUSIC);
			// 扬声器模式下,需要监听距离对声道进行实时修改
			this.watchProximity = plus.proximity.watchProximity((distance) => 
				// Android端接近为0,远离为5
				this.voicePlayer.pause();
				if (distance !== 0) 
					// 扬声器
					this.audioManager.setSpeakerphoneOn(true);
					this.audioManager.setMode(this.audioManager.MODE_NORMAL);
				 else 
					// 听筒
					this.voicePlayer.seekTo(0); // 播放进度调回0
					this.audioManager.setSpeakerphoneOn(false);
					this.audioManager.setMode(this.audioManager.MODE_IN_COMMUNICATION);
				
				this.voicePlayer.start();
			);
		 else 
			// 听筒
			this.audioManager.setSpeakerphoneOn(false);
			this.audioManager.setMode(this.audioManager.MODE_IN_COMMUNICATION);
			this.voicePlayer.setAudioStreamType(this.audioManager.STREAM_VOICE_CALL);
		
		// Android端需要设置唤醒模式才能在接近传感器激活时关闭屏幕
		let PowerManager = plus.android.importClass('android.os.PowerManager');
		let pm = main.getSystemService(Context.POWER_SERVICE);
		// 32代表PROXIMITY_SCREEN_OFF_WAKE_LOCK,唤醒锁定电平:当接近传感器激活时关闭屏幕
		let wakeStatus = pm.isWakeLockLevelSupported(PowerManager.PROXIMITY_SCREEN_OFF_WAKE_LOCK);
		// 系统支持该唤醒模式
		if (wakeStatus) 
			this.wakeLock = pm.newWakeLock(PowerManager.PROXIMITY_SCREEN_OFF_WAKE_LOCK, 'TAG');
			this.wakeLock.acquire();
		
	
	this.voicePlayer.prepare();
	this.voicePlayer.start();

语音播放完毕

voicePlayEnded() 
	// 销毁正在监听设备距离的监听器
	if (this.watchProximity) 
		plus.proximity.clearWatch(this.watchProximity);
		this.watchProximity = null;
	
	// 释放唤醒锁
	if (this.wakeLock) 
		this.wakeLock.release();
		this.wakeLock = null;
	
	// 放弃系统音频焦点
	if (this.audioFocus) 
		this.audioManager.setMode(this.audioManager.MODE_NORMAL);
		this.audioManager.abandonAudioFocus(null);
		this.audioFocus = false;
	
	// 释放资源
	this.voicePlayer.release();
	this.voicePlayer = null;

分析总结

上述代码片段基本实现了语音播放的各种细节,以及解决文章开头的三个问题,调用仅需要this.playVoice('xxx')传入语音文件路径即可。
针对问题1,使用音频焦点的请求聚焦及放弃,在播放音频时聚焦设置其持续时间为AUDIOFOCUS_GAIN_TRANSIENT,用于指示临时增益或音频焦点的请求,预计会持续很短的时间;
针对问题2,使用音频管理类的getDevices方法获取GET_DEVICES_OUTPUTS输出设备,配合AudioDeviceInfo类,判断输出设备的类型是否为耳机,最后配合问题3的解决方案即可实现耳机/扬声器的切换控制;
针对问题3,使用音频管理类的setSpeakerphoneOnsetMode方法来控制音频切换扬声器/听筒。
至此问题已全部解决,完美实现Android端的语音播放功能,继续加油吧…

附上参考链接:AudioManager类MediaPlayer类AudioAttributes类AudioDeviceInfo类

以上是关于uniapp语音播放Android端细节进阶实现的主要内容,如果未能解决你的问题,请参考以下文章

uniapp实战笔记实现语音播放功能

uniapp实现语音播放功能

uniapp实现语音播放功能

uniapp实现语音播放功能

Android国标接入端如何播放GB28181平台端语音广播数据

android开发实现语音数据实时采集/播放