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

10 篇文章 1 订阅
文章详细介绍了如何在uniapp中优化语音播放功能,解决播放时打断后台音乐、输出设备不可控的问题。通过使用Android.media.MediaPlayer和AudioManager类,实现了音频焦点的管理和音频输出设备的切换。在耳机插入时自动切换到听筒播放,同时处理了唤醒锁以确保接近传感器能正常工作。此外,还涉及到了音频聚焦的请求和释放,确保语音播放结束后音乐能自动恢复。
摘要由CSDN通过智能技术生成

承接上回语音功能的实现《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 // 音频聚焦状态
	}
},
onLoad() {
	plus.android.importClass('android.media.AudioManager');
}

初始化播放器

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类

  • 3
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
uni-app实现语音播放功能的思路如下: 1. 首先,从消息列表中筛选出单独的语音列表,并在消息列表中添加字段,用于标识语音列表的索引和消息列表的索引的对应关系。这样可以区分播放状态和展示状态。 2. 在消息列表中展示语音列表,并为点击事件传入语音列表的索引。 3. 创建一个audio对象来处理语音播放和切换事件,包括样式和语音的切换、暂停等事件。 4. 确保语音播放不跟随系统铃声模式。可以使用plus.audio.createPlayer创建播放实例,并使用setRoute()方法切换播放声道,支持扬声器/听筒播放。 5. 在扬声器模式下,当检测到距离接近时,需要将屏幕息屏以防止误触,并实时切换到听筒播放。当距离远离时,需要亮屏并切回扬声器播放。 6. 在听筒模式下,当检测到距离接近时,需要将屏幕息屏以防止误触。当距离远离时,需要亮屏。 通过以上的步骤,就可以在uni-app实现语音播放功能,同时满足需求要求。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *3* [uniapp实现语音播放功能](https://blog.csdn.net/weixin_43905402/article/details/120011089)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] - *2* [uni-app 实现语音播放实现思路和代码](https://blog.csdn.net/dingding1115/article/details/117436541)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值