From 271a19c05dd686f4fc0852d35cf4ad5c6c62d1b3 Mon Sep 17 00:00:00 2001 From: Karishma Chadha <kchadha@scratch.mit.edu> Date: Thu, 26 Apr 2018 19:17:41 -0400 Subject: [PATCH] Add runtime/render related functionality back to VM. --- src/io/video.js | 137 ++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 110 insertions(+), 27 deletions(-) diff --git a/src/io/video.js b/src/io/video.js index 70b753908..e65edbe65 100644 --- a/src/io/video.js +++ b/src/io/video.js @@ -1,7 +1,42 @@ class Video { constructor (runtime) { this.runtime = runtime; + + /** + * @typedef VideoProvider + * @property {Function} enableVideo - Requests camera access from the user, and upon success, + * enables the video feed + * @property {Function} disableVideo - Turns off the video feed + * @property {Function} getFrame - Return frame data from the video feed in + * specified dimensions, format, and mirroring. + */ this.provider = null; + + /** + * Id representing a Scratch Renderer skin the video is rendered to for + * previewing. + * @type {number} + */ + this._skinId = -1; + + /** + * The Scratch Renderer Skin object. + * @type {Skin} + */ + this._skin = null; + + /** + * Id for a drawable using the video's skin that will render as a video + * preview. + * @type {Drawable} + */ + this._drawable = -1; + + /** + * Store the last state of the video transparency ghost effect + * @type {number} + */ + this._ghost = 0; } static get FORMAT_IMAGE_DATA () { @@ -29,6 +64,14 @@ class Video { return 1; } + /** + * Set a video provider for this device. + * @param {VideoProvider} provider - Video provider to use + */ + setProvider (provider) { + this.provider = provider; + } + /** * Request video be enabled. Sets up video, creates video skin and enables preview. * @@ -37,8 +80,8 @@ class Video { * @return {Promise.<Video>} resolves a promise to this IO device when video is ready. */ enableVideo () { - if (this.provider) return this.provider.enableVideo(); - return null; + if (!this.provider) return null; + return this.provider.enableVideo().then(() => this._setupPreview()); } /** @@ -46,8 +89,9 @@ class Video { * @return {void} */ disableVideo () { - if (this.provider) return this.provider.disableVideo(); - return null; + if (!this.provider) return null; + this._disablePreview(); + this.provider.disableVideo(); } /** @@ -79,36 +123,75 @@ class Video { /** * Set the preview ghost effect * @param {number} ghost from 0 (visible) to 100 (invisible) - ghost effect - * @return {void} */ setPreviewGhost (ghost) { - if (this.provider) return this.provider.setPreviewGhost(ghost); - return null; + this._ghost = ghost; + if (this._drawable) { + this.runtime.renderer.updateDrawableProperties(this._drawable, {ghost}); + } + } + + _disablePreview () { + if (this._skin) { + this._skin.clear(); + this.runtime.renderer.updateDrawableProperties(this._drawable, {visible: false}); + } + this._renderPreviewFrame = null; + } + + _setupPreview () { + const {renderer} = this.runtime; + if (!renderer) return; + + if (this._skinId === -1 && this._skin === null && this._drawable === -1) { + this._skinId = renderer.createPenSkin(); + this._skin = renderer._allSkins[this._skinId]; + this._drawable = renderer.createDrawable(); + renderer.setDrawableOrder( + this._drawable, + Video.ORDER + ); + renderer.updateDrawableProperties(this._drawable, { + skinId: this._skinId + }); + } + + // if we haven't already created and started a preview frame render loop, do so + if (!this._renderPreviewFrame) { + renderer.updateDrawableProperties(this._drawable, { + ghost: this._ghost, + visible: true + }); + + this._renderPreviewFrame = () => { + clearTimeout(this._renderPreviewTimeout); + if (!this._renderPreviewFrame) { + return; + } + + this._renderPreviewTimeout = setTimeout(this._renderPreviewFrame, this.runtime.currentStepTime); + + const canvas = this.getFrame({format: Video.FORMAT_CANVAS}); + + if (!canvas) { + this._skin.clear(); + return; + } + + const xOffset = Video.DIMENSIONS[0] / -2; + const yOffset = Video.DIMENSIONS[1] / 2; + this._skin.drawStamp(canvas, xOffset, yOffset); + this.runtime.requestRedraw(); + }; + + this._renderPreviewFrame(); + } } get videoReady () { - if (this.provider) return this.provider.videoReady(); + if (this.provider) return this.provider.videoReady; return false; } - - /** - * @typedef VideoProvider - * @property {Function} enableVideo - Requests camera access from the user, and upon success, - * enables the video feed - * @property {Function} disableVideo - Turns off the video feed - * @property {Function} setGhostPreview - Controls the transparency of a visual layer - * over the video feed - * @property {Function} getFrame - Return frame data from the video feed in - * specified dimensions, format, and mirroring. - */ - - /** - * Set a video provider for this device. - * @param {VideoProvider} provider - Video provider to use - */ - setProvider (provider) { - this.provider = provider; - } }