- 小程序音视频合成初探
小程序音视频 最近使用了一下微信音视频相关 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 - 数据库原子操作和事务讲解
使用更新指令(如 inc、mul、addToSet)可以对云数据库的一条记录和记录内的子文档(结合反范式化设计)进行原子操作,但是如果要跨多个记录或跨多个集合的原子操作时,就需要使用云数据库的事务能力。 12.6.1 更新指令的原子操作关系型数据库是很难做到通过一个语句对数据强制一致性的需求来表示的,只能依赖事务。但是云开发数据库由于可以反范式化设计内嵌子文档,以及更新指定可以对单个记录或同一个记录内的子文档进行原子操作,所以通常情况下,云开发数据库不必使用事务。 比如调整某个订单项目的数量之后,应该同时更新该订单的总费用,我们可以设计采用如下方式设计该集合,比如订单的集合为 order: { "_id": "2020030922100983", "userID": "124785", "total":117, "orders": [{ "item":"苹果", "price":15, "number":3 },{ "item":"火龙果", "price":18, "number":4 }] } 客户在下单的时候经常会调整订单内某个商品比如苹果的购买数量,而下单的总价又必须同步更新,不能购买数量减少了,但是总价不变,这两个操作必须同时进行,如果是使用关系型数据库,则需要先通过两次查询,更新完数据之后,再存储进数据库,这个很容易出现有的成功,有的没有成功的情况。但是云开发的数据库则可以借助于更新指令做到一条更新来实现两个数据同时成功或失败: db.collection("order") .doc("2020030922100983") .update({ data: { "orders.0.number": _.inc(1), total: _.inc(15), }, }); 这个操作只是在单个记录里进行,那要实现跨记录要进行原子操作呢?更新指令其实是可以做到事务仿真的,但是比较麻烦,这时就建议用事务了。 12.6.2 事务与 ACID事务就是一段数据库语句的批处理,但是这个批处理是一个 atom(原子),多个增删改的操作是绑定在一起的,不可分割,要么都执行,要么回滚(rollback)都不执行。比如银行转账,需要做到一个账户的钱汇出去了,那另外一个账户就一定会收到钱,不能钱汇出去了,但是钱没有到另外一个的账上;也就是要执行转账这个事务,会对 A 用户的账户数据和 B 用户的账户数据做增删改的处理,这两个处理必须一起成功一起失败。 1、ACID一般来说,事务是必须满足 4 个条件(ACID): Atomicity(原子性)、Consistency(稳定性)、Isolation(隔离性)、Durability(可靠性): 原子性:整个事务中的所有操作要么全部提交成功,要么全部失败回滚,对于一个事务来说,不可能只执行其中一部分操作,一致性:事务的执行不能破坏数据库数据的完整性和一致性,一个事务在执行前后,数据库都必须处于一致性状态。换句话说,事务的执行结果必须是使数据库从一个一致性状态转变到另一个一致性状态。比如在执行事务前,A 用户账户有 50 元,B 用户账户有 150 元;执行 B 转给 A 50 元事务后,两个用户账户总和还是 200 元。隔离性:事务的隔离性是指在并发环境中,当不同的事务同时操纵相同的数据时,每个事务都有各自的完整数据空间事务之间,互不干扰。比如在线银行,同时转账的人虽然很多,但是不会出现影响 A 与 B 之间的转账;可靠性:即使发生系统崩溃或机器宕机等故障,只要数据库能够重新启动,那么一定能够将其恢复到事务成功结束时的状态,已提交事务的更新不会丢失。 2、云函数事务注意事项01不支持批量操作,只支持单记录操作 在事务中不支持批量操作(where 语句),只支持单记录操作(collection.doc, collection.add),这可以避免大量锁冲突、保证运行效率,并且大多数情况下,单记录操作足够满足需求,因为在事务中是可以对多个单个记录进行操作的,也就是可以比如说在一个事务中同时对集合 A 的记录 x 和 y 两个记录操作、又对集合 B 的记录 z 操作。 02云数据库采用的是快照隔离 对于两个并发执行的事务来说,如果涉及到操作同一条记录的时候,可能会发生问题。因为并发操作会带来数据的不一致性,包括脏读、不可重复读、幻读等。 脏读:指当一个事务正在访问数据,并且对数据进行了修改,而这种修改还没有提交到数据库中,这时,另外一个事务也访问这个数据,然后使用了这个数据;不可重复读:在一个事务内两次读到的数据是不一样的,受到另一个事务修改后提交的影响,因此称为是不可重复读幻读:第一个事务对表进行读取,当第二个事务对表进行增加或删除操作事务提交后,第一个事务再次读取,会出现增加或减少行数的情况云开发的数据库系统的事务过程采用的是快照隔离(Snapshot isolation),可以避免并发操作带来数据不一致的问题。 事务期间,读操作返回的是对象的快照,而非实际数据事务期间,写操作会:1. 改变快照,保证接下来的读的一致性;2. 给对象加上事务锁事务锁:如果对象上存在事务锁,那么:1. 其它事务的写入会直接失败;2. 普通的更新操作会被阻塞,直到事务锁释放或者超时事务提交后,操作完毕的快照会被原子性地写入数据库中 12.6.3 事务操作的两套 API云开发数据库的事务提供两种操作风格的接口,一个是简易的、带有冲突自动重试的 runTransaction 接口,一个是流程自定义控制的 startTransaction 接口。通过 runTransaction 回调中获得的参数 transaction 或通过 startTransaction 获得的返回值 transaction,我们将其类比为 db 对象,只是在其上进行的操作将在事务内的快照完成,保证原子性。transaction 上提供的接口树形图一览: transaction |-- collection 获取集合引用 | |-- doc 获取记录引用 | | |-- get 获取记录内容 | | |-- update 更新记录内容 | | |-- set 替换记录内容 | | |-- remove 删除记录 | |-- add 新增记录 |-- rollback 终止事务并回滚 |-- commit 提交事务(仅在使用 startTransaction 时需调用) 1、通过 runTransaction 回调获得 transaction以下提供一个使用 runTransaction 接口的,两个账户之间进行转账的简易示例。事务执行函数由开发者传入,函数接收一个参数 transaction,其上提供 collection 方法和 rollback 方法。collection 方法用于取数据库集合记录引用进行操作,rollback 方法用于在不想继续执行事务时终止并回滚事务。 const cloud = require("wx-server-sdk"); cloud.init({ env: cloud.DYNAMIC_CURRENT_ENV, }); const _ = db.command; exports.main = async (event) => { try { const result = await db.runTransaction(async (transaction) => { const aaaRes = await transaction.collection("account").doc("aaa").get(); const bbbRes = await transaction.collection("account").doc("bbb").get(); if (aaaRes.data && bbbRes.data) { const updateAAARes = await transaction .collection("account") .doc("aaa") .update({ data: { amount: _.inc(-10), }, }); const updateBBBRes = await transaction .collection("account") .doc("bbb") .update({ data: { amount: _.inc(10), }, }); console.log(`transaction succeeded`, result); return { aaaAccount: aaaRes.data.amount - 10, }; } else { await transaction.rollback(-100); } }); return { success: true, aaaAccount: result.aaaAccount, }; } catch (e) { console.error(`事务报错`, e); return { success: false, error: e, }; } }; 事务执行函数必须为 async 异步函数或返回 Promise 的函数,当事务执行函数返回时,SDK 会认为用户逻辑已完成,自动提交(commit)事务,因此务必确保用户事务逻辑完成后才在 async 异步函数中返回或 resolve Promise。 2、通过 startTransaction 获得 transactiondb.startTransaction(),开启一个新的事务,之后即可进行 CRUD 操作;db.startTransaction().transaction.commit(),提交事务保存数据,在提交之前事务中的变更的数据对外是不可见的;db.startTransaction().rollback(),事务终止并回滚事务,例如,一部分数据更新失败,对已修改过的数据也进行回滚。const cloud = require("wx-server-sdk"); cloud.init({ env: cloud.DYNAMIC_CURRENT_ENV, }); const db = cloud.database({ throwOnNotFound: false, }); const _ = db.command; exports.main = async (event) => { try { const transaction = await db.startTransaction(); const aaaRes = await transaction.collection("account").doc("aaa").get(); const bbbRes = await transaction.collection("account").doc("bbb").get(); if (aaaRes.data && bbbRes.data) { const updateAAARes = await transaction .collection("account") .doc("aaa") .update({ data: { amount: _.inc(-10), }, }); const updateBBBRes = await transaction .collection("account") .doc("bbb") .update({ data: { amount: _.inc(10), }, }); await transaction.commit(); return { success: true, aaaAccount: aaaRes.data.amount - 10, }; } else { await transaction.rollback(); return { success: false, error: `rollback`, rollbackCode: -100, }; } } catch (e) { console.error(`事务报错`, e); } }; 也就是说对于多用户同时操作(主要是写)数据库的并发处理问题,我们不仅可以使用原子更新,还可以使用事务。其中原子更新主要用户操作单个记录内的字段或单个记录里内嵌的数组对象里的字段,而事务则主要是用于跨记录和跨集合的处理。
2021-09-10 - 云开发原子操作实现秒杀功能
直接上代码: DB.collection('product').where({ _id: productId, stock: _.gt(0)//库存大于0 }).update({ data: { stock: _.inc(-1),//库存减 1,原子操作inc。 luckers: _.addToSet(openid)//记录获奖者的openid } }).then(res => { //注意如何判断结果 if (res.stats.updated == 1) { console.log('获得奖品') } if (res.stats.updated == 0) { console.log('啥也没抢到') } })
2022-11-09 - RenderingContext如何实现canvasContext.drawImage的功能?
const ctx = canvas.getContext('2d'); let img = canvas.createImage(); img.src = imgUrl; img.onload = (e) => { ctx.drawImage(img, 0, 0, 60, 60); wx.canvasGetImageData({ canvasId: canvasId, x: 0, y: 0, width: 60, height: 60, fail(e){ // 输出canvas内容是 empty, 要如何获取imageData } }) };
2022-02-17 - 小程序AR识别,三行代码实现Camera数据毫秒级转base64图片
关键词:小程序AR 图片 base64 相机 Camera onCameraFrame Canvas ArrayBuffer Uint8Array Uint8ClampedArray upng-js 核心步骤: 1 相机原始图像数据frame.data,即ArrayBuffer数组,转成Uint8Array数组 2 Uint8Array数组转成Uint8ClampedArray数组 3 wx.canvasPutImageData(Uint8ClampedArray) 详细流程如下: 最近因为项目需求,需要上传base64去做AR识别功能,和大家一起分享讨论下具体的实现方式。 首先说下实现原理,通过Camera的onCameraFrame获取实时帧数据,将实时帧数据添加到Canvas上,然后将Canvas保存为临时图片,再将临时图片转换为base64。 贴上核心实现代码: wxml: js: var nCounter = 0; openCamera: function (res) { var that = this var camera_ctx = wx.createCameraContext() listener = camera_ctx.onCameraFrame((frame) => { // nCounter等于30 是因为一开始相机会有一个对焦的过程,如果一开始获取数据,就是模糊的图片 if (nCounter == 30) { console.log(frame.data instanceof ArrayBuffer, frame.width, frame.height) var data = new Uint8Array(frame.data); var clamped = new Uint8ClampedArray(data); // 实时帧数据添加到Canvas上 wx.canvasPutImageData({ canvasId: 'myCanvas', x: 0, y: 0, width: frame.width, height: frame.height, data: clamped, success(res) { // 转换临时文件 wx.canvasToTempFilePath({ x: 0, y: 0, width: frame.width, height: frame.height, canvasId: 'myCanvas', fileType: 'jpg', destWidth: frame.width, destHeight: frame.height, // 精度修改 quality: 0.8, success(res) { // 临时文件转base64 wx.getFileSystemManager().readFile({ filePath: res.tempFilePath, //选择图片返回的相对路径 encoding: 'base64', //编码格式 success: res => { // 保存base64 that.data.mybase64 = res.data; } }) }, fail(res) { console.log(res); } }, that) } }) } nCounter++ // console.log(nCounter); if (nCounter >= 100) { nCounter = 0 } }) listener.start() } 目前网上有两种转换方式并对比下: 1:upng-js等第三方转码js库,将相机流转换成base64,一般需要1-2s左右 [图片] 2.使用canvas将相机流转变base64,都是使用js或者小程序官方的api进行转换,一般转换时间在1秒以下: [图片] 重点说明下: 如何使用wx.canvasPutImageData()将相机流添加canvas,我们查看该官方api,添加的data类型为:Uint8ClampedArray [图片] 而我们通过onCameraFrame获取的data类型为:ArrayBuffer [图片] 所有两者类型不一致,就需要转换,将ArrayBuffer=>Uint8Array=>Uint8ClampedArray var data = new Uint8Array(frame.data); var clamped = new Uint8ClampedArray(data); 成功的把onCameraFrame获取实时帧数据转换并canvasPutImageData在canvas上,并通过canvasToTempFilePath获取临时文件,如何获取临时文件getFileSystemManager转换为base64,传入云端进行AR识别,就大功告成! 技术分享来自于:北京晞翼科技有限公司 技术作者:le3d618、xiaoz0816 微信商务联系:le3d618
2020-04-30 - VideoDecoder 的 getFrameData API无法使用?
首先尝试使用 微信开发工具调试,显示: [图片] 于是,尝试使用真机调试 [图片] 代码很简单,初始化一个videoDecoder然后通过start函数载入一个本地视频地址。然后尝试seek stop。 然后报错: [图片]
2020-06-01 - 使用threejs渲染视频
琢磨了很久,翻了很多相关文档,才实现了用threejs渲染视频的方法 思路如下 1 在wxml文件里写两个canvas <canvas type="webgl" class="myCanvas" id="myCanvas"></canvas> <canvas style="position: absolute;left: 0;top:300rpx;background-color: red;" type="2d" id="target1"></canvas> 2 实例threeJs相关实例和方法,具体参考,可以百度, 3 添加2d相关实例方法, export default class VideoPlayer { constructor(component){// component 是组件里的this, this.decoder = wx.createVideoDecoder() this.component = component const videoUrl = 'https://cdn2.h5no1.com/static-cdn/cpbz/v.mp4'; this.dataUriList = [] } getCanvasNode(id) { return new Promise((resolve) => { this.component.createSelectorQuery() .select('#' + id) .node(res => resolve(res.node)) .exec(); }); } async downLoad(url){ // 下载视频并压缩视频,如果不压缩可能会卡 return new Promise(resolve=>{ wx.downloadFile({ url, success:res=>{ wx.compressVideo({ quality:'low', // resolution:0.9, // fps:60, src:res.tempFilePath, success:result=>{ console.log(result) resolve(result.tempFilePath) } }) }, fail:()=>{ resolve(url) } }) }) } async playVideo(id,videoUrl,cb){ const canvas = await this.getCanvasNode(id) //获取2dcanvas节点 const path = await this.downLoad(videoUrl) // 下载视频并返回本地地址 const context = canvas.getContext('2d') // 2d上下文 const render = ({ data, width, height }) => { // 渲染2d时,返回DataURI数据,作为纹理数据 canvas.height = height canvas.width = width const imageData = canvas.createImageData(data, width, height) context.putImageData(imageData, 0, 0) if(canvas.toDataURL()){ cb && cb(canvas.toDataURL()) // cb回调方法 } } const decoder = wx.createVideoDecoder() // 创建视频解码器 await decoder.start({ // 开始解码 abortAudio: true, source:path || videoUrl // tempFilePath, }) return Promise.resolve({ decoder, stop(){ return decoder.remove() }, play:async ()=>{ let imageData = decoder.getFrameData() // 获取下一帧的解码数据 if (imageData){ render(imageData) } } }) } } 4 在threejs中引入上面的代码 import VideoPlayer from "./video.js" /* 省略前面的代码 */ this.videoPlayer = new VideoPlayer(this) this.vp = null var geometry = new THREE.PlaneGeometry( 520, 320, 1); var material = new THREE.MeshBasicMaterial( {side: THREE.DoubleSide} ); var plane3 = new THREE.Mesh( geometry, material ); plane3.position.set(0,rangeSize/2/2,-rangeSize/2) scence.add( plane3 ); this.videoPlayer.playVideo('target1','https://video-qn.51miz.com/preview/video/00/00/12/22/V-122260-B7D997DF.mp4',async (res)=>{ if(res){ textureLoader.load(res,texture=>{ // 加载datauri数据到纹理,一定要写在回调里,不然也看着卡顿 texture.minFilter = THREE.NearestFilter plane3.material.map = texture }); } }).then(res=>{ this.vp =res }) // 动画帧方法 this.wgbglCanvas.requestAnimationFrame(()=>{ if(this.vp && this.vp.play){ this.vp.play() } }) 5 视频解码器 文档 https://developers.weixin.qq.com/miniprogram/dev/api/media/video-decoder/wx.createVideoDecoder.html 这个方法很重要,没有这个方法,根本就不能实现webgl视频播放
2022-08-30