123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065 |
- /*
- The MIT License (MIT)
- Copyright (c) 2014-2020 Nikolai Suslov and the Krestianstvo.org project contributors. (https://github.com/NikolaySuslov/livecodingspace/blob/master/LICENSE.md)
- This driver includes the port and some code parts from the "Croquet synced video demo" for implementing video elements syncing within LiveCoding.space applications and LCS Reflector / Luminary.
- Croquet synced video demo License
- Copyright 2020 Croquet Corporation
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
- http://www.apache.org/licenses/LICENSE-2.0
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
- */
- // TWO JS view driver
- import { Fabric } from '/core/vwf/fabric.js';
- class TwoView extends Fabric {
- constructor(module) {
- console.log("TwoView constructor");
- super(module, 'View');
- }
- factory() {
- let _self_ = this;
- return this.load(this.module,
- {
- // == Module Definition ====================================================================
- initialize: function (options) {
- let self = this;
- this.fabric = _self_;
- this.nodes = {};
- this.overChilds = [];
- this.state.appInitialized = false;
- if (options === undefined) { options = {}; }
- if (typeof options == "object") {
- this.rootSelector = options["application-root"];
- }
- else {
- this.rootSelector = options;
- }
- this.lastStatusCheck = vwf.time() * 1000 + 500;
- this.clicked = false;
- this.isIOS = [
- 'iPad Simulator',
- 'iPhone Simulator',
- 'iPod Simulator',
- 'iPad',
- 'iPhone',
- 'iPod'
- ].includes(navigator.platform)
- // iPad on iOS 13 detection
- || (navigator.userAgent.includes("Mac") && "ontouchend" in document);
- function videostepping() {
- let twodriver = vwf.views["/drivers/view/two"];
- if (!twodriver.clicked)
- twodriver.clicked = true;
- let videos = Object.values(twodriver.state.nodes).filter(el => el.fillType == "video");
- videos.forEach(el => {
- let viewNode = twodriver.nodes[el.ID];
- if (viewNode.isStepping) {
- console.log(`exiting step mode`);
- el.muted = false;
- viewNode.isStepping = false;
- _self_.applyPlayState(el.ID);
- //document.body.removeEventListener("click", videostepping, false);
- //document.body.removeEventListener("touchstart", videostepping, false);
- //return;
- }
- })
- }
- document.body.addEventListener("click", videostepping, false);
- document.body.addEventListener("touchstart", videostepping, false);
- },
- createdNode: function (nodeID, childID, childExtendsID, childImplementsIDs,
- childSource, childType, childIndex, childName, callback /* ( ready ) */) {
- let self = this;
- var node = this.state.nodes[childID];
- // If the "nodes" object does not have this object in it, it must not be one that
- // this driver cares about
- if (!node) {
- return;
- }
- if (this.state.scenes[childID]) {
- let scene = this.state.scenes[childID];
- let space = scene.obj;
- _self_.resizeScene(childID);
- //
- space.bind('update', function (frameCount) {
- // This code is called everytime two.update() is called.
- // Effectively 60 times per second.
- _self_.update(frameCount);
- }).play(); // Finally, start the animation loop
- //TODO: FIX
- let avatarName = 'avatar-' + self.kernel.moniker();
- console.log("creating avatar...");
- var newNode = {
- "id": avatarName,
- "uri": avatarName,
- "extends": "proxy/two/player.vwf",
- "properties": {}
- }
- if (!self.state.nodes[avatarName]) {
- vwf_view.kernel.createChild(childID, avatarName, newNode);
- vwf_view.kernel.callMethod(avatarName, "createPlayerBody", []);
- }
- }
- if (this.state.nodes[childID] && this.state.nodes[childID].obj) {
- this.nodes[childID] = {
- id: childID,
- extends: childExtendsID,
- liveBindings: {}
- // lastTransformStep: 0,
- // lastAnimationStep: 0
- };
- if (this.nodes[childID].extends == "proxy/two/scene.vwf") {
- this.nodes[childID].mouse = new Two.Vector();
- window.addEventListener('mousemove', function (e) {
- e.preventDefault();
- let scene = self.state.nodes[childID].obj.renderer.scene;
- let x = e.offsetX / scene.scale;
- let y = e.offsetY / scene.scale;
- self.nodes[childID].mouse.set(x, y);
- //vwf_view.kernel.callMethod(el.nodeID, "mousedownEvent", []);
- });
- window.addEventListener('touchstart', function (e) {
- //e.preventDefault();
- let touch = e.changedTouches[0];
- let scene = self.state.nodes[childID].obj.renderer.scene;
- const { x, y, width, height } = e.target.getBoundingClientRect();
- self.nodes[childID].mouse.set(touch.pageX / scene.scale, touch.pageY / scene.scale);
- _self_.updateAvatarPosition();
- _self_.mouseDown(node, touch.pageX, touch.pageY);
- }, { passive: false });
- window.addEventListener('touchend', function (e) {
- //e.preventDefault();
- let touch = e.changedTouches[0];
- //let scene = self.state.nodes[childID].obj.renderer.scene;
- let x = touch.pageX;
- let y = touch.pageY;
- // _self_.updateAvatarPosition();
- _self_.mouseUp(node, x, y);
- //return false;
- }, { passive: false });
- window.addEventListener('touchmove', function (e) {
- e.preventDefault();
- let touch = e.changedTouches[0];
- let scene = self.state.nodes[childID].obj.renderer.scene;
- let x = touch.pageX / scene.scale;
- let y = touch.pageY / scene.scale;
- self.nodes[childID].mouse.set(x, y);
- }, { passive: false });
- //resize event
- window.addEventListener("resize", function (event) {
- _self_.resizeScene(childID);
- });
- //node.scene.obj.update();
- node.obj.renderer.domElement.addEventListener('mousedown', function (e) {
- var x = e.clientX;
- var y = e.clientY;
- //let nodes = self.state.nodes;
- _self_.mouseDown(node, x, y);
- }, false);
- node.obj.renderer.domElement.addEventListener('mouseup', function (e) {
- var x = e.clientX;
- var y = e.clientY;
- // let nodes = self.state.nodes;
- _self_.mouseUp(node, x, y);
- }, false);
- }
- // IF RENDERER SVG
- // if(node.prototypes.includes("proxy/two/path.vwf")) {
- // let elm = node.obj;
- // node.scene.obj.update();
- // elm._renderer.elem.addEventListener('click', function() {
- // vwf_view.kernel.callMethod(childID, "svgClickEvent", []);
- // },false);
- // }
- }
- },
- executed: function (nodeID, scriptText, scriptType) {
- let self = this;
- let node = this.state.nodes[nodeID];
- if (!(node)) {
- return;
- }
- },
- initializedNode: function (nodeID, childID) {
- let self = this;
- var node = this.state.nodes[childID];
- if (!node) {
- return;
- }
- },
- createdProperty: function (nodeId, propertyName, propertyValue) {
- return this.satProperty(nodeId, propertyName, propertyValue);
- },
- initializedProperty: function (nodeId, propertyName, propertyValue) {
- return this.satProperty(nodeId, propertyName, propertyValue);
- },
- gotProperty: function (nodeId, propertyName, propertyValue) {
- var node = this.state.nodes[nodeId];
- if (!(node && node.aframeObj)) {
- return;
- }
- },
- satProperty: function (nodeId, propertyName, propertyValue) {
- let self = this;
- var node = this.state.nodes[nodeId];
- const viewNode = this.nodes[nodeId];
- if (!(node && node.obj)) {
- return;
- }
- if (propertyName == "startOffset" || propertyName == "pausedTime" || propertyName == "isPlaying") {
- if (!viewNode.latestPlayState) {
- viewNode.latestPlayState = {
- "startOffset": null,
- "pausedTime": null,
- "isPlaying": false
- }
- }
- viewNode.latestPlayState[propertyName] = propertyValue;
- if (propertyName == "isPlaying") {
- viewNode.isPlaying = propertyValue;
- _self_.applyPlayState(nodeId);
- }
- }
- if (propertyName == 'mask') {
- let mask = Object.values(this.state.nodes).filter(el => (el.name == propertyValue))[0];
- if (mask) {
- node.obj.mask = mask.obj;
- self.kernel.setProperty(mask.ID, "maskedNode", node.name)
- }
- }
- if (propertyName == "bodyNode") {
- let bodyNode = Object.values(this.state.nodes).filter(el => (el.name == propertyValue) && (el["ID"].includes("avatar") !== true))[0];
- if (bodyNode)
- node.bodyNode = bodyNode.ID;
- }
- if (propertyName == "motionData") {
- let url = propertyValue;
- if (url) {
- fetch(url)
- .then(response => response.json())
- .then(data => {
- //console.log(data);
- node.motionData = data;
- });
- }
- }
- if (propertyName == "bodyTrack") {
- viewNode.bodyTrack = propertyValue;
- }
- if (propertyName == "fill") {
- if (node.obj.fill instanceof Two.Texture) {
- if (node.obj.fill.image.nodeName == 'VIDEO') {
- const video = node.obj.fill.image;
- viewNode.playbackBoost = 0;
- //_self_.applyPlayState(nodeId);
- viewNode.lastTimingCheck = vwf.time() * 1000 + 500;
- viewNode.isPlaying = false;
- viewNode.isBlocked = false;
- //video.requestVideoFrameCallback(_self_.animate);
- // video.addEventListener('timeupdate', (event) => {
- // if(viewNode.bodyTrack){
- // // console.log('time: ', video.currentTime);
- // }
- // });
- }
- }
- }
- },
- deletedNode: function (childID) {
- delete this.nodes[childID];
- },
- firedEvent: function (nodeID, eventName, eventParameters) {
- let self = this;
- var node = this.state.nodes[nodeID];
- if (!(node)) {
- return;
- }
- var clientThatSatProperty = self.kernel.client();
- var me = self.kernel.moniker();
- var avatarName = 'avatar-' + self.kernel.moniker();
- // if (eventName == "clickEvent" ||
- // eventName == 'mousedownEvent' ||
- // eventName == 'mouseupEvent') {
- // if (clientThatSatProperty == me) {
- // let methodName = eventName + 'Method';
- // self.kernel.callMethod(nodeID, methodName, eventParameters);
- // if (eventName == "clickEvent") {
- // let mode = vwf.getProperty(avatarName, 'selectMode');
- // if (mode) {
- // console.log("allow to click!!!")
- // vwf_view.kernel.setProperty(avatarName, 'selectMode', false);
- // let editorDriver = vwf.views["/drivers/view/editor"];
- // if (editorDriver) {
- // let selectSwitch = document.querySelector('#selectNodeSwitch');
- // // const selectSwitchComp = new mdc.iconButton.MDCIconButtonToggle(selectSwitch); //new mdc.select.MDCIconToggle
- // selectSwitch._comp.on = false;
- // let currentNodeDIV = document.querySelector('#currentNode');
- // if (currentNodeDIV) currentNodeDIV._setNode(nodeID);
- // }
- // }
- // }
- // }
- // }
- // if (eventName == "clickEvent") {
- // if (self.kernel.moniker() == eventParameters[0]) {
- // let avatar = self.nodes[avatarName];
- // let mode = vwf.getProperty(avatarName, 'selectMode');
- // vwf_view.kernel.callMethod(nodeID, "clickEventMethod", [])
- // }
- // }
- },
- ticked: function (vwfTime) {
- let self = this;
- _self_.updateAvatarPosition();
- // _self_.updateFilters();
- //lerpTick ();
- },
- calledMethod: function (nodeID, methodName, methodParameters, methodValue) {
- let self = this;
- var node = this.state.nodes[nodeID];
- const viewNode = this.nodes[nodeID];
- if (!(node && node.obj)) {
- return;
- }
- // if(methodName == "setMask"){
- // let mask = this.state.nodes[methodParameters[0]];
- // node.obj.mask = mask.obj;
- // }
- if (methodName == "setScale") {
- if (!node.obj.matrix.manual)
- node.obj.matrix.manual = true;
- node.obj.matrix.scale(methodParameters[0], methodParameters[1])
- }
- if (methodName == "unmute") {
- node.obj.fill.image.muted = false;
- }
- if (methodName == "syncVideoState") {
- _self_.applyPlayState(nodeID);
- }
- if (methodName == "setVideoState") {
- if (!viewNode.latestPlayState)
- viewNode.latestPlayState = {}
- // "isPlaying",
- // "startOffset",
- // "pausedTime"
- viewNode.latestPlayState["isPlaying"] = methodParameters[0];
- viewNode.latestPlayState["startOffset"] = methodParameters[1];
- viewNode.latestPlayState["pausedTime"] = methodParameters[2];
- _self_.applyPlayState(nodeID);
- }
- if (methodName == "playVideo") {
- if (node.obj.fill instanceof Two.Texture) {
- if (node.obj.fill.image.nodeName == 'VIDEO') {
- const video = node.obj.fill.image;
- //video.currentTime = _self_.wrappedTime(videoTime, true);
- //video.play();
- if (!viewNode.latestPlayState) {
- viewNode.latestPlayState = {
- "isPlaying": false,
- "startOffset": null,
- "pausedTime": 0,
- }
- }
- const wantsToPlay = !viewNode.latestPlayState.isPlaying; // toggle
- //viewNode.isPlaying = wantsToPlay;
- if (!wantsToPlay) {
- viewNode.isPlaying = false;
- _self_.pause(undefined, video, nodeID);
- } // immediately!
- const videoTime = video.currentTime;
- const sessionTime = vwf.time() * 1000; // the session time corresponding to the video time
- const startOffset = wantsToPlay ? sessionTime - 1000 * videoTime : null;
- const pausedTime = wantsToPlay ? 0 : videoTime;
- vwf_view.kernel.callMethod(nodeID, "setVideoState", [wantsToPlay, startOffset, pausedTime]);
- }
- }
- }
- if (methodName == "viewTroughFilter") {
- var clientThatSatProperty = self.kernel.client();
- var me = self.kernel.moniker();
- //let avatarID = methodParameters[0];
- //&& avatarID.includes(me)
- if (clientThatSatProperty == me) {
- console.log("MY VIEW!!!");
- let maskedNode = self.state.nodes[methodParameters[0]];
- if (maskedNode) {
- //maskedNode.obj.visible = methodParameters[1]
- if (maskedNode.obj.fill.image) {
- if (!self.isIOS) { //TODO: IOS
- if (self.clicked) {
- //maskedNode.obj.fill.image.muted = methodParameters[1];
- if (methodParameters[1]) {
- maskedNode.obj.fill.image.volume = 0
- } else {
- if (maskedNode.obj.fill.image.muted)
- maskedNode.obj.fill.image.muted = false
- maskedNode.obj.fill.image.volume = 1
- }
- }
- }
- }
- }
- }
- }
- if (methodName == "checkOver") {
- var clientThatSatProperty = self.kernel.client();
- var me = self.kernel.moniker();
- // If the transform property was initially updated by this view....
- if (clientThatSatProperty == me) {
- let scene = node.scene.obj.scene;
- let scale = scene.scale;
- var x = methodParameters[0] * scale;
- var y = methodParameters[1] * scale;
- let allChilds = _self_.getOverlayChilds(node.scene.obj.scene, x, y).map(x => {
- return x.nodeID;
- });
- if (JSON.stringify(allChilds) !== JSON.stringify(self.overChilds)) {
- //console.log(allChilds);
- let end = self.overChilds.filter(x => !allChilds.includes(x));
- //console.log("END OVERLAY..", end);
- end.map(x => {
- vwf_view.kernel.callMethod(x, "overendEvent", ['avatar-', me]);
- })
- let start = allChilds.filter(x => !self.overChilds.includes(x));
- //console.log("START OVERLAY..", start);
- start.map(x => {
- vwf_view.kernel.callMethod(x, "overstartEvent", ['avatar-', me]);
- })
- self.overChilds = allChilds;
- }
- //let still = self.overChilds.filter(x => allChilds.includes(x));
- //if (still.length > 0)
- // console.log("STILL OVERLAY..", still)
- }
- }
- }
- });
- }
- ///
- mouseUp(node, x, y) {
- let self = this.instance;
- let avatarID = "avatar-" + vwf.moniker();
- vwf_view.kernel.setProperty(avatarID, "mouseevent", "mouseup");
- let allChilds = this.getOverlayChilds(node.obj.scene, x, y).map(x => {
- return x.nodeID;
- });
- allChilds.forEach(el => {
- vwf_view.kernel.callMethod(el, "mouseupEvent", []);
- vwf_view.kernel.callMethod(el, "checkForDragEnd", [avatarID]);
- })
- }
- mouseDown(node, x, y) {
- let self = this.instance;
- let avatarID = "avatar-" + vwf.moniker();
- vwf_view.kernel.setProperty(avatarID, "mouseevent", "mousedown");
- let allChilds = this.getOverlayChilds(node.obj.scene, x, y).map(x => {
- return x.nodeID;
- });
- allChilds.forEach(el => {
- vwf_view.kernel.callMethod(el, "mousedownEvent", []);
- vwf_view.kernel.callMethod(el, "checkForDragStart", [avatarID])
- })
- }
- resizeScene(childID) {
- let self = this.instance;
- let scene = self.state.nodes[childID].obj;
- let renderer = scene.renderer
- let elem = renderer.domElement;
- let scale = Math.min(
- elem.offsetWidth / 1280,
- elem.offsetHeight / 720
- );
- // var scale = //elem.offsetHeight / 1000;
- renderer.scene.scale = scale;
- renderer.setSize(elem.offsetWidth, elem.offsetHeight);
- }
- ///VIDEO & SOUND SYNC//////
- // doSomethingWithTheFrame = (now, metadata) => {
- // // Do something with the frame.
- // console.log(now, metadata);
- // // Re-register the callback to be notified about the next frame.
- // video.requestVideoFrameCallback(doSomethingWithTheFrame);
- // };
- // // Initially register the callback to be notified about the first frame.
- // video.requestVideoFrameCallback(doSomethingWithTheFrame);
- update(frameCount) {
- let self = this.instance;
- const now = vwf.time() * 1000;
- this.animate(frameCount);
- if (now - self.lastStatusCheck > 100) {
- self.lastStatusCheck = now;
- this.checkPlayStatus();
- }
- }
- applyPlayState(nodeID) {
- let self = this.instance;
- const node = self.state.nodes[nodeID];
- const viewNode = self.nodes[nodeID];
- const video = node.obj.fill.image;
- if (!viewNode.latestPlayState.isPlaying) {
- // this.iconVisible('play', true);
- // this.iconVisible('enableSound', false);
- this.pause(viewNode.latestPlayState.pausedTime, video, nodeID);
- } else {
- video.playbackRate = 1 + viewNode.playbackBoost * 0.01;
- viewNode.lastRateAdjust = vwf.time() * 1000; // make sure we don't adjust rate until playback has settled in, and after any emergency jump we decide to do
- viewNode.jumpIfNeeded = false;
- // if the video is blocked from playing, enter a stepping mode in which we move the video forward with successive pause() calls
- viewNode.isPlaying = true;
- this.play(video, this.calculateVideoTime(nodeID) + 0.1, nodeID).then(playStarted => {
- if (playStarted) {
- setTimeout(function () {
- viewNode.jumpIfNeeded = true;
- }, 250);
- }
- else if (!video.muted) {
- console.log(`trying with mute`);
- video.muted = true;
- this.applyPlayState(nodeID);
- }
- else {
- console.log(`reverting to stepped display`);
- viewNode.isStepping = true;
- this.stepWhileBlocked(nodeID);
- }
- //this.iconVisible('enableSound', !playStarted || videoElem.muted);
- //if (playStarted) this.future(250).triggerJumpCheck(); // leave it a little time to stabilise
- })
- }
- }
- animate(frameCount, metadata) {
- let driver = vwf.views["/drivers/view/two"];
- if (driver) {
- let self = driver.instance;
- let videos = Object.values(self.state.nodes).filter(el => el.fillType == "video");
- videos.forEach(el => {
- let viewNode = self.nodes[el.ID];
- let node = self.state.nodes[el.ID];
- let video = node.obj.fill.image;
- let currentTime = video.currentTime;
- //console.log(video.currentTime);
- if (node.bodyNode) {
- let bodyNode = self.state.nodes[node.bodyNode];
- //if(metadata.presentedFrames % 5 == 0){
- if (viewNode.bodyTrack && bodyNode.motionData) {
- //console.log(currentTime);
- let bodyFrameNumber = Object.keys(bodyNode.motionData).filter(n => (Math.abs(Number.parseFloat(n) - currentTime) < 0.02))[0];
- let bodyFrame = bodyNode.motionData[bodyFrameNumber];
- let mul = 950;
- if (bodyFrame) {
- bodyNode.obj.children.map((e, i) => {
- if (e.nodeID.includes("joint")) {
- e.translation.x = bodyFrame[i].x * mul;
- e.translation.y = bodyFrame[i].y * mul;
- }
- })
- //16-14-12-11-13-15 - topline
- //12-24-23-11 - bottomline
- //18,16,20 - rh // 16,22
- //17,15,19 - lh // 15,21
- //let faceArr = [10,8,6,4,1,3,7,9]
- let ta = [16, 14, 12, 11, 13, 15];
- let topline = bodyNode.obj.children.filter(e => (e.nodeID.includes("topline")))[0];
- if (topline) {
- ta.map((e, i) => {
- topline.vertices[i].set(bodyFrame[e].x * mul, bodyFrame[e].y * mul);
- })
- }
- let rh = [18, 16, 20];
- let rhline = bodyNode.obj.children.filter(e => (e.nodeID.includes("rhand")))[0];
- if (rhline) {
- rh.map((e, i) => {
- rhline.vertices[i].set(bodyFrame[e].x * mul, bodyFrame[e].y * mul);
- })
- }
- let lh = [17, 15, 19];
- let lhline = bodyNode.obj.children.filter(e => (e.nodeID.includes("lhand")))[0];
- if (lhline) {
- lh.map((e, i) => {
- lhline.vertices[i].set(bodyFrame[e].x * mul, bodyFrame[e].y * mul);
- })
- }
- let rh2 = [16, 22];
- let rhline2 = bodyNode.obj.children.filter(e => (e.nodeID.includes("rhand2")))[0];
- if (rhline2) {
- rh2.map((e, i) => {
- rhline2.vertices[i].set(bodyFrame[e].x * mul, bodyFrame[e].y * mul);
- })
- }
- let lh2 = [15, 21];
- let lhline2 = bodyNode.obj.children.filter(e => (e.nodeID.includes("lhand2")))[0];
- if (lhline2) {
- lh2.map((e, i) => {
- lhline2.vertices[i].set(bodyFrame[e].x * mul, bodyFrame[e].y * mul);
- })
- }
- let faceArr = [10, 8, 6, 4, 1, 3, 7, 9, 10];
- let faceline = bodyNode.obj.children.filter(e => (e.nodeID.includes("faceline")))[0];
- if (faceline) {
- faceArr.map((e, i) => {
- faceline.vertices[i].set(bodyFrame[e].x * mul, bodyFrame[e].y * mul);
- })
- }
- // let ba = [11,23]; //[12,24,23,11];
- // let bottomline = bodyNode.obj.children.filter(e=>(e.nodeID.includes("bottomline")))[0];
- // if(bottomline){
- // ba.map((e,i)=>{
- // bottomline.vertices[i].set(bodyFrame[e].x*mul, bodyFrame[e].y*mul);
- // })
- // }
- }
- }
- }
- //}
- //video.requestVideoFrameCallback(self.animate);
- })
- }
- }
- checkPlayStatus() {
- let self = this.instance;
- //let scene = self.nodes[vwf.application()];
- let videos = Object.values(self.state.nodes).filter(el => el.fillType == "video");
- videos.forEach(el => {
- this.checkPlayStatusForNode(el.ID);
- })
- let toneJSDriver = vwf.views["/drivers/view/tone"];
- if (toneJSDriver) {
- let toneTransport = Object.values(toneJSDriver.state.nodes).filter(el => el.extendsID == "proxy/tonejs/transport.vwf")[0];
- if (toneTransport) {
- toneJSDriver.fabric.checkPlayStatusForTransportNode(toneTransport.ID);
- }
- }
- }
- checkPlayStatusForNode(nodeID) {
- let self = this.instance;
- let viewNode = self.nodes[nodeID];
- let node = self.state.nodes[nodeID];
- let video = node.obj.fill.image;
- //if (this.videoView) {
- // this.adjustPlaybar();
- const lastTimingCheck = viewNode.lastTimingCheck || 0;
- const now = vwf.time() * 1000;
- // check video timing every 0.5s
- if (viewNode.isPlaying && !viewNode.isBlocked && (now - lastTimingCheck >= 500)) {
- viewNode.lastTimingCheck = now;
- const expectedTime = this.wrappedTime(this.calculateVideoTime(nodeID), false, video.duration);
- const videoTime = video.currentTime;
- const videoDiff = videoTime - expectedTime;
- const videoDiffMS = videoDiff * 1000; // +ve means *ahead* of where it should be
- if (videoDiff < video.duration / 2) { // otherwise presumably measured across a loop restart; just ignore.
- if (viewNode.jumpIfNeeded) { //this.jumpIfNeeded
- viewNode.jumpIfNeeded = false;
- // if there's a difference greater than 500ms, try to jump the video to the right place
- if (Math.abs(videoDiffMS) > 500) {
- console.log(`jumping video by ${-Math.round(videoDiffMS)}ms`);
- video.currentTime = this.wrappedTime(videoTime - videoDiff + 0.1, true, video.duration); // 0.1 to counteract the delay that the jump itself tends to introduce; true to ensure we're not jumping beyond the last video frame
- }
- } else {
- // every 3s, check video lag/advance, and set the playback rate accordingly.
- // current adjustment settings:
- // > 150ms off: set playback 3% faster/slower than normal
- // > 50ms: 1% faster/slower
- // < 25ms: normal (i.e., hysteresis between 50ms and 25ms in the same sense)
- const lastRateAdjust = viewNode.lastRateAdjust || 0;
- if (now - lastRateAdjust >= 3000) {
- //console.log(`${Math.round(videoDiff*1000)}ms`);
- const oldBoostPercent = viewNode.playbackBoost;
- const diffAbs = Math.abs(videoDiffMS), diffSign = Math.sign(videoDiffMS);
- const desiredBoostPercent = -diffSign * (diffAbs > 150 ? 3 : (diffAbs > 50 ? 1 : 0));
- if (desiredBoostPercent !== oldBoostPercent) {
- // apply hysteresis on the switch to boost=0.
- // for example, if old boost was +ve (because video was lagging),
- // and videoDiff is -ve (i.e., it's still lagging),
- // and the magnitude (of the lag) is greater than 25ms,
- // don't remove the boost yet.
- const hysteresisBlock = desiredBoostPercent === 0 && Math.sign(oldBoostPercent) === -diffSign && diffAbs >= 25;
- if (!hysteresisBlock) {
- viewNode.playbackBoost = desiredBoostPercent;
- const playbackRate = 1 + viewNode.playbackBoost * 0.01;
- console.log(`video playback rate: ${playbackRate}`);
- video.playbackRate = playbackRate;
- }
- }
- viewNode.lastRateAdjust = now;
- }
- }
- }
- }
- // }
- }
- wrappedTime(videoTime, guarded, duration) {
- if (duration) {
- while (videoTime > duration) videoTime -= duration; // assume it's looping, with no gap between plays
- if (guarded) videoTime = Math.min(duration - 0.1, videoTime); // the video element freaks out on being told to seek very close to the end
- }
- return videoTime;
- }
- calculateVideoTime(nodeID) {
- let self = this.instance;
- const node = self.state.nodes[nodeID];
- const viewNode = self.nodes[nodeID];
- //const video = node.obj.fill.image;
- // const { isPlaying, startOffset } = this.latestPlayState;
- //if (!isPlaying) debugger;
- const sessionNow = vwf.time() * 1000;
- let t = (sessionNow - viewNode.latestPlayState.startOffset) / 1000;
- // console.log('Time: ', t)
- return t;
- }
- pause(videoTime, video, nodeID) {
- let self = this.instance;
- const node = self.state.nodes[nodeID];
- const viewNode = self.nodes[nodeID];
- viewNode.isPlaying = viewNode.isBlocked = false; // might not be blocked next time.
- this.setStatic(videoTime, video);
- }
- setStatic(videoTime, video) {
- if (video) {
- if (videoTime !== undefined) video.currentTime = this.wrappedTime(videoTime, true, video.duration); // true => guarded from values too near the end
- video.pause(); // no return value; synchronous, instantaneous?
- }
- }
- triggerJumpCheck(nodeID) {
- let self = this.instance;
- //const node = self.state.nodes[nodeID];
- const viewNode = self.nodes[nodeID];
- viewNode.jumpIfNeeded = true;
- }
- stepWhileBlocked(nodeID) {
- let _self = this;
- let self = this.instance;
- const node = self.state.nodes[nodeID];
- const viewNode = self.nodes[nodeID];
- const video = node.obj.fill.image;
- if (!viewNode.isStepping) return; // we've left stepping mode
- if (!viewNode.isBlocked) {
- viewNode.isStepping = false;
- return;
- }
- this.setStatic(this.calculateVideoTime(nodeID), video);
- setTimeout(function () {
- _self.stepWhileBlocked(nodeID)
- }, 250);
- //this.future(250).stepWhileBlocked(); // jerky, but keeping up
- }
- async play(video, videoTime, nodeID) {
- let self = this.instance;
- const node = self.state.nodes[nodeID];
- const viewNode = self.nodes[nodeID];
- // return true if video play started successfully
- const duration = video.duration;
- video.currentTime = this.wrappedTime(videoTime, true, duration);
- viewNode.isPlaying = true;
- //this.isPlaying = true; // even if it turns out to be blocked by the browser
- // following guidelines from https://developer.mozilla.org/docs/Web/API/HTMLMediaElement/play
- try {
- await video.play(); // will throw exception if blocked
- viewNode.isBlocked = false;
- } catch (err) {
- console.warn("video play blocked");
- viewNode.isBlocked = viewNode.isPlaying; // just in case isPlaying was set false while we were trying
- }
- return !viewNode.isBlocked;
- }
- /////
- getOverlayChilds(node, x, y) {
- //let childs = node.scene.obj.scene.children;
- var childs = [];
- node.children.forEach(el => {
- if (!el.nodeID.includes('avatar')) {
- let rect = el.getBoundingClientRect();
- if (x >= rect.left && x <= rect.right && y >= rect.top && y <= rect.bottom) {
- childs.push(el);
- if (el.nodeName == "group") {
- childs = childs.concat(this.getOverlayChilds(el, x, y));
- }
- }
- }
- })
- return childs
- }
- updateAvatarPosition() {
- let self = this.instance;
- let avatarName = 'avatar-' + self.kernel.moniker();
- var node = self.state.nodes[avatarName];
- var nodeView = self.nodes[avatarName];
- let scene = self.nodes[vwf.application()];
- if (!node) return;
- if (!node.obj) return;
- let position = scene.mouse;
- if (!nodeView.lastPosition) {
- nodeView.lastPosition = new Two.Vector(position.x, position.y);
- }
- let lastPosition = nodeView.lastPosition;
- if (position && !(position.equals(lastPosition))) {
- // self.kernel.setProperty(avatarName, "x", position.x);
- // self.kernel.setProperty(avatarName, "y", position.y);
- self.kernel.callMethod(avatarName, "move", [position.x, position.y]);
- }
- nodeView.lastPosition.set(position.x, position.y)
- }
- postLoadAction(nodeID) {
- //vwf_view.kernel.fireEvent(nodeID, "postLoadAction")
- }
- }
- export { TwoView as default }
|