@bubo-squared/gyroview 0.3.0 → 0.3.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/CHANGELOG.md +16 -0
- package/README.md +19 -10
- package/dist/index.d.ts +20 -2
- package/dist/index.js +2 -2
- package/dist/packages/adapters/webcodecs/src/WebCodecsVideoDecoderPort.js +12 -3
- package/dist/packages/core/src/application/playback/probeDecoding.js +2 -2
- package/dist/packages/core/src/index.js +2 -2
- package/dist/packages/core/src/shared/errors/GyroViewError.js +55 -3
- package/dist/packages/player/src/controls/messages.js +2 -1
- package/dist/standalone.js +58 -58
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,22 @@
|
|
|
3
3
|
What changed for a page using the package, newest first. Until 1.0, a minor version may change
|
|
4
4
|
the API.
|
|
5
5
|
|
|
6
|
+
## 0.3.1 (2026-09-30)
|
|
7
|
+
|
|
8
|
+
New:
|
|
9
|
+
|
|
10
|
+
- Every error has a `category`, derived from its code, that says whose side the failure is on:
|
|
11
|
+
`browser`, `recording`, `source`, `usage` or `internal`; `GYRO_VIEW_ERROR_CATEGORIES` and the
|
|
12
|
+
type `GyroViewErrorCategory` list them. The README's "When a recording cannot play" gives the
|
|
13
|
+
codes of each, and what a `<video>` fallback can and cannot do (ADR 0030).
|
|
14
|
+
- The `webcodecs-unavailable` error: the browser has no WebCodecs, on a page not served over
|
|
15
|
+
HTTPS or in an old browser.
|
|
16
|
+
|
|
17
|
+
What a page may notice:
|
|
18
|
+
|
|
19
|
+
- A page on plain HTTP, or a browser without WebCodecs, hears `webcodecs-unavailable` where it
|
|
20
|
+
heard `codec-unsupported`, whose message blamed the codec.
|
|
21
|
+
|
|
6
22
|
## 0.3.0 (2026-09-30)
|
|
7
23
|
|
|
8
24
|
What a page may notice:
|
package/README.md
CHANGED
|
@@ -5,6 +5,9 @@ camera's dual-fisheye file directly, over HTTP byte ranges or from a local file,
|
|
|
5
5
|
lenses in hardware with WebCodecs, and stitches and gyro-stabilizes them on the GPU. No Insta360
|
|
6
6
|
Studio export step.
|
|
7
7
|
|
|
8
|
+
**Try it first:** [insv-player.com](https://insv-player.com/) plays `.insv` files with this
|
|
9
|
+
package. Drop your own recording to check that your camera and browser work before you install.
|
|
10
|
+
|
|
8
11
|
## Install
|
|
9
12
|
|
|
10
13
|
```sh
|
|
@@ -188,7 +191,8 @@ loads a recording only for the player in view, removing `src` from the others.
|
|
|
188
191
|
|
|
189
192
|
## Requirements
|
|
190
193
|
|
|
191
|
-
- **A secure page.** WebCodecs exists only on `https://` pages, or `http://localhost
|
|
194
|
+
- **A secure page.** WebCodecs exists only on `https://` pages, or `http://localhost`; elsewhere
|
|
195
|
+
the player fails with `webcodecs-unavailable`.
|
|
192
196
|
- **Recordings served in byte ranges.** The server answers `Range` requests with `206`, and
|
|
193
197
|
sends CORS headers when the recordings live on another origin than the page:
|
|
194
198
|
|
|
@@ -205,17 +209,20 @@ loads a recording only for the player in view, removing `src` from the others.
|
|
|
205
209
|
`Access-Control-Allow-Credentials: true`.
|
|
206
210
|
|
|
207
211
|
- **A hardware HEVC decoder.** 5.7K plays on recent laptops and phones; 8K needs a Level 6
|
|
208
|
-
decoder (Apple Silicon, recent NVIDIA and Intel).
|
|
209
|
-
|
|
212
|
+
decoder (Apple Silicon, recent NVIDIA and Intel). On Linux, Chrome reaches the decoder only
|
|
213
|
+
through VA-API: an Intel or AMD GPU whose driver offers HEVC, not NVIDIA's own driver or a
|
|
214
|
+
virtual machine (`chrome://gpu` lists `Decode hevc main` under Video Acceleration
|
|
215
|
+
Information when it can). H.264 recordings do not need it. A recording the browser cannot
|
|
216
|
+
decode fails with the `codec-unsupported` error.
|
|
210
217
|
|
|
211
218
|
The supported browsers, with the oldest versions that have what the player uses (WebCodecs,
|
|
212
219
|
WebGL 2, container queries, and on iPhone `ManagedMediaSource` for the sound):
|
|
213
220
|
|
|
214
|
-
| Browser | From | Notes
|
|
215
|
-
| --------------------- | ---- |
|
|
216
|
-
| Chrome, Edge desktop | 107 | HEVC is decoded in hardware from this version on.
|
|
217
|
-
| Safari on macOS | 16.4 |
|
|
218
|
-
| Safari on iPhone/iPad | 17.1 | 16.4 to 17.0 play without sound, with a `warning` event.
|
|
221
|
+
| Browser | From | Notes |
|
|
222
|
+
| --------------------- | ---- | ----------------------------------------------------------------- |
|
|
223
|
+
| Chrome, Edge desktop | 107 | HEVC is decoded in hardware from this version on (Linux: VA-API). |
|
|
224
|
+
| Safari on macOS | 16.4 | |
|
|
225
|
+
| Safari on iPhone/iPad | 17.1 | 16.4 to 17.0 play without sound, with a `warning` event. |
|
|
219
226
|
|
|
220
227
|
Firefox and Chrome on Android are untested: they play what their decoders accept.
|
|
221
228
|
|
|
@@ -224,8 +231,10 @@ Until 1.0, a minor version may change the API; [CHANGELOG.md](./CHANGELOG.md) sa
|
|
|
224
231
|
## Reference
|
|
225
232
|
|
|
226
233
|
Every attribute, method, event and keyboard shortcut is in the
|
|
227
|
-
[project README](https://github.com/bubo-squared/GyroView#using-the-player)
|
|
228
|
-
|
|
234
|
+
[project README](https://github.com/bubo-squared/GyroView#using-the-player), and what a failure
|
|
235
|
+
tells a page, with what a `<video>` fallback can do, under
|
|
236
|
+
[When a recording cannot play](https://github.com/bubo-squared/GyroView#when-a-recording-cannot-play);
|
|
237
|
+
hosting and the error codes are in [docs/DEPLOYMENT.md](https://github.com/bubo-squared/GyroView/blob/main/docs/DEPLOYMENT.md).
|
|
229
238
|
|
|
230
239
|
## License
|
|
231
240
|
|
package/dist/index.d.ts
CHANGED
|
@@ -261,7 +261,7 @@ interface DragDelta {
|
|
|
261
261
|
export type ViewMode = "raw-lenses" | "equirectangular" | "normal";
|
|
262
262
|
export declare const VIEW_MODES: readonly ViewMode[];
|
|
263
263
|
/**
|
|
264
|
-
* Stable machine-readable failure
|
|
264
|
+
* Stable machine-readable failure codes. Embedders switch on these; messages are for humans.
|
|
265
265
|
*/
|
|
266
266
|
export declare const GYRO_VIEW_ERROR_CODES: readonly [
|
|
267
267
|
"binary-out-of-bounds",
|
|
@@ -291,12 +291,30 @@ export declare const GYRO_VIEW_ERROR_CODES: readonly [
|
|
|
291
291
|
"unsupported-container",
|
|
292
292
|
"unsupported-gyro-record",
|
|
293
293
|
"unsupported-info-format",
|
|
294
|
-
"unsupported-layout"
|
|
294
|
+
"unsupported-layout",
|
|
295
|
+
"webcodecs-unavailable"
|
|
295
296
|
];
|
|
296
297
|
export type GyroViewErrorCode = (typeof GYRO_VIEW_ERROR_CODES)[number];
|
|
298
|
+
/**
|
|
299
|
+
* Whose side a failure is on, for embedders that handle failures by kind: the browser cannot
|
|
300
|
+
* decode or draw the recording, the file is not one the player can play, its bytes could not be
|
|
301
|
+
* read, the page misused the API, or the player failed in a way it did not expect.
|
|
302
|
+
*/
|
|
303
|
+
export declare const GYRO_VIEW_ERROR_CATEGORIES: readonly [
|
|
304
|
+
"browser",
|
|
305
|
+
"recording",
|
|
306
|
+
"source",
|
|
307
|
+
"usage",
|
|
308
|
+
"internal"
|
|
309
|
+
];
|
|
310
|
+
export type GyroViewErrorCategory = (typeof GYRO_VIEW_ERROR_CATEGORIES)[number];
|
|
297
311
|
export declare function isGyroViewErrorCode(value: unknown): value is GyroViewErrorCode;
|
|
298
312
|
export declare class GyroViewError extends Error {
|
|
299
313
|
readonly code: GyroViewErrorCode;
|
|
314
|
+
/**
|
|
315
|
+
* Its own property rather than a getter, so a serialized or logged copy of the error keeps it.
|
|
316
|
+
*/
|
|
317
|
+
readonly category: GyroViewErrorCategory;
|
|
300
318
|
constructor(code: GyroViewErrorCode, message: string, options?: ErrorOptions);
|
|
301
319
|
}
|
|
302
320
|
export declare function hasErrorCode(error: unknown, code: GyroViewErrorCode): boolean;
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { GYRO_VIEW_ERROR_CODES, GyroViewError, hasErrorCode, isGyroViewErrorCode } from "./packages/core/src/shared/errors/GyroViewError.js";
|
|
1
|
+
import { GYRO_VIEW_ERROR_CATEGORIES, GYRO_VIEW_ERROR_CODES, GyroViewError, hasErrorCode, isGyroViewErrorCode } from "./packages/core/src/shared/errors/GyroViewError.js";
|
|
2
2
|
import { STABILIZATION_MODES } from "./packages/core/src/domain/motion/stabilization/Stabilizer.js";
|
|
3
3
|
import { PICTURE_QUALITIES } from "./packages/core/src/domain/view/PictureQuality.js";
|
|
4
4
|
import { VIEW_MODES } from "./packages/core/src/domain/view/ViewMode.js";
|
|
@@ -10,4 +10,4 @@ import { inspectRecording } from "./packages/player/src/inspectRecording.js";
|
|
|
10
10
|
import { GyroViewElement } from "./packages/player/src/element/GyroViewElement.js";
|
|
11
11
|
import { GYRO_VIEW_TAG, defineGyroView } from "./packages/player/src/element/defineGyroView.js";
|
|
12
12
|
import "./packages/player/src/index.js";
|
|
13
|
-
export { GYRO_VIEW_ERROR_CODES, GYRO_VIEW_TAG, GyroViewElement, GyroViewError, PICTURE_QUALITIES, Player, STABILIZATION_MODES, VIEW_MODES, attachKeyboard, attachViewGestures, createBrowserPlayer, defineGyroView, hasErrorCode, inspectRecording, isGyroViewErrorCode };
|
|
13
|
+
export { GYRO_VIEW_ERROR_CATEGORIES, GYRO_VIEW_ERROR_CODES, GYRO_VIEW_TAG, GyroViewElement, GyroViewError, PICTURE_QUALITIES, Player, STABILIZATION_MODES, VIEW_MODES, attachKeyboard, attachViewGestures, createBrowserPlayer, defineGyroView, hasErrorCode, inspectRecording, isGyroViewErrorCode };
|
|
@@ -15,10 +15,11 @@ var HARDWARE_ACCELERATION = "no-preference";
|
|
|
15
15
|
*/
|
|
16
16
|
var WebCodecsVideoDecoderPort = class {
|
|
17
17
|
/**
|
|
18
|
-
* A configuration WebCodecs finds malformed is one it does not support.
|
|
18
|
+
* A configuration WebCodecs finds malformed is one it does not support. A browser without
|
|
19
|
+
* WebCodecs refuses to answer, since no configuration would do.
|
|
19
20
|
*/
|
|
20
21
|
async isSupported(configuration) {
|
|
21
|
-
if (typeof VideoDecoder === "undefined")
|
|
22
|
+
if (typeof VideoDecoder === "undefined") throw webCodecsUnavailable();
|
|
22
23
|
try {
|
|
23
24
|
return (await VideoDecoder.isConfigSupported(this.toWebCodecsConfig(configuration))).supported === true;
|
|
24
25
|
} catch {
|
|
@@ -33,7 +34,7 @@ var WebCodecsVideoDecoderPort = class {
|
|
|
33
34
|
}
|
|
34
35
|
}
|
|
35
36
|
configureDecoder(configuration, callbacks) {
|
|
36
|
-
if (typeof VideoDecoder === "undefined") throw
|
|
37
|
+
if (typeof VideoDecoder === "undefined") throw webCodecsUnavailable();
|
|
37
38
|
return new WebCodecsDecoderHandle(this.toWebCodecsConfig(configuration), callbacks);
|
|
38
39
|
}
|
|
39
40
|
toWebCodecsConfig(configuration) {
|
|
@@ -114,6 +115,14 @@ var WebCodecsDecoderHandle = class {
|
|
|
114
115
|
if (this.decoder.state !== "closed") this.decoder.close();
|
|
115
116
|
}
|
|
116
117
|
};
|
|
118
|
+
/**
|
|
119
|
+
* Browsers expose WebCodecs only in secure contexts, so a page served over plain HTTP is the
|
|
120
|
+
* likelier cause than an old browser, and the one its developer can fix.
|
|
121
|
+
*/
|
|
122
|
+
function webCodecsUnavailable() {
|
|
123
|
+
const reason = globalThis.isSecureContext ? "this browser has no WebCodecs VideoDecoder" : "WebCodecs exists only in secure contexts (HTTPS), and this page is not one";
|
|
124
|
+
return new GyroViewError("webcodecs-unavailable", reason);
|
|
125
|
+
}
|
|
117
126
|
function chunkOf(packet) {
|
|
118
127
|
return new EncodedVideoChunk({
|
|
119
128
|
type: packet.isKeyFrame ? "key" : "delta",
|
|
@@ -28,8 +28,8 @@ var KEY_FRAME_LATE = {
|
|
|
28
28
|
* a deadline. The host resolves `deadline` once the probe has taken too long (the core has no
|
|
29
29
|
* timers); sources still undecided then report `timed-out`, or `key-frame-late` while their key
|
|
30
30
|
* frame was still being read, and their decoders are closed. A track that cannot be read rejects
|
|
31
|
-
* the probe with its own failure,
|
|
32
|
-
* deadline.
|
|
31
|
+
* the probe with its own failure, as a platform without decoders does, and the other sources'
|
|
32
|
+
* decoders are closed then, not at the deadline.
|
|
33
33
|
*/
|
|
34
34
|
async function probeDecoding(frameSources, decoderPort, deadline) {
|
|
35
35
|
const probes = frameSources.map((track) => new SourceProbe(track, decoderPort));
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { degrees } from "./shared/units/angle.js";
|
|
2
2
|
import "./domain/optics/EquidistantModel.js";
|
|
3
3
|
import "./domain/format/calibration/CalibrationVersion.js";
|
|
4
|
-
import { GYRO_VIEW_ERROR_CODES, GyroViewError, asGyroViewError, ensureIndexInRange, ensureInvariant, hasErrorCode, isAbortError, isGyroViewErrorCode, messageOf } from "./shared/errors/GyroViewError.js";
|
|
4
|
+
import { GYRO_VIEW_ERROR_CATEGORIES, GYRO_VIEW_ERROR_CODES, GyroViewError, asGyroViewError, ensureIndexInRange, ensureInvariant, hasErrorCode, isAbortError, isGyroViewErrorCode, messageOf } from "./shared/errors/GyroViewError.js";
|
|
5
5
|
import { lazy } from "./shared/lazy.js";
|
|
6
6
|
import { microseconds, microsecondsToSeconds, milliseconds, seconds, secondsToMicroseconds } from "./shared/units/time.js";
|
|
7
7
|
import "./domain/motion/exposure/ExposureRecord.js";
|
|
@@ -64,4 +64,4 @@ import { WallClock } from "./application/playback/WallClock.js";
|
|
|
64
64
|
import { isFlowing } from "./domain/playback/PlayerState.js";
|
|
65
65
|
import { TypedEmitter } from "./shared/events/TypedEmitter.js";
|
|
66
66
|
import { PlaybackSession } from "./application/playback/PlaybackSession.js";
|
|
67
|
-
export { ByteRange, DEFAULT_FRAMING, DEFAULT_PICTURE_QUALITY, DEFAULT_STABILIZATION_MODE, DEFAULT_VIEW_MODE, Deferred, Ending, GYRO_VIEW_ERROR_CODES, GainMatchingFrameSink, GyroViewError, IDENTITY_MATRIX3, ITERATION_END, Outbox, PICTURE_QUALITIES, PlaybackSession, RecordingBuffer, RunStop, SCREEN_CENTRE, STABILIZATION_MODES, STOPPED, SourceByteStream, StabilizingFrameSink, TypedEmitter, VIEW_MODES, WallClock, asGyroViewError, aspectOf, aspectOfArea, buildStitchingSetup, clamp, clampView, degrees, detectLensLayout, downloadPolicyFor, ensureIndexInRange, ensureInvariant, fileNameOfUrl, hasErrorCode, inspectRecording, isAbortError, isFlowing, isGyroViewErrorCode, isSameFraming, isSameView, keysOf, lazy, lensFrameOrder, locateOtherLensFile, lookAt, messageOf, microseconds, microsecondsToSeconds, milliseconds, pixelRatioCapOf, planeHalfExtentOf, probeDecoding, readRecording, readSampleTable, seconds, secondsToMicroseconds, stabilizerFor, startFileDownload, timeRecording, viewModeRulesFor, zoomStepsForPinch };
|
|
67
|
+
export { ByteRange, DEFAULT_FRAMING, DEFAULT_PICTURE_QUALITY, DEFAULT_STABILIZATION_MODE, DEFAULT_VIEW_MODE, Deferred, Ending, GYRO_VIEW_ERROR_CATEGORIES, GYRO_VIEW_ERROR_CODES, GainMatchingFrameSink, GyroViewError, IDENTITY_MATRIX3, ITERATION_END, Outbox, PICTURE_QUALITIES, PlaybackSession, RecordingBuffer, RunStop, SCREEN_CENTRE, STABILIZATION_MODES, STOPPED, SourceByteStream, StabilizingFrameSink, TypedEmitter, VIEW_MODES, WallClock, asGyroViewError, aspectOf, aspectOfArea, buildStitchingSetup, clamp, clampView, degrees, detectLensLayout, downloadPolicyFor, ensureIndexInRange, ensureInvariant, fileNameOfUrl, hasErrorCode, inspectRecording, isAbortError, isFlowing, isGyroViewErrorCode, isSameFraming, isSameView, keysOf, lazy, lensFrameOrder, locateOtherLensFile, lookAt, messageOf, microseconds, microsecondsToSeconds, milliseconds, pixelRatioCapOf, planeHalfExtentOf, probeDecoding, readRecording, readSampleTable, seconds, secondsToMicroseconds, stabilizerFor, startFileDownload, timeRecording, viewModeRulesFor, zoomStepsForPinch };
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
//#region ../../packages/core/src/shared/errors/GyroViewError.ts
|
|
2
2
|
/**
|
|
3
|
-
* Stable machine-readable failure
|
|
3
|
+
* Stable machine-readable failure codes. Embedders switch on these; messages are for humans.
|
|
4
4
|
*/
|
|
5
5
|
var GYRO_VIEW_ERROR_CODES = [
|
|
6
6
|
"binary-out-of-bounds",
|
|
@@ -30,17 +30,69 @@ var GYRO_VIEW_ERROR_CODES = [
|
|
|
30
30
|
"unsupported-container",
|
|
31
31
|
"unsupported-gyro-record",
|
|
32
32
|
"unsupported-info-format",
|
|
33
|
-
"unsupported-layout"
|
|
33
|
+
"unsupported-layout",
|
|
34
|
+
"webcodecs-unavailable"
|
|
34
35
|
];
|
|
36
|
+
/**
|
|
37
|
+
* Whose side a failure is on, for embedders that handle failures by kind: the browser cannot
|
|
38
|
+
* decode or draw the recording, the file is not one the player can play, its bytes could not be
|
|
39
|
+
* read, the page misused the API, or the player failed in a way it did not expect.
|
|
40
|
+
*/
|
|
41
|
+
var GYRO_VIEW_ERROR_CATEGORIES = [
|
|
42
|
+
"browser",
|
|
43
|
+
"recording",
|
|
44
|
+
"source",
|
|
45
|
+
"usage",
|
|
46
|
+
"internal"
|
|
47
|
+
];
|
|
48
|
+
/**
|
|
49
|
+
* A record, so a new code cannot be added without its category.
|
|
50
|
+
*/
|
|
51
|
+
var CATEGORY_OF_CODE = {
|
|
52
|
+
"binary-out-of-bounds": "recording",
|
|
53
|
+
"binary-unsafe-integer": "recording",
|
|
54
|
+
"codec-unsupported": "browser",
|
|
55
|
+
cors: "source",
|
|
56
|
+
decode: "browser",
|
|
57
|
+
"embed-destroyed": "usage",
|
|
58
|
+
"index-out-of-range": "internal",
|
|
59
|
+
"invalid-argument": "usage",
|
|
60
|
+
"invalid-byte-range": "recording",
|
|
61
|
+
"invalid-calibration": "recording",
|
|
62
|
+
"invalid-protobuf": "recording",
|
|
63
|
+
"invalid-trailer": "recording",
|
|
64
|
+
"invariant-violation": "internal",
|
|
65
|
+
"missing-second-file": "recording",
|
|
66
|
+
"no-calibration": "recording",
|
|
67
|
+
"no-info-record": "recording",
|
|
68
|
+
"no-key-frame": "recording",
|
|
69
|
+
"playback-blocked": "browser",
|
|
70
|
+
"range-unsupported": "source",
|
|
71
|
+
"render-unavailable": "browser",
|
|
72
|
+
"source-changed": "source",
|
|
73
|
+
"source-truncated": "source",
|
|
74
|
+
"source-unreadable": "source",
|
|
75
|
+
"unsupported-calibration": "recording",
|
|
76
|
+
"unsupported-container": "recording",
|
|
77
|
+
"unsupported-gyro-record": "recording",
|
|
78
|
+
"unsupported-info-format": "recording",
|
|
79
|
+
"unsupported-layout": "recording",
|
|
80
|
+
"webcodecs-unavailable": "browser"
|
|
81
|
+
};
|
|
35
82
|
function isGyroViewErrorCode(value) {
|
|
36
83
|
return typeof value === "string" && GYRO_VIEW_ERROR_CODES.includes(value);
|
|
37
84
|
}
|
|
38
85
|
var GyroViewError = class extends Error {
|
|
39
86
|
code;
|
|
87
|
+
/**
|
|
88
|
+
* Its own property rather than a getter, so a serialized or logged copy of the error keeps it.
|
|
89
|
+
*/
|
|
90
|
+
category;
|
|
40
91
|
constructor(code, message, options) {
|
|
41
92
|
super(message, options);
|
|
42
93
|
this.code = code;
|
|
43
94
|
this.name = "GyroViewError";
|
|
95
|
+
this.category = CATEGORY_OF_CODE[code];
|
|
44
96
|
}
|
|
45
97
|
};
|
|
46
98
|
/**
|
|
@@ -79,4 +131,4 @@ function ensureIndexInRange(index, length, subject) {
|
|
|
79
131
|
if (!Number.isSafeInteger(index) || index < 0 || index >= length) throw new GyroViewError("index-out-of-range", `${subject} index ${index} is outside 0..${length - 1}`);
|
|
80
132
|
}
|
|
81
133
|
//#endregion
|
|
82
|
-
export { GYRO_VIEW_ERROR_CODES, GyroViewError, asGyroViewError, ensureIndexInRange, ensureInvariant, hasErrorCode, isAbortError, isGyroViewErrorCode, messageOf };
|
|
134
|
+
export { GYRO_VIEW_ERROR_CATEGORIES, GYRO_VIEW_ERROR_CODES, GyroViewError, asGyroViewError, ensureIndexInRange, ensureInvariant, hasErrorCode, isAbortError, isGyroViewErrorCode, messageOf };
|
|
@@ -78,7 +78,8 @@ var DEFAULT_MESSAGES = {
|
|
|
78
78
|
"unsupported-container": UNREADABLE,
|
|
79
79
|
"unsupported-gyro-record": UNREADABLE,
|
|
80
80
|
"unsupported-info-format": UNREADABLE,
|
|
81
|
-
"unsupported-layout": UNREADABLE
|
|
81
|
+
"unsupported-layout": UNREADABLE,
|
|
82
|
+
"webcodecs-unavailable": UNSUPPORTED_BROWSER
|
|
82
83
|
}
|
|
83
84
|
};
|
|
84
85
|
/**
|