PageSourceSearch

https://jbstudio.pro/wp-content/themes/web-jb/lib/activeframe/ActiveFrame.js?ver=1790583724

js jbstudio.pro collected 2026-09-30 21:45:53 UTC 7,770 bytes, 259 lines download raw bytes

1const cacheActiveFrameList = new Map();
2
3Promise.create = function () {
4    let resolve = null;
5    let reject = null;
6    const promise = new Promise((res, rej) => {
7        resolve = res;
8        reject = rej;
9    });
10
11    promise.resolve = resolve;
12    promise.reject = reject;
13    return promise;
14};
15
16window.ActiveFrame = class ActiveFrame {
17    file = null;
18    manifest = null;
19    data = null;
20    decoder = null;
21    frame = null;
22    desideredFrame = 0;
23    enabled = true;
24    framesByTimestamp = new Map();
25    frameProcessed = null;
26
27    constructor(file, {
28        process = () => {},
29        // texture = null,
30        hardwareAcceleration = 'prefer-hardware'
31    }) {
32        this.loading = Promise.create();
33        this.process = process;
34        // this.texture = texture;
35        this.hardwareAcceleration = hardwareAcceleration;
36
37        this.file = file;
38        this.init();
39    }
40
41    async init() {
42        cacheActiveFrameList.set(this.file, this.loadBinary(this.file));
43
44        const loading = await cacheActiveFrameList.get(this.file);
45        const { manifest, data } = loading;
46
47        this.manifest = manifest;
48        this.data = data;
49
50        this.manifest.frames.forEach(frame => {
51            // preallocate data view for faster access
52            frame.data = new Uint8Array(this.data, frame.o, frame.l);
53            this.framesByTimestamp.set(frame.t, frame.i);
54        });
55
56        await this.initDecoder();
57        this.loading.resolve();
58    }
59
60    async loadBinary(file) {
61        const res = await fetch(file);
62        const fullBuffer = await res.arrayBuffer();
63
64        // Last 4 bytes = LE uint32 offset where JSON starts
65        const footer = new DataView(fullBuffer, fullBuffer.byteLength - 4);
66        const manifestOffset = footer.getUint32(0, true);
67
68        const manifestBytes = new Uint8Array(fullBuffer, manifestOffset, fullBuffer.byteLength - 4 - manifestOffset);
69        const manifest = JSON.parse(new TextDecoder().decode(manifestBytes));
70
71        return {
72            manifest,
73            data: fullBuffer
74        };
75    }
76
77    decodeDescription(description) {
78        const binaryString = atob(description);
79        const bytes = new Uint8Array(binaryString.length);
80        for (let i = 0; i < binaryString.length; i++) {
81            bytes[i] = binaryString.charCodeAt(i);
82        }
83        return bytes;
84    }
85
86    async initDecoder() {
87        const baseConfig = {
88            codec: this.manifest.codec,
89            codedWidth: this.manifest.width,
90            codedHeight: this.manifest.height,
91            colorSpace: {
92                primaries: 'bt709',
93                transfer: 'bt709',
94                matrix: 'bt709',
95                fullRange: false
96            },
97            description: this.decodeDescription(this.manifest.description)
98        };
99
100        const candidates = [
101            { ...baseConfig, hardwareAcceleration: this.hardwareAcceleration, optimizeForLatency: true },
102            { ...baseConfig, hardwareAcceleration: this.hardwareAcceleration },
103            { ...baseConfig, optimizeForLatency: true },
104            { ...baseConfig }
105        ];
106
107        this.config = null;
108
109        for (const candidate of candidates) {
110            const support = await VideoDecoder.isConfigSupported(candidate);
111            if (support.supported) {
112                this.config = candidate;
113                break;
114            }
115        }
116
117        if (!this.config) {
118            throw new Error('Decoder not supported');
119        }
120
121        this.decoder = new VideoDecoder({
122            output: this.outputFrame.bind(this),
123            error: (e) => {
124                console.log(this.file);
125                console.log(this.config);
126                console.error('Decoder error:', e);
127            }
128        });
129
130        // this.decoder.reset();
131        this.decoder.configure(this.config);
132    }
133
134    async outputFrame(frame) {
135        if (!this.enabled) {
136            frame.close();
137            return;
138        };
139
140        const timestampToFrameId = this.framesByTimestamp.get(frame.timestamp);
141
142        if (this.desideredFrame !== timestampToFrameId) {
143            frame.close();
144            return;
145        }
146
147        this.frame = timestampToFrameId;
148
149        if (this.process) {
150            await this.process(frame);
151        }
152
153        // if (this.texture) {
154        //     this.texture.image = frame;
155        //     Texture.renderer.manualUpdateDynamic(this.texture);
156        // }
157
158        this.frameProcessed = timestampToFrameId;
159
160        frame.close();
161    }
162
163    setFrame(desideredFrame) {
164        if (!this.manifest) return;
165        if (!this.enabled) return;
166
167        desideredFrame = Math.round(Number(desideredFrame));
168        const maxFrame = Math.max(0, this.manifest.totalFrames - 1);
169        desideredFrame = Math.min(Math.max(desideredFrame, 0), maxFrame);
170        this.desideredFrame = desideredFrame;
171
172        if (this.desideredFrame === this.frame) return;
173        if (this.desideredFrame === this._pendingFrame) return;
174
175        this._pendingFrame = desideredFrame;
176
177        const frameMeta = this.manifest.frames[this.desideredFrame];
178
179        if (!frameMeta) {
180            return;
181        }
182
183        const isSequential = this.frame !== null
184        && this.desideredFrame === this.frame + 1
185        && frameMeta.ty === 'delta';
186
187        if (isSequential) {
188            this.decoder.decode(new EncodedVideoChunk({
189                type: frameMeta.ty,
190                timestamp: frameMeta.t,
191                data: frameMeta.data
192            }));
193            return;
194        }
195
196        if (this.decoder.decodeQueueSize > 0 || this.decoder.state !== 'configured') {
197            this.decoder.reset();
198            this.decoder.configure(this.config);
199        }
200
201        if (frameMeta.ty === 'key') {
202            this.decoder.decode(new EncodedVideoChunk({
203                type: frameMeta.ty,
204                timestamp: frameMeta.t,
205                data: frameMeta.data
206            }));
207
208        } else {
209            let keyFrame = null;
210            for (let i = this.desideredFrame; i >= 0; i--) {
211                const f = this.manifest.frames[i];
212                if (f.ty === 'key') {
213                    keyFrame = f;
214                    break;
215                }
216            }
217
218            if (!keyFrame || !keyFrame.data) {
219                console.error('No key frame found');
220                return;
221            }
222
223            this.decoder.decode(new EncodedVideoChunk({
224                type: keyFrame.ty,
225                timestamp: keyFrame.t,
226                data: keyFrame.data
227            }));
228
229            for (let i = keyFrame.i + 1; i <= this.desideredFrame; i++) {
230                const f = this.manifest.frames[i];
231                if (f.ty === 'delta') {
232                    this.decoder.decode(new EncodedVideoChunk({
233                        type: f.ty,
234                        timestamp: f.t,
235                        data: f.data
236                    }));
237                } else {
238                    break;
239                }
240            }
241        }
242    }
243
244    destroy() {
245        cacheActiveFrameList.delete(this.file);
246        this.stop();
247        this.decoder.close();
248        this.decoder = null;
249        this.data = null;
250        this.manifest = null;
251        this.file = null;
252        // this.texture?.destroy?.();
253        // this.texture = null;
254        this.process = null;
255        this.frameProcessed = null;
256        this.enabled = false;
257        this.framesByTimestamp.clear();
258    }
259}

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.