赞
踩
公司准备基于腾讯IM进行开发即时通讯功能,想用H5来开发,这样方便以后移植,在原生app里直接加载,通过三天时间的调查,以及与腾讯客服,技术来回沟通,已经有一定的成果,现将调查成果,以及调查过程中踩过的坑以及解决方法记录一下,希望可以使有相同需求的人少走弯路。
h5 demo使用的官方提供的体验 Demo,对应的url地址是(https://web.sdk.qcloud.com/im/demo/latest/index.html#/login),我是安卓设备,使用浏览器扫描即时通信IM体验Demo中的H5,在浏览器打开,点击语音通话,视频通话,均提示“权限获取失败:用户禁止使用设备”,但是我浏览器权限是打开的,我还重新下载了一个新的浏览器,也出现同样的问题,iOS会提示获取权限的对话框,授权以后是可以进行音视频通话的。
官方对这一问题的回复是:
您好,关于您的问题,您可以在Android上检测下能力测试,看下浏览器上正常嘛
https://web.sdk.qcloud.com/trtc/webrtc/demo/detect/index.html
我用浏览器加载这个网站,进行测试,结果是
按理说是不会有问题的,音视频设备都支持,但是确实不行,官方建议让使用chrome,我下载手机版chrome,结果音视频通话都可以,结果在浏览器与应用环境信息找到了答案,
官方也回应,存在兼容性问题。
既然支持移动版chrome浏览器,我想当然的觉得,安卓原生的Webview应该也是支持的,结果在android程序中加载url,在android Webview中,与文件选取有关的按钮点击没效果,音视频聊天不能使用,在IOS里,视频聊天的摄像头打不开。因为Webview存在很多坑吧,所以我换了TBS浏览器,依然有这个问题,我刚开始怀疑是权限问题,我用代码授权,手动设置,把权限都勾选上,还是有这个问题。同样,用webview加载那个检测地址(https://web.sdk.qcloud.com/trtc/webrtc/demo/detect/index.html),有一个问题,就是摄像头那个,切换0或者1,都看不到摄像头,有可能是这个问题,后边会说。
官方给的回应是:
您好,这个demo只能是作为展示,如果是需要内嵌到webview里面的话,需要您业务侧做二次开发哈。不能直接使用这个
好嘛,那就二次开发吧,就进入了编码阶段,跟着文档一步一步来,创建vue项目,集成UI,折腾半天,本地运行起来,用Webview加载本地地址,能访问到网页,但是与文件选取有关的按钮点击没效果,音视频聊天弹出权限获取失败的提示。
官方技术说这个音视频聊天,必须放到https环境里,好嘛,继续折腾。开始踩坑之旅。
坑1 提示检测到您暂未集成TUICallKit
我已经按照文档集成了TUICallKit,也开通了7天免费体验,结果还是报这个错,这个解决的办法是:
到这个页面CDN&音视频通信产品免费试用中心,开启免费试用套餐
这样才能使用。(那个7天体验期过了,还可以再续7天,我也是不经意发现的)
坑2(大坑) 发布后报Uncaught syntaxError: Unexpected token .
首先,我不是专业做前端的,第二,我们公司现有的项目都是vue 2,官方使用的是vue 3,对vue 3开发的经验不足,我本来以为都一样吧,很自信的发布到服务器,就是很简单的配置一下nginx嘛,结果直接报错,
Uncaught syntaxError: Unexpected token .
注意这个".“号。就寻找出错代码呗,发现出在”?."语法,就找到那行出错的代码,是在“< template>”部分,问公司前端,他说在js里支持这样写,在< template>应该不支持,去掉试试,然后我就一个一个改,把问号去掉,自信满满去试试,结果还是报错,不过这次找不到报错的地方了。然后查百度,各种尝试,我以为是发布问题,可能node.js版本太高,我就删除原来的高版本node.js,然后下载node v14.20.1,结果发布了还是不行。那是不是发布的路径问题,我试了vue.config.js里边publicPath 设置’./‘或者’/'都解决不了问题,进入了死胡同,不知道怎么做了,就尝试找官方技术解决,官方的小哥还挺热情,我就按照他的指导一步一步来,具体如下:
官方回复:
publicPath release 下设置为 “” 试试
我这边的技术也这样说的,我觉得问题不大,改了试试,结果还是一样,这样基本排除了发布的问题
继续报这个错,不过这次找到了出错的地方,
是这段代码。这个是不支持 "?."这样的语法。应该是es6语法不支持,开始配置babel。
vue —— 项目启动时无法识别es6的扩展语法
1.切换淘宝镜像
npm install -g cnpm --registry=http://registry.npm.taobao.org
cnpm install --legacy-peer-deps --save-dev babel-preset-stage-3
cnpm install --legacy-peer-deps --save-dev babel-plugin-transform-object-rest-spread
4.根目录下新增文件 .babelrc
{
"presets": [
["es2015", { "modules": false }]
],
"plugins": ["transform-object-rest-spread"] //不能解析es6语法关键解决
}
5.根目录下新增文件postcss.config.js
module.exports = {
plugins: {
'autoprefixer': {browsers: 'last 5 version'}
}
}
按照这个配置配置完,编译,会报
Error: Cannot find module ‘babel-preset-es2015‘
解决办法:
npm install babel-plugin-component -D
修改babel.config.js的内容,将es2015改为@babel/preset-env。编译,可能会报这个错误
Cannot read property ‘bindings‘ of null
解决办法
.babelrc (或webpack.config.js)中
presets: ["env"]
修改成
presets: ["@babel/preset-env"]
我这边还安装了一个plugin-proposal-optional-chaining,用来解决let str = a?.b?.c;
可选链操作符语法时会出现编译报错的情况,安装babel依赖@babel/plugin-proposal-optional-chaining,并添加babel.config.js中。
module.exports = {
presets: ['@babel/preset-env'],
plugins: ["@babel/plugin-proposal-optional-chaining"]
}
发布,之前的错误没有了,又换了一个
这个是 ?? 语法没有编译,按照这个进行配置
又出现这个
然后再引用这个@babel/preset-typescript
,@babel/preset-typescript,结果还是报错,按理说不应该的,官方问了一句浏览器版本,我才发现服务器上浏览器版本太低,换成版本高的服务器就好了。
现在我把最终的配置发一下
.babelrc
{
"presets": [
"@babel/preset-env","@babel/preset-typescript"
],
"plugins": ["transform-object-rest-spread"] //不能解析es6语法关键解决
}
babel.config.js
module.exports = {
// presets: [
// '@vue/cli-plugin-babel/preset'
// ]
presets: [
"@babel/preset-env","@babel/preset-typescript"
],
plugins: ["@babel/plugin-proposal-optional-chaining"]
}
package.json
{ "name": "chat-example", "version": "0.1.0", "private": true, "scripts": { "serve": "vue-cli-service serve", "build": "vue-cli-service build", "lint": "vue-cli-service lint" }, "dependencies": { "@tencentcloud/call-uikit-vue": "^1.4.2", "@tencentcloud/chat-uikit-vue": "^1.3.4", "babel-polyfill": "^6.26.0", "core-js": "^3.8.3", "es6-promise": "^4.2.8", "vue": "^3.2.13", "vue-class-component": "^8.0.0-0" }, "devDependencies": { "@babel/plugin-proposal-optional-chaining": "^7.21.0", "@babel/preset-typescript": "^7.21.0", "@typescript-eslint/eslint-plugin": "^5.4.0", "@typescript-eslint/parser": "^5.4.0", "@vue/cli-plugin-babel": "~5.0.0", "@vue/cli-plugin-eslint": "~5.0.0", "@vue/cli-plugin-typescript": "~5.0.0", "@vue/cli-service": "~5.0.0", "@vue/eslint-config-typescript": "^9.1.0", "babel-plugin-component": "^1.1.1", "babel-plugin-transform-object-rest-spread": "^6.26.0", "babel-preset-env": "^1.7.0", "babel-preset-stage-3": "^6.24.1", "eslint": "^7.32.0", "eslint-plugin-vue": "^8.0.3", "sass": "^1.32.7", "sass-loader": "^12.0.0", "typescript": "~4.5.5", "vconsole": "^3.15.0", "vite-plugin-vconsole": "^1.3.1" } }
vue.config.js
const path = require("path"); // const CompressionWebpackPlugin = require("compression-webpack-plugin"); // // 定义压缩文件类型 // const productionGzipExtensions = ["js", "css"]; // let timeStamp = new Date().getTime(); function resolve(dir) { return path.join(__dirname, dir); } module.exports = { publicPath: "",// publicPath: process.env.BASE_URL, outputDir: `dist`, assetsDir: 'static', lintOnSave: process.env.NODE_ENV === 'development', productionSourceMap: true, runtimeCompiler: true, devServer: { // 设置主机地址 // 设置默认端口 port: 8080, }, // use the full build with in-browser compiler? // https://vuejs.org/v2/guide/installation.html#Runtime-Compiler-vs-Runtime-only // compiler: false, // webpack配置 // see https://github.com/vuejs/vue-cli/blob/dev/docs/webpack.md webpack链接API,用于生成和修改webapck配置 //部署打包html带引号 chainWebpack: (config) => { config.resolve.alias.set("@", resolve("src")); config.plugin("html").tap((args) => { args[0].minify = false; return args; }); }, //压缩打包文件大小 configureWebpack: (config) => { if (process.env.NODE_ENV === "Production") { // config.output.filename = `assets/js/[name].${timeStamp}.js`; // config.output.chunkFilename = `assets/js/[name].${timeStamp}.js`; config.plugins.push( new CompressionWebpackPlugin({ algorithm: "gzip", test: new RegExp("\\.(" + productionGzipExtensions.join("|") + ")$"), threshold: 10240, minRatio: 0.8, }) ); } config.externals = { // 'vue': 'Vue', // 'vuex': 'Vuex', // 'vue-router': 'VueRouter', // 'element-ui': 'ELEMENT', // 'Axios': 'axios', // 'jquery': '$', // 'moment': 'moment', // 'js-cookie': 'Cookies', // 'echarts': 'echarts', // 'tinymce/tinymce': 'tinymce' }; // } }, // 生产环境是否生成 sourceMap 文件 productionSourceMap: false, // 第三方插件配置 pluginOptions: { // ... }, };
结论:需要确认下最终产品的目标浏览器版本范围。如果要覆盖低版本(不支持 es6的语法),还是需要配置 babel 进行兼容。否则可以忽略
坑3 安卓Webview不支持选取文件操作
把开发好的vue项目发布到服务器上以后,用Webview加载,还是不能选取文件,官方给的建议是参照Android-WebView-解决对选择文件 input type=“file“无响应 来操作,我试了一下,很容易报错,而且代码很多,也有点老,就在网上找了找,发现一个简单的配置,分享一下
private ValueCallback<Uri> mUploadMessage; public ValueCallback<Uri[]> uploadMessage; public static final int REQUEST_SELECT_FILE = 100; private final static int FILECHOOSER_RESULTCODE = 2; webview.setWebChromeClient(new WebChromeClient(){ // For 3.0+ Devices (Start) // onActivityResult attached before constructor protected void openFileChooser(ValueCallback uploadMsg, String acceptType) { mUploadMessage = uploadMsg; Intent i = new Intent(Intent.ACTION_GET_CONTENT); i.addCategory(Intent.CATEGORY_OPENABLE); i.setType("image/*"); startActivityForResult(Intent.createChooser(i, "File Browser"), FILECHOOSER_RESULTCODE); } // For Lollipop 5.0+ Devices @TargetApi(Build.VERSION_CODES.LOLLIPOP) public boolean onShowFileChooser(WebView mWebView, ValueCallback<Uri[]> filePathCallback, WebChromeClient.FileChooserParams fileChooserParams) { if (uploadMessage != null) { uploadMessage.onReceiveValue(null); uploadMessage = null; } uploadMessage = filePathCallback; Intent intent = fileChooserParams.createIntent(); try { startActivityForResult(intent, REQUEST_SELECT_FILE); } catch (ActivityNotFoundException e) { uploadMessage = null; Toast.makeText(getBaseContext(), "Cannot Open File Chooser", Toast.LENGTH_LONG).show(); return false; } return true; } //For Android 4.1 only protected void openFileChooser(ValueCallback<Uri> uploadMsg, String acceptType, String capture) { mUploadMessage = uploadMsg; Intent intent = new Intent(Intent.ACTION_GET_CONTENT); intent.addCategory(Intent.CATEGORY_OPENABLE); intent.setType("image/*"); startActivityForResult(Intent.createChooser(intent, "File Browser"), FILECHOOSER_RESULTCODE); } protected void openFileChooser(ValueCallback<Uri> uploadMsg) { mUploadMessage = uploadMsg; Intent i = new Intent(Intent.ACTION_GET_CONTENT); i.addCategory(Intent.CATEGORY_OPENABLE); i.setType("image/*"); startActivityForResult(Intent.createChooser(i, "File Chooser"), FILECHOOSER_RESULTCODE); } }); } @Override public void onActivityResult(int requestCode, int resultCode, Intent intent) { if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { if (requestCode == REQUEST_SELECT_FILE) { if (uploadMessage == null) return; uploadMessage.onReceiveValue(WebChromeClient.FileChooserParams.parseResult(resultCode, intent)); uploadMessage = null; } } else if (requestCode == FILECHOOSER_RESULTCODE) { if (null == mUploadMessage) return; // Use MainActivity.RESULT_OK if you're implementing WebView inside Fragment // Use RESULT_OK only if you're implementing WebView inside an Activity Uri result = intent == null || resultCode != MainActivity.RESULT_OK ? null : intent.getData(); mUploadMessage.onReceiveValue(result); mUploadMessage = null; } else Toast.makeText(getBaseContext(), "Failed to Upload Image", Toast.LENGTH_LONG).show(); }
配置好了就可以选取文件,相册了
坑4 还是不能调起摄像头进行音视频聊天
现在已经按照文档开发集成,并且发布到了外网,但是使用webview加载,音视频聊天还是弹出权限获取失败的提示,我这边检查过,权限都是有的。官方给的回应是
尊敬的用户: 您好,web端SDK只能使用js调用获取摄像头和麦克风的,底层使用的getUserMedia。如果获取不到需要检查app
webview的配置是否正确。上述能力检查页面是否能听到麦克风声音呢。可以用下面原生的HTML Demo测试一下是否能拿到https://developer.mozilla.org/en-US/docs/Web/API/MediaDevices/getUserMedia
这个得修改安卓原生代码了,我这边只是测试一下,所以在首页的时候,已经把摄像头和语音的权限全部授权了
mWebView.setWebChromeClient(new WebChromeClient() {
@Override
public void onPermissionRequest(PermissionRequest request) {
request.grant(request.getResources());
}
});
腾讯im 的h5版本,在原生支持情况,可以通过设置原生代码来适配,而且对浏览器的兼容性要求比较高,最大的问题是即时性问题,因为没有离线推送,不能随时把控进度,不能离开页面。比如用户需要排队,等待与医生聊天,就可能会长时间停留在网页,不能退出,退出不能及时收到消息。
所以是否选择h5 im,需要根据公司成本和需求来吧。原生app对接Im sdk是最好的,但是需要安装app,剩下的是小程序,我现在把调研的小程序和h5的区别分享一下
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。