@splinetool/viewer 2.0.9 → 2.0.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/build/hana-ui.js +73 -43
- package/build/hana-ui.wasm +0 -0
- package/build/snippets/hana-82e9eb82a79dead4/js-imports.js +204 -77
- package/build/spline-viewer.cjs +247 -247
- package/build/spline-viewer.js +247 -247
- package/build/spline-viewer.webgl.js +178 -178
- package/build/spline-viewer.webgpu.js +111 -111
- package/package.json +1 -1
package/build/hana-ui.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/* @ts-self-types="./hana.d.ts" */
|
|
2
|
-
import { loadImageFromBytes, loadImageFromUrl, loadVideoFromBytes, loadVideoFromUrl } from './snippets/hana-82e9eb82a79dead4/js-imports.js';
|
|
2
|
+
import { disposeVideo, loadImageFromBytes, loadImageFromUrl, loadVideoFromBytes, loadVideoFromUrl, videoFrameToken } from './snippets/hana-82e9eb82a79dead4/js-imports.js';
|
|
3
3
|
|
|
4
4
|
|
|
5
5
|
export class DeviceLostPromise {
|
|
@@ -331,6 +331,16 @@ export class SceneController {
|
|
|
331
331
|
const ret = wasm.scenecontroller_centerVectorData(this.__wbg_ptr);
|
|
332
332
|
return takeObject(ret);
|
|
333
333
|
}
|
|
334
|
+
/**
|
|
335
|
+
* Drop the export-time frame override for one video asset (see
|
|
336
|
+
* `setVideoExportFrame`): the renderer resumes sampling its `<video>`
|
|
337
|
+
* element and `seekVideos` drives it again. The export loop's per-video
|
|
338
|
+
* fallback for a WebCodecs source that stopped delivering mid-export.
|
|
339
|
+
* @param {ID} id
|
|
340
|
+
*/
|
|
341
|
+
clearVideoExportFrame(id) {
|
|
342
|
+
wasm.scenecontroller_clearVideoExportFrame(this.__wbg_ptr, addHeapObject(id));
|
|
343
|
+
}
|
|
334
344
|
/**
|
|
335
345
|
* Drop every export-time video frame override (see `setVideoExportFrame`).
|
|
336
346
|
*/
|
|
@@ -1216,8 +1226,9 @@ export class SceneController {
|
|
|
1216
1226
|
}
|
|
1217
1227
|
/**
|
|
1218
1228
|
* The video fills the export loop should decode with WebCodecs, as
|
|
1219
|
-
* `{ id, src }` objects (`src` is the loaded `<video>` element's
|
|
1220
|
-
*
|
|
1229
|
+
* `{ id, src }` objects (`src` is the loaded `<video>` element's source:
|
|
1230
|
+
* the asset's own URL for streamed loads, a `blob:` URL for byte-loaded
|
|
1231
|
+
* ones — either way the same URL the element itself fetched). Enumerated from the start events' video actions — the exact set
|
|
1221
1232
|
* `seekVideos` drives — so the WebCodecs path animates precisely the
|
|
1222
1233
|
* fills the element path would: a fill without a play action stays a
|
|
1223
1234
|
* static frame on both paths and both backends. Scoped to actions
|
|
@@ -1807,6 +1818,17 @@ function __wbg_get_imports() {
|
|
|
1807
1818
|
__wbg_dispatchWorkgroups_afb2344298c62227: function(arg0, arg1, arg2, arg3) {
|
|
1808
1819
|
getObject(arg0).dispatchWorkgroups(arg1 >>> 0, arg2 >>> 0, arg3 >>> 0);
|
|
1809
1820
|
},
|
|
1821
|
+
__wbg_displayHeight_465673c1823a1db3: function(arg0) {
|
|
1822
|
+
const ret = getObject(arg0).displayHeight;
|
|
1823
|
+
return ret;
|
|
1824
|
+
},
|
|
1825
|
+
__wbg_displayWidth_73729fd8eb965076: function(arg0) {
|
|
1826
|
+
const ret = getObject(arg0).displayWidth;
|
|
1827
|
+
return ret;
|
|
1828
|
+
},
|
|
1829
|
+
__wbg_disposeVideo_c94c1ccd57a63f68: function(arg0) {
|
|
1830
|
+
disposeVideo(getObject(arg0));
|
|
1831
|
+
},
|
|
1810
1832
|
__wbg_document_aceb08cd6489baf5: function(arg0) {
|
|
1811
1833
|
const ret = getObject(arg0).document;
|
|
1812
1834
|
return isLikeNone(ret) ? 0 : addHeapObject(ret);
|
|
@@ -2453,7 +2475,7 @@ function __wbg_get_imports() {
|
|
|
2453
2475
|
const a = state0.a;
|
|
2454
2476
|
state0.a = 0;
|
|
2455
2477
|
try {
|
|
2456
|
-
return
|
|
2478
|
+
return __wasm_bindgen_func_elem_14186(a, state0.b, arg0, arg1);
|
|
2457
2479
|
} finally {
|
|
2458
2480
|
state0.a = a;
|
|
2459
2481
|
}
|
|
@@ -2499,7 +2521,7 @@ function __wbg_get_imports() {
|
|
|
2499
2521
|
const a = state0.a;
|
|
2500
2522
|
state0.a = 0;
|
|
2501
2523
|
try {
|
|
2502
|
-
return
|
|
2524
|
+
return __wasm_bindgen_func_elem_14186(a, state0.b, arg0, arg1);
|
|
2503
2525
|
} finally {
|
|
2504
2526
|
state0.a = a;
|
|
2505
2527
|
}
|
|
@@ -2626,6 +2648,10 @@ function __wbg_get_imports() {
|
|
|
2626
2648
|
__wbg_readPixels_d00c1cae608273d8: function() { return handleError(function (arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7) {
|
|
2627
2649
|
getObject(arg0).readPixels(arg1, arg2, arg3, arg4, arg5 >>> 0, arg6 >>> 0, arg7);
|
|
2628
2650
|
}, arguments); },
|
|
2651
|
+
__wbg_readyState_77b1b1db1b8c2a5f: function(arg0) {
|
|
2652
|
+
const ret = getObject(arg0).readyState;
|
|
2653
|
+
return ret;
|
|
2654
|
+
},
|
|
2629
2655
|
__wbg_reason_b02cd587d1a09948: function(arg0) {
|
|
2630
2656
|
const ret = getObject(arg0).reason;
|
|
2631
2657
|
return (__wbindgen_enum_GpuDeviceLostReason.indexOf(ret) + 1 || 3) - 1;
|
|
@@ -3581,6 +3607,10 @@ function __wbg_get_imports() {
|
|
|
3581
3607
|
__wbg_vertexAttribPointer_3bc50c0431082f73: function(arg0, arg1, arg2, arg3, arg4, arg5, arg6) {
|
|
3582
3608
|
getObject(arg0).vertexAttribPointer(arg1 >>> 0, arg2, arg3 >>> 0, arg4 !== 0, arg5, arg6);
|
|
3583
3609
|
},
|
|
3610
|
+
__wbg_videoFrameToken_6f05524e85fa2b75: function(arg0) {
|
|
3611
|
+
const ret = videoFrameToken(getObject(arg0));
|
|
3612
|
+
return ret;
|
|
3613
|
+
},
|
|
3584
3614
|
__wbg_videoHeight_e267fd1e74bb1b06: function(arg0) {
|
|
3585
3615
|
const ret = getObject(arg0).videoHeight;
|
|
3586
3616
|
return ret;
|
|
@@ -3606,48 +3636,48 @@ function __wbg_get_imports() {
|
|
|
3606
3636
|
getObject(arg0).writeTexture(getObject(arg1), getArrayU8FromWasm0(arg2, arg3), getObject(arg4), getObject(arg5));
|
|
3607
3637
|
}, arguments); },
|
|
3608
3638
|
__wbindgen_cast_0000000000000001: function(arg0, arg1) {
|
|
3609
|
-
// Cast intrinsic for `Closure(Closure { owned: true, function: Function { arguments: [Externref], shim_idx:
|
|
3610
|
-
const ret = makeMutClosure(arg0, arg1,
|
|
3639
|
+
// Cast intrinsic for `Closure(Closure { owned: true, function: Function { arguments: [Externref], shim_idx: 1054, ret: Result(Unit), inner_ret: Some(Result(Unit)) }, mutable: true }) -> Externref`.
|
|
3640
|
+
const ret = makeMutClosure(arg0, arg1, __wasm_bindgen_func_elem_14184);
|
|
3611
3641
|
return addHeapObject(ret);
|
|
3612
3642
|
},
|
|
3613
3643
|
__wbindgen_cast_0000000000000002: function(arg0, arg1) {
|
|
3614
|
-
// Cast intrinsic for `Closure(Closure { owned: true, function: Function { arguments: [NamedExternref("Event")], shim_idx:
|
|
3615
|
-
const ret = makeMutClosure(arg0, arg1,
|
|
3644
|
+
// Cast intrinsic for `Closure(Closure { owned: true, function: Function { arguments: [NamedExternref("Event")], shim_idx: 34, ret: Unit, inner_ret: Some(Unit) }, mutable: true }) -> Externref`.
|
|
3645
|
+
const ret = makeMutClosure(arg0, arg1, __wasm_bindgen_func_elem_5119);
|
|
3616
3646
|
return addHeapObject(ret);
|
|
3617
3647
|
},
|
|
3618
3648
|
__wbindgen_cast_0000000000000003: function(arg0, arg1) {
|
|
3619
|
-
// Cast intrinsic for `Closure(Closure { owned: true, function: Function { arguments: [NamedExternref("GPUComputePipeline")], shim_idx:
|
|
3620
|
-
const ret = makeMutClosure(arg0, arg1,
|
|
3649
|
+
// Cast intrinsic for `Closure(Closure { owned: true, function: Function { arguments: [NamedExternref("GPUComputePipeline")], shim_idx: 929, ret: Result(Unit), inner_ret: Some(Result(Unit)) }, mutable: true }) -> Externref`.
|
|
3650
|
+
const ret = makeMutClosure(arg0, arg1, __wasm_bindgen_func_elem_11140);
|
|
3621
3651
|
return addHeapObject(ret);
|
|
3622
3652
|
},
|
|
3623
3653
|
__wbindgen_cast_0000000000000004: function(arg0, arg1) {
|
|
3624
|
-
// Cast intrinsic for `Closure(Closure { owned: true, function: Function { arguments: [NamedExternref("GPUDevice")], shim_idx:
|
|
3625
|
-
const ret = makeMutClosure(arg0, arg1,
|
|
3654
|
+
// Cast intrinsic for `Closure(Closure { owned: true, function: Function { arguments: [NamedExternref("GPUDevice")], shim_idx: 929, ret: Result(Unit), inner_ret: Some(Result(Unit)) }, mutable: true }) -> Externref`.
|
|
3655
|
+
const ret = makeMutClosure(arg0, arg1, __wasm_bindgen_func_elem_11140_3);
|
|
3626
3656
|
return addHeapObject(ret);
|
|
3627
3657
|
},
|
|
3628
3658
|
__wbindgen_cast_0000000000000005: function(arg0, arg1) {
|
|
3629
|
-
// Cast intrinsic for `Closure(Closure { owned: true, function: Function { arguments: [NamedExternref("GPUDeviceLostInfo")], shim_idx:
|
|
3630
|
-
const ret = makeMutClosure(arg0, arg1,
|
|
3659
|
+
// Cast intrinsic for `Closure(Closure { owned: true, function: Function { arguments: [NamedExternref("GPUDeviceLostInfo")], shim_idx: 930, ret: Unit, inner_ret: Some(Unit) }, mutable: true }) -> Externref`.
|
|
3660
|
+
const ret = makeMutClosure(arg0, arg1, __wasm_bindgen_func_elem_11141);
|
|
3631
3661
|
return addHeapObject(ret);
|
|
3632
3662
|
},
|
|
3633
3663
|
__wbindgen_cast_0000000000000006: function(arg0, arg1) {
|
|
3634
|
-
// Cast intrinsic for `Closure(Closure { owned: true, function: Function { arguments: [NamedExternref("GPURenderPipeline")], shim_idx:
|
|
3635
|
-
const ret = makeMutClosure(arg0, arg1,
|
|
3664
|
+
// Cast intrinsic for `Closure(Closure { owned: true, function: Function { arguments: [NamedExternref("GPURenderPipeline")], shim_idx: 929, ret: Result(Unit), inner_ret: Some(Result(Unit)) }, mutable: true }) -> Externref`.
|
|
3665
|
+
const ret = makeMutClosure(arg0, arg1, __wasm_bindgen_func_elem_11140_5);
|
|
3636
3666
|
return addHeapObject(ret);
|
|
3637
3667
|
},
|
|
3638
3668
|
__wbindgen_cast_0000000000000007: function(arg0, arg1) {
|
|
3639
|
-
// Cast intrinsic for `Closure(Closure { owned: true, function: Function { arguments: [NamedExternref("any")], shim_idx:
|
|
3640
|
-
const ret = makeMutClosure(arg0, arg1,
|
|
3669
|
+
// Cast intrinsic for `Closure(Closure { owned: true, function: Function { arguments: [NamedExternref("any")], shim_idx: 929, ret: Result(Unit), inner_ret: Some(Result(Unit)) }, mutable: true }) -> Externref`.
|
|
3670
|
+
const ret = makeMutClosure(arg0, arg1, __wasm_bindgen_func_elem_11140_6);
|
|
3641
3671
|
return addHeapObject(ret);
|
|
3642
3672
|
},
|
|
3643
3673
|
__wbindgen_cast_0000000000000008: function(arg0, arg1) {
|
|
3644
|
-
// Cast intrinsic for `Closure(Closure { owned: true, function: Function { arguments: [NamedExternref("undefined")], shim_idx:
|
|
3645
|
-
const ret = makeMutClosure(arg0, arg1,
|
|
3674
|
+
// Cast intrinsic for `Closure(Closure { owned: true, function: Function { arguments: [NamedExternref("undefined")], shim_idx: 929, ret: Result(Unit), inner_ret: Some(Result(Unit)) }, mutable: true }) -> Externref`.
|
|
3675
|
+
const ret = makeMutClosure(arg0, arg1, __wasm_bindgen_func_elem_11140_7);
|
|
3646
3676
|
return addHeapObject(ret);
|
|
3647
3677
|
},
|
|
3648
3678
|
__wbindgen_cast_0000000000000009: function(arg0, arg1) {
|
|
3649
|
-
// Cast intrinsic for `Closure(Closure { owned: true, function: Function { arguments: [], shim_idx:
|
|
3650
|
-
const ret = makeMutClosure(arg0, arg1,
|
|
3679
|
+
// Cast intrinsic for `Closure(Closure { owned: true, function: Function { arguments: [], shim_idx: 1037, ret: Unit, inner_ret: Some(Unit) }, mutable: true }) -> Externref`.
|
|
3680
|
+
const ret = makeMutClosure(arg0, arg1, __wasm_bindgen_func_elem_12547);
|
|
3651
3681
|
return addHeapObject(ret);
|
|
3652
3682
|
},
|
|
3653
3683
|
__wbindgen_cast_000000000000000a: function(arg0) {
|
|
@@ -3721,22 +3751,22 @@ function __wbg_get_imports() {
|
|
|
3721
3751
|
};
|
|
3722
3752
|
}
|
|
3723
3753
|
|
|
3724
|
-
function
|
|
3725
|
-
wasm.
|
|
3754
|
+
function __wasm_bindgen_func_elem_12547(arg0, arg1) {
|
|
3755
|
+
wasm.__wasm_bindgen_func_elem_12547(arg0, arg1);
|
|
3726
3756
|
}
|
|
3727
3757
|
|
|
3728
|
-
function
|
|
3729
|
-
wasm.
|
|
3758
|
+
function __wasm_bindgen_func_elem_5119(arg0, arg1, arg2) {
|
|
3759
|
+
wasm.__wasm_bindgen_func_elem_5119(arg0, arg1, addHeapObject(arg2));
|
|
3730
3760
|
}
|
|
3731
3761
|
|
|
3732
|
-
function
|
|
3733
|
-
wasm.
|
|
3762
|
+
function __wasm_bindgen_func_elem_11141(arg0, arg1, arg2) {
|
|
3763
|
+
wasm.__wasm_bindgen_func_elem_11141(arg0, arg1, addHeapObject(arg2));
|
|
3734
3764
|
}
|
|
3735
3765
|
|
|
3736
|
-
function
|
|
3766
|
+
function __wasm_bindgen_func_elem_14184(arg0, arg1, arg2) {
|
|
3737
3767
|
try {
|
|
3738
3768
|
const retptr = wasm.__wbindgen_add_to_stack_pointer(-16);
|
|
3739
|
-
wasm.
|
|
3769
|
+
wasm.__wasm_bindgen_func_elem_14184(retptr, arg0, arg1, addHeapObject(arg2));
|
|
3740
3770
|
var r0 = getDataViewMemory0().getInt32(retptr + 4 * 0, true);
|
|
3741
3771
|
var r1 = getDataViewMemory0().getInt32(retptr + 4 * 1, true);
|
|
3742
3772
|
if (r1) {
|
|
@@ -3747,10 +3777,10 @@ function __wasm_bindgen_func_elem_14148(arg0, arg1, arg2) {
|
|
|
3747
3777
|
}
|
|
3748
3778
|
}
|
|
3749
3779
|
|
|
3750
|
-
function
|
|
3780
|
+
function __wasm_bindgen_func_elem_11140(arg0, arg1, arg2) {
|
|
3751
3781
|
try {
|
|
3752
3782
|
const retptr = wasm.__wbindgen_add_to_stack_pointer(-16);
|
|
3753
|
-
wasm.
|
|
3783
|
+
wasm.__wasm_bindgen_func_elem_11140(retptr, arg0, arg1, addHeapObject(arg2));
|
|
3754
3784
|
var r0 = getDataViewMemory0().getInt32(retptr + 4 * 0, true);
|
|
3755
3785
|
var r1 = getDataViewMemory0().getInt32(retptr + 4 * 1, true);
|
|
3756
3786
|
if (r1) {
|
|
@@ -3761,10 +3791,10 @@ function __wasm_bindgen_func_elem_11107(arg0, arg1, arg2) {
|
|
|
3761
3791
|
}
|
|
3762
3792
|
}
|
|
3763
3793
|
|
|
3764
|
-
function
|
|
3794
|
+
function __wasm_bindgen_func_elem_11140_3(arg0, arg1, arg2) {
|
|
3765
3795
|
try {
|
|
3766
3796
|
const retptr = wasm.__wbindgen_add_to_stack_pointer(-16);
|
|
3767
|
-
wasm.
|
|
3797
|
+
wasm.__wasm_bindgen_func_elem_11140_3(retptr, arg0, arg1, addHeapObject(arg2));
|
|
3768
3798
|
var r0 = getDataViewMemory0().getInt32(retptr + 4 * 0, true);
|
|
3769
3799
|
var r1 = getDataViewMemory0().getInt32(retptr + 4 * 1, true);
|
|
3770
3800
|
if (r1) {
|
|
@@ -3775,10 +3805,10 @@ function __wasm_bindgen_func_elem_11107_3(arg0, arg1, arg2) {
|
|
|
3775
3805
|
}
|
|
3776
3806
|
}
|
|
3777
3807
|
|
|
3778
|
-
function
|
|
3808
|
+
function __wasm_bindgen_func_elem_11140_5(arg0, arg1, arg2) {
|
|
3779
3809
|
try {
|
|
3780
3810
|
const retptr = wasm.__wbindgen_add_to_stack_pointer(-16);
|
|
3781
|
-
wasm.
|
|
3811
|
+
wasm.__wasm_bindgen_func_elem_11140_5(retptr, arg0, arg1, addHeapObject(arg2));
|
|
3782
3812
|
var r0 = getDataViewMemory0().getInt32(retptr + 4 * 0, true);
|
|
3783
3813
|
var r1 = getDataViewMemory0().getInt32(retptr + 4 * 1, true);
|
|
3784
3814
|
if (r1) {
|
|
@@ -3789,10 +3819,10 @@ function __wasm_bindgen_func_elem_11107_5(arg0, arg1, arg2) {
|
|
|
3789
3819
|
}
|
|
3790
3820
|
}
|
|
3791
3821
|
|
|
3792
|
-
function
|
|
3822
|
+
function __wasm_bindgen_func_elem_11140_6(arg0, arg1, arg2) {
|
|
3793
3823
|
try {
|
|
3794
3824
|
const retptr = wasm.__wbindgen_add_to_stack_pointer(-16);
|
|
3795
|
-
wasm.
|
|
3825
|
+
wasm.__wasm_bindgen_func_elem_11140_6(retptr, arg0, arg1, addHeapObject(arg2));
|
|
3796
3826
|
var r0 = getDataViewMemory0().getInt32(retptr + 4 * 0, true);
|
|
3797
3827
|
var r1 = getDataViewMemory0().getInt32(retptr + 4 * 1, true);
|
|
3798
3828
|
if (r1) {
|
|
@@ -3803,10 +3833,10 @@ function __wasm_bindgen_func_elem_11107_6(arg0, arg1, arg2) {
|
|
|
3803
3833
|
}
|
|
3804
3834
|
}
|
|
3805
3835
|
|
|
3806
|
-
function
|
|
3836
|
+
function __wasm_bindgen_func_elem_11140_7(arg0, arg1, arg2) {
|
|
3807
3837
|
try {
|
|
3808
3838
|
const retptr = wasm.__wbindgen_add_to_stack_pointer(-16);
|
|
3809
|
-
wasm.
|
|
3839
|
+
wasm.__wasm_bindgen_func_elem_11140_7(retptr, arg0, arg1, addHeapObject(arg2));
|
|
3810
3840
|
var r0 = getDataViewMemory0().getInt32(retptr + 4 * 0, true);
|
|
3811
3841
|
var r1 = getDataViewMemory0().getInt32(retptr + 4 * 1, true);
|
|
3812
3842
|
if (r1) {
|
|
@@ -3817,8 +3847,8 @@ function __wasm_bindgen_func_elem_11107_7(arg0, arg1, arg2) {
|
|
|
3817
3847
|
}
|
|
3818
3848
|
}
|
|
3819
3849
|
|
|
3820
|
-
function
|
|
3821
|
-
wasm.
|
|
3850
|
+
function __wasm_bindgen_func_elem_14186(arg0, arg1, arg2, arg3) {
|
|
3851
|
+
wasm.__wasm_bindgen_func_elem_14186(arg0, arg1, addHeapObject(arg2), addHeapObject(arg3));
|
|
3822
3852
|
}
|
|
3823
3853
|
|
|
3824
3854
|
|
package/build/hana-ui.wasm
CHANGED
|
Binary file
|
|
@@ -1,93 +1,220 @@
|
|
|
1
|
-
|
|
2
1
|
export async function loadImageFromBytes(bytes) {
|
|
3
|
-
|
|
4
|
-
const url = URL.createObjectURL(blob)
|
|
5
|
-
return createImageBitmap(blob).finally(() => {
|
|
6
|
-
URL.revokeObjectURL(url)
|
|
7
|
-
})
|
|
2
|
+
return createImageBitmap(new Blob([bytes]))
|
|
8
3
|
}
|
|
9
4
|
|
|
10
5
|
export async function loadImageFromUrl(url) {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
6
|
+
return fetch(url)
|
|
7
|
+
.then((response) => {
|
|
8
|
+
if (!response.ok) {
|
|
9
|
+
throw new Error(`Failed to fetch image: ${response.statusText}`)
|
|
10
|
+
}
|
|
11
|
+
return response.blob()
|
|
12
|
+
})
|
|
13
|
+
.then((blob) => createImageBitmap(blob))
|
|
19
14
|
}
|
|
20
15
|
|
|
21
16
|
// Video Imports
|
|
22
|
-
function setupVideo(video) {
|
|
23
|
-
video.preload = "auto";
|
|
24
|
-
video.autoplay = true; //iOs hack to show the first frame
|
|
25
|
-
video.loop = true;
|
|
26
|
-
video.muted = true;
|
|
27
|
-
video.playsInline = true;
|
|
28
|
-
video.currentTime = 0.01;
|
|
29
|
-
video.load();
|
|
30
|
-
|
|
31
|
-
video.onloadeddata = () => {
|
|
32
|
-
video.muted = true;
|
|
33
|
-
// DOMException: The play() request was interrupted by a new load request.
|
|
34
|
-
// https://developers.google.com/web/updates/2017/06/play-request-was-interrupted
|
|
35
|
-
const playPromise = video.play();
|
|
36
|
-
|
|
37
|
-
if (playPromise !== undefined) {
|
|
38
|
-
playPromise
|
|
39
|
-
.then((_) => {
|
|
40
|
-
// Automatic playback started! Do nothing all good!
|
|
41
|
-
})
|
|
42
|
-
.catch(() => {
|
|
43
|
-
// for some reason the video could not be played (The play() request was interrupted by a new load request.)
|
|
44
|
-
// so let's try to play it again
|
|
45
|
-
video.play();
|
|
46
|
-
}).finally(() => {
|
|
47
|
-
video.pause();
|
|
48
|
-
});
|
|
49
|
-
}
|
|
50
|
-
};
|
|
51
|
-
|
|
52
|
-
let copyVideo = false;
|
|
53
|
-
video.addEventListener("playing", function() {
|
|
54
|
-
copyVideo = true;
|
|
55
|
-
}, true);
|
|
56
|
-
|
|
57
|
-
return new Promise((resolve) => {
|
|
58
|
-
const checkVideo = () => {
|
|
59
|
-
if (copyVideo) {
|
|
60
|
-
resolve(video);
|
|
61
|
-
} else {
|
|
62
|
-
setTimeout(checkVideo, 10);
|
|
63
|
-
}
|
|
64
|
-
};
|
|
65
|
-
checkVideo();
|
|
66
|
-
});
|
|
67
|
-
}
|
|
68
17
|
|
|
18
|
+
let videoFrameSeq = 0
|
|
69
19
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
20
|
+
// Fallback tokens (see `videoFrameToken`) are the media clock offset per element
|
|
21
|
+
// by this stride, so two elements' ranges stay disjoint for any media under ~11.5 days.
|
|
22
|
+
const MEDIA_CLOCK_STRIDE = 1e6
|
|
23
|
+
|
|
24
|
+
// `HTMLMediaElement.HAVE_CURRENT_DATA` — a decoded frame the renderer can sample.
|
|
25
|
+
const HAVE_CURRENT_DATA = 2
|
|
26
|
+
|
|
27
|
+
// Generous, because a slow decode is not a failure — but bounded, because a source
|
|
28
|
+
// the browser silently declines must not wedge the load forever.
|
|
29
|
+
const VIDEO_READY_TIMEOUT_MS = 30000
|
|
30
|
+
|
|
31
|
+
// Maintains a token that changes exactly when the element presents a new frame,
|
|
32
|
+
// so the renderer can tell whether a GPU copy is needed at all.
|
|
33
|
+
//
|
|
34
|
+
// The callback chain re-arms itself and keeps `video` reachable until
|
|
35
|
+
// `disposeVideo` cancels it. It only fires on a presented frame, so a paused or
|
|
36
|
+
// stalled element costs nothing.
|
|
37
|
+
function trackPresentedFrames(video) {
|
|
38
|
+
if (typeof video.requestVideoFrameCallback !== 'function') {
|
|
39
|
+
// No presented-frame signal: `videoFrameToken` falls back to the media clock,
|
|
40
|
+
// offset per element to keep the guarantee below.
|
|
41
|
+
video.hanaClockBase = ++videoFrameSeq * MEDIA_CLOCK_STRIDE
|
|
42
|
+
return
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// One global sequence, so no two elements ever report the same token: when an
|
|
46
|
+
// asset is replaced, the token the renderer still holds for the displaced
|
|
47
|
+
// element cannot collide with the replacement's.
|
|
48
|
+
video.hanaPresentedFrames = ++videoFrameSeq
|
|
49
|
+
const bump = () => {
|
|
50
|
+
// Disposal detaches the source, which fires `seeked` on the way out.
|
|
51
|
+
if (video.hanaDisposed) return
|
|
52
|
+
video.hanaPresentedFrames = ++videoFrameSeq
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// A seek repaints a paused element without necessarily running the frame
|
|
56
|
+
// callback, and the video export seeks frame by frame with the element paused —
|
|
57
|
+
// it must never be handed the texture from the previous seek.
|
|
58
|
+
video.addEventListener('seeked', bump)
|
|
59
|
+
|
|
60
|
+
const onFrame = () => {
|
|
61
|
+
bump()
|
|
62
|
+
// Never re-arm a disposed element: that resurrects the cancelled chain.
|
|
63
|
+
if (video.hanaDisposed) return
|
|
64
|
+
video.hanaFrameCallback = video.requestVideoFrameCallback(onFrame)
|
|
65
|
+
}
|
|
66
|
+
video.hanaFrameCallback = video.requestVideoFrameCallback(onFrame)
|
|
75
67
|
}
|
|
76
68
|
|
|
69
|
+
// Read by the renderer once per frame per video. Where
|
|
70
|
+
// `requestVideoFrameCallback` is missing, falls back to the media clock: browsers
|
|
71
|
+
// advance `currentTime` at frame granularity, so it answers the same question. The
|
|
72
|
+
// per-element base keeps the cross-element guarantee across a reload, where
|
|
73
|
+
// `Assets::insert_video` replaces the element under the same id.
|
|
74
|
+
export function videoFrameToken(video) {
|
|
75
|
+
const presented = video.hanaPresentedFrames
|
|
76
|
+
return presented === undefined ? video.hanaClockBase + video.currentTime : presented
|
|
77
|
+
}
|
|
77
78
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
79
|
+
// Only for `Local(bytes)`, which has no URL to stream from. A blob: URL pins its
|
|
80
|
+
// Blob — the whole file — until revoked, so keep it on the element for
|
|
81
|
+
// `disposeVideo` to release.
|
|
82
|
+
function setVideoSourceFromBlob(video, blob) {
|
|
83
|
+
video.hanaObjectUrl = URL.createObjectURL(blob)
|
|
84
|
+
video.src = video.hanaObjectUrl
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// Releases everything the element holds: the frame-callback chain, the blob: URL
|
|
88
|
+
// mapping, and the buffered media and decoder behind it. Runs from `OwnedVideo`'s
|
|
89
|
+
// `Drop` in `assets.rs`. Idempotent, and each step is independently conditional —
|
|
90
|
+
// an element loaded without a blob: URL still gets the rest of the teardown.
|
|
91
|
+
export function disposeVideo(video) {
|
|
92
|
+
if (video.hanaDisposed) return
|
|
93
|
+
// Set first: the steps below fire events that must not read as a live element.
|
|
94
|
+
video.hanaDisposed = true
|
|
95
|
+
|
|
96
|
+
const callback = video.hanaFrameCallback
|
|
97
|
+
if (callback !== undefined) {
|
|
98
|
+
video.hanaFrameCallback = undefined
|
|
99
|
+
// A throw here must not skip the rest of the teardown.
|
|
100
|
+
if (typeof video.cancelVideoFrameCallback === 'function') {
|
|
101
|
+
video.cancelVideoFrameCallback(callback)
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const url = video.hanaObjectUrl
|
|
106
|
+
if (url !== undefined) {
|
|
107
|
+
video.hanaObjectUrl = undefined
|
|
108
|
+
URL.revokeObjectURL(url)
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
video.pause()
|
|
112
|
+
// Revoking only drops the URL -> Blob mapping. Detaching the source is what
|
|
113
|
+
// actually releases the media the element has buffered and its decoder.
|
|
114
|
+
video.removeAttribute('src')
|
|
115
|
+
video.load()
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// Resolves once the element holds a frame the renderer can sample, rejects if it
|
|
119
|
+
// never will. Every branch settles: an unsettled load holds its `PendingLoadGuard`,
|
|
120
|
+
// and headless hosts poll `Assets::pending_loads` to know a document is ready.
|
|
121
|
+
//
|
|
122
|
+
// Readiness is `loadeddata`, not `playing`: the video export drives the element
|
|
123
|
+
// paused, so `playing` never fires there, and a blocked autoplay is not a failure.
|
|
124
|
+
function whenSampleable(video) {
|
|
125
|
+
const mediaError = (prefix) => {
|
|
126
|
+
const err = video.error
|
|
127
|
+
return new Error(err === null ? prefix : `${prefix} (code ${err.code}: ${err.message})`)
|
|
128
|
+
}
|
|
129
|
+
// A rejected load never becomes an `OwnedVideo`, so nothing else releases the
|
|
130
|
+
// element: it would pin its blob: URL for the page lifetime and, if it buffers
|
|
131
|
+
// after a timeout, decode invisibly on loop (`autoplay` and `loop` stay set). The
|
|
132
|
+
// error is built first — `disposeVideo`'s `load()` clears `video.error`.
|
|
133
|
+
const failure = (prefix) => {
|
|
134
|
+
const err = mediaError(prefix)
|
|
135
|
+
disposeVideo(video)
|
|
136
|
+
return err
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
if (video.error !== null) return Promise.reject(failure('video failed to load'))
|
|
140
|
+
if (video.readyState >= HAVE_CURRENT_DATA) return Promise.resolve(video)
|
|
141
|
+
|
|
142
|
+
return new Promise((resolve, reject) => {
|
|
143
|
+
// Armed first so `settle` can clear it; nothing below runs this tick.
|
|
144
|
+
const timer = setTimeout(
|
|
145
|
+
() => settle(reject, failure('video timed out before presenting a frame')),
|
|
146
|
+
VIDEO_READY_TIMEOUT_MS,
|
|
147
|
+
)
|
|
148
|
+
const settle = (finish, value) => {
|
|
149
|
+
clearTimeout(timer)
|
|
150
|
+
video.removeEventListener('loadeddata', onReady)
|
|
151
|
+
video.removeEventListener('canplay', onReady)
|
|
152
|
+
video.removeEventListener('error', onError)
|
|
153
|
+
finish(value)
|
|
154
|
+
}
|
|
155
|
+
const onReady = () => settle(resolve, video)
|
|
156
|
+
const onError = () => settle(reject, failure('video failed to load'))
|
|
157
|
+
|
|
158
|
+
video.addEventListener('loadeddata', onReady)
|
|
159
|
+
// A partly-loaded source can become sampleable without a fresh `loadeddata`.
|
|
160
|
+
video.addEventListener('canplay', onReady)
|
|
161
|
+
video.addEventListener('error', onError)
|
|
162
|
+
})
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
function setupVideo(video) {
|
|
166
|
+
video.preload = 'auto'
|
|
167
|
+
video.autoplay = true //iOs hack to show the first frame
|
|
168
|
+
video.loop = true
|
|
169
|
+
video.muted = true
|
|
170
|
+
video.playsInline = true
|
|
171
|
+
video.currentTime = 0.01
|
|
172
|
+
video.load()
|
|
173
|
+
|
|
174
|
+
video.onloadeddata = () => {
|
|
175
|
+
video.muted = true
|
|
176
|
+
// DOMException: The play() request was interrupted by a new load request.
|
|
177
|
+
// https://developers.google.com/web/updates/2017/06/play-request-was-interrupted
|
|
178
|
+
const playPromise = video.play()
|
|
179
|
+
|
|
180
|
+
if (playPromise !== undefined) {
|
|
181
|
+
playPromise
|
|
182
|
+
.then((_) => {
|
|
183
|
+
// Automatic playback started! Do nothing all good!
|
|
184
|
+
})
|
|
185
|
+
.catch(() => {
|
|
186
|
+
// for some reason the video could not be played (The play() request was interrupted by a new load request.)
|
|
187
|
+
// so let's try to play it again
|
|
188
|
+
video.play()
|
|
189
|
+
})
|
|
190
|
+
.finally(() => {
|
|
191
|
+
video.pause()
|
|
85
192
|
})
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
trackPresentedFrames(video)
|
|
197
|
+
|
|
198
|
+
return whenSampleable(video)
|
|
91
199
|
}
|
|
92
200
|
|
|
201
|
+
export async function loadVideoFromBytes(bytes) {
|
|
202
|
+
const video = document.createElement('video')
|
|
203
|
+
setVideoSourceFromBlob(video, new Blob([bytes]))
|
|
204
|
+
return setupVideo(video)
|
|
205
|
+
}
|
|
93
206
|
|
|
207
|
+
// Streams from `url` instead of fetching it into a Blob first, so the browser can
|
|
208
|
+
// range-request it, playback starts before the last byte lands, and the file never
|
|
209
|
+
// sits in memory whole.
|
|
210
|
+
//
|
|
211
|
+
// `crossOrigin` must be set before `src`: without it a cross-origin element is
|
|
212
|
+
// tainted and every texture upload throws a SecurityError. It costs no reach — the
|
|
213
|
+
// `fetch` this replaces was already a CORS request, so any URL that used to load
|
|
214
|
+
// still does.
|
|
215
|
+
export async function loadVideoFromUrl(url) {
|
|
216
|
+
const video = document.createElement('video')
|
|
217
|
+
video.crossOrigin = 'anonymous'
|
|
218
|
+
video.src = url
|
|
219
|
+
return setupVideo(video)
|
|
220
|
+
}
|