tav-media
Version:
Cross platform media editing framework
262 lines (261 loc) • 8.85 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());
});
};
import { MediaReader } from './wasm/media-reader';
import { TAVAudioReader } from './engine/tav-audio-reader';
import { TAVSurface } from './engine/tav-surface';
import { TAVVideoReader } from './engine/tav-video-reader';
import { EventManager } from './utils/event-manager';
/**
* TAVMediaView is a view that can play a TAVMedia Clip.
* @category Engine
*/
export class TAVMediaView {
constructor() {
/**
* Subscribes events.
*/
this.events = new EventManager();
/**
* Set scale mode to surface.
*/
this.scaleMode = 2 /* ScaleMode.LetterBox */;
this._frameRate = 30;
this.lastFrame = -1;
this.isPlaying = false;
this.progress = -1;
this.renderTask = -1;
this.offset = 0;
}
/**
* Make a TAVMediaView from a canvas and MediaClip.
* @param canvasOrSelector HTMLCanvasElement or canvas selector
* @param media The root media clip to play
* @returns A TAVMediaView instance
*/
static MakeFromHtmlCanvas(canvasOrSelector, media) {
const surface = TAVSurface.FromHtmlCanvas(canvasOrSelector);
const view = new TAVMediaView();
view.setSurface(surface);
if (media) {
view.setMedia(media);
}
return view;
}
/**
* Get the duration of the media.
*/
get duration() {
return this.root.duration;
}
/**
* Get the frame rate.
*/
get frameRate() {
return this._frameRate;
}
/**
* Set the frame rate to render.
* @param frameRate Frame rate
* @returns void
*/
setFrameRate(frameRate = 30) {
if (frameRate === this._frameRate)
return;
this._frameRate = frameRate;
this.setMedia(this.root);
}
/**
* Get the TAVSurface in use.
* @returns The surface.
*/
getSurface() {
return this.surface;
}
/**
* Get current TAVVideoReader.
* @returns Current TAVVideoReader
*/
getVideoReader() {
return this.videoReader;
}
/**
* Set a new surface to render to.
* @param surface The new surface
* @returns void
*/
setSurface(surface) {
return __awaiter(this, void 0, void 0, function* () {
if (surface === this.surface) {
return;
}
if (this.surface) {
this.surface.freeCache();
this.surface.release();
}
this.surface = surface;
if (this.root) {
yield this.setMedia(this.root);
}
});
}
/**
* Set the media clip to play.
* @param media The media clip to play
* @returns void
*/
setMedia(media) {
return __awaiter(this, void 0, void 0, function* () {
if (!media)
return;
if (!this.surface)
throw 'Please set surface first';
const nativeRoot = yield media.build();
if (this.lastNativeRoot === nativeRoot) {
return;
}
this.root = media;
this.lastNativeRoot = nativeRoot;
const playing = this.isPlaying;
yield this.pause();
if (this.videoReader) {
this.videoReader.release();
this.videoReader = null;
}
if (this.audioReader) {
this.audioReader.release();
this.audioReader = null;
}
const videoReader = yield TAVVideoReader.MakeFrom(media, this.frameRate, false);
// 当只有audioClip时,videoReader = null
if (videoReader) {
yield videoReader.setScaleMode(this.scaleMode);
yield videoReader.setSurface(this.surface);
this.videoReader = videoReader;
}
this.audioReader = yield TAVAudioReader.MakeFrom(media, 44100, 1024, 2, false);
if (playing) {
yield this.play();
}
});
}
/**
* Continue to play.
*/
play() {
return __awaiter(this, void 0, void 0, function* () {
if (this.isPlaying)
return;
if (this.progress === -1) {
this.progress = 0;
}
this.resetProgress(this.progress);
this.isPlaying = true;
yield this.resumeMediaReaders();
this.requestRender();
});
}
/**
* Pause the playback.
* @returns void
*/
pause() {
return __awaiter(this, void 0, void 0, function* () {
if (!this.isPlaying)
return;
this.isPlaying = false;
yield this.pauseMediaReaders();
yield this.requestRender(true);
});
}
/**
* Set the progress of the playback.
* @param progress Current progress, in microsecond, 1s = 1000000us
*/
seek(progress) {
return __awaiter(this, void 0, void 0, function* () {
this.resetProgress(progress);
yield this.requestRender(true);
});
}
/**
* Get the current progress of the playback.
* @returns The current progress, in microsecond, 1s = 1000000us
*/
getProgress() {
return this.progress;
}
render(force = false) {
var _a, _b, _c, _d, _e, _f;
return __awaiter(this, void 0, void 0, function* () {
if (!this.videoReader) {
return;
}
if (!this.isPlaying && !force) {
return;
}
this.progress = (Date.now() - this.startTime) * 1000;
if (this.progress > ((_a = this.root) === null || _a === void 0 ? void 0 : _a.duration)) {
this.resetProgress(0);
}
const invalidated = (_b = this.root) === null || _b === void 0 ? void 0 : _b.isNativeInvalidated;
if (invalidated) {
yield this.setMedia(this.root);
}
// Keep audio rendering
yield ((_c = this.audioReader) === null || _c === void 0 ? void 0 : _c.seekTo(this.progress));
if (!force)
yield ((_d = this.audioReader) === null || _d === void 0 ? void 0 : _d.readNextFrame());
// Skip video rendering if browser frame rate is much higher than us.
{
let frame = this.progress / (1000000 / this.frameRate);
frame = Math.floor(frame);
if (force || this.lastFrame !== frame) {
const offset = force ? this.getOffset() : 0;
yield ((_e = this.videoReader) === null || _e === void 0 ? void 0 : _e.seekTo(this.progress + offset));
yield ((_f = this.videoReader) === null || _f === void 0 ? void 0 : _f.readNextFrame());
}
this.lastFrame = frame;
}
this.events.emit('progress', this.progress);
// 这里等渲染完成再注册回掉,防止前一帧没有渲染结束,重入 render
this.requestRender();
});
}
requestRender(force = false) {
if (this.renderTask !== -1) {
cancelAnimationFrame(this.renderTask);
}
this.renderTask = requestAnimationFrame(() => {
this.renderTask = -1;
this.render(force);
});
}
resetProgress(progress) {
this.startTime = Date.now() - progress / 1000;
this.progress = progress;
}
pauseMediaReaders() {
return __awaiter(this, void 0, void 0, function* () {
yield Promise.all(MediaReader.readers.map(element => element.pause()));
});
}
resumeMediaReaders() {
return __awaiter(this, void 0, void 0, function* () {
yield Promise.all(MediaReader.readers.map(element => element.continue()));
});
}
getOffset() {
this.offset += 1;
if (this.offset > 5) {
this.offset = 0;
}
return this.offset;
}
}