alpha-video-gpu-web 0.0.0-stage → 1.0.1
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/LICENSE +21 -0
- package/README.md +129 -2
- package/THIRD_PARTY_NOTICES.md +12 -0
- package/dist/chunk-P3MJ6UC3.mjs +1770 -0
- package/dist/index.d.mts +268 -0
- package/dist/index.d.ts +268 -0
- package/dist/index.js +1895 -0
- package/dist/index.mjs +43 -0
- package/dist/react.d.mts +75 -0
- package/dist/react.d.ts +75 -0
- package/dist/react.js +1994 -0
- package/dist/react.mjs +133 -0
- package/dist/software-astc-G42RTJ34.mjs +57 -0
- package/dist/types-C6J2fa98.d.mts +96 -0
- package/dist/types-C6J2fa98.d.ts +96 -0
- package/licenses/Apache-2.0.txt +208 -0
- package/licenses/zstddec-LICENSE.txt +54 -0
- package/package.json +82 -4
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Daniel Kasprowicz
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,130 @@
|
|
|
1
|
-
#
|
|
1
|
+
# alpha-video-gpu-web
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
<p align="center">
|
|
4
|
+
<img src="https://raw.githubusercontent.com/MediaCacheDev/alpha-video-gpu/main/docs/alpha-video-gpu.avif" alt="Transparent ASTC videos playing over a checkerboard in the demo app on Android" width="360">
|
|
5
|
+
<br>
|
|
6
|
+
<a href="https://github.com/MediaCacheDev/alpha-video-test">Compare transparent formats in the demo app</a>
|
|
7
|
+
</p>
|
|
8
|
+
|
|
9
|
+
Plays transparent (alpha) animations stored as ASTC texture arrays in KTX2 files, in a `<canvas>` with WebGL 2. Where the browser has `WEBGL_compressed_texture_astc` the GPU samples the ASTC directly; elsewhere the frames are decoded to RGBA in WebAssembly, as the Android and iOS players do without ASTC. The zstd decoder (JS and WebAssembly) is bundled inline: there are no files to host and no `<script>` tags.
|
|
10
|
+
|
|
11
|
+
It is the web half of [alpha-video-gpu](https://github.com/MediaCacheDev/alpha-video-gpu), whose React Native component uses it on React Native Web. Encoding, streaming and the file format are described there.
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
npm install alpha-video-gpu-web
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## Plain JS/TS
|
|
18
|
+
|
|
19
|
+
```ts
|
|
20
|
+
import { createAlphaVideoGpuPlayer } from 'alpha-video-gpu-web';
|
|
21
|
+
|
|
22
|
+
const player = createAlphaVideoGpuPlayer(document.querySelector('canvas')!, {
|
|
23
|
+
autoPlay: true,
|
|
24
|
+
loop: true,
|
|
25
|
+
stream: true,
|
|
26
|
+
onLoad: ({ width, height, frameCount, frameRate, duration }) => {},
|
|
27
|
+
onStateChange: (state) => {},
|
|
28
|
+
onError: (error) => console.error(error),
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
await player.setSource('https://example.com/animation.ktx2');
|
|
32
|
+
player.pause();
|
|
33
|
+
player.resume();
|
|
34
|
+
player.seekToFrame(0);
|
|
35
|
+
player.seekToTime(1.5);
|
|
36
|
+
player.destroy();
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
A seek shows the frame, clamped to the clip, and playback carries on from it or stays paused; seeks before the source loads are ignored. While streaming, a seek outside the buffered frames starts the zstd decoder at the file's nearest seek point (the encoder writes one every 8 MB of ASTC by default), or at the start of a file written as one stream. `stop()` pauses on the first frame. `setPaused(paused)` is the `paused` prop: it holds playback, and unpausing plays again when `autoPlay` is on.
|
|
40
|
+
|
|
41
|
+
`AlphaVideoGpuCore` is the player class itself, for supplying your own decoders.
|
|
42
|
+
|
|
43
|
+
## Sources and storage
|
|
44
|
+
|
|
45
|
+
`setSource(source, { cacheKey, headers })` takes:
|
|
46
|
+
|
|
47
|
+
- **A URL:** `https://`, relative, or `asset://path` (served from the site root). It is read with HTTP range requests, so streaming starts after the first few hundred kilobytes, and a seek fetches only from its seek point on. `headers` (for example `Authorization`) go with every request. A server without range support works too, read progressively from the start.
|
|
48
|
+
- **A `Blob` or `File`,** read with `slice`.
|
|
49
|
+
- **An `ArrayBuffer` or `Uint8Array`.**
|
|
50
|
+
- **A byte source** of your own: `{ size, read(offset, length, signal) }`, returning a `Promise<Uint8Array>`. It's for apps that fetch, decrypt or cache in their own code. See [range-source-web](https://github.com/MediaCacheDev/range-source/tree/main/web#sources), which the player depends on, for the full interface; `httpSource`, `blobSource` and `memorySource` are re-exported here.
|
|
51
|
+
|
|
52
|
+
What a URL's bytes are kept in between plays is the `storage` option, read when the player is created:
|
|
53
|
+
|
|
54
|
+
- `'auto'` (the default for URLs) is OPFS, falling back to IndexedDB where OPFS isn't available.
|
|
55
|
+
- `'opfs'`, `'indexeddb'`, `'cache'` (the Cache API) or `'memory'` (this page only) pick one. Where it isn't available, the player falls back to IndexedDB, then memory.
|
|
56
|
+
- `'none'` stores nothing. Clips up to 16 MB still stay in memory, so loops don't fetch again.
|
|
57
|
+
- A `ByteStore` of your own (from `createStore`, or written against range-source-web's interface).
|
|
58
|
+
|
|
59
|
+
Storage is partial: it keeps the ranges played so far, and a clip held whole plays again without the network. `maxStorageBytes` (default 512 MB) caps what this player keeps across every clip, evicting the least recently played. Stored clips are named by `cacheKey`, else the URL, so a presigned URL that changes on each visit can keep the same copy. A copy is dropped when the server's `ETag`, `Last-Modified` or length no longer matches it. A clip held whole isn't checked again; give a changed file a new `cacheKey` or URL.
|
|
60
|
+
|
|
61
|
+
Blobs, byte sources and bytes in memory default to `'none'`, as the data is local already, or the source does its own caching. With `storage` and a `cacheKey` they are stored too.
|
|
62
|
+
|
|
63
|
+
A Service Worker controlling the page sees the player's range requests as plain `fetch`es, so it can answer them from its own cache.
|
|
64
|
+
|
|
65
|
+
Cross-origin URLs need CORS. To check versions, the server must expose `ETag` (or `Last-Modified`) and `Content-Range` with `Access-Control-Expose-Headers`; without them, copies aren't checked.
|
|
66
|
+
|
|
67
|
+
## React
|
|
68
|
+
|
|
69
|
+
```tsx
|
|
70
|
+
import { AlphaVideoGpuPlayer } from 'alpha-video-gpu-web/react';
|
|
71
|
+
|
|
72
|
+
<AlphaVideoGpuPlayer
|
|
73
|
+
src="https://example.com/animation.ktx2"
|
|
74
|
+
width={300}
|
|
75
|
+
height={300}
|
|
76
|
+
autoPlay
|
|
77
|
+
loop
|
|
78
|
+
onError={(error) => console.error(error)}
|
|
79
|
+
/>
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
Props: `src` (any source `setSource` takes), `cacheKey`, `headers`, `storage`, `maxStorageBytes`, `width`, `height`, `className`, `style`, `autoPlay`, `loop`, `playbackSpeed`, `paused`, `stream`, `streamingPrefetchAhead`, `memoryStrategy`, `verbose`, and the callbacks `onLoad`, `onStateChange`, `onFrameUpdate`, `onFinished`, `onError`. A ref has `play()`, `pause()`, `stop()`, `seekToFrame(frame)` and `seekToTime(seconds)`. A new `src` loads again, as does a new `cacheKey` for a URL. New `headers` are used from the next load, and `storage` and `maxStorageBytes` are read when the component mounts. React 16.8 or later is an optional peer dependency.
|
|
83
|
+
|
|
84
|
+
## Without GPU ASTC
|
|
85
|
+
|
|
86
|
+
When the browser has no `WEBGL_compressed_texture_astc`, each frame is decoded to RGBA on the main thread when it's uploaded, with the decoder the Android and iOS players use, compiled to WebAssembly. It loads as a separate 42 KB chunk, only in those browsers. Bundlers (webpack, Vite, esbuild) take the ESM build even for a `require`, as the `module` export condition comes first, so React Native for Web gets the chunk too; only Node's CommonJS build inlines it. What changes:
|
|
87
|
+
|
|
88
|
+
- **Speed.** About 1.2 ms per 400×400 frame on an Apple M4, so roughly 16 ms at 1080p; slower CPUs take proportionally longer.
|
|
89
|
+
- **Memory.** Textures are RGBA8, 4 bytes a pixel against 1 for `astc4x4` (and less for larger blocks), so a VRAM streaming ring of `streamingPrefetchAhead` + 2 frames takes that much more GPU memory. Preloaded (`stream={false}`) playback keeps the clip as ASTC in memory and decodes only the shown frame, whatever `memoryStrategy` says, as every frame in RGBA could need gigabytes.
|
|
90
|
+
|
|
91
|
+
`verbose` logs which path a player took.
|
|
92
|
+
|
|
93
|
+
## Errors
|
|
94
|
+
|
|
95
|
+
`onError` gets an `Error`. The ones a page may want to handle are an `AlphaVideoGpuError` with a `code`:
|
|
96
|
+
|
|
97
|
+
- `'ASTC_UNSUPPORTED'`: the browser lacks `WEBGL_compressed_texture_astc`, and the software decoder couldn't load either, as when a Content-Security-Policy doesn't allow WebAssembly (`'wasm-unsafe-eval'`). Show something else in its place.
|
|
98
|
+
- `'WEBGL_UNSUPPORTED'`: no WebGL 2 context could be created.
|
|
99
|
+
|
|
100
|
+
```ts
|
|
101
|
+
import type { AlphaVideoGpuError } from 'alpha-video-gpu-web';
|
|
102
|
+
|
|
103
|
+
onError: (error) => {
|
|
104
|
+
if ((error as AlphaVideoGpuError).code === 'ASTC_UNSUPPORTED') {
|
|
105
|
+
showPosterImage();
|
|
106
|
+
}
|
|
107
|
+
},
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
## Building
|
|
111
|
+
|
|
112
|
+
`npm run build` builds `dist/` (ESM, CommonJS and type declarations) with tsup. `npm run build:wasm` rebuilds the ASTC decoder's WebAssembly into `src/astc-wasm.ts`, which is committed; it needs Emscripten (`emcc`).
|
|
113
|
+
|
|
114
|
+
## Testing
|
|
115
|
+
|
|
116
|
+
`npm test` runs the unit tests, which include decoding every frame of the test-card fixtures in `../tests/fixtures` with the software decoder and reading back each frame's index from its pixels.
|
|
117
|
+
|
|
118
|
+
`test/browser/index.html` plays the test cards in a real browser, in every streaming and memory mode, and checks the frames from the canvas after seeks and in playback. Build `dist/`, serve a folder that holds this repository over HTTP, and open the page; `?astc=off` hides `WEBGL_compressed_texture_astc` to test the software decoder, `?cards=<path>` points at other cards (the demo app's include uncompressed files), and `?only=<regex>` picks cases. Results show on the page and in `window.results`.
|
|
119
|
+
|
|
120
|
+
Other parameters exercise sources and storage:
|
|
121
|
+
|
|
122
|
+
- `?storage=<kind>` stores what's played (default `none`). The page clears that store first.
|
|
123
|
+
- `?replay` plays each case a second time. With a persistent store, it checks that the clip was stored whole and played again without a request.
|
|
124
|
+
- `?input=blob|bytes|buffer` loads each file as a `Blob`, a minimal `{ size, read }` byte source, or an `ArrayBuffer` instead of a URL.
|
|
125
|
+
|
|
126
|
+
To test range requests, serve the folder with range-source-web's server: `npm run serve -- <folder> --port 8766` in `range-source/web`. Add `--no-range` for a server without range support. Python's `http.server` doesn't support ranges either.
|
|
127
|
+
|
|
128
|
+
## License
|
|
129
|
+
|
|
130
|
+
MIT. The bundled zstd decoder is MIT (zstddec) and BSD-3-Clause (zstd), and the ASTC decoder Apache-2.0; see [THIRD_PARTY_NOTICES.md](THIRD_PARTY_NOTICES.md).
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# Third-party notices
|
|
2
|
+
|
|
3
|
+
The code in this package is MIT-licensed (see `LICENSE`). The built files in `dist/` also contain, inlined as JavaScript and base64 WebAssembly:
|
|
4
|
+
|
|
5
|
+
| Component | Version | Licence | Licence text |
|
|
6
|
+
|---|---|---|---|
|
|
7
|
+
| [zstddec](https://github.com/donmccurdy/zstddec), `dist/zstddec-stream.esm.js`, modified | 0.2.0-alpha.4 | MIT; the WebAssembly decoder inside it is [Zstandard](https://github.com/facebook/zstd), BSD-3-Clause | `licenses/zstddec-LICENSE.txt` (both licences) |
|
|
8
|
+
| ASTC decoder (`astc_decomp.cpp`, compiled to WebAssembly in the `software-astc` chunk): the Android Open Source Project's drawElements Quality Program decoder, as adapted in [Basis Universal](https://github.com/BinomialLLC/basis_universal); the same file the Android and iOS libraries use | from Basis Universal 1.16.4 | Apache-2.0 | `licenses/Apache-2.0.txt` |
|
|
9
|
+
|
|
10
|
+
The zstddec changes: an added `createStreamingDecoder()` takes its input one array at a time, so input read asynchronously (HTTP range reads) feeds one decoder. `decodeStreaming` is built on it, as a native generator that frees its WebAssembly buffers in a `finally`, so a stream closed early with `return()` doesn't leak them. Without the transpiled generator, the regenerator-runtime helpers it needed were removed.
|
|
11
|
+
|
|
12
|
+
The source file is `src/zstddec-stream-bundled.js` in the [repository](https://github.com/MediaCacheDev/alpha-video-gpu/tree/main/web). The ASTC decoder's source is `android/src/main/cpp/astc_decomp.cpp`, built with `web/wasm/build.sh` around `web/wasm/astc_decode.cpp`.
|