UNPKG

lazy-widgets

Version:

Typescript retained mode GUI for the HTML canvas API

240 lines 10 kB
import { Msg } from '../core/Strings.js'; import { BackingMediaEventType } from './BackingMediaEventType.js'; import { getBackingMediaSourceType } from './BackingMediaSource.js'; import { BackingMediaSourceType } from './BackingMediaSourceType.js'; import { incrementUint31 } from './incrementUint31.js'; import { Notifier } from './Notifier.js'; // TODO move to a unified media source system, instead of using helper wrappers. // in the future, media won't even be in the main thread, so we can't use // BackingMediaSource directly in widgets export class BackingMediaWrapper extends Notifier { constructor(source) { super(); this.source = source; this.rvf = 0; this._presentationHash = 0; this.videoLoaded = false; this.dispatchResized = () => { this.dispatchEvent(BackingMediaEventType.Resized); }; this.dispatchDirty = () => { this._presentationHash = incrementUint31(this._presentationHash); this.dispatchEvent(BackingMediaEventType.Dirty); }; this.dispatchLoaded = () => { this.dispatchEvent(BackingMediaEventType.Loaded); }; this.onImageLoaded = () => { this.dispatchResized(); this.dispatchLoaded(); this.dispatchDirty(); }; this.onReadyStateChanged = () => { const video = this.source; if (this.videoLoaded) { if (video.readyState < 2) { this.videoLoaded = false; } } else if (video.readyState >= 2) { this.videoLoaded = true; this.dispatchLoaded(); } }; this.dispatchDirtyAndRVF = () => { this.dispatchDirty(); this.rvf = this.source.requestVideoFrameCallback(this.dispatchDirtyAndRVF); }; this.redirectEvents = (event) => { this.dispatchEvent(event); }; this.sourceType = getBackingMediaSourceType(source); } addSourceListeners() { switch (this.sourceType) { case BackingMediaSourceType.HTMLImageElement: // XXX need to assume that the image changed, because the source of // the image is not guaranteed to remain the same this._presentationHash = incrementUint31(this._presentationHash); // falls through case BackingMediaSourceType.SVGImageElement: this.source.addEventListener('load', this.onImageLoaded); break; case BackingMediaSourceType.HTMLVideoElement: { const video = this.source; video.addEventListener('resize', this.dispatchResized); video.addEventListener('loadeddata', this.onReadyStateChanged); video.addEventListener('loadedmetadata', this.onReadyStateChanged); video.addEventListener('canplay', this.onReadyStateChanged); video.addEventListener('canplaythrough', this.onReadyStateChanged); video.addEventListener('waiting', this.onReadyStateChanged); this.onReadyStateChanged(); if ('requestVideoFrameCallback' in video) { console.warn(Msg.VIDEO_API_AVAILABLE); this.rvf = video.requestVideoFrameCallback(this.dispatchDirtyAndRVF); } else { video.addEventListener('timeupdate', this.dispatchDirty); } } break; case BackingMediaSourceType.AsyncMedia: this.source.addEventListener(this.redirectEvents); break; } } removeSourceListeners() { switch (this.sourceType) { case BackingMediaSourceType.HTMLImageElement: case BackingMediaSourceType.SVGImageElement: this.source.removeEventListener('load', this.onImageLoaded); break; case BackingMediaSourceType.HTMLVideoElement: { const video = this.source; video.removeEventListener('resize', this.dispatchResized); video.removeEventListener('loadeddata', this.onReadyStateChanged); video.removeEventListener('loadedmetadata', this.onReadyStateChanged); video.removeEventListener('canplay', this.onReadyStateChanged); video.removeEventListener('canplaythrough', this.onReadyStateChanged); video.removeEventListener('waiting', this.onReadyStateChanged); if ('requestVideoFrameCallback' in video) { video.cancelVideoFrameCallback(this.rvf); } else { video.removeEventListener('timeupdate', this.dispatchDirty); } } break; case BackingMediaSourceType.AsyncMedia: this.source.removeEventListener(this.redirectEvents); break; } } addEventListener(listener) { super.addEventListener(listener); if (this.listeners.size == 1) { this.addSourceListeners(); } } removeEventListener(listener) { const removed = super.removeEventListener(listener); if (removed && this.listeners.size == 0) { this.removeSourceListeners(); } return removed; } get width() { switch (this.sourceType) { case BackingMediaSourceType.HTMLImageElement: { const source = this.source; let width = source.naturalWidth; // HACK firefox has a naturalWidth of 0 for some SVGs. note // that images will likely have a bad aspect ratio if (width === 0 && source.complete) { width = 150; } return width; } case BackingMediaSourceType.HTMLVideoElement: return this.source.videoWidth; case BackingMediaSourceType.SVGImageElement: { const baseVal = this.source.width.baseVal; if (baseVal.unitType === SVGLength.SVG_LENGTHTYPE_PX) { return baseVal.value; } else { baseVal.convertToSpecifiedUnits(SVGLength.SVG_LENGTHTYPE_PX); return baseVal.valueInSpecifiedUnits; } } case BackingMediaSourceType.VideoFrame: return this.source.codedWidth; default: return this.source.width; } } get height() { switch (this.sourceType) { case BackingMediaSourceType.HTMLImageElement: { const source = this.source; let height = source.naturalHeight; // HACK firefox has a naturalHeight of 0 for some SVGs. note // that images will likely have a bad aspect ratio if (height === 0 && source.complete) { height = 150; } return height; } case BackingMediaSourceType.HTMLVideoElement: return this.source.videoHeight; case BackingMediaSourceType.SVGImageElement: { const baseVal = this.source.height.baseVal; if (baseVal.unitType === SVGLength.SVG_LENGTHTYPE_PX) { return baseVal.value; } else { baseVal.convertToSpecifiedUnits(SVGLength.SVG_LENGTHTYPE_PX); return baseVal.valueInSpecifiedUnits; } } case BackingMediaSourceType.VideoFrame: return this.source.codedHeight; default: return this.source.height; } } get loaded() { switch (this.sourceType) { case BackingMediaSourceType.HTMLImageElement: return this.source.complete; case BackingMediaSourceType.HTMLVideoElement: return this.source.readyState >= 2; case BackingMediaSourceType.SVGImageElement: { const baseVal = this.source.height.baseVal; if (baseVal.unitType === SVGLength.SVG_LENGTHTYPE_PX) { return baseVal.value; } else { baseVal.convertToSpecifiedUnits(SVGLength.SVG_LENGTHTYPE_PX); return baseVal.valueInSpecifiedUnits; } } case BackingMediaSourceType.AsyncMedia: return !!this.source.currentFrame; default: // assume anything else is already loaded, including SVGElement, // because not all browsers implement the load event properly return true; } } get canvasImageSource() { if (this.sourceType === BackingMediaSourceType.AsyncMedia) { return this.source.currentFrame; } else { return this.source; } } get fastCanvasImageSource() { switch (this.sourceType) { case BackingMediaSourceType.AsyncMedia: return this.source.currentFrame; case BackingMediaSourceType.VideoFrame: case BackingMediaSourceType.ImageBitmap: return this.source; default: return null; } } get presentationHash() { if (this.sourceType === BackingMediaSourceType.AsyncMedia) { return this.source.presentationHash; } else { return this._presentationHash; } } } //# sourceMappingURL=BackingMediaWrapper.js.map