- 微信支付平台证书更换指引
微信支付平台证书(以下简称平台证书)是微信支付和商户交互过程中用于认证微信支付平台身份的证书,商户会在微信支付APIv3接口的请求应答、回调、敏感信息加密场景用到平台证书。 平台证书的有效期为5年,如不及时更换,会导致商户调用微信支付接口失败,出现商户业务中断的情况。如果你有使用微信支付APIv3接口,请务必重视。 如果你有收到微信支付商户平台站内信、电话等渠道通知你更换平台证书,请参考以下指引处理。另外,有不少商户和开发者容易混淆平台证书和商户API证书,请注意它们并不相同,因此即使你已更换过商户API证书,也依然要更换平台证书。 平台证书简介及使用说明:https://pay.weixin.qq.com/doc/v3/merchant/4012068814 平台证书更换操作指引:https://pay.weixin.qq.com/doc/v3/merchant/4012068829 为避免更换不及时或更换过程中出现的系统风险影响业务,商户可将平台证书模式切换为微信支付公钥模式。 平台证书切换微信支付公钥指引:https://pay.weixin.qq.com/doc/v3/partner/4012925289 如有疑问,通过如下链接联系到技术支持寻求帮助:https://support.pay.weixin.qq.com/online-service?from=wechatpay
03-24 - “网赚”小程序,你只了解1%
大部分微信开发者对“网赚”的初步认识仅仅局限于网上刷单赚佣金、或阅读文章赚佣金等业务模式。 除以上模式外,还包括自行或协助他人以拟人程序、利诱其他用户参与、转发、下载或委托刷单平台等方式等网赚行为。 今天小编通过实际案例给大家详细剖析相关网赚违规行为: 1. 小程序内纯粹做分享文章/内容后可立即得到奖励的内容(奖励包括但不限于现金、积分、礼品等) 违规示例:如下图违规小程序通过做阅读/转发文章即可获得金币奖励的模式贯穿业务,金币支持兑换现金并提现到账,属于网赚行为。[图片] 2. 小程序内含网赚刷单业务 违规示例:如下图违规小程序为APP提供刷单业务,完成刷单任务后,下载APP即可获取收益,属于网赚行为。[图片] 3. 小程序内存在通过体验APP/小程序/小游戏等产品赚取奖励的行为 违规示例:如下图违规小程序通过体验/转发小程序获取奖励,奖励包括但不限于现金、礼品、积分等,属于网赚行为。 [图片] 4. 小程序内存在通过体验自身业务获取奖励的行为 违规示例:如下图违规小程序通过体验自身业务15秒,即可获得5-10g水滴,水滴可兑换实物或现金等,属于网赚行为。 [图片] 5. 小程序昵称/简介/头像含明显网赚信息 违规示例:如下图违规小程序的头像/简介/昵称含明显网赚信息,诱导进入后获取用户信息,达到网赚推广目的,属于网赚行为。 [图片] 6. 小程序涉及以体验赚奖励、分享赚奖励等业务模式贯穿整个业务 违规示例:如下图违规小程序表面包装成打卡瓜分奖金的业务形态,实际必须通过跳转体验其他小程序、公众号后完成体验任务,才能完成每日打卡任务,属于网赚行为。 [图片] 7、小程序内无实质内容,存在通过批量观看激励视频的形式进行网赚的行为 违规示例①:如下图违规小程序内无实质内容,通过批量观看视频广告获得刮刮卡解锁机会。 [图片] 违规示例②:如下图违规小程序的每一份测试题结果,均需通过观看视频广告才能获得。[图片] 通过以上网赚违规类型及示例的介绍,希望开发者们能对小程序网赚违规有更进一步的了解。如若小程序存在网赚内容,平台将下发警告限期整改,视违规情节严重程度对小程序功能进行限制,或封号处理。
2020-03-18 - 重渲染与自定义组件优化(下)
[视频] 接下来我们看实践二实现wxs版本的stopwatch组件。 另一个组件stopwatch_wxs这个组件版本与stopwatch组件它实现了相同的方法,调用方式也是一样的,不同点在于我们wxs组件。这个版本的组件,它在wxml这个页面里边引入了一个wxs脚本。关于时间计算的逻辑,包括格式化等等这些全部从逻辑层移到了wxs脚本里面去,从这个组件的代码来看wxs组件的JS代码与我们原来的stopwatch组件,它的代码是一样的。 主要也是实现了三个方法start、stop和switch,并且它的wxss的样式代码相比原组件也没有一个变化。在组件的wxml代码里面引入了一个wxs脚本,JS这个代码层对wxs脚本的一个控制,它是通过一个叫做change:mode这样的一个属性去完成的。index.wxs这个脚本的这个内容我们可以看一下,稍后会看到,这个文件只能使用ES5的语法,所有ES6的语法都不能使用,并且这和我们在这个项目详情面板里边是否开启将JS变成ES5没有关系,你开不开启这个设置你都不可以使用,这样一个ES6语法。 wxs脚本它没有定时器,我们可以使用组件对象的requestAnimationFrame方法代替于原来的定时器方法,在这个组件对象上我们当然也可以使用setTimeout方法创建定制器,setInterval也可以,但显然在这个地方与帧频天然合拍的requestAnimationFrame方法,它更适合干这个工作,它能使我们这个数字的切换刷新的动画看起来更加的一个流畅。在测试的时候我们仍然可以打开Performance面板,使用wxs脚本实现了单次视图更新,它的消耗大概是在20毫秒左右,比原来的执行性能要稍好一些。 下面我们开始代码实践。 首先第一步我们需要去创建另外一个版本也就是wxs版本的这样的一个组件。这个组件在我们最终源码里面都有了,都已经实现了。我们看一下它的一个效果。首先我们看wxml这个标签代码,标签代码相比原来我们增加了第一个,增加了这个模块wxs这个模块的一个引入,然后在下面这个地方加了一个change:mode等于index.modeObserver。这个是为了实现一个属性监听,就是将我们对mode属性的一个变化,将这个事件然后传递给这样的一个方法 传递给它去驱动这个方法的一个执行。当我们加了这个属性以后,后面mode等于什么一个值我们必须也是要添加的,这个是必不可少的。 再看一下JS代码里面,首先这个地方,我们可以看到把它放在了data里面,因为要通过setData去改变它去触发我们视图上面属性它的一个改变,所以我们把这个地方把它放到data数据对象里面。然后start方法很简单,就是一个setData的调用,stop也是。switch方法没有变化,整个现在我们自定义组件对于wxs这个版本它的JS逻辑层代码已经达到了最简化,它没有一点多余的一个代码了。 那么这个组件的功能在哪里实现的,我们看一下。最重要的一个角色就是index.wxs脚本,这个脚本我们刚才提到了是在这个地方引入的对吧,引入以后然后我们看它里面干了什么事情。它有一个导出的方法在最下面,模块导出然后modeobserver这个方法干了什么事情,我们看一下。在这个地方它接收了一些参数,这些参数都是视图在调用它的时候负责传递给它的,这是它的新值就是mode的新值,因为这个监听的是mode属性,然后这是它的旧值,ownerInstance,这个是它本身组件它所属的实例,它属于哪个实例对象,这个它本身的一个实例。在这个里面我们需要去做判断,然后去看新值,判断如果它等于start,我们就调用这个start,如果它等于stop,我们就调用stop对吧。 start与stop怎么实现的?我们再接着往上面看,start里面这个地方有一个配置,这个配置其实传的其实就是ownerInstance。如果说我们组件在这个页面里面的话,它本身它的所有者其实就是一个page,它不是page也没有关系,因为我们用的它里面的这个方法是callMethod,调用它的方法还有requestAnimation调用它,只要这个方法可以调用就可以了,在这个里面我们再看一下干了一个什么事情。这个地方仍然会有一个convertTimeStampToString这样的一个时间格式化的方法,这个方法全是用ES5的这样一个语法写的。这个逻辑我们可以看到与我们原来的组件的逻辑,我们可以看一下是一样的,我们只是把它换了一个地方,然后拷贝在这个地方,同时将我们的let关键字改成var。为啥改成var?因为前面我们提到了,在wxs脚本里面你不可以使用ES6的语法,只能使用ES5对吧,然后这个地方就是为了格式化。 格式化以后在这个地方注意,我们有了一个小小的处理,为啥要处理?因为本身在wxs脚本里边去调callMethod的方法也是间接调setData,我们这个代码调用本身也是要通过Native层进行中转的。如果你这个方法调得太频繁的话显然它也会影响性能,所以我们这个地方做了一个限流,判断一下消失的时间是不是大于100毫秒,如果大于100毫秒然后允许它调用,如果没有那就先跳过去本次更新 让更新不太频繁。100毫秒间隔是可以的,因为我们人类对变化的一个感知时间大概是200毫秒,你可以感知它的一个变化100毫秒的话,基本上你对人类来讲是无感知的。 这个地方会有一个mode等于start的判断,如果是它还等于start这个状态,组件这个状态的话我们继续用requestAnimationFrame去启动本身上面的调用它本身是一个递归调用。只有当mode等于stop的时候,它才会停止调用 不再会调用了,这是它的一个调用方法。这个地方我们再多说一下,这地方有一个是什么 requestAnimationFrame这个方法本身是由我们渲染的时候,视图它本身帧频去驱动的,就是视图层渲染一次,它触发一次这个事件,然后我们通过这个方法去绑定的这些回调执行,它也会再跟着再执行一次,就这样一种方式。 它可以最大程度的跟我们这个视图的渲染进行合拍,如果是你不用这种方式,你用定时器的话,定时器写在逻辑层里面很可能跟我们这个视图渲染它不是合拍的,视图渲染了比如说你每秒渲染30帧,然后JS定时器你要求它每秒更新60帧,它跟不上。它跟不上的时候视图就会卡顿。所以这个地方我们用requestAnimationFrame这个方法,这是我们优先要使用的一个方法。前面我们提到了在这个里面,我们不可以使用setInterval setTimeout,本身在wxs脚本里面是不能使用的。但是我们在我们传进来的对象上比如说像ownerInstance,我们在这个上面它其实是有定时器方法的,你明白吗?它是有定时器方法的,就是我们可以在这个上面去调用定时器方法,有这个选项可以选择。但是就如我们刚才所说它性能其实不如requestAnimationFrame,所以我们在这个地方还是选择这个方法。 下面我们再看stop,其实它什么事都没干,为什么?正常情况下我们在这个地方要干一个清扫的工作,就是我们要调cancelAnimationFrame,但这个cancelAnimationFrame这个方法在这个对象上面它是不存在的。你把这个配置换成这个也是一样的,本身这个方法在这个上面目前是不存在的,以后可能会有,如果以后有的话,这个地方我们要做一个清扫工作,但目前它没有,所以这个地方这个方法什么也没有做这样的一种方式。 还有一个地方我们需要说明一下,就是这个mode,这个mode我们可以看一下我们是在哪里定义的,是本身在这个模块代码里面对吧。在这个地方进行定义的,它相当于什么,它其实相当于是一个模块变量。这个模块当它在我们的页面里面 在这个地方,当它在这个地方引入的时候,其实它已经有了一个模块的实例。明白了吗?它已经有了一个实例,当它有了一个实例以后,里面的这些变量它其实都是有状态的,都可以自己持有自己的一个状态,持有自己一种状态。所以在这个地方我们可以拿这个mode然后去做临时的一个状态的储存,然后这个地方可以去判断。 如果我们不用这种方式还有另外一种方式可以用是什么方式,就是通过instance.getDataset然后调用它的mode,调用它的mode去判断是否等于start,这个取它是从哪取的,它其实从我们这个组件。我们看一下传递过来的事件是这个,因为这个事件是从这传的 ,instance它其实等于谁?它其实等于这个view对不对,我们取它的dataset取到哪里,取到这个地方看到没有?这个地方有一个data-mode,等于mode对不对。如果是我们按照我们后面提到的这个方式去取,取到的信息其实是它。当然这个地方我们要有一个对比,在这个模块里边让它自己持有这个状态,与我们从这个视图上去取这个状态,两种方式进行对比。 我们下面的这种方式它的效率会更高,因为它本身自己就把这个状态给它做了很好的一个判断,你不需要去麻烦别人再去取那个状态了,你多了一层调用效率又会降低,这是关于这个脚本的一个说明说得稍微多一点,因为所有的代码都是在这里面实现的。 这个工作做完以后,接下来我们要做什么?因为我们组件的向外暴露的方法都是一致的,行为也是一致的,所以我们只需要在我们组件引入的在这个地方加了一个wxs,只需要加这个以后组件引用就变成从原来引入这个组件,然后变成了引入这个组件,这个组件名我们不变,组件名我们仍然可以按原来的方式进行使用,现在代码终于改完了。然后单击编译,我们看一下它的实际运行效果。选择我们首页然后运行首页 ,已经渲染出来了,然后单击 然后它运行,我们看到也在快速地进行切换,现在我们要对比,要打开Performance面板,可以让它先停下来把我们原来的给它清除一下,重新单击录制,然后单击开始,已经有了 然后停止 不需要太长,因为我们只需要测一段时间,只要它有执行就可以了,看一下 仍然是有问题的。因为我们的优化看来是无止境的,而我们对比一下看看跟原来相比是不是有所改善,你比如这个我们看到它渲染任务大概是54.30对不对,然后它里边这里面有个setData,然后这个地方也是大概57.40,这个是66 稍微长一点66,这个地方是61,然后这个大概是26,这个里面我看一下,它里面其实也是有setData的 每次都不一样。 这个地方它性能我们可以做个小小的测试,它主要是由什么样的一个代码然后引发的,我们可以改一个地方 改哪里,你觉得我们改哪里可以让性能稍微变好一点,改我们的这个地方对不对。因为它现在性能其实主要是在这个地方受影响的,我们只需要在这个地方将这个阈值改一下,我们改成200 改成200之后再做一个测试,再看一下表现 把这个清掉,然后再单击开始 然后录制,它本身面板的开启也是需要CPU的,所以它开启的时候我们可以明显感到它有一个卡顿,好,停止,现在这个测试我们看一下跟我们刚才的操作相比,明显可以感到我这个界面没有刚才卡顿了对不对,比刚才好了很多,这个地方已经没有红三角了,红三角的显示与刚才相比已经少了很多,看我们刚才这些东西已经没有了对不对,我们可以看到里面的方法,这个地方这有一个 很多都是匿名函数的一个回调,我们再看这个地方有setData对不对,setData大概是浪费了6.98毫秒,它整体上这个时间 我们渲染这个时间大概是消耗了到36.40,刚才我们看到大概是60毫秒,现在我们把它 间隔100改成200以后,消耗大概是变到了36毫秒,基本上效率提升了一半。 这和我们的预想也是一致的,因为我们将本身这个地方将100改成200的时候,从理论上来讲它这个性能应该会提升100对吧,这个从侧面也说明这个地方它其实就是影响我们性能的主要的一个关键点。从理论上讲也是因为我们所有的这些其他的一个代码,它都是这个wxs代码对吧,只有这一个地方我们是用到了JS的逻辑层计算这个结果,然后要通过这样一种方式传给JS逻辑层,由JS逻辑层再去更新这样视图里面的数据,本质上我们这种优化把这个组件变成wxs这样的一个版本,变了以后 其实只有我们上面的这些计算代码是用新的脚本语言,就是wxs脚本去执行的,而渲染数据的传递还是原来的那种方式并且它还绕了一个弯对不对,所以从这两种组件的表现来看,我们新的组件它在渲染的时候 它性能比原来的组件会稍微好那么一点点,但是好的也不算太多,因为本质上它还是受限于callMethod以及setData这个方法的一个限制,它有限制在这个地方,我们这个演示就说到这里。 最后我们总结一下,小程序的视图更新有重渲染机制,当这个逻辑层代码通过setData方法 改变视图数据的时候它会触发新的wxml节点树的一个生成以及新旧节点树的一个差异比较将数据密集更新的功能组件化可以显著提升我们视图渲染的运行时效率。此外wxs脚本,它由于不需要底层的一个中转,也就是通过evalulateJavaScript方法的一个帮助,它可以绕过逻辑层直接操作这个视图层的一个组件,使用它辅助完成这个组件的一个开发也可以大大提升我们这个视图渲染的一个效率,在wxs脚本与逻辑层需要相互调用的地方我们也有办法进行处理 我们举个例子,例如在wxs脚本里面可以通过ownerInstance的callMethod方法去调用这个页面上的方法。例如刚才我们看到的对setData这个方法的一个调用反过来逻辑层怎么样去调用wxs脚本里面的方法,我们可以通过视图层上绑定一个名称,为changexxx这样的一个特别属性,触发对wxs脚本的里面的方法的一个调用。 这节课我们涉及到的文档如屏幕上所示,这节课我们就讲到这里。 这节课我们主要学习了如何将数据更新频繁的功能区域进行组件化以及如何使用wxs脚本辅助完成组件的一个自定义以提升运行时的渲染性能。 下节课我们学习代码按需注入与初始渲染缓存,这里有个问题请你思考一下:在传统动态的网页开发里面,可以将HTML这个页面在这个服务器端缓存下来甚至写成静态的HTML文件以此来加快下一次用户访问,同一个页面的加载速度在这个小程序开发里面有类似的技术也可以实现这样的一个效果,你知道是哪个技术吗? 下节课我们一起来深入探讨一下这个问题。 点击查看开放文档: WXS 语法参考WXS响应事件
2022-07-13 - 小程序性能优化实践
小程序性能优化课程基于实际开发场景,由资深开发者分享小程序性能优化的各项能力及应用实践,提升小程序性能表现,满足用户体验。
2024-10-09 - 这些 Canvas 小技巧,保证你新年用得上
来自「微信开发者」公众号,作者为微信小程序技术研发工程师binnie。 本文主要介绍了3个隐藏的 Canvas 小技巧: - 绘制并生成图片 - Video 绘制 Canvas / webgl - 视频解码并绘制到 webgl - 录制并导出 webgl 视频 一键加滤镜 快速合成音视频 轻松挑选视频封面 …… Canvas 能够做这些? 作为资深的开发者,相信大家对 Canvas 都不陌生。这项能力在绘制图形方面发挥着极大的作用,高效支持图片编辑、数据可视化等应用场景。但是只局限于一般能力应用,那格局就小了。 Canvas 的应用场景非常丰富!赶紧往下看看这些隐藏的 Canvas 小技巧,保证你新年用得上!还有手把手教程以及文末彩蛋哟。 -- • 绘制并生成图片 • -- [图片] 示例:新年模板长按保存祝福 适用场景:图片分享海报 相关 API:RenderingContext/Canvas/wx.canvasToTempFilePath Step 1: 创建实例获取对象 创建 Canvas 实例,获取 CanvasRenderingContext2D 对象(Canvas 绘图上下文)来绘制形状、文本、图像等。 const query = wx.createSelectorQuery() let canvas = null query.select('#myCanvas') .fields({ node: true, size: true }) .exec((res) => { // 通过 wx.createSelectorQuery 获取到 canvas 实例 canvas = res[0].node // 通过 canvas.getContext('2d') 获取 CanvasRenderingContext2D 对象 const ctx = canvas.getContext('2d') }) Step 2: 设置宽高调整图片 获取 Canvas 绘图上下文后,将 Canvas 的宽高设置为节点宽高 * 设备像素比,绘制出来的图片更清晰 // 获取设备像素比 const dpr = wx.getSystemInfoSync().pixelRatio // 将 canvas 宽高设置为 canvas.width = res[0].width * dpr canvas.height = res[0].height * dpr Step 3: 绘制内容 使用 CanvasRenderingContext2D 绘制,根据业务需要在画布中绘制头像、文字、背景等 // 矩形 ctx.fillStyle = '#FFFFFF' ctx.fillRect(0, 0, canvas.width , canvas.height ) // 图片 var image = canvas.createImage() himage.src = 'https://example.com/example.jpg' headImage.onload = (res) => { ctx.drawImage(himage 0, 0, 32, 32; } // 文本 ctx.font = "18px SimHei"; ctx.textAlgin = "left" ctx.fillStyle = "#07c160"; ctx.fillText("这是我的名字", 0, 0); Step 4: 生成并保存本地 使用 wx.canvasToTempFilePath 将画布生成图片,wx.saveImageToPhotosAlbum 将图片保存到本地。 wx.canvasToTempFilePath({ canvas: canvas, // canvas 实例 success(res) { // canvas 生成图片成功 wx.saveImageToPhotosAlbum({ filePath: res.tempFilePath, success(res) { // 保存成功 } }) } }) -- • Video 绘制 Canvas / webgl • -- [图片] 示例:视频文件绘制 Canvas 适用场景:制作 Video 滤镜、挑选 Video 封面等 相关 API:RenderingContext/Canvas Step 1: 获取实例 通过 wx.createSelectorQuery 获取 VideoContext 实例 let video = null wx.createSelectorQuery().select('#video').context(res => { // 通过 wx.createSelectorQuery 获取 VideoContext 实例 video = res.context; }) Step 2: 绘制内容 获取 VideoContext 实例后,将 VideoContext 传递给 Canvas 进行绘制。开发者根据业务需求选择绘制类型: Canvas 2d 写法:canvas.drawImage(video, ...)webgl 写法:gl.texImage2D(..., video) wx.createSelectorQuery().selectAll('#myCanvas,#webglCanvas').node(res => { const ctx = res[0].node.getContext('2d') const gl = res[1].node.getContext('webgl') setInterval(() => { // canvas 2d // 将 video 纹理对象传入 drawImage 进行绘制 ctx1.drawImage(video, 0, 0, w * dpr, h * dpr); // 添加一个蒙层 ctx1.fillStyle = 'rgba(0, 0, 0, 0.3)' ctx1.fillRect(0, 0, w * dpr, h * dpr); // webgl const render = createRenderer(res[1].node, w, h) render(new Uint8Array(ctx1.getImageData(0, 0, w * dpr, h * dpr).data), w * dpr, h * dpr) }, 1000 / 24) }).exec() function createRenderer(canvas, width, height) { const gl = canvas.getContext("webgl") ... return (arrayBuffer, width, height) => { ... // 指定二维纹理图像 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) } } -- • 视频解码并绘制到 webgl • -- [图片] 示例:视频一键解码并绘制到 webgl 适用场景:添加特效、贴图等视频编辑场景 相关 API:wx.createVideoDecoder/VideoDecoder/RenderingContext/Canvas.requestAnimationFrame/wx.createMediaAudioPlayer/MediaAudioPlayer Step 1: 创建视频解码器进行解码 1. 调用 createVideoDecoder 对视频进行解码 2. 使用 videodecoder.start 启动解码,视频源文件不限制本地或远程路径 3. 通过 videodecoder.on('start', res => {}) 监听解码,通过 videodecoder.getFrameData() 获取到解码数据 // 获取视频解码器 getVideoDecoder(source, abortAudio) { return new Promise((resolve, reject) => { // 创建视频解码器 videodecoder = wx.createVideoDecoder() // 开始解码 videodecoder.start({ abortAudio: abortAudio, source: source, // 视频源文件,支持本地路径&远程路径 mode: 0 // 按pts解码,保证音画同步 }) // 监听解码 开始 videodecoder.on('start', res => { console.log('videodecoder start', res) // 状态初始化 isStop = false resolve(videodecoder) }) // 监听解码 结束 videodecoder.on('ended', res => { // 状态设置为结束,停止画面录制器 isStop = true }) }) }, Step 2: 解码数据绘制到 webgl 1. 通过 gl.texImage2D(..., image) 将解码数据绘制到 webgl 2. 使用 webgl.requestAnimationFrame 继续绘制,效果更加流畅 // 将解码数据绘制到 webgl 中 const query = wx.createSelectorQuery() query.select('#webglCanvas').node().exec((res) => { const webgl = res[0].node const requestAnimationFrame = webgl.requestAnimationFrame; // 初始化webgl let render = null if (!render) { render = createRenderer(webgl, 600, 400) } /** * 绘制视频帧到 canvas */ let i = 1 let loop = () => { // 解码结束,停止循环 if (isStop) { return } // 获取解码数据,绘制到 webgl 中 const imageData = videodecoder.getFrameData() if (imageData) { // render 的高宽需要设置为图片的宽高才可以绘制出来 render(new Uint8Array(imageData.data), imageData.width, imageData.height) } // 继续绘制 console.log('绘制帧数:', i++) requestAnimationFrame(loop) } // 启动录制循环 requestAnimationFrame(loop) }) Step 3: 添加音频播放器同步播放音频 完成 Step2 后,webgl 只有视频播放,缺少音频。因此使用 wx.createMediaAudioPlayer(),支持 addAudioSource 传入 videodecoder,保证视频帧渲染音画同步 /** * 创建媒体音频播放器 */ let mediaAudioPlayer = null let addAudio = () => { if (mediaAudioPlayer) return mediaAudioPlayer = wx.createMediaAudioPlayer() mediaAudioPlayer.start().then(() => { // 添加播放器音频来源 mediaAudioPlayer.addAudioSource(videodecoder).then(res => { console.log('add mediaAudioPlayer: ',) }) }) } // render 绘制视频同时添加音频 render(new Uint8Array(imageData.data), imageData.width, imageData.height) addAudio() -- • 录制并导出 webgl 视频 • -- [图片] 示例:录制并一键导出 webgl 视频 适用场景:将动画、编辑过的视频导出视频文件保存 相关 API:wx.createMediaRecorder/MediaRecorder/wx.createMediaContainer/MediaContainer/MediaTrack Step 1: 创建 webgl 画面录制器进行录制 通过 createMediaRecorder 创建页面录制器,并且绑定 webgl(建议离屏状态,效果更好)进行录制 /** * 获取画面录制器 */ getRecorder() { let canvas = this.getMainCanvasNode() let recorder = wx.createMediaRecorder(canvas, { fps: choosedVideoInfo.fps, // 实际视频的 fps videoBitsPerSecond: choosedVideoInfo.bitrate, // 实际视频的 bitrate gop: 12 }) // 监听录制事件 recorder.on("timeupdate", (res) => { console.log('recorder 录制中,当前时间:', res.currentTime) }) recorder.on("stop", (res) => { console.log('recorder停止') this.saveMedia(res.tempFilePath) }) // 开始录制 recorder.start() this.recorder = recorder return recorder }, // 初始化 画面录制器 并进行录制 await this.initRenderer() this.getDecoder().then((decoder) => { let recorder = this.getRecorder() var self = this function loop() { if (self.stopped) { return } let frameData = decoder.getFrameData() if (!frameData) { console.log('没取到帧') setTimeout(() => { loop() }, 1000/60) } else { self.renderFrame(frameData) recorder.requestFrame(() => { console.log('录制帧数:', i++) loop() }) } } loop() }) Step 2: 添加音频合成音视频 1. 通过 createMediaContainer 创建音视频处理容器来合成音视频 2. 通过 MediaContainer.extractDataSource 将视频源分离出视频轨道和音频轨道,将需要的轨道通过 MediaContainer.addTrack 添加到容器中 3. 通过 MediaContainer.export 导出即可获得合成后的视频文件 /** * 将视频和音频合到一起并保存到本地 * @param {*} videoTempFilePath */ saveMedia(videoTempFilePath) { const self = this let choosedFile = this.choosedFile const MediaContainer = wx.createMediaContainer() // webgl的取视频 MediaContainer.extractDataSource({ source: videoTempFilePath, success(res) { MediaContainer.addTrack(res.tracks[0]) // 源视频取音频 MediaContainer.extractDataSource({ source: choosedFile, success(res) { // 拿到音频轨道并加入到容器 res.tracks[0].kind == 'audio' && MediaContainer.addTrack(res.tracks[0]) res.tracks[1].kind == 'audio' && MediaContainer.addTrack(res.tracks[1]) // 合成视频并导出视频文件 MediaContainer.export({ success(res) { // 保存视频到本地 wx.saveVideoToPhotosAlbum({ filePath: res.tempFilePath, success() { wx.showToast({ title: '导出成功', icon: 'success', duration: 2000 }) self.destroy() } }) } }) } }) } }) }, -- •高效图像处理彩蛋 • -- 学会以上这些 Canvas 小技巧,还担心新年的美图美照美视频处理不过来?赶紧码下这个 Canvas 代码包,保证你就是家里最闪耀的靓女靓仔。 预祝大家新的一年 Canvas 在手,红包一直有!
2022-03-24 - 小程序链接生成与使用规则调整公告
各位开发者: 为确保小程序链接合理使用,自 2022 年 4 月 11 日起,URL Scheme 和 URL Link (以下统称为 “链接” )接口能力规则将进行以下调整: 每个 URL Scheme 或 URL Link 有效期最长 30 天,均不再支持永久有效的链接、不再区分短期有效链接与长期有效链接;链接生成后,若在微信外打开,用户可以在浏览器页面点击进入小程序。每个独立的链接被用户访问后,仅此用户可以再次访问并打开对应小程序,其他用户无法再次通过相同链接打开该小程序;单个小程序每天生成链接数(URL Scheme 和 URL Link 总数)上限为 50 万条。 对于上述 1,在开发层面,相应的服务端接口 urlscheme.generate 和 urllink.generate 将进行以下调整: is_expire 值固定为 true,可不再传该值,若传值为 false 也与 true 一样会生成到期失效链接;若 expire_type 传值为 0,需注意 expire_time 传值的时间戳不超过 30 天,即该参数最长传值有效期为 30 天;若 expire_type 传值为 1,需注意 expire_interval 传值范围为 [1, 30],即该参数最长传值间隔天数为 30。详细对比见下表: [图片] 已使用该后端接口的开发者可以不进行任何修改,不会出现返回异常。若传值超过新规则合法值,或声明使用永久有效的链接,则均会被赋最长有效期值(30天);需注意以上新规则生效后的有效期和访问规则变化。 在本次规则调整生效前已经生成的链接,也将自动生效以下规则: 如果有效期超过30天或长期会被降级为30天有效,开始时间从调整日期开始计算;在调整生效后,只能被1个用户访问。 当前已使用微信云开发 静态网站H5跳小程序 与 短信跳小程序、微信服务平台短信服务为用户提供链接的功能不受影响,但同样适用以上规则。 微信团队 2022年3月9日 相关QAQ1:每天下发的短信量级超过50万条,不够用怎么办? A1:可将生成 scheme 的时机改为在用户打开 H5 时再生成: [图片]
2023-09-26 - 小程序关联公众号策略调整
各位开发者,大家好。 目前,小程序需要与公众号关联,才可被使用在公众号自定义菜单、模板消息、客服消息等场景中。而公众号关联小程序时,需要小程序管理员确认,该环节增加了开发者之间的沟通成本。 为了降低公众号与小程序间的合作门槛,我们将调整小程序关联公众号策略如下: 公众号关联小程序将无需小程序管理员确认。 取消“小程序最多关联500个公众号”的限制。 若希望小程序在被关联时保留管理员确认环节,可前往“小程序管理后台-设置-基本设置-关联公众号设置”修改设置项。 公众号文章中可直接使用小程序素材,无需关联小程序。 开发者可在“小程序管理后台-设置-关联设置”中管理已关联的公众号。 微信团队 2019.04.04
2019-04-08 - 小程序搜索优化指南(SEO)
2019年上半年微信发布了基于小程序页面的搜索,为了让我们更好地发现及理解小程序的页面,结合过去一段时间来我们遇到的各种情况,我们强烈建议各位开发者花一些宝贵的时间认真阅读本文:) 爬虫访问小程序内页面时,会携带特定的 user-agent "mpcrawler" 及场景值:1129 1. 小程序里跳转的页面 (url) 可被直接打开。 小程序页面内的跳转url是我们爬虫发现页面的重要来源,且搜索引擎召回的结果页面 (url) 是必须能直接打开,不依赖上下文状态的。特别的:建议页面所需的参数都包含在url 2. 页面跳转优先采用navigator组件。 小程序提供了两种页面路由方式: a.navigator 组件 b. 路由 API,包括 navigateTo / redirectTo / switchTab / navigateBack / reLaunch 建议使用 navigator 组件,若不得不使用API,可在爬虫访问时屏蔽针对点击设置的时间锁或变量锁。 3.清晰简洁的页面参数。 结构清晰、简洁、参数有含义的 querystring 对抓取以及后续的分析都有很大帮助,但是将 JSON 数据作为参数的方式是比较糟糕的实现。 4. 必要的时候才请求用户进行授权、登录、绑定手机号等。 建议在必须的时候才要求用户授权(比如阅读文章可以匿名,而发表评论需要留名)。 5. 我们不收录 web-view 中的任何内容。 我们暂时做不到这一点,长期来看,我们可能也做不到。 6. 利用 sitemap 配置引导爬虫抓取,同时屏蔽无搜索价值的路径。 https://developers.weixin.qq.com/miniprogram/dev/framework/sitemap.html 7. 设置一个清晰的标题和页面缩略图。 页面标题和缩略图对于我们理解页面和提高曝光转化有重要的作用。 通过wx.setNavigationBarTitle或 自定义转发内容onShareAppMessage对页面的标题和缩略图设置,另外也为 video、audio 组件补齐 poster /poster-for-crawler属性。 8. 使用页面路径推送能力 可极大丰富微信可以收录的内容,进而提高小程序内容的曝光机会。请参考: https://developers.weixin.qq.com/miniprogram/dev/api-backend/open-api/search/search.submitPages.html
2020-01-14 - 小程序模板消息能力调整通知
小程序模板消息能力在帮助小程序实现服务闭环的同时,也存在一些问题,如: 1. 部分开发者在用户无预期或未进行服务的情况下发送与用户无关的消息,对用户产生了骚扰; 2. 模板消息需在用户访问小程序后的 7 天内下发,不能满足部分业务的时间要求。 为提升小程序模板消息能力的使用体验,我们对模板消息的下发条件进行了调整,由用户自主订阅所需消息。 一次性订阅消息 一次性订阅消息用于解决用户使用小程序后,后续服务环节的通知问题。用户自主订阅后,开发者可不限时间地下发一条对应的服务消息;每条消息可单独订阅或退订。 [图片] (一次性订阅示例) 长期性订阅消息 一次性订阅消息可满足小程序的大部分服务场景需求,但线下公共服务领域存在一次性订阅无法满足的场景,如航班延误,需根据航班实时动态来多次发送消息提醒。为便于服务,我们提供了长期性订阅消息,用户订阅一次后,开发者可长期下发多条消息。 目前长期性订阅消息仅向政务民生、医疗、交通、金融、教育等线下公共服务开放,后期将逐步支持到其他线下公共服务业务。 调整计划 小程序订阅消息接口上线后,原先的模板消息接口将停止使用,详情如下: 1. 开发者可登录小程序管理后台开启订阅消息功能,接口开发可参考文档:《小程序订阅消息》 2. 开发者使用订阅消息能力时,需遵循运营规范,不可用奖励或其它形式强制用户订阅,不可下发与用户预期不符或违反国家法律法规的内容。具体可参考文档:《小程序订阅消息接口运营规范》 3. 原有的小程序模板消息接口将于 2020 年 1 月 10 日下线,届时将无法使用此接口发送模板消息,请各位开发者注意及时调整接口。 微信团队 2019.10.12
2019-10-13