tav-media
Version:
Cross platform media editing framework
233 lines (232 loc) • 8.99 kB
JavaScript
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());
});
};
/* eslint-disable no-param-reassign */
import { tav } from '../tav';
import { Http } from '../io/http';
import { setMediaSource, waitForEvent } from './media-element-utils';
const FarLessThanOneFrameTime = 0.01;
export class MediaReader {
constructor(path) {
this.contentTime = 0;
this.textureId = 0;
this.released = false;
this.playbackRate = 1;
this.alwaysPause = false;
this.volume = 1;
this.adjustRange = 0.16;
/**
* Browser, it will be 1 in WeChat reader
*/
this.type = 0;
this.useCount = 0;
this.isPaused = true;
this.startTime = 0;
this.duration = -1;
this.resetTask = -1;
this.onMediaTimeUpdate = () => {
this.checkEndTimeAndPause();
};
this.path = path;
MediaReader.readers.push(this);
MediaReader.preload(path);
this.videoEl = MediaReader.videos[path];
if (this.videoEl.videoHeight) {
this.updateSize();
}
else {
this.videoEl.addEventListener('loadedmetadata', () => this.updateSize());
}
MediaReader.videos[path] = null;
this.reuse();
}
static preload(clipAssetPath) {
if (this.videos[clipAssetPath] && this.videos[clipAssetPath].videoWidth > 0) {
return Promise.resolve(this.videos[clipAssetPath]);
}
if (this.videoPreloadTask[clipAssetPath]) {
return this.videoPreloadTask[clipAssetPath];
}
const videoEl = document.createElement('video');
const src = Http.getUrl(clipAssetPath);
setMediaSource(videoEl, src);
videoEl.preload = 'auto';
videoEl.crossOrigin = 'anonymous';
videoEl.volume = 1;
videoEl.loop = false;
this.videos[clipAssetPath] = videoEl;
const task = new Promise((res, rej) => {
videoEl.onloadedmetadata = _e => {
delete this.videoPreloadTask[clipAssetPath];
res(videoEl);
};
videoEl.onerror = _e => rej(videoEl);
videoEl.load();
});
this.videoPreloadTask[clipAssetPath] = task;
return task;
}
static Make(filePath) {
const existing = MediaReader.readers.find(reader => reader.path === filePath);
if (existing) {
existing.reuse();
return existing;
}
return new MediaReader(filePath);
}
static MakeFromBytes(bytesOffset, length) {
const module = tav;
const blob = new Blob([new Uint8Array(module.HEAPU8.buffer, bytesOffset, length)]);
const url = URL.createObjectURL(blob);
return this.Make(url);
}
decodeAudio() {
return Promise.resolve(this);
}
updateSize() {
this.width = this.videoEl.videoWidth || this.width;
this.height = this.videoEl.videoHeight || this.height;
}
renderToTexture(GL, textureId) {
const gl = GL.currentContext.GLctx;
if (this.videoEl.readyState >= 1) {
gl.bindTexture(gl.TEXTURE_2D, GL.textures[textureId]);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, this.videoEl);
}
}
setOptions(options) {
if (typeof (options.playbackRate) !== 'undefined') {
this.alwaysPause = options.playbackRate === 0 || (options.playbackRate < 0.01);
const playbackRate = Math.max(0.125, Math.min(options.playbackRate, 4));
this.videoEl.playbackRate = playbackRate;
}
if (typeof (options.startTime) !== 'undefined'
&& (this.duration !== options.duration || this.startTime !== options.startTime)
&& options.duration >= 0
&& options.startTime >= 0) {
const startTime = options.startTime / 1000000;
const duration = options.duration / 1000000;
if (this.duration < 0
|| this.videoEl.currentTime > (startTime + duration)
|| this.videoEl.currentTime < startTime) {
this.videoEl.currentTime = startTime;
}
this.startTime = startTime;
this.duration = duration;
}
if (typeof (options.volume) !== 'undefined') {
this.videoEl.volume = options.volume;
}
}
getNearestKeyFrame(time) {
return Math.ceil(1.0 * time / (0.8 * 1000000));
}
seekToSync(contentTime) {
const targetTime = contentTime / 1000000;
this.contentTime = contentTime;
if (Math.abs(targetTime - this.videoEl.currentTime) < this.adjustRange) {
return;
}
this.videoEl.currentTime = targetTime;
}
pause() {
this.isPaused = true;
this.videoEl.pause();
this.reset();
return Promise.resolve(true);
}
continue() {
this.isPaused = false;
}
seekTo(contentTime, forceSeek = false) {
return __awaiter(this, void 0, void 0, function* () {
if (contentTime === this.contentTime && !forceSeek) {
return Promise.resolve(true);
}
if (this.resetTask !== -1) {
clearTimeout(this.resetTask);
this.resetTask = -1;
}
const targetTime = contentTime / 1000000;
this.contentTime = contentTime;
// Get the nearest key frame to the target time when paused
if ((this.isPaused && Math.abs(this.videoEl.currentTime - targetTime) > FarLessThanOneFrameTime)
|| forceSeek
|| Math.abs(this.videoEl.currentTime - targetTime) > 2
|| (this.videoEl.currentTime === 0 && targetTime !== 0)
|| this.videoEl.readyState < this.videoEl.HAVE_FUTURE_DATA) {
this.videoEl.currentTime = targetTime;
yield waitForEvent(this.videoEl, 'seeked');
}
// Playing
if (!(this.isPaused || this.alwaysPause)) {
if (this.videoEl.paused) {
this.videoEl.currentTime = targetTime;
this.videoEl.play().catch(() => { });
}
if (this.videoEl.readyState < this.videoEl.HAVE_FUTURE_DATA) {
yield waitForEvent(this.videoEl, 'playing');
}
// Keep one frame from the target time
if (this.videoEl.currentTime < (targetTime - 1 / 30 * 1.5)) {
this.videoEl.playbackRate = this.playbackRate * 2;
}
else if (this.videoEl.currentTime > (targetTime - 1 / 30 * 0.5)) {
this.videoEl.playbackRate = this.playbackRate * 0.5;
}
else {
this.videoEl.playbackRate = this.playbackRate;
}
if (Math.abs(targetTime - (this.startTime + this.duration)) < 0.05) {
this.videoEl.pause();
}
}
return true;
});
}
reset() {
this.resetTask = window.setTimeout(() => {
this.videoEl.pause();
this.videoEl.currentTime = this.startTime;
}, 60);
}
release() {
this.useCount -= 1;
if (this.useCount > 0) {
return;
}
MediaReader.readers.splice(MediaReader.readers.indexOf(this), 1);
this.released = true;
this.isPaused = true;
this.duration = -1;
this.videoEl.pause();
this.videoEl.removeEventListener('timeupdate', this.onMediaTimeUpdate);
if (!MediaReader.videos[this.path]) {
MediaReader.videos[this.path] = this.videoEl;
}
else {
this.videoEl.remove();
}
}
reuse() {
this.useCount += 1;
this.released = false;
this.videoEl.volume = 1;
this.videoEl.playbackRate = this.playbackRate;
this.videoEl.addEventListener('timeupdate', this.onMediaTimeUpdate);
}
checkEndTimeAndPause() {
if (this.videoEl.currentTime > this.startTime + this.duration) {
this.videoEl.pause();
}
}
}
MediaReader.videos = {};
MediaReader.videoPreloadTask = {};
MediaReader.readers = [];