diff web/light9-vidref-replay.js @ 2376:4556eebe5d73

topdir reorgs; let pdm have its src/ dir; separate vite area from light9/
author drewp@bigasterisk.com
date Sun, 12 May 2024 19:02:10 -0700
parents light9/web/light9-vidref-replay.js@b64a4db527e2
children
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/web/light9-vidref-replay.js	Sun May 12 19:02:10 2024 -0700
@@ -0,0 +1,142 @@
+import { LitElement, TemplateResult, html, css } from '/node_modules/lit-element/lit-element.js';
+import debug from '/lib/debug/debug-build-es6.js';
+import _ from '/lib/underscore/underscore-min-es6.js';
+import { rounding }  from '/node_modules/significant-rounding/index.js';
+
+const log = debug('replay');
+
+class Light9VidrefReplay extends LitElement {
+    
+    static get properties() {
+        return {
+            uri: { type: String },
+            videoUrl: { type: String },
+            songToVideo: { type: Object },
+            videoTime: { type: Number },
+            outVideoCurrentTime: { type: Number },
+            timeErr: { type: Number },
+            playRate: { type: Number },
+            size: { type: String, attribute: true }
+        };
+    }
+
+    estimateRate() {
+        const n = this.songToVideo.length;
+        const x0 = Math.round(n * .3);
+        const x1 = Math.round(n * .6);
+        const pt0 = this.songToVideo[x0];
+        const pt1 = this.songToVideo[x1];
+        return (pt1[1] - pt0[1]) / (pt1[0] - pt0[0]);
+    }
+
+    setVideoTimeFromSongTime(songTime, isPlaying) {
+        if (!this.songToVideo || !this.outVideo || this.outVideo.readyState < 1) {
+            return;
+        }
+        const i = _.sortedIndex(this.songToVideo, [songTime],
+                                (row) => { return row[0]; });
+        if (i == 0 || i > this.songToVideo.length - 1) {
+            isPlaying = false;
+        }
+        
+        this.videoTime = this.songToVideo[Math.max(0, i - 1)][1];
+
+        this.outVideoCurrentTime = this.outVideo.currentTime;
+        
+        if (isPlaying) {
+            if (this.outVideo.paused) {
+                this.outVideo.play();
+                this.setRate(this.estimateRate());
+            }
+            const err = this.outVideo.currentTime - this.videoTime;
+            this.timeErr = err;
+
+            if (Math.abs(err) > window.thresh) {
+                this.outVideo.currentTime = this.videoTime;
+                const p = window.p;
+                if (err > 0) {
+                    this.setRate(this.playRate - err * p);
+                } else {
+                    this.setRate(this.playRate - err * p);
+                }
+            }
+        } else {
+            this.outVideo.pause();
+            this.outVideoCurrentTime = this.outVideo.currentTime = this.videoTime;
+            this.timeErr = 0;
+        }
+    }
+
+    setRate(r) {
+        this.playRate = Math.max(.1, Math.min(4, r));
+        this.outVideo.playbackRate = this.playRate;
+    }
+
+    firstUpdated() {
+        this.outVideo = this.shadowRoot.querySelector('#replay');
+        this.playRate = this.outVideo.playbackRate = 1.0;
+    }
+
+    onDelete() {
+        const u = new URL(window.location.href);
+        u.pathname = '/vidref/clips'
+        u.searchParams.set('uri', this.uri);
+        fetch(u.toString(), {method: 'DELETE'}).then((resp) => {
+            let event = new CustomEvent('clips-changed', {detail: {}});
+            this.dispatchEvent(event);
+        });
+    }
+
+    static get styles() {
+        return css`
+        :host {
+            border: 2px solid #46a79f;
+            display: flex;
+            flex-direction: column;
+        }
+        div {
+            padding: 5px;
+        }
+        .num {
+            display: inline-block;
+            width: 4em;
+            color: #29ffa0;
+        }
+        a {
+            color: rgb(97, 97, 255);
+        }
+        video {
+            width: 100%;
+        }
+        `;
+    }
+    
+    render() {
+        let details = '';
+        if (this.size != 'small') {
+            details = html`
+  <div>
+    take is <a href="${this.uri}">${this.uri}</a> 
+    (${Object.keys(this.songToVideo).length} frames)
+    <button @click="${this.onDelete}">Delete</button>
+  </div>
+  <!-- here, put a little canvas showing what coverage we have with the 
+       actual/goal time cursors -->
+  <div>
+    video time should be <span class="num">${this.videoTime} </span>
+    actual = <span class="num">${rounding(this.outVideoCurrentTime, 3, 3, true)}</span>, 
+    err = <span class="num">${rounding(this.timeErr, 3, 4, true)}</span>
+    rate = <span class="num">${rounding(this.playRate, 3, 3, true)}</span>
+  </div>
+            `;
+        }
+        return html`
+          <video id="replay" class="size-${this.size}" src="${this.videoUrl}"></video>
+          ${details}
+        `;
+
+    }
+}
+customElements.define('light9-vidref-replay', Light9VidrefReplay);
+window.thresh=.3
+window.p=.3