一、前言
主要针对前端适配录音能力的简要记录,针对默认的wav及其可能需要转换到特定的mp3之类格式以适配需求的问题。(这类通常是兼容tts或客服语音备份)
这里纠结点主要会是在 ios/android设备的兼容及 类型转换的问题
二、内容组成
主要围绕下面几个重要组成部分
①权限获取
②针对设备兼容
③内容类型转换
④传输存储
1、权限获取
这里首先需要确定的一点是,获取录音权限,除了通常的
localhost
之外,仅有证书存在的domain/ip
浏览器才会允许获取权限。(哪怕是自定义证书也是可以的)
针对app端部分内容嵌入适配
这里主要是考虑到app的基础配置,基于前者的情况下(截止操作时间2023.8.1)
Android:
其实根据通用的方式,就是动态设置,哪个webview使用设置哪个。
<!-- 所需权限列表 -->
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
//伪代码部分
AudioWebView.setWebChromeClient(new WebChromeClient() {
@Override
public void onProgressChanged(WebView view, int newProgress) {
super.onProgressChanged(view, newProgress);
}
@Override
public void onPermissionRequest(PermissionRequest request) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP){
//直接无脑同意即可
request.grant(request.getResources());
}
}
});
//这里是基础链接
AudioWebView.loadUrl("{{baseUrl}}");
IOS
这里补充一点,即便webview已经允许你调用录音,某种情况下,其实也存在某些版本每次调用前询问,所以最好做好预案
Tips:
ios中叫做 WKWebView
//伪代码部分
import AVFoundation
// 请求录音权限
func requestRecordPermission() {
AVAudioSession.sharedInstance().requestRecordPermission { granted in
if granted {
// 用户授权了录音权限,可以加载WebView了
self.loadWebView()
} else {
// 用户拒绝了录音权限,处理相应逻辑
// 例如给出提示或者禁用录音功能
}
}
}
// 加载WebView
func loadWebView() {
// 在此处加载WebView,调用录音的网页功能
}
2、针对设备兼容
这里的兼容主要是针对设备部分的区分吧。
首先声明ios
与Android
的区别
android : 在对应的webview中当且通常仅需首次获取权限即可。(高版本会有选项永久还是单次)
ios: 通常在wkwebvie
中,每次都需要获取录音权限(这个是兼容低版本ios考虑)
建议解决方案:
android
针对安卓的内容可以采取默认仅请求一次,然后利用本地缓存来保存标记。
当标记失效
,则可以通过其他方式让其重新授权即可
ios
针对苹果的内容,不可排除新版本已经解决这个问题,但是出于兼容考虑
还是预先做好调用权限
的处理,避免需要的时候不存在权限。
3、内容类型转换
这里主要是考虑到可能存在的调用
asr接口
,会需要适配不同的音频类型。
可以简单参考 语音厂商参数对比
默认来说,浏览器部分,我们通常采用 window.AudioContext || window.webkitAudioContext
的模式来获取录音内容。
如果需要切换类型,我们可以采用 lame.js
来转换所需的MP3
类型 。
Tip: 其他类型,目前暂未需要,如有建议,感谢提供
4、传输存储
整个过程中,其实最好使用blob格式传输,附加文件类型
示例 :let blob = new Blob(mp3Data, { type: "audio/mp3" });
例如websock中传递的时候,实际上大都需要我们重新加载一下格式,如果直接blob可能存在无法识别的问题
// 伪代码
// 其中 res 为传递过程中的blob内容
if (suffix == ".mp3" || suffix == ".wav") {
let newBlob = new Blob([res.blob], {
type: "audio/" + types,
});
console.log("image types:" + types);
console.log("and the new blob is:" + newBlob);
let originSrc = (
window.URL || window.webkitURL
).createObjectURL(newBlob);
attachmentDom =
'<audio controls="controls" src="' +
originSrc +
'"></audio>';
suffixHandler = true;
}
三、拓展内容
自动播放部分
这里其实android 很容易实现,直接通过controls即可控制
auto
自动播放
主要是ios
部分, 由于ios的一些机制问题,不能直接去自动播放可以参考如下的方式
其实就是模拟用户交互来达成
/**
* 初始化录音
*/
function forceSafariPlayAudio() {
if (trys == 0) {
audio.src = 'https://www.runoob.com/try/demo_source/horse.mp3'
audio.load(); // iOS 9 还需要额外的 load 一下, 否则直接 play 无效
audio.play(); // iOS 7/8 仅需要 play 一下
trys = 1
audioType = 1
}
}
$(function () {
function log(info) {
console.log(info);
}
audio = document.getElementById('bgmusic');
audio.loop = false
audio.muted = true
// 可以自动播放时正确的事件顺序是
// loadstart --> loadedmetadata --> loadeddata -->canplay -->play --> playing
// 不能自动播放时触发的事件是
// iPhone5 iOS 7.0.6 loadstart
// iPhone6s iOS 9.1 loadstart -> loadedmetadata -> loadeddata -> canplay
audio.addEventListener('loadstart', function () {
log('loadstart');
}, false);
audio.addEventListener('loadeddata', function () {
log('loadeddata');
}, false);
audio.addEventListener('loadedmetadata', function () {
log('loadedmetadata');
}, false);
audio.addEventListener('canplay', function () {
log('canplay');
}, false);
audio.addEventListener('play', function () {
log('play');
// 当 audio 能够播放后, 移除这个事件
console.log("初始化成功")
window.removeEventListener('touchstart', forceSafariPlayAudio, false);
}, false);
audio.addEventListener('playing', function () {
log('playing');
}, false);
audio.addEventListener('pause', function () {
log('pause');
}, false);
audio.addEventListener('ended', function () {
audioType = 0;
}, false);
// 由于 iOS Safari 限制不允许 audio autoplay, 必须用户主动交互(例如 click)后才能播放 audio,
// 因此我们通过一个用户交互事件来主动 play 一下 audio.
window.addEventListener('touchstart', forceSafariPlayAudio, false);
audio.src = 'https://www.runoob.com/try/demo_source/horse.mp3'
})