- 流量主上传电子发票,增值税普通电子发票可以吗?
电子发票,专票和普通发票都可以上传吗? 我只能开 增值税普通电子发票
2022-03-30 - 小程序音视频合成初探
小程序音视频 最近使用了一下微信音视频相关 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 - 小程序流量主运营篇-三招收益翻倍
目前小程序场景可接入banner广告、视频广告、前贴视频广告、插屏广告、格子广告、激励视频广告六种。在多种选择下,流量主可针对不同页面使用合适的广告组件来提升流量主收益。(本文不涉及【前贴视频广告】,作者为个人小程序) [图片] 很多同学收益很低,每天收益都在1-100以内,学会下面三招可以让你目前的收益翻倍,希望能帮助到你。 第一招:广告组件合理接入 1、单页面仅可接入一条信息流广告,即banner、视频、格子,三选一,超过两条,流量主审核会失败。 2、单页面可接入多类型广告,即信息流广告(banner、视频、格子其中之一)、插屏广告、激励视频广告可同时出现在一个页面。 3、广告代码接入,从后台创建好之后,可以获取代码信息,放到合适的位置 3-1、banner、格子、视频广告接入 banner广告代码:<ad unit-id="adunit-99e304189164f191"></ad>放到你wxml页面的合适位置,如下图: [图片] 视频广告、格子广告类似,同样复制代码即可: 视频广告代码:<ad unit-id="adunit-4b35bf8bb66516f7" ad-type="video" ad-theme="white"></ad> 格子广告代码:<ad unit-id="adunit-ad2ee97ce13ffc2a" ad-type="grid" grid-opacity="0.8" grid-count="5" ad-theme="white"></ad> 3-2、插屏广告接入 插屏广告代码需要放到js页面中: 3-2-1、你想让用户打开你的小程序或者打开某个页面直接弹出插屏广告,那么可以把插屏广告代码复制到onload/onshow或者其他方法里面 3-2-2、你想让用户点击某个按钮后弹出插屏广告,那么可以把插屏广告代码复制到触发的事件中。 [图片] 3-3、激励视频广告接入 激励视频不可随意接入,它需要用户看完广告后,要给用户返回一些有价值内容,比如我这个这小程序,是AI写诗的,我设置的激励广告是,用户输入完主体 内容,然后给用户返回AI诗句。同样,代码要放在对应的JS文件中。 [图片] 4、流量主审核失败汇总 诱导用户点击广告、部分机型广告遮挡、单页面出现2个以上信息流广告、激励广告无奖励下发等 点击→小程序审核被拒的情况汇总第二招:挖掘广告接入场景 一个小程序如果广告组件接入场景单一,考虑到平衡用户体验,在该场景下能承载的广告就相对有限。因此,挖掘更多的接入场景,能够为流量主带来广告收入的增长。 举例:单页面多信息流广告,增加遮罩层(与第一招背道而驰,且符合流量主审核标准) [图片] 第三招:广告组件优化,灵活搭配 1、广告位名字 创建完广告位一定要记住这个广告位放在哪儿,为了直观预览,大家可以给给自己的广告起名字如:首页插屏、首页banner、首页激励等 2、分析用户页面停留时间,放合适的广告 [图片] 视频广告的收益包含两种:一是完整曝光收益、二是用户点击收益。一个视频广告的完整曝光在15-30秒之间,也就是说当用户在此页面停留30秒以上,适合放视频广告,前提是视频广告在用户的可视区域内。当用户在此页面停留低于15秒,个人建议可换成banner广告或者取消此广告位。有两个原因:一是视频广告占位比较大影响用户体验,二是视频曝光不完整,没有收益。 3、收益分析放合适广告 流量主-数据统计-广告指标明细-细分数据 [图片] 3-1、首先看拉取/曝光量,当某个广告位拉取/曝光量偏低时,可以根据对应广告位名字去更改/取消页面广告位 3-2、其次看点击率,当某个广告位点击率偏低时,可根据对应广告位名字去更改/删除页面光广告位 3-3、最后看收益,当某个广告位收益偏低时,可根据对应广告位名字去更改/删除页面光广告位 最后希望看到文章的你,收益多多~有缘再见~ 点击→流量主运营技巧机票点击→小程序起名字技巧机票
2020-04-25 - 看朋友圈说腾讯2月11才上班,之前说审核暂停到1月30日,会延迟吗?
看朋友圈说腾讯2月11才上班,之前说审核暂停到1月30日,会延迟吗?
2020-01-29