Skip to content

add: VideoDecoder #13

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
265 changes: 265 additions & 0 deletions miniprogram/js/api/media/videoDecoder/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,265 @@
import view from './view';
import { errMsgGlobal } from '../../../errMsg/index';
import show from '../../../libs/show';
import createRenderer from './render';

module.exports = function (PIXI, app, obj) {
let videoDecoder, frameData, currentTime, isPaused;
let videoSprite;
let baseTexture;
let texture;
let renderer;
let canvas;

// 初始化视频解码器
const initVideoDecoder = (data = {}) => {
if (wx.getSystemInfoSync().platform === 'devtools') {
show.Modal('开发者工具不支持,需要使用预览在真机进行调试', '提示');
return;
}

try {
const defaultConfig = {
source: 'https://baikebcs.bdimg.com/baike-other/big-buck-bunny.mp4',
width: 1280,
height: 720
};

const finalConfig = { ...defaultConfig, ...data };
console.log('开始初始化视频解码器', finalConfig);

// 创建视频解码器
try {
videoDecoder = wx.createVideoDecoder({
type: "wemedia"
});
console.log('视频解码器创建成功');
} catch (e) {
console.error('视频解码器创建失败:', e);
throw e;
}

// 创建渲染相关对象
try {
canvas = wx.createCanvas();
canvas.width = 1280; // 设置一个合适的初始宽度
canvas.height = 720; // 16:9 比例

if (!!GameGlobal.isIOSHighPerformanceModePlus) {
// 高性能+模式使用WebGL渲染器
renderer = createRenderer(canvas);
// 创建PIXI纹理,但使用WebGL渲染
baseTexture = new PIXI.BaseTexture(canvas);
texture = new PIXI.Texture(baseTexture);
} else {
// 普通模式使用PIXI
baseTexture = new PIXI.BaseTexture(canvas);
texture = new PIXI.Texture(baseTexture);
}

videoSprite = new PIXI.Sprite(texture);
console.log('渲染对象创建成功');
} catch (e) {
console.error('渲染对象创建失败:', e);
throw e;
}

// 设置精灵位置和大小
try {
const headerHeight = 150;
const availableHeight = obj.height - headerHeight;

const targetRatio = 16 / 9;
let displayWidth, displayHeight;

displayWidth = obj.width * 0.9;
displayHeight = displayWidth / targetRatio;

if (displayHeight > availableHeight * 0.8) {
displayHeight = availableHeight * 0.8;
displayWidth = displayHeight * targetRatio;
}

const xPos = (obj.width - displayWidth) / 2;
const yPos = headerHeight + (availableHeight - displayHeight) / 2;

videoSprite.width = displayWidth;
videoSprite.height = displayHeight;
videoSprite.x = xPos;
videoSprite.y = yPos;

console.log('Sprite位置和大小设置成功:', {
width: displayWidth,
height: displayHeight,
x: xPos,
y: yPos
});
} catch (e) {
console.error('精灵属性设置失败:', e);
throw e;
}

// 添加到舞台
try {
app.stage.addChild(videoSprite);
console.log('Sprite添加到舞台成功');
} catch (e) {
console.error('添加到舞台失败:', e);
throw e;
}

// 设置错误处理
videoDecoder.onError && videoDecoder.onError(res => {
console.error('视频解码器错误:', res);
for (let i = 0, errMsglist = Object.keys(errMsgGlobal); i < errMsglist.length; i++) {
if (res.errMsg.includes(errMsglist[i])) {
errMsgGlobal[errMsglist[i]]({
callback(res) {
show.Modal(res, '发生错误');
}
});
break;
}
}
});

// 监听解码器事件
videoDecoder.on("start", () => {
console.log('解码开始');
show.Toast('开始解码', 'success', 1000);
});

videoDecoder.on("stop", () => {
console.log('解码停止');
show.Toast('停止解码', 'success', 1000);
});

videoDecoder.on("ended", () => {
console.log('播放完成');
show.Toast('播放完成', 'success', 1000);
});

videoDecoder.on('frame', (res) => {
try {
closeFrame();
frameData = res;
currentTime = res.pts / 1000;

if (frameData) {
const { width, height, data } = frameData;

if (!!GameGlobal.isIOSHighPerformanceModePlus && renderer) {
// 高性能+模式:使用WebGL渲染器
if (canvas.width !== width || canvas.height !== height) {
canvas.width = width;
canvas.height = height;
}
renderer(data, width, height);
// 通知PIXI纹理更新
baseTexture.update();
} else {
// 普通模式:使用2D Canvas
const ctx = canvas.getContext('2d');

if (canvas.width !== width || canvas.height !== height) {
canvas.width = width;
canvas.height = height;
}

const imageData = ctx.createImageData(width, height);
imageData.data.set(new Uint8ClampedArray(data));
ctx.putImageData(imageData, 0, 0);
baseTexture.update();
}
}
} catch (e) {
console.error('帧处理失败:', e);
}
});

// 配置播放选项
const startOption = {
source: finalConfig.source,
mode: 1,
}

if (!!GameGlobal.isIOSHighPerformanceModePlus) {
startOption.videoDataType = 2;
}

console.log('开始播放视频,配置:', startOption);

// 开始播放
videoDecoder.start(startOption).catch(error => {
console.error('视频解码器启动失败:', error);
show.Modal('视频解码器启动失败', '错误');
});

// 监听小游戏前后台切换
wx.onHide(() => {
if (videoDecoder) {
videoDecoder.wait(true);
isPaused = true;
}
});

wx.onShow(() => {
if (videoDecoder && isPaused) {
videoDecoder.wait(false);
isPaused = false;
}
});

} catch (e) {
console.error('初始化失败,详细错误:', e);
console.error('错误堆栈:', e.stack);
show.Modal(`初始化失败: ${e.message}`, '错误');
throw e;
}
};

// 销毁渲染过的数据
const closeFrame = () => {
if (frameData) {
frameData.close?.();
}
frameData = null;
};

return view(PIXI, app, obj, res => {
const { status, data = {} } = res;
switch (status) {
case 'createVideoDecoder':
try {
console.log('收到创建视频解码器请求:', data);
initVideoDecoder(data);
} catch (e) {
console.error('视频解码器创建失败:', e);
}
break;
case 'destroy':
console.log('开始清理资源');
if (videoDecoder) {
videoDecoder.stop();
videoDecoder = null;
}
closeFrame();
if (videoSprite) {
app.stage.removeChild(videoSprite);
if (texture) {
texture.destroy(true);
}
if (baseTexture) {
baseTexture.destroy();
}
videoSprite.destroy();
videoSprite = null;
texture = null;
baseTexture = null;
renderer = null;
}
console.log('资源清理完成');
break;
}
});
};
85 changes: 85 additions & 0 deletions miniprogram/js/api/media/videoDecoder/render.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
const isIOSHighPerformanceModePlus = !!GameGlobal.isIOSHighPerformanceModePlus;
var vs = "\n attribute vec3 aPos;\n attribute vec2 aVertexTextureCoord;\n varying highp vec2 vTextureCoord;\n\n void main(void){\n gl_Position = vec4(aPos, 1);\n vTextureCoord = aVertexTextureCoord;\n }\n";
var fs = "\n varying highp vec2 vTextureCoord;\n uniform sampler2D uSampler;\n\n void main(void) {\n gl_FragColor = texture2D(uSampler, vTextureCoord);\n }\n";
var buffers = {};
var vertex = [
-1, -1, 0.0,
1, -1, 0.0,
1, 1, 0.0,
-1, 1, 0.0,
];
var vertexIndice = [
0, 1, 2,
0, 2, 3,
];
var texCoords = [
0.0, 0.0,
1.0, 0.0,
1.0, 1.0,
0.0, 1.0,
];
function createShader(gl, src, type) {
var 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;
}
export default function createRenderer(canvas) {
var gl = canvas.getContext('webgl');
if (!gl) {
console.error('Unable to get webgl context.');
return;
}
gl.viewport(0, 0, gl.drawingBufferWidth, gl.drawingBufferHeight);
var vertexShader = createShader(gl, vs, gl.VERTEX_SHADER);
var fragmentShader = createShader(gl, fs, gl.FRAGMENT_SHADER);
var 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);
var 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);
var 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);
var vertexTexCoordAttribute = gl.getAttribLocation(program, 'aVertexTextureCoord');
gl.enableVertexAttribArray(vertexTexCoordAttribute);
gl.vertexAttribPointer(vertexTexCoordAttribute, 2, gl.FLOAT, false, 0, 0);
var samplerUniform = gl.getUniformLocation(program, 'uSampler');
gl.uniform1i(samplerUniform, 0);
return function (arrayBuffer, width, height) {
gl.bindTexture(gl.TEXTURE_2D, texture);
if (isIOSHighPerformanceModePlus) {
// 高性能+只能使用6参数版本
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, arrayBuffer);
} else {
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, width, height, 0, gl.RGBA, gl.UNSIGNED_BYTE, new Uint8ClampedArray(arrayBuffer));
}
gl.drawElements(gl.TRIANGLES, 6, gl.UNSIGNED_SHORT, 0);
};
}

24 changes: 24 additions & 0 deletions miniprogram/js/api/media/videoDecoder/view.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import fixedTemplate from '../../../libs/template/fixed';

// 主视图组件(保持原有的默认导出)
module.exports = function(PIXI, app, obj, callBack) {
let container = new PIXI.Container(),
{ goBack, title, api_name, underline, logo, logoName } = fixedTemplate(PIXI, {
obj,
title: '视频解码器',
api_name: 'videoDecoder'
});

// 创建视频 开始
callBack({
status: 'createVideoDecoder',
});
// 创建视频 结束

goBack.callBack = callBack.bind(null, { status: 'destroy' });
container.addChild(goBack, title, api_name, underline, logo, logoName);

app.stage.addChild(container);

return container;
};
5 changes: 5 additions & 0 deletions miniprogram/js/api/router.js
Original file line number Diff line number Diff line change
Expand Up @@ -287,6 +287,11 @@ const signIn = [
name: 'camera',
path: 'media/camera/index',
},
{
label: '视频解码器',
name: 'videoDecoder',
path: 'media/videoDecoder/index',
}
],
},
{
Expand Down