fundus 0.11.0 → 0.12.0
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/README.md +7 -5
- package/build/client/_app/immutable/assets/2.DOKgeL9V.css +1 -0
- package/build/client/_app/immutable/assets/2.DOKgeL9V.css.br +0 -0
- package/build/client/_app/immutable/assets/2.DOKgeL9V.css.gz +0 -0
- package/build/client/_app/immutable/assets/{3.BuCXJbRG.css → 3.D6PF4wEW.css} +1 -1
- package/build/client/_app/immutable/assets/3.D6PF4wEW.css.br +0 -0
- package/build/client/_app/immutable/assets/3.D6PF4wEW.css.gz +0 -0
- package/build/client/_app/immutable/assets/LibraryContextMenu.B6By2-yb.css +1 -0
- package/build/client/_app/immutable/assets/LibraryContextMenu.B6By2-yb.css.br +0 -0
- package/build/client/_app/immutable/assets/LibraryContextMenu.B6By2-yb.css.gz +0 -0
- package/build/client/_app/immutable/assets/Popover.x-2x_hPE.css +1 -0
- package/build/client/_app/immutable/assets/Popover.x-2x_hPE.css.br +0 -0
- package/build/client/_app/immutable/assets/Popover.x-2x_hPE.css.gz +0 -0
- package/build/client/_app/immutable/chunks/B9HdDE5n.js +1 -0
- package/build/client/_app/immutable/chunks/B9HdDE5n.js.br +0 -0
- package/build/client/_app/immutable/chunks/B9HdDE5n.js.gz +0 -0
- package/build/client/_app/immutable/chunks/BfQXIsM7.js +1 -0
- package/build/client/_app/immutable/chunks/BfQXIsM7.js.br +0 -0
- package/build/client/_app/immutable/chunks/BfQXIsM7.js.gz +0 -0
- package/build/client/_app/immutable/chunks/Bj31DJwy.js +1 -0
- package/build/client/_app/immutable/chunks/Bj31DJwy.js.br +0 -0
- package/build/client/_app/immutable/chunks/Bj31DJwy.js.gz +0 -0
- package/build/client/_app/immutable/chunks/{DFRlZOKz.js → BufZeTod.js} +2 -2
- package/build/client/_app/immutable/chunks/BufZeTod.js.br +0 -0
- package/build/client/_app/immutable/chunks/BufZeTod.js.gz +0 -0
- package/build/client/_app/immutable/chunks/BxCbL__p.js +1 -0
- package/build/client/_app/immutable/chunks/BxCbL__p.js.br +0 -0
- package/build/client/_app/immutable/chunks/BxCbL__p.js.gz +0 -0
- package/build/client/_app/immutable/chunks/{DpFtnEPx.js → CjB5zQ4_.js} +1 -1
- package/build/client/_app/immutable/chunks/CjB5zQ4_.js.br +0 -0
- package/build/client/_app/immutable/chunks/CjB5zQ4_.js.gz +0 -0
- package/build/client/_app/immutable/chunks/DuXWzgb4.js +108 -0
- package/build/client/_app/immutable/chunks/DuXWzgb4.js.br +0 -0
- package/build/client/_app/immutable/chunks/DuXWzgb4.js.gz +0 -0
- package/build/client/_app/immutable/chunks/{BMORe2Gb.js → YpiA5Xry.js} +1 -1
- package/build/client/_app/immutable/chunks/YpiA5Xry.js.br +0 -0
- package/build/client/_app/immutable/chunks/{BMORe2Gb.js.gz → YpiA5Xry.js.gz} +0 -0
- package/build/client/_app/immutable/entry/app.DHoRwUAh.js +2 -0
- package/build/client/_app/immutable/entry/app.DHoRwUAh.js.br +0 -0
- package/build/client/_app/immutable/entry/app.DHoRwUAh.js.gz +0 -0
- package/build/client/_app/immutable/entry/start.Xp5z9Bmx.js +1 -0
- package/build/client/_app/immutable/entry/start.Xp5z9Bmx.js.br +0 -0
- package/build/client/_app/immutable/entry/start.Xp5z9Bmx.js.gz +0 -0
- package/build/client/_app/immutable/nodes/0.Df_skSaZ.js +1 -0
- package/build/client/_app/immutable/nodes/0.Df_skSaZ.js.br +0 -0
- package/build/client/_app/immutable/nodes/0.Df_skSaZ.js.gz +0 -0
- package/build/client/_app/immutable/nodes/1.B-Gp2MAj.js +1 -0
- package/build/client/_app/immutable/nodes/1.B-Gp2MAj.js.br +0 -0
- package/build/client/_app/immutable/nodes/1.B-Gp2MAj.js.gz +0 -0
- package/build/client/_app/immutable/nodes/2.DFHvXQZ-.js +3 -0
- package/build/client/_app/immutable/nodes/2.DFHvXQZ-.js.br +0 -0
- package/build/client/_app/immutable/nodes/2.DFHvXQZ-.js.gz +0 -0
- package/build/client/_app/immutable/nodes/3.DVl0Y9iQ.js +1 -0
- package/build/client/_app/immutable/nodes/3.DVl0Y9iQ.js.br +0 -0
- package/build/client/_app/immutable/nodes/3.DVl0Y9iQ.js.gz +0 -0
- package/build/client/_app/immutable/nodes/4.Dw8y5rjM.js +2 -0
- package/build/client/_app/immutable/nodes/4.Dw8y5rjM.js.br +0 -0
- package/build/client/_app/immutable/nodes/4.Dw8y5rjM.js.gz +0 -0
- package/build/client/_app/version.json +1 -1
- package/build/client/_app/version.json.br +0 -0
- package/build/client/_app/version.json.gz +0 -0
- package/build/handler.js +7 -7
- package/build/index.js +7 -7
- package/build/server/chunks/chunks/{context.js-BJfk3ZiR.js → context.js-BNdRFspW.js} +100 -1
- package/build/server/chunks/chunks/context.js-BNdRFspW.js.map +1 -0
- package/build/server/chunks/chunks/{exports.js-aVVdkXgn.js → exports.js-CUFgHhPL.js} +2 -2
- package/build/server/chunks/chunks/{exports.js-aVVdkXgn.js.map → exports.js-CUFgHhPL.js.map} +1 -1
- package/build/server/chunks/chunks/{internal.js-C6cIfe5d.js → internal.js-DUquB_BJ.js} +3 -3
- package/build/server/chunks/chunks/{internal.js-C6cIfe5d.js.map → internal.js-DUquB_BJ.js.map} +1 -1
- package/build/server/chunks/chunks/internal2.js-BlXt2rfE.js.map +1 -1
- package/build/server/chunks/chunks/{shared.js-BNd0HSxp.js → shared.js-BXL1A-vX.js} +2 -2
- package/build/server/chunks/chunks/{shared.js-BNd0HSxp.js.map → shared.js-BXL1A-vX.js.map} +1 -1
- package/build/server/chunks/chunks/{utils.js-6HSqnONB.js → utils.js-xC62cOm2.js} +3 -3
- package/build/server/chunks/chunks/{utils.js-6HSqnONB.js.map → utils.js-xC62cOm2.js.map} +1 -1
- package/build/server/chunks/entries/endpoints/api/assets/_id_/{_server.ts.js-eMNgdaf4.js → _server.ts.js-DEfBMmVd.js} +2 -2
- package/build/server/chunks/entries/endpoints/api/assets/_id_/{_server.ts.js-eMNgdaf4.js.map → _server.ts.js-DEfBMmVd.js.map} +1 -1
- package/build/server/chunks/entries/endpoints/api/assets/_id_/raw/{_server.ts.js-CEQd4Wxn.js → _server.ts.js-BEJcx3ya.js} +2 -2
- package/build/server/chunks/entries/endpoints/api/assets/_id_/raw/{_server.ts.js-CEQd4Wxn.js.map → _server.ts.js-BEJcx3ya.js.map} +1 -1
- package/build/server/chunks/entries/endpoints/api/assets/_id_/reimport/{_server.ts.js-B2JoGCzy.js → _server.ts.js-CibkO-4G.js} +2 -2
- package/build/server/chunks/entries/endpoints/api/assets/_id_/reimport/{_server.ts.js-B2JoGCzy.js.map → _server.ts.js-CibkO-4G.js.map} +1 -1
- package/build/server/chunks/entries/endpoints/api/assets/{_server.ts.js-DTpkVywC.js → _server.ts.js-BdisCQk6.js} +2 -2
- package/build/server/chunks/entries/endpoints/api/assets/{_server.ts.js-DTpkVywC.js.map → _server.ts.js-BdisCQk6.js.map} +1 -1
- package/build/server/chunks/entries/endpoints/api/assets/delete/{_server.ts.js-D6Z_pGEZ.js → _server.ts.js-Bk2hojus.js} +2 -2
- package/build/server/chunks/entries/endpoints/api/assets/delete/{_server.ts.js-D6Z_pGEZ.js.map → _server.ts.js-Bk2hojus.js.map} +1 -1
- package/build/server/chunks/entries/endpoints/api/assets/move/{_server.ts.js-CnDi9p0a.js → _server.ts.js-CTeQlqUL.js} +2 -2
- package/build/server/chunks/entries/endpoints/api/assets/move/{_server.ts.js-CnDi9p0a.js.map → _server.ts.js-CTeQlqUL.js.map} +1 -1
- package/build/server/chunks/entries/endpoints/api/folders/_path_/{_server.ts.js-BCzuPX5Z.js → _server.ts.js-DLxdefrX.js} +2 -2
- package/build/server/chunks/entries/endpoints/api/folders/_path_/{_server.ts.js-BCzuPX5Z.js.map → _server.ts.js-DLxdefrX.js.map} +1 -1
- package/build/server/chunks/entries/endpoints/api/folders/{_server.ts.js-CGbYyJ6S.js → _server.ts.js-Ceq1tMTz.js} +2 -2
- package/build/server/chunks/entries/endpoints/api/folders/{_server.ts.js-CGbYyJ6S.js.map → _server.ts.js-Ceq1tMTz.js.map} +1 -1
- package/build/server/chunks/entries/endpoints/api/imports/{_server.ts.js-CL_89klb.js → _server.ts.js-DxAWqf9V.js} +2 -2
- package/build/server/chunks/entries/endpoints/api/imports/{_server.ts.js-CL_89klb.js.map → _server.ts.js-DxAWqf9V.js.map} +1 -1
- package/build/server/chunks/entries/endpoints/api/manifests/_name_/{_server.ts.js-DZUKtjz9.js → _server.ts.js-BB_6WywB.js} +2 -2
- package/build/server/chunks/entries/endpoints/api/manifests/_name_/{_server.ts.js-DZUKtjz9.js.map → _server.ts.js-BB_6WywB.js.map} +1 -1
- package/build/server/chunks/entries/endpoints/api/manifests/{_server.ts.js-Dz25LG_P.js → _server.ts.js-BLRkduAp.js} +2 -2
- package/build/server/chunks/entries/endpoints/api/manifests/{_server.ts.js-Dz25LG_P.js.map → _server.ts.js-BLRkduAp.js.map} +1 -1
- package/build/server/chunks/entries/endpoints/api/raw/adopt/{_server.ts.js-C-NuN3JJ.js → _server.ts.js-Ba94-p0V.js} +2 -2
- package/build/server/chunks/entries/endpoints/api/raw/adopt/{_server.ts.js-C-NuN3JJ.js.map → _server.ts.js-Ba94-p0V.js.map} +1 -1
- package/build/server/chunks/entries/endpoints/api/raw/edited/accept/{_server.ts.js-CwL9UPh3.js → _server.ts.js-CNhj0R4-.js} +2 -2
- package/build/server/chunks/entries/endpoints/api/raw/edited/accept/{_server.ts.js-CwL9UPh3.js.map → _server.ts.js-CNhj0R4-.js.map} +1 -1
- package/build/server/chunks/entries/endpoints/api/raw/edited/dismiss/{_server.ts.js-DanXYrj1.js → _server.ts.js-DWEV8mgx.js} +2 -2
- package/build/server/chunks/entries/endpoints/api/raw/edited/dismiss/{_server.ts.js-DanXYrj1.js.map → _server.ts.js-DWEV8mgx.js.map} +1 -1
- package/build/server/chunks/entries/endpoints/api/reconcile/{_server.ts.js-BZrun0up.js → _server.ts.js-BnDnZdPy.js} +2 -2
- package/build/server/chunks/entries/endpoints/api/reconcile/{_server.ts.js-BZrun0up.js.map → _server.ts.js-BnDnZdPy.js.map} +1 -1
- package/build/server/chunks/entries/endpoints/api/reveal/{_server.ts.js-DQOabHbS.js → _server.ts.js-1y2Lu6HB.js} +2 -2
- package/build/server/chunks/entries/endpoints/api/reveal/{_server.ts.js-DQOabHbS.js.map → _server.ts.js-1y2Lu6HB.js.map} +1 -1
- package/build/server/chunks/entries/endpoints/api/state/{_server.ts.js-k8Ueydx1.js → _server.ts.js-CnLJo2iZ.js} +2 -2
- package/build/server/chunks/entries/endpoints/api/state/{_server.ts.js-k8Ueydx1.js.map → _server.ts.js-CnLJo2iZ.js.map} +1 -1
- package/build/server/chunks/entries/endpoints/api/usage/{_server.ts.js-DmMmiFiu.js → _server.ts.js-Dqs6d6ul.js} +2 -2
- package/build/server/chunks/entries/endpoints/api/usage/{_server.ts.js-DmMmiFiu.js.map → _server.ts.js-Dqs6d6ul.js.map} +1 -1
- package/build/server/chunks/entries/endpoints/files/proxy/_...path_/{_server.ts.js-BQTC0OSC.js → _server.ts.js-BHdIopR2.js} +2 -2
- package/build/server/chunks/entries/endpoints/files/proxy/_...path_/{_server.ts.js-BQTC0OSC.js.map → _server.ts.js-BHdIopR2.js.map} +1 -1
- package/build/server/chunks/entries/endpoints/files/raw/_...path_/{_server.ts.js-mv9cqSuS.js → _server.ts.js-CFWfVYjg.js} +2 -2
- package/build/server/chunks/entries/endpoints/files/raw/_...path_/{_server.ts.js-mv9cqSuS.js.map → _server.ts.js-CFWfVYjg.js.map} +1 -1
- package/build/server/chunks/entries/{hooks.server.js-BNQO3cZr.js → hooks.server.js-BT84TVOj.js} +2 -2
- package/build/server/chunks/entries/{hooks.server.js-BNQO3cZr.js.map → hooks.server.js-BT84TVOj.js.map} +1 -1
- package/build/server/chunks/{handler-rG16rikT.js → handler-ChRrsqKi.js} +5 -5
- package/build/server/chunks/{handler-rG16rikT.js.map → handler-ChRrsqKi.js.map} +1 -1
- package/build/server/chunks/{index.js-DYK_SfL5.js → index.js-1EfZPKuZ.js} +5 -5
- package/build/server/chunks/{index.js-DYK_SfL5.js.map → index.js-1EfZPKuZ.js.map} +1 -1
- package/build/server/chunks/{manifest.js-h2lDpEPs.js → manifest.js-DR0qLyIe.js} +27 -27
- package/build/server/chunks/{manifest.js-h2lDpEPs.js.map → manifest.js-DR0qLyIe.js.map} +1 -1
- package/build/server/chunks/nodes/{0.js-Db28RwnA.js → 0.js-CHb0mVEc.js} +2 -2
- package/build/server/chunks/nodes/{0.js-Db28RwnA.js.map → 0.js-CHb0mVEc.js.map} +1 -1
- package/build/server/chunks/nodes/1.js-DXh-Y7sw.js +7 -0
- package/build/server/chunks/nodes/{1.js-D26Uv3YT.js.map → 1.js-DXh-Y7sw.js.map} +1 -1
- package/build/server/chunks/nodes/2.js-BSEFm6uL.js +7 -0
- package/build/server/chunks/nodes/2.js-BSEFm6uL.js.map +1 -0
- package/build/server/chunks/nodes/3.js-Cob8rFik.js +7 -0
- package/build/server/chunks/nodes/3.js-Cob8rFik.js.map +1 -0
- package/build/server/chunks/nodes/4.js-CsI1onmR.js +7 -0
- package/build/server/chunks/nodes/4.js-CsI1onmR.js.map +1 -0
- package/dist/core/api-types.d.ts +2 -0
- package/dist/runtime/chroma-key-video-lifecycle.d.ts +2 -2
- package/dist/runtime/chroma-key-video-lifecycle.js +220 -124
- package/dist/runtime/components/ChromaKeyVideo.svelte +13 -2
- package/dist/runtime/components/Slice.svelte +22 -12
- package/dist/runtime/components/Slice.svelte.d.ts +3 -6
- package/dist/runtime/components/SliceCanvas.svelte +51 -40
- package/dist/runtime/components/SliceCanvas.svelte.d.ts +2 -2
- package/dist/runtime/components/SliceDom.svelte +96 -42
- package/dist/runtime/components/SliceDom.svelte.d.ts +2 -2
- package/dist/runtime/slice-source-lifecycle.d.ts +14 -0
- package/dist/runtime/slice-source-lifecycle.js +70 -0
- package/dist/server/check.d.ts +1 -1
- package/dist/server/check.js +16 -2
- package/dist/server/cli-commands.d.ts +1 -1
- package/dist/server/cli-commands.js +2 -1
- package/dist/server/image-duplicates.d.ts +18 -0
- package/dist/server/image-duplicates.js +138 -0
- package/dist/server/operations.js +4 -0
- package/package.json +4 -1
- package/build/client/_app/immutable/assets/2.6TBn1n3x.css +0 -1
- package/build/client/_app/immutable/assets/2.6TBn1n3x.css.br +0 -0
- package/build/client/_app/immutable/assets/2.6TBn1n3x.css.gz +0 -0
- package/build/client/_app/immutable/assets/3.BuCXJbRG.css.br +0 -0
- package/build/client/_app/immutable/assets/3.BuCXJbRG.css.gz +0 -0
- package/build/client/_app/immutable/assets/ConfirmDialog.BdKlitwi.css +0 -1
- package/build/client/_app/immutable/assets/ConfirmDialog.BdKlitwi.css.br +0 -0
- package/build/client/_app/immutable/assets/ConfirmDialog.BdKlitwi.css.gz +0 -0
- package/build/client/_app/immutable/assets/LibraryContextMenu.BSnKt_Is.css +0 -1
- package/build/client/_app/immutable/assets/LibraryContextMenu.BSnKt_Is.css.br +0 -0
- package/build/client/_app/immutable/assets/LibraryContextMenu.BSnKt_Is.css.gz +0 -0
- package/build/client/_app/immutable/assets/Popover.Et4sBRbI.css +0 -1
- package/build/client/_app/immutable/assets/Popover.Et4sBRbI.css.br +0 -0
- package/build/client/_app/immutable/assets/Popover.Et4sBRbI.css.gz +0 -0
- package/build/client/_app/immutable/chunks/BAva9YXm.js +0 -1
- package/build/client/_app/immutable/chunks/BAva9YXm.js.br +0 -0
- package/build/client/_app/immutable/chunks/BAva9YXm.js.gz +0 -0
- package/build/client/_app/immutable/chunks/BGAUmugt.js +0 -1
- package/build/client/_app/immutable/chunks/BGAUmugt.js.br +0 -0
- package/build/client/_app/immutable/chunks/BGAUmugt.js.gz +0 -0
- package/build/client/_app/immutable/chunks/BMORe2Gb.js.br +0 -0
- package/build/client/_app/immutable/chunks/CBygkKLq.js +0 -1
- package/build/client/_app/immutable/chunks/CBygkKLq.js.br +0 -0
- package/build/client/_app/immutable/chunks/CBygkKLq.js.gz +0 -0
- package/build/client/_app/immutable/chunks/Cil0r9BR.js +0 -108
- package/build/client/_app/immutable/chunks/Cil0r9BR.js.br +0 -0
- package/build/client/_app/immutable/chunks/Cil0r9BR.js.gz +0 -0
- package/build/client/_app/immutable/chunks/D-p-QSzz.js +0 -1
- package/build/client/_app/immutable/chunks/D-p-QSzz.js.br +0 -0
- package/build/client/_app/immutable/chunks/D-p-QSzz.js.gz +0 -0
- package/build/client/_app/immutable/chunks/D15yxLq9.js +0 -1
- package/build/client/_app/immutable/chunks/D15yxLq9.js.br +0 -0
- package/build/client/_app/immutable/chunks/D15yxLq9.js.gz +0 -0
- package/build/client/_app/immutable/chunks/DFRlZOKz.js.br +0 -0
- package/build/client/_app/immutable/chunks/DFRlZOKz.js.gz +0 -0
- package/build/client/_app/immutable/chunks/DpFtnEPx.js.br +0 -4
- package/build/client/_app/immutable/chunks/DpFtnEPx.js.gz +0 -0
- package/build/client/_app/immutable/entry/app.BaP08B0_.js +0 -2
- package/build/client/_app/immutable/entry/app.BaP08B0_.js.br +0 -0
- package/build/client/_app/immutable/entry/app.BaP08B0_.js.gz +0 -0
- package/build/client/_app/immutable/entry/start.DntfygIK.js +0 -1
- package/build/client/_app/immutable/entry/start.DntfygIK.js.br +0 -0
- package/build/client/_app/immutable/entry/start.DntfygIK.js.gz +0 -0
- package/build/client/_app/immutable/nodes/0.6A6JK0Xm.js +0 -1
- package/build/client/_app/immutable/nodes/0.6A6JK0Xm.js.br +0 -0
- package/build/client/_app/immutable/nodes/0.6A6JK0Xm.js.gz +0 -0
- package/build/client/_app/immutable/nodes/1.8K7wLW9R.js +0 -1
- package/build/client/_app/immutable/nodes/1.8K7wLW9R.js.br +0 -0
- package/build/client/_app/immutable/nodes/1.8K7wLW9R.js.gz +0 -0
- package/build/client/_app/immutable/nodes/2.CB_PeUBC.js +0 -3
- package/build/client/_app/immutable/nodes/2.CB_PeUBC.js.br +0 -0
- package/build/client/_app/immutable/nodes/2.CB_PeUBC.js.gz +0 -0
- package/build/client/_app/immutable/nodes/3.qIkVQyP6.js +0 -1
- package/build/client/_app/immutable/nodes/3.qIkVQyP6.js.br +0 -0
- package/build/client/_app/immutable/nodes/3.qIkVQyP6.js.gz +0 -0
- package/build/client/_app/immutable/nodes/4.SmoVIy5D.js +0 -2
- package/build/client/_app/immutable/nodes/4.SmoVIy5D.js.br +0 -0
- package/build/client/_app/immutable/nodes/4.SmoVIy5D.js.gz +0 -0
- package/build/server/chunks/chunks/context.js-BJfk3ZiR.js.map +0 -1
- package/build/server/chunks/nodes/1.js-D26Uv3YT.js +0 -7
- package/build/server/chunks/nodes/2.js-BeRlEFid.js +0 -7
- package/build/server/chunks/nodes/2.js-BeRlEFid.js.map +0 -1
- package/build/server/chunks/nodes/3.js-BRUNmCyG.js +0 -7
- package/build/server/chunks/nodes/3.js-BRUNmCyG.js.map +0 -1
- package/build/server/chunks/nodes/4.js-CTHWqDf1.js +0 -7
- package/build/server/chunks/nodes/4.js-CTHWqDf1.js.map +0 -1
|
@@ -5,63 +5,81 @@ function copyChromaKey(parameters) {
|
|
|
5
5
|
keyColor: [...parameters.keyColor]
|
|
6
6
|
};
|
|
7
7
|
}
|
|
8
|
-
|
|
8
|
+
function copyPlaybackSettings(from, to) {
|
|
9
|
+
to.loop = from.loop;
|
|
10
|
+
to.playbackRate = from.playbackRate;
|
|
11
|
+
}
|
|
12
|
+
/** Internal media state machine that stages replacements before presenting them. */
|
|
9
13
|
export class ChromaKeyVideoLifecycle {
|
|
10
|
-
#
|
|
14
|
+
#videos;
|
|
11
15
|
#renderer;
|
|
12
|
-
#
|
|
16
|
+
#onPresentedVideoChange;
|
|
17
|
+
#presented = null;
|
|
18
|
+
#loading = null;
|
|
13
19
|
#sourceGeneration = 0;
|
|
14
20
|
#playing = false;
|
|
15
21
|
#disposed = false;
|
|
22
|
+
#frameHeld = false;
|
|
16
23
|
#loopGeneration = 0;
|
|
17
24
|
#loopRunning = false;
|
|
18
25
|
#pendingFrame = null;
|
|
19
|
-
constructor(video, renderer) {
|
|
20
|
-
this.#
|
|
26
|
+
constructor(video, renderer, standbyVideo = video, onPresentedVideoChange) {
|
|
27
|
+
this.#videos = [video, standbyVideo];
|
|
21
28
|
this.#renderer = renderer;
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
video.addEventListener('seeked', this.#handleSeeked);
|
|
26
|
-
video.addEventListener('loadeddata', this.#handleLoadedData);
|
|
27
|
-
video.addEventListener('error', this.#handleError);
|
|
29
|
+
this.#onPresentedVideoChange = onPresentedVideoChange;
|
|
30
|
+
for (const element of new Set(this.#videos))
|
|
31
|
+
this.#listen(element);
|
|
28
32
|
}
|
|
29
33
|
switchSource(snapshot) {
|
|
30
34
|
if (this.#disposed)
|
|
31
35
|
return;
|
|
32
|
-
const
|
|
33
|
-
if (
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
36
|
+
const loading = this.#loading;
|
|
37
|
+
if (loading && this.#matches(loading, snapshot) && loading.status === 'loading')
|
|
38
|
+
return;
|
|
39
|
+
const presented = this.#presented;
|
|
40
|
+
if (presented && this.#matches(presented, snapshot) && presented.status === 'settled') {
|
|
41
|
+
this.#cancelLoading();
|
|
42
|
+
presented.chromaKey = copyChromaKey(snapshot.chromaKey);
|
|
37
43
|
return;
|
|
38
44
|
}
|
|
39
|
-
this.#
|
|
40
|
-
this.#
|
|
45
|
+
this.#cancelLoading();
|
|
46
|
+
const loadingVideo = this.#loadingVideoFor(presented);
|
|
47
|
+
const playAfterHandoff = presented
|
|
48
|
+
? this.#playing &&
|
|
49
|
+
(presented.video.ended || !presented.video.paused || presented.playRequestPending)
|
|
50
|
+
: this.#playing;
|
|
51
|
+
if (presented && loadingVideo === presented.video) {
|
|
52
|
+
this.#invalidatePlayRequest(presented);
|
|
53
|
+
this.#stopLoop();
|
|
54
|
+
presented.status = 'disposed';
|
|
55
|
+
this.#presented = null;
|
|
56
|
+
presented.video.pause();
|
|
57
|
+
}
|
|
41
58
|
const active = {
|
|
42
59
|
generation: ++this.#sourceGeneration,
|
|
43
60
|
status: 'loading',
|
|
61
|
+
video: loadingVideo,
|
|
44
62
|
rawVideoUrl: snapshot.rawVideoUrl,
|
|
45
63
|
normalizedVideoUrl: snapshot.resolvedVideoUrl,
|
|
46
64
|
maskUrl: snapshot.maskUrl,
|
|
65
|
+
maskImage: null,
|
|
47
66
|
chromaKey: copyChromaKey(snapshot.chromaKey),
|
|
48
67
|
mediaSettled: false,
|
|
49
68
|
maskSettled: !snapshot.maskUrl,
|
|
69
|
+
playAfterHandoff,
|
|
50
70
|
playRequestToken: 0,
|
|
51
71
|
playRequestPending: false,
|
|
52
72
|
playRejectedForCurrentIntent: false
|
|
53
73
|
};
|
|
54
|
-
this.#
|
|
55
|
-
this.#
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
// draw and the new source's first decodable frame.
|
|
60
|
-
this.#video.src = snapshot.resolvedVideoUrl;
|
|
74
|
+
this.#loading = active;
|
|
75
|
+
if (!this.#frameHeld)
|
|
76
|
+
this.#renderer.setMask(null);
|
|
77
|
+
loadingVideo.pause();
|
|
78
|
+
loadingVideo.src = snapshot.resolvedVideoUrl;
|
|
61
79
|
// Reading the reflected property gives the browser-normalized absolute URL.
|
|
62
|
-
active.normalizedVideoUrl =
|
|
63
|
-
|
|
64
|
-
this.#primeMedia();
|
|
80
|
+
active.normalizedVideoUrl = loadingVideo.src;
|
|
81
|
+
loadingVideo.load();
|
|
82
|
+
this.#primeMedia(active);
|
|
65
83
|
if (snapshot.maskUrl)
|
|
66
84
|
this.#loadMask(active, snapshot.maskUrl);
|
|
67
85
|
}
|
|
@@ -70,30 +88,34 @@ export class ChromaKeyVideoLifecycle {
|
|
|
70
88
|
return;
|
|
71
89
|
const changed = this.#playing !== playing;
|
|
72
90
|
this.#playing = playing;
|
|
73
|
-
const
|
|
91
|
+
const presented = this.#presented;
|
|
92
|
+
const loading = this.#loading;
|
|
93
|
+
if (loading?.status === 'loading')
|
|
94
|
+
loading.playAfterHandoff = playing;
|
|
74
95
|
if (!playing) {
|
|
75
|
-
this.#invalidatePlayRequest(
|
|
96
|
+
this.#invalidatePlayRequest(presented);
|
|
76
97
|
this.#stopLoop();
|
|
77
|
-
|
|
78
|
-
// decodable frame. Some engines treat preload="auto" as metadata-only and
|
|
79
|
-
// do not reach loadeddata until playback has started.
|
|
80
|
-
if (!active || active.status !== 'loading' || active.mediaSettled) {
|
|
81
|
-
this.#video.pause();
|
|
82
|
-
}
|
|
98
|
+
presented?.video.pause();
|
|
83
99
|
return;
|
|
84
100
|
}
|
|
85
|
-
if (changed &&
|
|
86
|
-
|
|
87
|
-
|
|
101
|
+
if (changed && presented)
|
|
102
|
+
presented.playRejectedForCurrentIntent = false;
|
|
103
|
+
if (presented) {
|
|
104
|
+
presented.playAfterHandoff = true;
|
|
105
|
+
this.#requestPlayback(presented);
|
|
106
|
+
}
|
|
88
107
|
}
|
|
89
108
|
setChromaKey(parameters) {
|
|
90
109
|
if (this.#disposed)
|
|
91
110
|
return;
|
|
92
|
-
const active = this.#
|
|
111
|
+
const active = this.#loading ?? this.#presented;
|
|
93
112
|
if (!active || active.status === 'failed' || active.status === 'disposed')
|
|
94
113
|
return;
|
|
95
114
|
active.chromaKey = copyChromaKey(parameters);
|
|
96
|
-
if (active
|
|
115
|
+
if (active === this.#presented &&
|
|
116
|
+
active.status === 'settled' &&
|
|
117
|
+
active.video.paused &&
|
|
118
|
+
this.#isCurrentResource(active)) {
|
|
97
119
|
this.#draw(active);
|
|
98
120
|
}
|
|
99
121
|
}
|
|
@@ -102,19 +124,52 @@ export class ChromaKeyVideoLifecycle {
|
|
|
102
124
|
return;
|
|
103
125
|
this.#disposed = true;
|
|
104
126
|
this.#sourceGeneration++;
|
|
105
|
-
|
|
106
|
-
this.#invalidatePlayRequest(
|
|
127
|
+
this.#invalidatePlayRequest(this.#presented);
|
|
128
|
+
this.#invalidatePlayRequest(this.#loading);
|
|
107
129
|
this.#stopLoop();
|
|
108
|
-
if (
|
|
109
|
-
|
|
110
|
-
this.#
|
|
111
|
-
|
|
112
|
-
this.#
|
|
113
|
-
this.#
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
130
|
+
if (this.#presented)
|
|
131
|
+
this.#presented.status = 'disposed';
|
|
132
|
+
if (this.#loading)
|
|
133
|
+
this.#loading.status = 'disposed';
|
|
134
|
+
this.#presented = null;
|
|
135
|
+
this.#loading = null;
|
|
136
|
+
for (const video of new Set(this.#videos)) {
|
|
137
|
+
this.#unlisten(video);
|
|
138
|
+
video.pause();
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
#listen(video) {
|
|
142
|
+
video.addEventListener('play', this.#handlePlay);
|
|
143
|
+
video.addEventListener('pause', this.#handleStop);
|
|
144
|
+
video.addEventListener('ended', this.#handleStop);
|
|
145
|
+
video.addEventListener('seeked', this.#handleSeeked);
|
|
146
|
+
video.addEventListener('loadeddata', this.#handleLoadedData);
|
|
147
|
+
video.addEventListener('error', this.#handleError);
|
|
148
|
+
}
|
|
149
|
+
#unlisten(video) {
|
|
150
|
+
video.removeEventListener('play', this.#handlePlay);
|
|
151
|
+
video.removeEventListener('pause', this.#handleStop);
|
|
152
|
+
video.removeEventListener('ended', this.#handleStop);
|
|
153
|
+
video.removeEventListener('seeked', this.#handleSeeked);
|
|
154
|
+
video.removeEventListener('loadeddata', this.#handleLoadedData);
|
|
155
|
+
video.removeEventListener('error', this.#handleError);
|
|
156
|
+
}
|
|
157
|
+
#matches(active, snapshot) {
|
|
158
|
+
return active.rawVideoUrl === snapshot.rawVideoUrl && active.maskUrl === snapshot.maskUrl;
|
|
159
|
+
}
|
|
160
|
+
#loadingVideoFor(presented) {
|
|
161
|
+
if (!presented)
|
|
162
|
+
return this.#videos[0];
|
|
163
|
+
return this.#videos[0] === presented.video ? this.#videos[1] : this.#videos[0];
|
|
164
|
+
}
|
|
165
|
+
#cancelLoading() {
|
|
166
|
+
const loading = this.#loading;
|
|
167
|
+
if (!loading)
|
|
168
|
+
return;
|
|
169
|
+
this.#invalidatePlayRequest(loading);
|
|
170
|
+
loading.status = 'disposed';
|
|
171
|
+
loading.video.pause();
|
|
172
|
+
this.#loading = null;
|
|
118
173
|
}
|
|
119
174
|
#loadMask(active, maskUrl) {
|
|
120
175
|
const image = new Image();
|
|
@@ -122,122 +177,161 @@ export class ChromaKeyVideoLifecycle {
|
|
|
122
177
|
image
|
|
123
178
|
.decode()
|
|
124
179
|
.then(() => {
|
|
125
|
-
if (!this.#
|
|
180
|
+
if (!this.#isLoading(active))
|
|
126
181
|
return;
|
|
127
|
-
|
|
182
|
+
active.maskImage = image;
|
|
128
183
|
active.maskSettled = true;
|
|
129
184
|
this.#settleIfReady(active);
|
|
130
185
|
})
|
|
131
186
|
.catch(() => {
|
|
132
|
-
if (!this.#
|
|
187
|
+
if (!this.#isLoading(active))
|
|
133
188
|
return;
|
|
134
189
|
console.warn(`Failed to load chroma-key mask "${maskUrl}".`);
|
|
135
190
|
active.maskSettled = true;
|
|
136
191
|
this.#settleIfReady(active);
|
|
137
192
|
});
|
|
138
193
|
}
|
|
139
|
-
#primeMedia() {
|
|
194
|
+
#primeMedia(active) {
|
|
140
195
|
let request;
|
|
141
196
|
try {
|
|
142
|
-
request =
|
|
197
|
+
request = active.video.play();
|
|
143
198
|
}
|
|
144
199
|
catch {
|
|
145
200
|
return;
|
|
146
201
|
}
|
|
147
202
|
Promise.resolve(request).catch(() => {
|
|
148
|
-
// Muted warm-up is best-effort.
|
|
149
|
-
// normal settled playback request below gets its own retry state.
|
|
203
|
+
// Muted warm-up is best-effort. preload may still reach loadeddata.
|
|
150
204
|
});
|
|
151
205
|
}
|
|
152
|
-
#handleLoadedData = () => {
|
|
153
|
-
const active = this.#
|
|
154
|
-
if (!active || active.
|
|
206
|
+
#handleLoadedData = (event) => {
|
|
207
|
+
const active = this.#loading;
|
|
208
|
+
if (!active || active.video !== event.currentTarget || !this.#isLoading(active))
|
|
155
209
|
return;
|
|
156
210
|
if (!this.#isCurrentResource(active))
|
|
157
211
|
return;
|
|
158
212
|
active.mediaSettled = true;
|
|
159
|
-
|
|
160
|
-
|
|
213
|
+
// Freeze the staged source at its first decodable frame until its mask is ready.
|
|
214
|
+
active.video.pause();
|
|
161
215
|
this.#settleIfReady(active);
|
|
162
216
|
};
|
|
163
|
-
#handleError = () => {
|
|
164
|
-
const
|
|
165
|
-
|
|
217
|
+
#handleError = (event) => {
|
|
218
|
+
const video = event.currentTarget;
|
|
219
|
+
const loading = this.#loading;
|
|
220
|
+
if (loading?.video === video && loading.status === 'loading') {
|
|
221
|
+
loading.status = 'failed';
|
|
222
|
+
this.#invalidatePlayRequest(loading);
|
|
223
|
+
loading.video.pause();
|
|
224
|
+
// A failed replacement never erases an already presented frame.
|
|
225
|
+
if (!this.#presented && !this.#frameHeld)
|
|
226
|
+
this.#clear();
|
|
166
227
|
return;
|
|
167
|
-
|
|
168
|
-
this.#
|
|
228
|
+
}
|
|
229
|
+
const presented = this.#presented;
|
|
230
|
+
if (!presented || presented.video !== video || presented.status !== 'settled')
|
|
231
|
+
return;
|
|
232
|
+
presented.status = 'failed';
|
|
233
|
+
this.#invalidatePlayRequest(presented);
|
|
169
234
|
this.#stopLoop();
|
|
170
|
-
|
|
171
|
-
this.#
|
|
235
|
+
presented.video.pause();
|
|
236
|
+
if (!this.#loading)
|
|
237
|
+
this.#clear();
|
|
172
238
|
};
|
|
173
|
-
#handlePlay = () => {
|
|
174
|
-
const
|
|
175
|
-
|
|
176
|
-
// Drawing and the frame loop remain gated on video + mask settlement.
|
|
177
|
-
if (active?.status === 'loading')
|
|
239
|
+
#handlePlay = (event) => {
|
|
240
|
+
const video = event.currentTarget;
|
|
241
|
+
if (this.#loading?.video === video)
|
|
178
242
|
return;
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
243
|
+
const presented = this.#presented;
|
|
244
|
+
if (!presented || presented.video !== video || presented.status !== 'settled') {
|
|
245
|
+
if (video && 'pause' in video)
|
|
246
|
+
video.pause();
|
|
182
247
|
return;
|
|
183
248
|
}
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
249
|
+
presented.playRejectedForCurrentIntent = false;
|
|
250
|
+
presented.playAfterHandoff = true;
|
|
251
|
+
if (this.#loading?.status === 'loading')
|
|
252
|
+
this.#loading.playAfterHandoff = true;
|
|
253
|
+
if (!presented.video.paused)
|
|
254
|
+
this.#startLoop(presented);
|
|
187
255
|
};
|
|
188
|
-
#handleStop = () => {
|
|
189
|
-
const
|
|
256
|
+
#handleStop = (event) => {
|
|
257
|
+
const presented = this.#presented;
|
|
258
|
+
if (!presented || presented.video !== event.currentTarget || presented.status !== 'settled')
|
|
259
|
+
return;
|
|
260
|
+
this.#invalidatePlayRequest(presented);
|
|
190
261
|
this.#stopLoop();
|
|
191
|
-
|
|
192
|
-
|
|
262
|
+
this.#draw(presented);
|
|
263
|
+
presented.playAfterHandoff = false;
|
|
264
|
+
if (this.#loading?.status === 'loading')
|
|
265
|
+
this.#loading.playAfterHandoff = false;
|
|
193
266
|
};
|
|
194
|
-
#handleSeeked = () => {
|
|
195
|
-
const
|
|
196
|
-
if (!
|
|
267
|
+
#handleSeeked = (event) => {
|
|
268
|
+
const presented = this.#presented;
|
|
269
|
+
if (!presented ||
|
|
270
|
+
presented.video !== event.currentTarget ||
|
|
271
|
+
presented.status !== 'settled' ||
|
|
272
|
+
!presented.video.paused) {
|
|
197
273
|
return;
|
|
198
|
-
|
|
274
|
+
}
|
|
275
|
+
this.#draw(presented);
|
|
199
276
|
};
|
|
200
277
|
#settleIfReady(active) {
|
|
201
|
-
if (!this.#
|
|
202
|
-
active.status !== 'loading' ||
|
|
278
|
+
if (!this.#isLoading(active) ||
|
|
203
279
|
!active.mediaSettled ||
|
|
204
|
-
!active.maskSettled
|
|
280
|
+
!active.maskSettled ||
|
|
281
|
+
!this.#isCurrentResource(active)) {
|
|
205
282
|
return;
|
|
206
283
|
}
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
284
|
+
const previous = this.#presented;
|
|
285
|
+
this.#stopLoop();
|
|
286
|
+
this.#invalidatePlayRequest(previous);
|
|
287
|
+
if (previous && previous.video !== active.video) {
|
|
288
|
+
copyPlaybackSettings(previous.video, active.video);
|
|
210
289
|
}
|
|
211
|
-
|
|
212
|
-
this.#video.pause();
|
|
290
|
+
active.video.pause();
|
|
213
291
|
active.status = 'settled';
|
|
292
|
+
this.#loading = null;
|
|
293
|
+
this.#presented = active;
|
|
294
|
+
this.#renderer.setMask(active.maskImage);
|
|
214
295
|
this.#draw(active);
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
296
|
+
this.#onPresentedVideoChange?.(active.video);
|
|
297
|
+
if (previous && previous !== active) {
|
|
298
|
+
previous.status = 'disposed';
|
|
299
|
+
previous.video.pause();
|
|
300
|
+
}
|
|
301
|
+
if (this.#playing && active.playAfterHandoff)
|
|
218
302
|
this.#requestPlayback(active);
|
|
219
303
|
}
|
|
220
304
|
#draw(active) {
|
|
221
|
-
if (
|
|
305
|
+
if (this.#disposed ||
|
|
306
|
+
this.#presented !== active ||
|
|
222
307
|
active.status !== 'settled' ||
|
|
223
308
|
!this.#isCurrentResource(active)) {
|
|
224
309
|
return;
|
|
225
310
|
}
|
|
226
|
-
this.#renderer.draw(
|
|
311
|
+
this.#renderer.draw(active.video, active.chromaKey);
|
|
312
|
+
this.#frameHeld = true;
|
|
313
|
+
}
|
|
314
|
+
#clear() {
|
|
315
|
+
this.#renderer.clear();
|
|
316
|
+
this.#frameHeld = false;
|
|
227
317
|
}
|
|
228
318
|
#isCurrentResource(active) {
|
|
229
|
-
return (
|
|
230
|
-
|
|
319
|
+
return (active.video.readyState >= HAVE_CURRENT_DATA &&
|
|
320
|
+
active.video.currentSrc === active.normalizedVideoUrl);
|
|
231
321
|
}
|
|
232
|
-
#
|
|
233
|
-
return (!this.#disposed &&
|
|
322
|
+
#isLoading(active) {
|
|
323
|
+
return (!this.#disposed &&
|
|
324
|
+
this.#loading === active &&
|
|
325
|
+
active.status === 'loading' &&
|
|
326
|
+
active.generation === this.#sourceGeneration);
|
|
234
327
|
}
|
|
235
328
|
#requestPlayback(active) {
|
|
236
|
-
if (
|
|
237
|
-
|
|
329
|
+
if (this.#disposed ||
|
|
330
|
+
this.#presented !== active ||
|
|
238
331
|
active.status !== 'settled' ||
|
|
239
332
|
!this.#playing ||
|
|
240
|
-
!
|
|
333
|
+
!active.playAfterHandoff ||
|
|
334
|
+
!active.video.paused ||
|
|
241
335
|
active.playRequestPending ||
|
|
242
336
|
active.playRejectedForCurrentIntent) {
|
|
243
337
|
return;
|
|
@@ -246,7 +340,7 @@ export class ChromaKeyVideoLifecycle {
|
|
|
246
340
|
active.playRequestPending = true;
|
|
247
341
|
let request;
|
|
248
342
|
try {
|
|
249
|
-
request =
|
|
343
|
+
request = active.video.play();
|
|
250
344
|
}
|
|
251
345
|
catch {
|
|
252
346
|
this.#finishPlayRequest(active, requestToken, false);
|
|
@@ -255,7 +349,7 @@ export class ChromaKeyVideoLifecycle {
|
|
|
255
349
|
Promise.resolve(request).then(() => this.#finishPlayRequest(active, requestToken, true), () => this.#finishPlayRequest(active, requestToken, false));
|
|
256
350
|
}
|
|
257
351
|
#finishPlayRequest(active, requestToken, succeeded) {
|
|
258
|
-
if (
|
|
352
|
+
if (this.#disposed || this.#presented !== active || active.playRequestToken !== requestToken)
|
|
259
353
|
return;
|
|
260
354
|
active.playRequestPending = false;
|
|
261
355
|
active.playRejectedForCurrentIntent = !succeeded;
|
|
@@ -268,9 +362,10 @@ export class ChromaKeyVideoLifecycle {
|
|
|
268
362
|
}
|
|
269
363
|
#startLoop(active) {
|
|
270
364
|
if (this.#loopRunning ||
|
|
271
|
-
|
|
365
|
+
this.#disposed ||
|
|
366
|
+
this.#presented !== active ||
|
|
272
367
|
active.status !== 'settled' ||
|
|
273
|
-
|
|
368
|
+
active.video.paused) {
|
|
274
369
|
return;
|
|
275
370
|
}
|
|
276
371
|
this.#loopRunning = true;
|
|
@@ -279,6 +374,7 @@ export class ChromaKeyVideoLifecycle {
|
|
|
279
374
|
}
|
|
280
375
|
#scheduleNextFrame(active, loopGeneration) {
|
|
281
376
|
const sourceGeneration = active.generation;
|
|
377
|
+
const video = active.video;
|
|
282
378
|
let handle = 0;
|
|
283
379
|
const callback = () => {
|
|
284
380
|
const pending = this.#pendingFrame;
|
|
@@ -287,28 +383,28 @@ export class ChromaKeyVideoLifecycle {
|
|
|
287
383
|
pending.handle !== handle ||
|
|
288
384
|
this.#loopGeneration !== loopGeneration ||
|
|
289
385
|
!this.#loopRunning ||
|
|
290
|
-
|
|
386
|
+
this.#presented !== active ||
|
|
291
387
|
active.generation !== sourceGeneration ||
|
|
292
388
|
active.status !== 'settled' ||
|
|
293
|
-
|
|
389
|
+
video.paused) {
|
|
294
390
|
return;
|
|
295
391
|
}
|
|
296
392
|
this.#pendingFrame = null;
|
|
297
393
|
this.#draw(active);
|
|
298
394
|
if (this.#loopRunning &&
|
|
299
395
|
this.#loopGeneration === loopGeneration &&
|
|
300
|
-
this.#
|
|
301
|
-
!
|
|
396
|
+
this.#presented === active &&
|
|
397
|
+
!video.paused) {
|
|
302
398
|
this.#scheduleNextFrame(active, loopGeneration);
|
|
303
399
|
}
|
|
304
400
|
};
|
|
305
|
-
if (
|
|
306
|
-
handle =
|
|
307
|
-
this.#pendingFrame = { generation: loopGeneration, kind: 'rvfc', handle };
|
|
401
|
+
if (video.requestVideoFrameCallback) {
|
|
402
|
+
handle = video.requestVideoFrameCallback(callback);
|
|
403
|
+
this.#pendingFrame = { generation: loopGeneration, kind: 'rvfc', handle, video };
|
|
308
404
|
}
|
|
309
405
|
else {
|
|
310
406
|
handle = requestAnimationFrame(callback);
|
|
311
|
-
this.#pendingFrame = { generation: loopGeneration, kind: 'raf', handle };
|
|
407
|
+
this.#pendingFrame = { generation: loopGeneration, kind: 'raf', handle, video };
|
|
312
408
|
}
|
|
313
409
|
}
|
|
314
410
|
#stopLoop() {
|
|
@@ -319,7 +415,7 @@ export class ChromaKeyVideoLifecycle {
|
|
|
319
415
|
if (!pending)
|
|
320
416
|
return;
|
|
321
417
|
if (pending.kind === 'rvfc')
|
|
322
|
-
|
|
418
|
+
pending.video.cancelVideoFrameCallback?.(pending.handle);
|
|
323
419
|
else
|
|
324
420
|
cancelAnimationFrame(pending.handle);
|
|
325
421
|
}
|
|
@@ -33,6 +33,8 @@
|
|
|
33
33
|
}: Props = $props();
|
|
34
34
|
|
|
35
35
|
let canvasElement = $state<HTMLCanvasElement>();
|
|
36
|
+
let primaryVideoElement = $state<HTMLVideoElement>();
|
|
37
|
+
let standbyVideoElement = $state<HTMLVideoElement>();
|
|
36
38
|
let webglUnavailable = $state(false);
|
|
37
39
|
|
|
38
40
|
let renderer: ChromaKeyGl | null = null;
|
|
@@ -49,7 +51,15 @@
|
|
|
49
51
|
return;
|
|
50
52
|
}
|
|
51
53
|
const activeRenderer = renderer;
|
|
52
|
-
|
|
54
|
+
videoElement = primaryVideoElement!;
|
|
55
|
+
const activeLifecycle = new ChromaKeyVideoLifecycle(
|
|
56
|
+
primaryVideoElement!,
|
|
57
|
+
activeRenderer,
|
|
58
|
+
standbyVideoElement!,
|
|
59
|
+
(activeVideo) => {
|
|
60
|
+
videoElement = activeVideo;
|
|
61
|
+
}
|
|
62
|
+
);
|
|
53
63
|
lifecycle = activeLifecycle;
|
|
54
64
|
|
|
55
65
|
return () => {
|
|
@@ -102,7 +112,8 @@
|
|
|
102
112
|
{/if}
|
|
103
113
|
{:else}
|
|
104
114
|
<canvas bind:this={canvasElement}></canvas>
|
|
105
|
-
<video bind:this={
|
|
115
|
+
<video bind:this={primaryVideoElement} {loop} muted playsinline preload="auto"></video>
|
|
116
|
+
<video bind:this={standbyVideoElement} {loop} muted playsinline preload="auto"></video>
|
|
106
117
|
{/if}
|
|
107
118
|
</div>
|
|
108
119
|
|
|
@@ -1,23 +1,23 @@
|
|
|
1
1
|
<!--
|
|
2
|
-
The slice component hosts
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
2
|
+
The slice component hosts rendering: one component, backend chosen per use.
|
|
3
|
+
Dispatches between the two private implementations — SliceDom (default) and
|
|
4
|
+
SliceCanvas — which share `computeRenderCells` geometry. The DOM backend maps
|
|
5
|
+
that geometry to one CSS border-image paint; Canvas draws the cells into one
|
|
6
|
+
canvas backing store.
|
|
6
7
|
-->
|
|
7
8
|
<script lang="ts">
|
|
9
|
+
import { onDestroy } from 'svelte';
|
|
8
10
|
import type { SliceManifestEntry } from '../../core/index.js';
|
|
11
|
+
import { SliceSourceLifecycle, type LoadedSliceSnapshot } from '../slice-source-lifecycle.js';
|
|
9
12
|
import SliceDom from './SliceDom.svelte';
|
|
10
13
|
import SliceCanvas from './SliceCanvas.svelte';
|
|
11
14
|
|
|
12
15
|
interface Props {
|
|
13
16
|
src: SliceManifestEntry;
|
|
14
17
|
/**
|
|
15
|
-
* Rendering backend. `dom` (default) paints
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
* flicker in some situations (cells are separate elements the browser
|
|
19
|
-
* composites independently, so a resize mid-animation can tear), so
|
|
20
|
-
* hosts pick `canvas` where that hurts.
|
|
18
|
+
* Rendering backend. `dom` (default) paints the shared slice geometry as
|
|
19
|
+
* one CSS `border-image`; `canvas` draws the same cells into one `<canvas>`
|
|
20
|
+
* with a device-pixel-ratio-sized backing store.
|
|
21
21
|
*/
|
|
22
22
|
backend?: 'dom' | 'canvas';
|
|
23
23
|
/** Accessible label for the rendered graphic. */
|
|
@@ -26,10 +26,20 @@
|
|
|
26
26
|
}
|
|
27
27
|
|
|
28
28
|
let { src, backend = 'dom', alt = '', class: className = '' }: Props = $props();
|
|
29
|
+
let snapshot = $state.raw<LoadedSliceSnapshot | null>(null);
|
|
30
|
+
const sourceLifecycle = new SliceSourceLifecycle((loaded) => {
|
|
31
|
+
snapshot = loaded;
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
$effect(() => {
|
|
35
|
+
sourceLifecycle.request(src);
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
onDestroy(() => sourceLifecycle.dispose());
|
|
29
39
|
</script>
|
|
30
40
|
|
|
31
41
|
{#if backend === 'canvas'}
|
|
32
|
-
<SliceCanvas {
|
|
42
|
+
<SliceCanvas {snapshot} {alt} class={className} />
|
|
33
43
|
{:else}
|
|
34
|
-
<SliceDom {
|
|
44
|
+
<SliceDom {snapshot} {alt} class={className} />
|
|
35
45
|
{/if}
|
|
@@ -2,12 +2,9 @@ import type { SliceManifestEntry } from '../../core/index.js';
|
|
|
2
2
|
interface Props {
|
|
3
3
|
src: SliceManifestEntry;
|
|
4
4
|
/**
|
|
5
|
-
* Rendering backend. `dom` (default) paints
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
* flicker in some situations (cells are separate elements the browser
|
|
9
|
-
* composites independently, so a resize mid-animation can tear), so
|
|
10
|
-
* hosts pick `canvas` where that hurts.
|
|
5
|
+
* Rendering backend. `dom` (default) paints the shared slice geometry as
|
|
6
|
+
* one CSS `border-image`; `canvas` draws the same cells into one `<canvas>`
|
|
7
|
+
* with a device-pixel-ratio-sized backing store.
|
|
11
8
|
*/
|
|
12
9
|
backend?: 'dom' | 'canvas';
|
|
13
10
|
/** Accessible label for the rendered graphic. */
|