UNPKG

tav-media

Version:

Cross platform media editing framework

262 lines (261 loc) 8.85 kB
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; } }