Advanced implementation of uniapp voice playback Android terminal details

Undertaking the implementation of the last voice function "uniapp realizes the voice playback function" , there are still flaws, the problems are as follows:
1. When the voice is playing, it will directly interrupt the music that is playing in the background, and the music will not automatically resume after the playback.
2. Voice playback The output (headphone/speaker) depends on the realization of various brands of mobile phones and cannot be controlled

Based on this, the blogger has studied it in depth, and the current Android端optimization solution has no iOS terminal o(╥﹏╥)o

background

First of all, the solution for voice playback to interrupt music has been solved in the previous blog "uniapp realizes audio playback to seize system audio focus" , and the details of the implementation have been clearly specified. Due to the initial loading of the audio, it is implemented by using the class
instead of the original one, which leads to problem 3. The switching problem of the audio output channel (speaker/handset)plus.audio.createPlayerandroid.media.MediaPlayer

Initialize the player

Declare related variables first

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');
}

Initialize the player

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);
			}
		);
	});
}

play voice

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();
}

Voice finished

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;
}

analysis Summary

The above code snippet basically implements various details of voice playback, and solves the three problems at the beginning of the article. The call only needs to this.playVoice('xxx')pass in the voice file path.
For problem 1, use the audio focus request to focus and give up, and set its duration to focus when playing audio, which is AUDIOFOCUS_GAIN_TRANSIENTused to indicate a temporary gain or audio focus request, which is expected to last for a short time;
for problem 2, use audio management Class getDevicesmethod to obtain GET_DEVICES_OUTPUTSthe output device, cooperate with AudioDeviceInfothe class to determine whether the type of the output device is a headset, and finally cooperate with the solution of problem 3 to realize the switching control of the headset/speaker; for problem 3, use the method of the audio
management class to control the audio Switch speaker/earpiece. So far, all the problems have been solved, and the voice playback function has been perfectly realized, let's continue to work hard...setSpeakerphoneOnsetMode
Android端

Attached are reference links: AudioManager class , MediaPlayer class , AudioAttributes class , AudioDeviceInfo class

Guess you like

Origin blog.csdn.net/weixin_43905402/article/details/128559559