- 小程序音视频合成初探
小程序音视频 最近使用了一下微信音视频相关 api, 这绝对是一件振奋人心的事, 让视频的合成在小程序端就能完成, 以下是我使用这些 api 的一些记录。 以下代码片段可以直接在开发者工具中预览 视频合成 音视频合成主要用到 [代码]wx.createMediaContainer()[代码] 方法, 该方法会返回一个 [代码]MediaContainer[代码] 对象, 可以将视频源传入到容器中, 对视频轨和音频轨进行一些操作。 下面以合成视频为例: [代码]const mediaContainer = wx.createMediaContainer(); const { tempFilePath } = this.data; // 本地视频文件地址 // 将视频源传入轨道当中 mediaContainer.extractDataSource({ source: tempFilePath, success: (res) => { // 返回的结果中的 tracks 是一个类数组对象, 第一项是音频轨道, 第二项是视频轨道 const [audioTrack, mediaTrack] = res.tracks; mediaContainer.addTrack(mediaTrack); // 将视频轨道加入到待合成容器中 mediaTrack.slice(1000, 5000); // 截取视频轨道中第 1-5 秒的视频 mediaContainer.addTrack(audioTrack); // 将音频轨道加入到待合成容器中 audioTrack.slice(1000, 5000); // 截取音频轨道中第 1-5 秒的视频 // 导出合成容器中的音频和视频 mediaContainer.export({ success: (res) => { // 拿到导出之后的视频 console .log(res.tempFilePath); }, }); }, }); [代码] 视频解码和录制 如果想要给视频加滤镜和贴图可以采用, [代码]VideoDecoder[代码] + [代码]MediaRecorder[代码] + [代码]WebGL[代码] 的方式, 通过 [代码]VideoDecoder[代码] 将视频解码, 获取视频的每一帧画面, 再绘制到 [代码]canvas[代码] 上, 再通过 [代码]glsl[代码] 着色器给画面加滤镜。同时用 [代码]MediaRecorder[代码] 去录制 [代码]canvas[代码] 上的画面, 最后可以导出一段视频。 以下是将一个视频的前十秒加上黑白滤镜合成出来的主要代码: [代码]let w = 300 let h = 200 const vs = ` attribute vec3 aPos; attribute vec2 aVertexTextureCoord; varying highp vec2 vTextureCoord; void main(void){ gl_Position = vec4(aPos, 1); vTextureCoord = aVertexTextureCoord; } ` const fs = ` varying highp vec2 vTextureCoord; uniform sampler2D uSampler; #ifdef GL_ES precision lowp float; #endif void main(void) { vec4 color = texture2D(uSampler, vTextureCoord); float gray = 0.2989*color.r + 0.5870*color.g + 0.1140*color.b; gl_FragColor = vec4(gray, gray, gray, color.a); } ` const vertex = [ -1, -1, 0.0, 1, -1, 0.0, 1, 1, 0.0, -1, 1, 0.0 ] const vertexIndice = [ 0, 1, 2, 0, 2, 3 ] const texCoords = [ 0.0, 0.0, 1.0, 0.0, 1.0, 1.0, 0.0, 1.0 ] function createShader(gl, src, type) { const shader = gl.createShader(type) gl.shaderSource(shader, src) gl.compileShader(shader) if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) { console.error('Error compiling shader: ' + gl.getShaderInfoLog(shader)) } return shader } function createRenderer(canvas, width, height) { const gl = canvas.getContext("webgl") if (!gl) { console.error('Unable to get webgl context.') return } const info = wx.getSystemInfoSync() gl.canvas.width = width //info.pixelRatio * width gl.canvas.height = height // info.pixelRatio * height gl.viewport(0, 0, gl.drawingBufferWidth, gl.drawingBufferHeight) const vertexShader = createShader(gl, vs, gl.VERTEX_SHADER) const fragmentShader = createShader(gl, fs, gl.FRAGMENT_SHADER) const program = gl.createProgram() gl.attachShader(program, vertexShader) gl.attachShader(program, fragmentShader) gl.linkProgram(program) if (!gl.getProgramParameter(program, gl.LINK_STATUS)) { console.error('Unable to initialize the shader program.') return } gl.useProgram(program) const texture = gl.createTexture() gl.activeTexture(gl.TEXTURE0) gl.bindTexture(gl.TEXTURE_2D, texture) gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, true) gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST) gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST) gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE) gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE) gl.bindTexture(gl.TEXTURE_2D, null) buffers.vertexBuffer = gl.createBuffer() gl.bindBuffer(gl.ARRAY_BUFFER, buffers.vertexBuffer) gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertex), gl.STATIC_DRAW) buffers.vertexIndiceBuffer = gl.createBuffer() gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, buffers.vertexIndiceBuffer) gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint16Array(vertexIndice), gl.STATIC_DRAW) const aVertexPosition = gl.getAttribLocation(program, 'aPos') gl.vertexAttribPointer(aVertexPosition, 3, gl.FLOAT, false, 0, 0) gl.enableVertexAttribArray(aVertexPosition) buffers.trianglesTexCoordBuffer = gl.createBuffer() gl.bindBuffer(gl.ARRAY_BUFFER, buffers.trianglesTexCoordBuffer) gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(texCoords), gl.STATIC_DRAW) const vertexTexCoordAttribute = gl.getAttribLocation(program, "aVertexTextureCoord") gl.enableVertexAttribArray(vertexTexCoordAttribute) gl.vertexAttribPointer(vertexTexCoordAttribute, 2, gl.FLOAT, false, 0, 0) const samplerUniform = gl.getUniformLocation(program, 'uSampler') gl.uniform1i(samplerUniform, 0) return (arrayBuffer, width, height) => { gl.bindTexture(gl.TEXTURE_2D, texture) gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, width, height, 0, gl.RGBA, gl.UNSIGNED_BYTE, arrayBuffer) gl.drawElements(gl.TRIANGLES, 6, gl.UNSIGNED_SHORT, 0) } } wx.createSelectorQuery().select('#video').context(res => { const video = this.video = res.context // 获取 VideoContext video.pause() const decoder = wx.createVideoDecoder() // 创建解码器 const dpr = 1 const webgl = wx.createOffscreenCanvas() const render = createRenderer(webgl, w, h) console.log('webgl', webgl.id, w, h, dpr, webgl.width, webgl.height) const fps = 25 const recorder = wx.createMediaRecorder(webgl, { fps, videoBitsPerSecond: 3000, timeUpdateInterval: 30 }) const startTime = Date.now() let counter = 0 let loopStopped = false let timeCnt = 0 setTimeout(() => { recorder.stop(); decoder.stop(); }, 3000); function loop() { const renderTime = (counter + 1) * (1000 / fps) if (loopStopped || renderTime > 10100) { console.log('recorder stop.', timeCnt, Date.now() - startTime) recorder.stop() return } const ts = Date.now() const imageData = decoder.getFrameData() if (imageData) { render(new Uint8Array(imageData.data), w * dpr, h * dpr) timeCnt += Date.now() - ts counter++ } console.log('render end', counter, Date.now() - ts); recorder.requestFrame(loop) } recorder.on('start', () => { console.log('start render') loopStopped = false loop() }) recorder.on('timeupdate', ({ currentTime }) => { console.log('timeupdate', currentTime) }) recorder.on('stop', (res) => { console.log('recorder finished.', timeCnt, Date.now() - startTime, res) this.setData({ distSrc: res.tempFilePath }) recorder.destroy() }) decoder.on('start', () => { console.log('decoder start 2', decoder.seek) decoder.on('seek', () => { console.log('decoder seeked') recorder.start() }) decoder.seek(0) }) decoder.start({ source: this.data.src, // 这里是一个视频的本地路径, 可通过 wx.chooseVideo 获取 }) }).exec() [代码] 不过此时录制出来的视频是没有声音, 需要通过上面讲到的 [代码]MediaContainer[代码] 截取前 10s 的音频, 将录制出的视频和音频合成得到一段完整的视频。[代码]CanvasRenderingContext2D[代码] 的 [代码]drawImage[代码] 方法 2.10.0 起支持传入通过 [代码]SelectorQuery[代码] 获取的 video 对象, 所以对视频的操作可以先使用 canvas 预览, 再使用 [代码]MediaRecorder[代码] 进行录制。 小程序音视频相关 api 就介绍到这儿, 更多具体的文档, 可以参考微信官方文档。这些 api 大大方便了开发者对音视频的处理, 也期待更多小程序音视频 api 的开放。
2020-07-01 - 小程序当前页怎么截图保存?
有一个分享页面的需求,需要把最后的结果页截图保存到本地,但是页面元素太多,canvas一点点画实现不了,有可以截图的方法么?
2020-03-16 - UNI-APP使用云开发跨全端开发实战讲解
UNI-APP 是一个使用 Vue.js 开发所有前端应用的框架,开发者编写一套代码,可发布到iOS、Android、Web(响应式)、以及各种小程序(微信/支付宝/百度/头条/QQ/钉钉/淘宝)、快应用等多个平台。 本文为大家讲解如何采用云开发官方JS-SDK,接入云开发后端服务并支持UNI-APP全部端(不止于微信小程序) JS-SDK和UNI-APP适配器1.JS-SDK和适配器云开发官方提供的@cloudbase/js-sdk,主要用来做常规WEB、H5等应用(浏览器运行)的云开发资源调用,也是目前最为完善的客户端SDK。 目前市面上大部分的轻应用、小程序包括移动应用APP都是采用JS来作为开发语言的,所以我们可以对TA进行轻微改造,就可以轻松使用在各种平台中。 但是单独改造SDK包会有些许风险,比如在原SDK包升级时需要重新构造,就造成了无穷无尽的麻烦,改造成本相当大。 官方的产品小哥哥深知这种不适和痛苦,所以在@cloudbase/js-sdk 中提供一套完整的适配扩展方案,遵循此方案规范可开发对应平台的适配器,然后搭配 @cloudbase/js-sdk 和适配器实现平台的兼容性。 不了解的小伙伴肯定会有些茫然,我来用浅显的语言解释一下,就是@cloudbase/js-sdk 将底层的网络请求以及相关基础需求以接口的形式暴露出来,我们按照平台的特殊API来补充这些接口,sdk就可以根据这些补充的接口,无障碍的运行在平台中了。 如果我们想在UNI-APP中使用@cloudbase/js-sdk ,底层网络请求你需要来补充,因为sdk原本是适应浏览器的,TA不知道UNI-APP怎么对外发请求,所以你需要将uni.request 方法补充到TA暴露的接口中。补充完毕后,@cloudbase/js-sdk 就可以在UNI-APP中活泼的运行了。 我们将所有的uni方法全部补充到JS-SDK暴漏的接口中去,就形成了一个完整的适配器,我们将其成为uni-app适配器。 2.UNI-APP适配器UNI-APP的整体接口都是公开透明的,我们在开发UNI-APP时也都遵照同一套接口标准。所以小编已经将uni-app适配器制作完毕,大家只需要在使用时接入适配器就可以了。 我们在项目目录main.js中引入云开发JS-SDK,然后接入我们的UNI-APP适配器即可。 import cloudbase from '@cloudbase/js-sdk' import adapter from 'uni-app/adapter.js' cloudbase.useAdapters(adapter); cloudbase.init({ env: '',//云开发环境ID appSign: '',//凭证描述 appSecret: { appAccessKeyId: 1,//凭证版本 appAccessKey: ''//凭证 } }) 移动应用登录凭证云开发SDK在使用过程中,向云开发服务系统发送的请求都会需要验证请求来源的合法性。 我们常规 Web 通过验证安全域名,而由于 UNI-APP 并没有域名的概念,所以需要借助安全应用凭证区分请求来源是否合法。 登录云开发 CloudBase 控制台,在安全配置页面中的移动应用安全来源一栏:[图片] 点击“添加应用”按钮,输入应用标识:uni-app(也可以输入其他有标志性的名称),需要注意应用标识必须是能够标记应用唯一性的信息,比如微信小程序的 appId 、移动应用的包名等。[图片] 添加成功后会创建一个安全应用的信息,如下图所示:[图片] 我们需要保存一下上图中的版本(示例为1)、应用标识(示例为uni-app)、以及点击获取到的凭证(示例为demosecret) 在项目目录中,我们将main.js中的init部分补全 import cloudbase from '@cloudbase/js-sdk' import adapter from 'uni-app/adapter.js' cloudbase.useAdapters(adapter); cloudbase.init({ env: 'envid',//云开发环境ID,保证与你操作登录凭证一致 appSign: 'uni-app',//凭证描述 appSecret: { appAccessKeyId: 1,//凭证版本 appAccessKey: 'demosecret'//凭证 } }) 如此,你就可以正常的进行云开发的登录使用了。 需要注意以下4点: 你需要设置uni-app的各端安全域名为:request:tcb-api.tencentcloudapi.com、uploadFile:cos.ap-shanghai.myqcloud.com、download:按不同地域配置使用此种方法接入云开发是全端支持,并不会享有微信小程序生态的一些便利,微信小程序开发还是需要依赖正常请求调用过程(将云开发作为服务器来对待),但你可以判断wx来使用wx.cloud来兼容。使用云开发的匿名登录时,受各端实际情况影响,可能不能作为常久唯一登录id,需要根据自身业务建立统一账户体系,具体可使用自定义登录来进行。UNI-APP支持WEB网页端上线时,需要将网页域名配置到云开发安全域名中(防止WEB下载文件导致跨域)示例代码详解示例项目中已经基本构建了uni-app使用云开发的各种流程代码。 在页面中进行匿名登录: // index.vue import cloudbase from '@cloudbase/js-sdk' export default { data() { return { title: '登录中' } }, onLoad() { cloudbase.auth().anonymousAuthProvider().signIn().then(res => { this.title = '匿名登录成功' }).catch(err => { console.error(err) }) } } 调用云函数并收到返回结果: import cloudbase from '@cloudbase/js-sdk' export default { methods: { call: function() { cloudbase.callFunction({ name: "test", data: { a: 1 } }).then((res) => { console.log(res) }); } } } 操作数据库: import cloudbase from '@cloudbase/js-sdk' export default { methods: { database: function() { cloudbase.database().collection('test').get().then(res => { console.log(res) }) } } } 实时数据库监听: import cloudbase from '@cloudbase/js-sdk' export default { methods: { socket: function() { let ref = cloudbase.database().collection('test').where({}).watch({ onChange: (snapshot) => { console.log("收到snapshot", snapshot); }, onError: (error) => { console.log("收到error", error); } }); } } } 上传文件(框架限制,WEB端无法操作): import cloudbase from '@cloudbase/js-sdk' export default { methods: { upload: function() { uni.chooseImage({ count: 1, sizeType: ['original', 'compressed'], sourceType: ['album'], success: function(res) { console.log(res.tempFilePaths[0]) cloudbase.uploadFile({ cloudPath: "test-admin.png", filePath: res.tempFilePaths[0], onUploadProgress: function(progressEvent) { console.log(progressEvent); var percentCompleted = Math.round((progressEvent.loaded * 100) / progressEvent.total); } }).then((result) => { console.log(result) }); } }); } } } 下载文件(需要注意地域域名,配置安全域名): import cloudbase from '@cloudbase/js-sdk' export default { methods: { download: function() { cloudbase.downloadFile({ fileID: "cloud://demo-env-1293829/test-admin.png" }).then((res) => { console.log(res) }); } } } 部署步骤将项目下载后使用HBuilderX打开。按照获取移动安全凭证的指引,填写至mian.js相应处。打开目录命令行,npm i执行安装依赖。打开云开发控制台,开启匿名登录。新建一个默认的云函数,名称为test(逻辑内容直接返回event即可)新建一个数据库,名称为test(随便添加几个记录,设置权限为所有人可读)调整项目pages/index/index.vue中,21行代码,在登录成功后调用相应函数。以下是WEB端运行时展示:[图片] 关于uni-app适配器在util/adapter中,只进行了简单的测试,保证可用性,后续请关注官网获取最新适配器依赖此方法有别与uniCloud,是直接使用uni请求底层,依赖官方JS-SDK进行云开发服务的交互处理,在使用时注意区别。项目地址:https://github.com/AceZCY/UNI-for-CloudBase
2020-12-08 - Vue PC端框架
1. Element Element,一套为开发者、设计师和产品经理准备的基于 Vue 2.0 的桌面端组件库 中文文档 | github地址 [图片] 2. iView 一套基于 Vue.js 的高质量UI 组件库 中文文档 | github地址 [图片] 3. vue-element-admin vue-element-admin是基于 Vue2.0,配合使用 Element UI 组件库的一个前端管理后台集成解决方案。它使用了最新的前端技术栈,提炼了典型的业务模型,提供了丰富的功能组件,它可以帮助你快速搭建企业级中后台产品原型。 中文文档 | github地址 [图片] 4. Vue Material 通过Vue Material和Vue 2.0建立精美的app应用 中文文档 | github地址 [图片] 5. VueStrap 基于 Vue.js 构建的 Bootstrap 组件。该仓库包含一系列基于 Bootstrap 标记和 CSS 的本地 Vue.js 组件。所以不需要 jQuery 和 Bootstrap 的 JavaScript 文件,唯一需要依赖的是: Vue.js (要求版本为 ^0.12,基于 0.12.10 版本做的测试) Bootstrap CSS (需要版本为 3.x.x, 基于 3.3.5 版本做的测试)。 VueStrap 不依赖某个非常精确的 Bootstrap 版本。 在线文档 | github地址 [图片] 6. Keen UI 由Vue编写的基本UI组件的轻量级集合,并受Material Design的启发。 在线文档 | github地址 [图片] 7. Radon UI 一个帮助你快速开发产品的Vue组件库,简洁好用,效率高,让你摆脱各种定制化的烦恼。 中文文档 | github地址 [图片] 8. N3-components N3组件库是基于Vue.js构建的,让前端工程师和全栈工程师能快速构建页面和应用。致力于构建良好的Vue开发者生态圈,提供良好的开发体验。 在线文档 | github地址 [图片] 9. Muse-UI 基于 Vue 2.0 优雅的 Material Design UI 组件库 中文文档 | github地址 [图片] 10. Vue Antd 这里是 Ant Design 的 Vue 实现,开发和服务于企业级后台产品。 中文文档 | github地址 [图片] 11. Vuetify Vuetify是一个渐进式的框架,试图推动前端开发发展到一个新的水平。Vuetify 支持SSR(服务端渲染),SPA(单页应用程序),PWA(渐进式Web应用程序)和标准HTML页面。 中文文档 | github地址 [图片] 12. Buefy Buefy 基于 Bulma 和 Vue.js 的轻量级UI组件,它提供了即装即用的轻量级组件。 在线文档 | github地址 [图片] 13. Vue Beauty 基于 ant design 的漂亮的 vue 组件库;vue-beauty 是一套基于 vue.js 和 ant-design样式 的PC端 UI 组件库,旨在帮助开发者提升产品体验和开发效率、降低维护成本。 在线文档 | github地址 [图片] 14. AT UI AT UI 是一款基于 Vue.js 2.0 的前端 UI 组件库,主要用于快速开发 PC 网站中后台产品。AT-UI提供了一套 npm + webpack + babel 前端开发工作流程。小编非常喜欢的UI风格。 中文文档 | github地址 [图片] 15. Vue-Blu Vue-Blu是基于Vuejs和Bulma开发的开源UI组件库。旨在为PC端的前端开发(特别是中后台产品)提供一个快速且灵活的解决方案。 中文文档 | github地址 [图片] 16. D2Admin D2Admin是一个完全 开源免费 的企业中后台产品前端集成方案,使用最新的前端技术栈,已经做好大部分项目前期准备工作,并且带有大量示例代码,助力管理系统敏捷开发。特别感谢 tanyf 的贡献。 中文文档 | github地址 | 预览地址 [图片] 17. Best Resume Ever Best Resume Ever 是一个帮助你快速生成漂亮简历的工具,它基于 Vue 和 LESS,生成的简历可导出为 PDF 格式。 中文文档 | github地址 | 预览地址 [图片] 18. Quasar Quasar(发音为/kweɪ.zɑɹ/)是MIT许可的开源框架(基于Vue),允许开发人员编写一次代码,然后使用相同的代码库同时部署为网站、PWA、Mobile App和Electron App。使用最先进的CLI设计应用程序,并提供精心编写,速度非常快的Quasar Web组件。 中文文档 | github地址 [图片] 19. Vue Baidu Map Vue Baidu Map是基于Vue 2.x的百度地图组件。 中文文档 | github地址 | 在线预览 [图片] 20. vue-admin-beautiful vue-admin-beautiful是github开源admin中最优秀的集成框架之一,它是国内首个基于vue3.0的开源admin项目,同时支持电脑,手机,平板,默认分支使用vue3.x+antdv开发,master分支使用的是vue2.x+element开发。开箱即用,非常方便。 github地址 | 在线预览 [图片] 相关文章 Vue 移动端框架 别走,还有后续呐······ 如果小伙伴们有比较好的PC端框架,欢迎在评论区留言砸场,我会持续更新在简书上,谢谢你的贡献。
2020-10-18