@bubo-squared/gyroview 0.2.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.
Files changed (74) hide show
  1. package/CHANGELOG.md +42 -0
  2. package/README.md +35 -15
  3. package/dist/index.d.ts +21 -2
  4. package/dist/index.js +2 -2
  5. package/dist/packages/adapters/fetch/src/HttpByteStream.js +174 -0
  6. package/dist/packages/adapters/fetch/src/HttpRangeSource.js +14 -170
  7. package/dist/packages/adapters/fetch/src/HttpResource.js +192 -0
  8. package/dist/packages/adapters/fetch/src/contentRange.js +15 -0
  9. package/dist/packages/adapters/fetch/src/httpRequest.js +5 -3
  10. package/dist/packages/adapters/fetch/src/index.js +3 -1
  11. package/dist/packages/adapters/fetch/src/passingFailures.js +24 -0
  12. package/dist/packages/adapters/fetch/src/rangeFailures.js +17 -0
  13. package/dist/packages/adapters/fetch/src/recordingVersion.js +37 -0
  14. package/dist/packages/adapters/mediabunny/src/MediabunnyAudioPackager.js +129 -0
  15. package/dist/packages/adapters/mediabunny/src/MediabunnyCodecReader.js +80 -0
  16. package/dist/packages/adapters/mediabunny/src/SegmentChannel.js +58 -6
  17. package/dist/packages/adapters/mediabunny/src/decoderConfigurations.js +28 -0
  18. package/dist/packages/adapters/mediabunny/src/index.js +3 -2
  19. package/dist/packages/adapters/webcodecs/src/WebCodecsVideoDecoderPort.js +12 -3
  20. package/dist/packages/core/src/application/download/BlockStore.js +97 -0
  21. package/dist/packages/core/src/application/download/DownloadedAudioSamples.js +50 -0
  22. package/dist/packages/core/src/application/download/DownloadedVideoTrack.js +122 -0
  23. package/dist/packages/core/src/application/download/FileDownload.js +183 -0
  24. package/dist/packages/core/src/application/download/RecordingBuffer.js +22 -0
  25. package/dist/packages/core/src/application/download/SampleCursor.js +88 -0
  26. package/dist/packages/core/src/application/download/SourceByteStream.js +46 -0
  27. package/dist/packages/core/src/application/download/Transfers.js +75 -0
  28. package/dist/packages/core/src/application/download/startFileDownload.js +37 -0
  29. package/dist/packages/core/src/application/playback/Buffering.js +53 -0
  30. package/dist/packages/core/src/application/playback/DecodePipeline.js +11 -7
  31. package/dist/packages/core/src/application/playback/DecodeRun.js +15 -0
  32. package/dist/packages/core/src/application/playback/PlaybackSession.js +18 -27
  33. package/dist/packages/core/src/application/playback/keyframeTimeAt.js +1 -1
  34. package/dist/packages/core/src/application/playback/probeDecoding.js +17 -3
  35. package/dist/packages/core/src/application/recording/readSampleTable.js +27 -0
  36. package/dist/packages/core/src/application/recording/timeRecording.js +3 -3
  37. package/dist/packages/core/src/domain/container/KeyframeRule.js +7 -0
  38. package/dist/packages/core/src/domain/container/SampleTable.js +29 -0
  39. package/dist/packages/core/src/domain/container/TrackSampleTable.js +96 -0
  40. package/dist/packages/core/src/domain/container/presentationOrder.js +10 -0
  41. package/dist/packages/core/src/domain/download/DownloadPolicy.js +41 -0
  42. package/dist/packages/core/src/domain/download/isReadyToResume.js +27 -0
  43. package/dist/packages/core/src/domain/download/planDownloads.js +175 -0
  44. package/dist/packages/core/src/domain/download/trackNeeds.js +32 -0
  45. package/dist/packages/core/src/domain/format/boxes/boxHeader.js +44 -0
  46. package/dist/packages/core/src/domain/format/boxes/scanBoxes.js +7 -34
  47. package/dist/packages/core/src/domain/format/mp4/keyframeRules.js +81 -0
  48. package/dist/packages/core/src/domain/format/mp4/movieBoxes.js +52 -0
  49. package/dist/packages/core/src/domain/format/mp4/mp4BoxTypes.js +46 -0
  50. package/dist/packages/core/src/domain/format/mp4/mp4Layouts.js +183 -0
  51. package/dist/packages/core/src/domain/format/mp4/parseMovie.js +67 -0
  52. package/dist/packages/core/src/domain/format/mp4/readTable.js +31 -0
  53. package/dist/packages/core/src/domain/format/mp4/sampleLocations.js +69 -0
  54. package/dist/packages/core/src/domain/format/mp4/sampleTiming.js +109 -0
  55. package/dist/packages/core/src/domain/format/mp4/trackHeaders.js +75 -0
  56. package/dist/packages/core/src/index.js +10 -2
  57. package/dist/packages/core/src/shared/async/iteration.js +24 -0
  58. package/dist/packages/core/src/shared/binary/ByteRange.js +12 -0
  59. package/dist/packages/core/src/shared/binary/ByteRangeSet.js +85 -0
  60. package/dist/packages/core/src/shared/binary/ByteReader.js +21 -3
  61. package/dist/packages/core/src/shared/binary/concatenated.js +15 -0
  62. package/dist/packages/core/src/shared/errors/GyroViewError.js +56 -3
  63. package/dist/packages/core/src/shared/math/countAtOrBelow.js +17 -0
  64. package/dist/packages/player/src/composition/browserPorts.js +23 -4
  65. package/dist/packages/player/src/composition/buildPipeline.js +15 -3
  66. package/dist/packages/player/src/composition/openInputs.js +51 -107
  67. package/dist/packages/player/src/composition/readInputs.js +111 -0
  68. package/dist/packages/player/src/controls/messages.js +3 -1
  69. package/dist/packages/player/src/inspectRecording.js +1 -1
  70. package/dist/standalone.js +58 -58
  71. package/package.json +1 -1
  72. package/dist/packages/adapters/mediabunny/src/MediabunnyAudioSegments.js +0 -133
  73. package/dist/packages/adapters/mediabunny/src/MediabunnyDemuxer.js +0 -63
  74. package/dist/packages/adapters/mediabunny/src/MediabunnyVideoTrackReader.js +0 -84
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bubo-squared/gyroview",
3
- "version": "0.2.0",
3
+ "version": "0.3.1",
4
4
  "description": "Play raw Insta360 .insv recordings (X3, X4, X5) in the browser: a <gyro-view> web component that stitches and gyro-stabilizes the camera's dual-fisheye video on the GPU.",
5
5
  "keywords": [
6
6
  "insta360",
@@ -1,133 +0,0 @@
1
- import { GyroViewError } from "../../../core/src/shared/errors/GyroViewError.js";
2
- import { seconds } from "../../../core/src/shared/units/time.js";
3
- import "../../../core/src/index.js";
4
- import { SegmentChannel } from "./SegmentChannel.js";
5
- import { EncodedAudioPacketSource, EncodedPacketSink, Mp4OutputFormat, NullTarget, Output } from "mediabunny";
6
- //#region ../../packages/adapters/mediabunny/src/MediabunnyAudioSegments.ts
7
- /**
8
- * Seconds of audio per media segment: short enough that a seek has sound quickly, long enough
9
- * that appends stay cheap.
10
- */
11
- var FRAGMENT_DURATION_SECONDS = 1;
12
- /**
13
- * Segments (each fragment is two: `moof` and `mdat`) that may wait for the consumer before
14
- * re-packaging pauses.
15
- */
16
- var SEGMENTS_AHEAD = 4;
17
- /**
18
- * An audio track's packets re-packaged, unchanged, into fragmented MP4 with mediabunny.
19
- * Segments are taken from the muxer's box callbacks rather than from its byte stream: that
20
- * yields whole `ftyp`/`moov`/`moof`/`mdat` boxes and leaves out the `mfra` index the muxer
21
- * appends at the end, which Media Source Extensions do not accept. Fragmented output keeps the
22
- * track's own timestamps, so a run started mid-track lands at its true time.
23
- */
24
- var MediabunnyAudioSegments = class MediabunnyAudioSegments {
25
- parts;
26
- duration;
27
- constructor(parts, duration) {
28
- this.parts = parts;
29
- this.duration = duration;
30
- }
31
- /**
32
- * Rejects with `codec-unsupported`, naming the track, when mediabunny cannot re-package it: the
33
- * picture then plays without sound rather than not at all.
34
- */
35
- static async open(track, trackIndex) {
36
- const [codec, decoderConfig, duration] = await Promise.all([
37
- track.getCodec(),
38
- track.getDecoderConfig(),
39
- track.computeDuration()
40
- ]);
41
- if (codec === null || decoderConfig === null) {
42
- const named = await track.getCodecParameterString() ?? "an unknown codec";
43
- throw new GyroViewError("codec-unsupported", `audio track ${trackIndex} (${named}) cannot be re-packaged for playback`);
44
- }
45
- return new MediabunnyAudioSegments({
46
- track,
47
- codec,
48
- decoderConfig
49
- }, seconds(duration));
50
- }
51
- get mimeType() {
52
- return `audio/mp4; codecs="${this.parts.decoderConfig.codec}"`;
53
- }
54
- segmentsFrom(from) {
55
- return remux(this.parts, from);
56
- }
57
- };
58
- async function* remux(parts, from) {
59
- const channel = new SegmentChannel(SEGMENTS_AHEAD);
60
- const output = new Output({
61
- format: fragmentedMp4Into(channel),
62
- target: new NullTarget()
63
- });
64
- const source = new EncodedAudioPacketSource(parts.codec);
65
- output.addAudioTrack(source);
66
- const production = produce({
67
- parts,
68
- from,
69
- output,
70
- source,
71
- channel
72
- });
73
- try {
74
- yield* channel.segments();
75
- } finally {
76
- channel.close();
77
- releaseAfter(production, output);
78
- }
79
- }
80
- /**
81
- * Frees the muxer once the producer has noticed the closed channel, without making a consumer
82
- * that left early (a seek) wait for a packet read still in flight.
83
- */
84
- async function releaseAfter(production, output) {
85
- await production;
86
- if (output.state === "finalized") return;
87
- try {
88
- await output.cancel();
89
- } catch {}
90
- }
91
- /**
92
- * Feeds packets to the muxer at the consumer's pace; every failure ends up in the channel.
93
- */
94
- async function produce(run) {
95
- try {
96
- await run.output.start();
97
- let meta = { decoderConfig: run.parts.decoderConfig };
98
- for await (const packet of packetsFrom(run.parts.track, run.from)) {
99
- await run.channel.waitForRoom();
100
- if (run.channel.isClosed) return;
101
- await run.source.add(packet, meta);
102
- meta = void 0;
103
- }
104
- await run.output.finalize();
105
- run.channel.close();
106
- } catch (error) {
107
- run.channel.fail(error);
108
- }
109
- }
110
- function fragmentedMp4Into(channel) {
111
- const forward = (data) => {
112
- channel.push(new Uint8Array(data));
113
- };
114
- return new Mp4OutputFormat({
115
- fastStart: "fragmented",
116
- minimumFragmentDuration: FRAGMENT_DURATION_SECONDS,
117
- onFtyp: forward,
118
- onMoov: forward,
119
- onMoof: forward,
120
- onMdat: forward
121
- });
122
- }
123
- /**
124
- * Packets in decode order from the one playing at `time` (or the first one after it) to the end.
125
- */
126
- async function* packetsFrom(track, time) {
127
- const sink = new EncodedPacketSink(track);
128
- const start = await sink.getPacket(time) ?? await sink.getFirstPacket();
129
- if (start === null) return;
130
- yield* sink.packets(start);
131
- }
132
- //#endregion
133
- export { MediabunnyAudioSegments };
@@ -1,63 +0,0 @@
1
- import { GyroViewError, isAbortError } from "../../../core/src/shared/errors/GyroViewError.js";
2
- import { seconds } from "../../../core/src/shared/units/time.js";
3
- import { ByteRange } from "../../../core/src/shared/binary/ByteRange.js";
4
- import "../../../core/src/index.js";
5
- import { MediabunnyAudioSegments } from "./MediabunnyAudioSegments.js";
6
- import { MediabunnyVideoTrackReader } from "./MediabunnyVideoTrackReader.js";
7
- import { ALL_FORMATS, CustomSource, Input } from "mediabunny";
8
- //#region ../../packages/adapters/mediabunny/src/MediabunnyDemuxer.ts
9
- /**
10
- * Demuxer over mediabunny. Bytes flow through the core's RandomAccessSource port (one HTTP
11
- * client, one cache), presented to mediabunny as a CustomSource with network prefetching.
12
- */
13
- var MediabunnyDemuxer = class {
14
- async open(source, name) {
15
- const input = new Input({
16
- formats: ALL_FORMATS,
17
- source: new CustomSource({
18
- getSize: () => source.size(),
19
- read: (start, end) => source.read(ByteRange.of(start, end - start)),
20
- prefetchProfile: "network",
21
- handleUnhandledError: ignoreFailedPrefetch
22
- })
23
- });
24
- try {
25
- return await describeInput(input, name);
26
- } catch (error) {
27
- input.dispose();
28
- throw failureOf(error, name);
29
- }
30
- }
31
- };
32
- /**
33
- * The source's own failures and a caller's abort pass as they are; anything else the demuxer
34
- * throws means the bytes are no media file it can read.
35
- */
36
- function failureOf(error, name) {
37
- const isPassedOn = isAbortError(error) || error instanceof GyroViewError;
38
- const message = `${name ?? "the input"} is not a readable media file`;
39
- return isPassedOn ? error : new GyroViewError("unsupported-container", message, { cause: error });
40
- }
41
- /**
42
- * A read ahead that fails while no read waits for it: left unhandled it would reach the host
43
- * page's error reporting. The read that needs those bytes asks again and fails through the port.
44
- */
45
- function ignoreFailedPrefetch() {}
46
- async function describeInput(input, name) {
47
- const [videoTracks, audioTracks, duration] = await Promise.all([
48
- input.getVideoTracks(),
49
- input.getAudioTracks(),
50
- input.computeDuration()
51
- ]);
52
- return {
53
- name,
54
- duration: seconds(duration),
55
- videoTracks: await Promise.all(videoTracks.map((track, trackIndex) => MediabunnyVideoTrackReader.open(track, trackIndex))),
56
- audioTracks: audioTracks.map((track, trackIndex) => ({ openSegments: () => MediabunnyAudioSegments.open(track, trackIndex) })),
57
- dispose: () => {
58
- input.dispose();
59
- }
60
- };
61
- }
62
- //#endregion
63
- export { MediabunnyDemuxer };
@@ -1,84 +0,0 @@
1
- import { GyroViewError } from "../../../core/src/shared/errors/GyroViewError.js";
2
- import { seconds } from "../../../core/src/shared/units/time.js";
3
- import "../../../core/src/index.js";
4
- import { copyOfBytes } from "./bufferSources.js";
5
- import { EncodedPacketSink } from "mediabunny";
6
- //#region ../../packages/adapters/mediabunny/src/MediabunnyVideoTrackReader.ts
7
- /**
8
- * Container metadata may mark a packet as a key packet that is not one; the bitstream decides.
9
- */
10
- var VERIFIED = { verifyKeyPackets: true };
11
- /**
12
- * VideoTrackReader over one mediabunny video track. Packets handed out are plain data; the
13
- * mediabunny packet behind each is remembered so iteration can resume from it.
14
- */
15
- var MediabunnyVideoTrackReader = class MediabunnyVideoTrackReader {
16
- track;
17
- description;
18
- sink;
19
- originals = /* @__PURE__ */ new WeakMap();
20
- constructor(track, description) {
21
- this.track = track;
22
- this.description = description;
23
- this.sink = new EncodedPacketSink(track);
24
- }
25
- static async open(track, trackIndex) {
26
- const [codec, codedWidth, codedHeight] = await Promise.all([
27
- track.getCodecParameterString(),
28
- track.getCodedWidth(),
29
- track.getCodedHeight()
30
- ]);
31
- if (codec === null) throw new GyroViewError("codec-unsupported", `video track ${trackIndex} has an unknown codec`);
32
- return new MediabunnyVideoTrackReader(track, {
33
- trackIndex,
34
- codedWidth,
35
- codedHeight,
36
- codec
37
- });
38
- }
39
- async decoderConfiguration() {
40
- const config = await this.track.getDecoderConfig();
41
- if (config === null) throw new GyroViewError("codec-unsupported", `video track ${this.description.trackIndex} (${this.description.codec}) cannot be configured for decoding`);
42
- return {
43
- codec: config.codec,
44
- codedWidth: config.codedWidth ?? this.description.codedWidth,
45
- codedHeight: config.codedHeight ?? this.description.codedHeight,
46
- description: config.description === void 0 ? void 0 : copyOfBytes(config.description),
47
- isFullRange: config.colorSpace?.fullRange ?? void 0
48
- };
49
- }
50
- async keyPacketAt(time) {
51
- const packet = await this.sink.getKeyPacket(time, VERIFIED);
52
- return packet === null ? void 0 : this.wrap(packet);
53
- }
54
- async firstKeyPacket() {
55
- const packet = await this.sink.getFirstKeyPacket(VERIFIED);
56
- return packet === null ? void 0 : this.wrap(packet);
57
- }
58
- async *packetsFrom(start) {
59
- const original = this.originals.get(start);
60
- if (!original) throw new GyroViewError("invariant-violation", "packetsFrom needs a packet handed out by this reader");
61
- for await (const packet of this.sink.packets(original)) yield this.wrap(packet);
62
- }
63
- async sampleTimestamps() {
64
- const timestamps = [];
65
- const metadataOnly = this.sink.packets(void 0, void 0, { metadataOnly: true });
66
- for await (const packet of metadataOnly) timestamps.push(seconds(packet.timestamp));
67
- return timestamps.toSorted((left, right) => left - right);
68
- }
69
- async frameCount() {
70
- return (await this.track.computePacketStats()).packetCount;
71
- }
72
- wrap(packet) {
73
- const wrapped = {
74
- timestamp: seconds(packet.timestamp),
75
- duration: seconds(packet.duration),
76
- isKeyFrame: packet.type === "key",
77
- data: packet.data
78
- };
79
- this.originals.set(wrapped, packet);
80
- return wrapped;
81
- }
82
- };
83
- //#endregion
84
- export { MediabunnyVideoTrackReader };