tav-media
Version:
Cross platform media editing framework
158 lines (157 loc) • 6.67 kB
JavaScript
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
import { destroyVerify } from '../utils/decorators';
import { MP4_CACHE_PATH } from './constant';
import { touchDirectory, writeFile } from './file-utils';
const BUFFER_MAX_SIZE = 6;
const BUFFER_MIN_SIZE = 2;
const GET_FRAME_DATA_INTERVAL = 2; // ms
const frameDataOptions2FrameData = (id, options) => {
const data = new ArrayBuffer(options.data.byteLength);
new Uint8Array(data).set(new Uint8Array(options.data));
return {
id: id,
data: data,
width: options.width,
height: options.height,
};
};
let VideoReader = class VideoReader {
constructor(mp4Data, width, height, frameRate, staticTimeRanges) {
this.frameData = null;
this.frameDataBuffers = [];
this.bufferIndex = 0; // next frameData id
this.getFrameDataLooping = false;
this.getFrameDataResolve = null;
this.getFrameDataLoopTimer = null;
this.seeking = false;
this.frameRate = frameRate;
this.currentFrame = -1;
this.mp4Path = `${MP4_CACHE_PATH}${new Date().getTime()}.mp4`;
touchDirectory(MP4_CACHE_PATH);
writeFile(this.mp4Path, mp4Data.buffer.slice(mp4Data.byteOffset, mp4Data.byteLength + mp4Data.byteOffset));
this.videoDecoder = wx.createVideoDecoder();
this.videoDecoder.on('ended', () => {
var _a;
(_a = this.videoDecoder) === null || _a === void 0 ? void 0 : _a.seek(0).then(() => {
this.bufferIndex = 0;
});
});
this.videoDecoderPromise = this.videoDecoder.start({ source: this.mp4Path, mode: 1 }).then(() => {
this.startGetFrameDataLoop();
});
}
prepare(targetFrame) {
return __awaiter(this, void 0, void 0, function* () {
if (targetFrame === this.currentFrame) {
return true;
}
// Wait for videoDecoder ready
yield this.videoDecoderPromise;
if (this.frameDataBuffers.length > 0) {
const index = this.frameDataBuffers.findIndex((frameData) => frameData.id === targetFrame);
if (index !== -1) {
this.frameDataBuffers = this.frameDataBuffers.slice(index);
this.frameData = yield this.getFrameData();
this.currentFrame = targetFrame;
return true;
}
this.frameDataBuffers = [];
}
if (targetFrame !== this.bufferIndex) {
this.seeking = true;
yield this.videoDecoder.seek(Math.floor((targetFrame / this.frameRate) * 1000));
this.seeking = false;
}
this.frameData = yield this.getFrameData();
this.currentFrame = targetFrame;
return true;
});
}
renderToTexture(GL, textureID) {
const gl = GL.currentContext.GLctx;
gl.bindTexture(gl.TEXTURE_2D, GL.textures[textureID]);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, this.frameData.width, this.frameData.height, 0, gl.RGBA, gl.UNSIGNED_BYTE, new Uint8Array(this.frameData.data));
}
onDestroy() {
this.videoDecoder.remove();
}
getFrameData() {
return new Promise((resolve) => {
if (this.frameDataBuffers.length <= BUFFER_MIN_SIZE && !this.getFrameDataLooping) {
this.startGetFrameDataLoop();
}
if (this.frameDataBuffers.length === 0) {
this.getFrameDataResolve = resolve;
return;
}
const res = this.frameDataBuffers.shift();
if (!res) {
this.getFrameDataResolve = resolve;
return;
}
resolve(res);
});
}
startGetFrameDataLoop() {
this.getFrameDataLooping = true;
this.getFrameDataLoopTimer = setInterval(() => {
this.getFrameDataLoop();
}, GET_FRAME_DATA_INTERVAL);
}
getFrameDataLoop() {
if (this.seeking)
return;
if (!this.videoDecoder) {
this.clearFrameDataLoop();
throw new Error('VideoDecoder is not ready!');
}
if (this.frameDataBuffers.length >= BUFFER_MAX_SIZE) {
this.getFrameDataLooping = false;
this.clearFrameDataLoop();
return;
}
const frameDataOptions = this.videoDecoder.getFrameData();
if (frameDataOptions !== null) {
if (this.getFrameDataResolve) {
this.getFrameDataResolve(frameDataOptions2FrameData(this.bufferIndex, frameDataOptions));
this.getFrameDataResolve = null;
}
else {
this.frameDataBuffers.push(frameDataOptions2FrameData(this.bufferIndex, frameDataOptions));
}
this.bufferIndex += 1;
}
}
clearFrameDataLoop() {
if (this.getFrameDataLoopTimer) {
clearInterval(this.getFrameDataLoopTimer);
this.getFrameDataLoopTimer = null;
}
this.getFrameDataLooping = false;
}
};
VideoReader.isIOS = () => {
// need't to check platform on wechat miniprogram
return false;
};
VideoReader.isAndroidMiniprogram = () => {
return wx.getSystemInfoSync().platform === 'android';
};
VideoReader = __decorate([
destroyVerify
], VideoReader);
export { VideoReader };