@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.
- package/CHANGELOG.md +42 -0
- package/README.md +35 -15
- package/dist/index.d.ts +21 -2
- package/dist/index.js +2 -2
- package/dist/packages/adapters/fetch/src/HttpByteStream.js +174 -0
- package/dist/packages/adapters/fetch/src/HttpRangeSource.js +14 -170
- package/dist/packages/adapters/fetch/src/HttpResource.js +192 -0
- package/dist/packages/adapters/fetch/src/contentRange.js +15 -0
- package/dist/packages/adapters/fetch/src/httpRequest.js +5 -3
- package/dist/packages/adapters/fetch/src/index.js +3 -1
- package/dist/packages/adapters/fetch/src/passingFailures.js +24 -0
- package/dist/packages/adapters/fetch/src/rangeFailures.js +17 -0
- package/dist/packages/adapters/fetch/src/recordingVersion.js +37 -0
- package/dist/packages/adapters/mediabunny/src/MediabunnyAudioPackager.js +129 -0
- package/dist/packages/adapters/mediabunny/src/MediabunnyCodecReader.js +80 -0
- package/dist/packages/adapters/mediabunny/src/SegmentChannel.js +58 -6
- package/dist/packages/adapters/mediabunny/src/decoderConfigurations.js +28 -0
- package/dist/packages/adapters/mediabunny/src/index.js +3 -2
- package/dist/packages/adapters/webcodecs/src/WebCodecsVideoDecoderPort.js +12 -3
- package/dist/packages/core/src/application/download/BlockStore.js +97 -0
- package/dist/packages/core/src/application/download/DownloadedAudioSamples.js +50 -0
- package/dist/packages/core/src/application/download/DownloadedVideoTrack.js +122 -0
- package/dist/packages/core/src/application/download/FileDownload.js +183 -0
- package/dist/packages/core/src/application/download/RecordingBuffer.js +22 -0
- package/dist/packages/core/src/application/download/SampleCursor.js +88 -0
- package/dist/packages/core/src/application/download/SourceByteStream.js +46 -0
- package/dist/packages/core/src/application/download/Transfers.js +75 -0
- package/dist/packages/core/src/application/download/startFileDownload.js +37 -0
- package/dist/packages/core/src/application/playback/Buffering.js +53 -0
- package/dist/packages/core/src/application/playback/DecodePipeline.js +11 -7
- package/dist/packages/core/src/application/playback/DecodeRun.js +15 -0
- package/dist/packages/core/src/application/playback/PlaybackSession.js +18 -27
- package/dist/packages/core/src/application/playback/keyframeTimeAt.js +1 -1
- package/dist/packages/core/src/application/playback/probeDecoding.js +17 -3
- package/dist/packages/core/src/application/recording/readSampleTable.js +27 -0
- package/dist/packages/core/src/application/recording/timeRecording.js +3 -3
- package/dist/packages/core/src/domain/container/KeyframeRule.js +7 -0
- package/dist/packages/core/src/domain/container/SampleTable.js +29 -0
- package/dist/packages/core/src/domain/container/TrackSampleTable.js +96 -0
- package/dist/packages/core/src/domain/container/presentationOrder.js +10 -0
- package/dist/packages/core/src/domain/download/DownloadPolicy.js +41 -0
- package/dist/packages/core/src/domain/download/isReadyToResume.js +27 -0
- package/dist/packages/core/src/domain/download/planDownloads.js +175 -0
- package/dist/packages/core/src/domain/download/trackNeeds.js +32 -0
- package/dist/packages/core/src/domain/format/boxes/boxHeader.js +44 -0
- package/dist/packages/core/src/domain/format/boxes/scanBoxes.js +7 -34
- package/dist/packages/core/src/domain/format/mp4/keyframeRules.js +81 -0
- package/dist/packages/core/src/domain/format/mp4/movieBoxes.js +52 -0
- package/dist/packages/core/src/domain/format/mp4/mp4BoxTypes.js +46 -0
- package/dist/packages/core/src/domain/format/mp4/mp4Layouts.js +183 -0
- package/dist/packages/core/src/domain/format/mp4/parseMovie.js +67 -0
- package/dist/packages/core/src/domain/format/mp4/readTable.js +31 -0
- package/dist/packages/core/src/domain/format/mp4/sampleLocations.js +69 -0
- package/dist/packages/core/src/domain/format/mp4/sampleTiming.js +109 -0
- package/dist/packages/core/src/domain/format/mp4/trackHeaders.js +75 -0
- package/dist/packages/core/src/index.js +10 -2
- package/dist/packages/core/src/shared/async/iteration.js +24 -0
- package/dist/packages/core/src/shared/binary/ByteRange.js +12 -0
- package/dist/packages/core/src/shared/binary/ByteRangeSet.js +85 -0
- package/dist/packages/core/src/shared/binary/ByteReader.js +21 -3
- package/dist/packages/core/src/shared/binary/concatenated.js +15 -0
- package/dist/packages/core/src/shared/errors/GyroViewError.js +56 -3
- package/dist/packages/core/src/shared/math/countAtOrBelow.js +17 -0
- package/dist/packages/player/src/composition/browserPorts.js +23 -4
- package/dist/packages/player/src/composition/buildPipeline.js +15 -3
- package/dist/packages/player/src/composition/openInputs.js +51 -107
- package/dist/packages/player/src/composition/readInputs.js +111 -0
- package/dist/packages/player/src/controls/messages.js +3 -1
- package/dist/packages/player/src/inspectRecording.js +1 -1
- package/dist/standalone.js +58 -58
- package/package.json +1 -1
- package/dist/packages/adapters/mediabunny/src/MediabunnyAudioSegments.js +0 -133
- package/dist/packages/adapters/mediabunny/src/MediabunnyDemuxer.js +0 -63
- 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.
|
|
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 };
|