More linting.
This commit is contained in:
parent
f05bd5a0ac
commit
cc8792da09
|
@ -1,8 +1,6 @@
|
||||||
name: server
|
name: server
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
|
||||||
branches: [ "main" ]
|
|
||||||
pull_request:
|
pull_request:
|
||||||
branches: [ "main" ]
|
branches: [ "main" ]
|
||||||
|
|
||||||
|
@ -15,9 +13,23 @@ jobs:
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
- name: build
|
|
||||||
working-directory: server
|
- name: toolchain
|
||||||
run: cargo build --verbose
|
uses: actions-rust-lang/setup-rust-toolchain@v1
|
||||||
|
with:
|
||||||
|
components: clippy, rustfmt
|
||||||
|
|
||||||
- name: test
|
- name: test
|
||||||
working-directory: server
|
working-directory: server
|
||||||
run: cargo test --verbose
|
run: cargo test --verbose
|
||||||
|
|
||||||
|
- name: fmt
|
||||||
|
uses: mbrobbel/rustfmt-check@master
|
||||||
|
with:
|
||||||
|
token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
|
- name: clippy
|
||||||
|
uses: actions-rs/clippy-check@v1
|
||||||
|
with:
|
||||||
|
token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
args: --all-features
|
||||||
|
|
|
@ -1,8 +1,6 @@
|
||||||
name: web
|
name: web
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
|
||||||
branches: [ "main" ]
|
|
||||||
pull_request:
|
pull_request:
|
||||||
branches: [ "main" ]
|
branches: [ "main" ]
|
||||||
|
|
||||||
|
@ -12,15 +10,23 @@ jobs:
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
|
|
||||||
- name: install
|
- name: install
|
||||||
working-directory: web
|
working-directory: web
|
||||||
run: yarn install
|
run: yarn install
|
||||||
|
|
||||||
- name: cert
|
- name: cert
|
||||||
working-directory: cert
|
working-directory: cert
|
||||||
run: ./generate
|
run: ./generate
|
||||||
|
|
||||||
- name: build
|
- name: build
|
||||||
working-directory: web
|
working-directory: web
|
||||||
run: yarn build
|
run: yarn build
|
||||||
|
|
||||||
|
- name: fmt
|
||||||
|
working-directory: web
|
||||||
|
run: yarn prettier --check .
|
||||||
|
|
||||||
- name: lint
|
- name: lint
|
||||||
working-directory: web
|
working-directory: web
|
||||||
run: yarn lint
|
run: yarn lint
|
||||||
|
|
|
@ -1,10 +1,14 @@
|
||||||
/* eslint-env node */
|
/* eslint-env node */
|
||||||
module.exports = {
|
module.exports = {
|
||||||
extends: ['eslint:recommended', 'plugin:@typescript-eslint/recommended'],
|
extends: [
|
||||||
parser: '@typescript-eslint/parser',
|
"eslint:recommended",
|
||||||
plugins: ['@typescript-eslint'],
|
"plugin:@typescript-eslint/recommended",
|
||||||
|
"prettier",
|
||||||
|
],
|
||||||
|
parser: "@typescript-eslint/parser",
|
||||||
|
plugins: ["@typescript-eslint"],
|
||||||
root: true,
|
root: true,
|
||||||
ignorePatterns: [ 'dist', 'node_modules' ],
|
ignorePatterns: ["dist", "node_modules"],
|
||||||
rules: {
|
rules: {
|
||||||
"@typescript-eslint/ban-ts-comment": "off",
|
"@typescript-eslint/ban-ts-comment": "off",
|
||||||
"@typescript-eslint/no-non-null-assertion": "off",
|
"@typescript-eslint/no-non-null-assertion": "off",
|
||||||
|
@ -13,10 +17,10 @@ module.exports = {
|
||||||
"@typescript-eslint/no-unused-vars": [
|
"@typescript-eslint/no-unused-vars": [
|
||||||
"warn", // or "error"
|
"warn", // or "error"
|
||||||
{
|
{
|
||||||
"argsIgnorePattern": "^_",
|
argsIgnorePattern: "^_",
|
||||||
"varsIgnorePattern": "^_",
|
varsIgnorePattern: "^_",
|
||||||
"caughtErrorsIgnorePattern": "^_"
|
caughtErrorsIgnorePattern: "^_",
|
||||||
}
|
},
|
||||||
],
|
],
|
||||||
}
|
},
|
||||||
};
|
}
|
||||||
|
|
|
@ -0,0 +1,2 @@
|
||||||
|
dist
|
||||||
|
node_modules
|
|
@ -0,0 +1,5 @@
|
||||||
|
{
|
||||||
|
"useTabs": true,
|
||||||
|
"tabWidth": 4,
|
||||||
|
"semi": false
|
||||||
|
}
|
|
@ -1,7 +1,7 @@
|
||||||
module.exports = function (app) {
|
module.exports = function (app) {
|
||||||
app.use((req, res, next) => {
|
app.use((req, res, next) => {
|
||||||
res.setHeader('Cross-Origin-Opener-Policy', 'same-origin');
|
res.setHeader("Cross-Origin-Opener-Policy", "same-origin")
|
||||||
res.setHeader('Cross-Origin-Embedder-Policy', 'require-corp');
|
res.setHeader("Cross-Origin-Embedder-Policy", "require-corp")
|
||||||
next();
|
next()
|
||||||
});
|
})
|
||||||
};
|
}
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -5,7 +5,8 @@
|
||||||
"serve": "parcel serve --https --cert ../cert/localhost.crt --key ../cert/localhost.key --port 4444 --open",
|
"serve": "parcel serve --https --cert ../cert/localhost.crt --key ../cert/localhost.key --port 4444 --open",
|
||||||
"build": "parcel build",
|
"build": "parcel build",
|
||||||
"check": "tsc --noEmit",
|
"check": "tsc --noEmit",
|
||||||
"lint": "eslint ."
|
"lint": "eslint .",
|
||||||
|
"fmt": "prettier --write ."
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@parcel/transformer-inline-string": "2.8.3",
|
"@parcel/transformer-inline-string": "2.8.3",
|
||||||
|
@ -15,7 +16,9 @@
|
||||||
"@typescript-eslint/eslint-plugin": "^5.59.7",
|
"@typescript-eslint/eslint-plugin": "^5.59.7",
|
||||||
"@typescript-eslint/parser": "^5.59.7",
|
"@typescript-eslint/parser": "^5.59.7",
|
||||||
"eslint": "^8.41.0",
|
"eslint": "^8.41.0",
|
||||||
|
"eslint-config-prettier": "^8.8.0",
|
||||||
"parcel": "^2.8.0",
|
"parcel": "^2.8.0",
|
||||||
|
"prettier": "^2.8.8",
|
||||||
"typescript": "^5.0.4"
|
"typescript": "^5.0.4"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
|
|
@ -6,19 +6,19 @@ export class Encoder {
|
||||||
video: VideoEncoder
|
video: VideoEncoder
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
this.container = new MP4.ISOFile();
|
this.container = new MP4.ISOFile()
|
||||||
|
|
||||||
this.audio = new AudioEncoder({
|
this.audio = new AudioEncoder({
|
||||||
output: this.onAudio.bind(this),
|
output: this.onAudio.bind(this),
|
||||||
error: console.warn,
|
error: console.warn,
|
||||||
});
|
})
|
||||||
|
|
||||||
this.video = new VideoEncoder({
|
this.video = new VideoEncoder({
|
||||||
output: this.onVideo.bind(this),
|
output: this.onVideo.bind(this),
|
||||||
error: console.warn,
|
error: console.warn,
|
||||||
});
|
})
|
||||||
|
|
||||||
this.container.init();
|
this.container.init()
|
||||||
|
|
||||||
this.audio.configure({
|
this.audio.configure({
|
||||||
codec: "mp4a.40.2",
|
codec: "mp4a.40.2",
|
||||||
|
@ -43,7 +43,7 @@ export class Encoder {
|
||||||
|
|
||||||
onAudio(frame: EncodedAudioChunk, metadata: EncodedAudioChunkMetadata) {
|
onAudio(frame: EncodedAudioChunk, metadata: EncodedAudioChunkMetadata) {
|
||||||
const config = metadata.decoderConfig!
|
const config = metadata.decoderConfig!
|
||||||
const track_id = 1;
|
const track_id = 1
|
||||||
|
|
||||||
if (!this.container.getTrackById(track_id)) {
|
if (!this.container.getTrackById(track_id)) {
|
||||||
this.container.addTrack({
|
this.container.addTrack({
|
||||||
|
@ -56,25 +56,25 @@ export class Encoder {
|
||||||
|
|
||||||
description: config.description, // TODO verify
|
description: config.description, // TODO verify
|
||||||
// TODO description_boxes?: Box[];
|
// TODO description_boxes?: Box[];
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const buffer = new Uint8Array(frame.byteLength);
|
const buffer = new Uint8Array(frame.byteLength)
|
||||||
frame.copyTo(buffer);
|
frame.copyTo(buffer)
|
||||||
|
|
||||||
// TODO cts?
|
// TODO cts?
|
||||||
const sample = this.container.addSample(track_id, buffer, {
|
const sample = this.container.addSample(track_id, buffer, {
|
||||||
is_sync: frame.type == "key",
|
is_sync: frame.type == "key",
|
||||||
duration: frame.duration!,
|
duration: frame.duration!,
|
||||||
dts: frame.timestamp,
|
dts: frame.timestamp,
|
||||||
});
|
})
|
||||||
|
|
||||||
const _stream = this.container.createSingleSampleMoof(sample);
|
const _stream = this.container.createSingleSampleMoof(sample)
|
||||||
}
|
}
|
||||||
|
|
||||||
onVideo(frame: EncodedVideoChunk, metadata?: EncodedVideoChunkMetadata) {
|
onVideo(frame: EncodedVideoChunk, metadata?: EncodedVideoChunkMetadata) {
|
||||||
const config = metadata!.decoderConfig!
|
const config = metadata!.decoderConfig!
|
||||||
const track_id = 2;
|
const track_id = 2
|
||||||
|
|
||||||
if (!this.container.getTrackById(track_id)) {
|
if (!this.container.getTrackById(track_id)) {
|
||||||
this.container.addTrack({
|
this.container.addTrack({
|
||||||
|
@ -86,19 +86,19 @@ export class Encoder {
|
||||||
|
|
||||||
description: config.description, // TODO verify
|
description: config.description, // TODO verify
|
||||||
// TODO description_boxes?: Box[];
|
// TODO description_boxes?: Box[];
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const buffer = new Uint8Array(frame.byteLength);
|
const buffer = new Uint8Array(frame.byteLength)
|
||||||
frame.copyTo(buffer);
|
frame.copyTo(buffer)
|
||||||
|
|
||||||
// TODO cts?
|
// TODO cts?
|
||||||
const sample = this.container.addSample(track_id, buffer, {
|
const sample = this.container.addSample(track_id, buffer, {
|
||||||
is_sync: frame.type == "key",
|
is_sync: frame.type == "key",
|
||||||
duration: frame.duration!,
|
duration: frame.duration!,
|
||||||
dts: frame.timestamp,
|
dts: frame.timestamp,
|
||||||
});
|
})
|
||||||
|
|
||||||
const _stream = this.container.createSingleSampleMoof(sample);
|
const _stream = this.container.createSingleSampleMoof(sample)
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,4 +1,6 @@
|
||||||
html, body, #player {
|
html,
|
||||||
|
body,
|
||||||
|
#player {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,14 +1,13 @@
|
||||||
<!doctype html>
|
<!DOCTYPE html>
|
||||||
<html>
|
<html>
|
||||||
|
<head>
|
||||||
<head>
|
<meta charset="UTF-8" />
|
||||||
<meta charset="UTF-8">
|
|
||||||
<title>WARP</title>
|
<title>WARP</title>
|
||||||
|
|
||||||
<link rel="stylesheet" href="index.css">
|
<link rel="stylesheet" href="index.css" />
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
<div id="player">
|
<div id="player">
|
||||||
<div id="screen">
|
<div id="screen">
|
||||||
<div id="play"><span>click to play</span></div>
|
<div id="play"><span>click to play</span></div>
|
||||||
|
@ -30,6 +29,5 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script src="index.ts" type="module"></script>
|
<script src="index.ts" type="module"></script>
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
</html>
|
</html>
|
|
@ -2,12 +2,12 @@ import Player from "./player"
|
||||||
import Transport from "./transport"
|
import Transport from "./transport"
|
||||||
|
|
||||||
// @ts-ignore embed the certificate fingerprint using bundler
|
// @ts-ignore embed the certificate fingerprint using bundler
|
||||||
import fingerprintHex from 'bundle-text:../fingerprint.hex';
|
import fingerprintHex from "bundle-text:../fingerprint.hex"
|
||||||
|
|
||||||
// Convert the hex to binary.
|
// Convert the hex to binary.
|
||||||
const fingerprint = [];
|
const fingerprint = []
|
||||||
for (let c = 0; c < fingerprintHex.length - 1; c += 2) {
|
for (let c = 0; c < fingerprintHex.length - 1; c += 2) {
|
||||||
fingerprint.push(parseInt(fingerprintHex.substring(c, c + 2), 16));
|
fingerprint.push(parseInt(fingerprintHex.substring(c, c + 2), 16))
|
||||||
}
|
}
|
||||||
|
|
||||||
const params = new URLSearchParams(window.location.search)
|
const params = new URLSearchParams(window.location.search)
|
||||||
|
@ -17,9 +17,10 @@ const canvas = document.querySelector<HTMLCanvasElement>("canvas#video")!
|
||||||
|
|
||||||
const transport = new Transport({
|
const transport = new Transport({
|
||||||
url: url,
|
url: url,
|
||||||
fingerprint: { // TODO remove when Chrome accepts the system CA
|
fingerprint: {
|
||||||
"algorithm": "sha-256",
|
// TODO remove when Chrome accepts the system CA
|
||||||
"value": new Uint8Array(fingerprint),
|
algorithm: "sha-256",
|
||||||
|
value: new Uint8Array(fingerprint),
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -34,8 +35,8 @@ const playFunc = (e: Event) => {
|
||||||
player.play()
|
player.play()
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
|
|
||||||
play.removeEventListener('click', playFunc)
|
play.removeEventListener("click", playFunc)
|
||||||
play.style.display = "none"
|
play.style.display = "none"
|
||||||
}
|
}
|
||||||
|
|
||||||
play.addEventListener('click', playFunc)
|
play.addEventListener("click", playFunc)
|
||||||
|
|
|
@ -1,16 +1,16 @@
|
||||||
import * as MP4 from "./index"
|
import * as MP4 from "./index"
|
||||||
|
|
||||||
export interface Init {
|
export interface Init {
|
||||||
raw: MP4.ArrayBuffer;
|
raw: MP4.ArrayBuffer
|
||||||
info: MP4.Info;
|
info: MP4.Info
|
||||||
}
|
}
|
||||||
|
|
||||||
export class InitParser {
|
export class InitParser {
|
||||||
mp4box: MP4.File;
|
mp4box: MP4.File
|
||||||
offset: number;
|
offset: number
|
||||||
|
|
||||||
raw: MP4.ArrayBuffer[];
|
raw: MP4.ArrayBuffer[]
|
||||||
info: Promise<MP4.Info>;
|
info: Promise<MP4.Info>
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
this.mp4box = MP4.New()
|
this.mp4box = MP4.New()
|
||||||
|
@ -26,7 +26,7 @@ export class InitParser {
|
||||||
|
|
||||||
push(data: Uint8Array) {
|
push(data: Uint8Array) {
|
||||||
// Make a copy of the atom because mp4box only accepts an ArrayBuffer unfortunately
|
// Make a copy of the atom because mp4box only accepts an ArrayBuffer unfortunately
|
||||||
const box = new Uint8Array(data.byteLength);
|
const box = new Uint8Array(data.byteLength)
|
||||||
box.set(data)
|
box.set(data)
|
||||||
|
|
||||||
// and for some reason we need to modify the underlying ArrayBuffer with fileStart
|
// and for some reason we need to modify the underlying ArrayBuffer with fileStart
|
||||||
|
|
|
@ -2,224 +2,242 @@
|
||||||
|
|
||||||
declare module "mp4box" {
|
declare module "mp4box" {
|
||||||
export interface MP4MediaTrack {
|
export interface MP4MediaTrack {
|
||||||
id: number;
|
id: number
|
||||||
created: Date;
|
created: Date
|
||||||
modified: Date;
|
modified: Date
|
||||||
movie_duration: number;
|
movie_duration: number
|
||||||
layer: number;
|
layer: number
|
||||||
alternate_group: number;
|
alternate_group: number
|
||||||
volume: number;
|
volume: number
|
||||||
track_width: number;
|
track_width: number
|
||||||
track_height: number;
|
track_height: number
|
||||||
timescale: number;
|
timescale: number
|
||||||
duration: number;
|
duration: number
|
||||||
bitrate: number;
|
bitrate: number
|
||||||
codec: string;
|
codec: string
|
||||||
language: string;
|
language: string
|
||||||
nb_samples: number;
|
nb_samples: number
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface MP4VideoData {
|
export interface MP4VideoData {
|
||||||
width: number;
|
width: number
|
||||||
height: number;
|
height: number
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface MP4VideoTrack extends MP4MediaTrack {
|
export interface MP4VideoTrack extends MP4MediaTrack {
|
||||||
video: MP4VideoData;
|
video: MP4VideoData
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface MP4AudioData {
|
export interface MP4AudioData {
|
||||||
sample_rate: number;
|
sample_rate: number
|
||||||
channel_count: number;
|
channel_count: number
|
||||||
sample_size: number;
|
sample_size: number
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface MP4AudioTrack extends MP4MediaTrack {
|
export interface MP4AudioTrack extends MP4MediaTrack {
|
||||||
audio: MP4AudioData;
|
audio: MP4AudioData
|
||||||
}
|
}
|
||||||
|
|
||||||
export type MP4Track = MP4VideoTrack | MP4AudioTrack;
|
export type MP4Track = MP4VideoTrack | MP4AudioTrack
|
||||||
|
|
||||||
export interface MP4Info {
|
export interface MP4Info {
|
||||||
duration: number;
|
duration: number
|
||||||
timescale: number;
|
timescale: number
|
||||||
fragment_duration: number;
|
fragment_duration: number
|
||||||
isFragmented: boolean;
|
isFragmented: boolean
|
||||||
isProgressive: boolean;
|
isProgressive: boolean
|
||||||
hasIOD: boolean;
|
hasIOD: boolean
|
||||||
brands: string[];
|
brands: string[]
|
||||||
created: Date;
|
created: Date
|
||||||
modified: Date;
|
modified: Date
|
||||||
tracks: MP4Track[];
|
tracks: MP4Track[]
|
||||||
mime: string;
|
mime: string
|
||||||
audioTracks: MP4AudioTrack[];
|
audioTracks: MP4AudioTrack[]
|
||||||
videoTracks: MP4VideoTrack[];
|
videoTracks: MP4VideoTrack[]
|
||||||
}
|
}
|
||||||
|
|
||||||
export type MP4ArrayBuffer = ArrayBuffer & {fileStart: number};
|
export type MP4ArrayBuffer = ArrayBuffer & { fileStart: number }
|
||||||
|
|
||||||
export interface MP4File {
|
export interface MP4File {
|
||||||
onMoovStart?: () => void;
|
onMoovStart?: () => void
|
||||||
onReady?: (info: MP4Info) => void;
|
onReady?: (info: MP4Info) => void
|
||||||
onError?: (e: string) => void;
|
onError?: (e: string) => void
|
||||||
onSamples?: (id: number, user: any, samples: Sample[]) => void;
|
onSamples?: (id: number, user: any, samples: Sample[]) => void
|
||||||
|
|
||||||
appendBuffer(data: MP4ArrayBuffer): number;
|
appendBuffer(data: MP4ArrayBuffer): number
|
||||||
start(): void;
|
start(): void
|
||||||
stop(): void;
|
stop(): void
|
||||||
flush(): void;
|
flush(): void
|
||||||
|
|
||||||
setExtractionOptions(id: number, user: any, options: ExtractionOptions): void;
|
setExtractionOptions(
|
||||||
|
id: number,
|
||||||
|
user: any,
|
||||||
|
options: ExtractionOptions
|
||||||
|
): void
|
||||||
}
|
}
|
||||||
|
|
||||||
export function createFile(): MP4File;
|
export function createFile(): MP4File
|
||||||
|
|
||||||
export interface Sample {
|
export interface Sample {
|
||||||
number: number;
|
number: number
|
||||||
track_id: number;
|
track_id: number
|
||||||
timescale: number;
|
timescale: number
|
||||||
description_index: number;
|
description_index: number
|
||||||
description: any;
|
description: any
|
||||||
data: ArrayBuffer;
|
data: ArrayBuffer
|
||||||
size: number;
|
size: number
|
||||||
alreadyRead?: number;
|
alreadyRead?: number
|
||||||
duration: number;
|
duration: number
|
||||||
cts: number;
|
cts: number
|
||||||
dts: number;
|
dts: number
|
||||||
is_sync: boolean;
|
is_sync: boolean
|
||||||
is_leading: number;
|
is_leading: number
|
||||||
depends_on: number;
|
depends_on: number
|
||||||
is_depended_on: number;
|
is_depended_on: number
|
||||||
has_redundancy: number;
|
has_redundancy: number
|
||||||
degration_priority: number;
|
degration_priority: number
|
||||||
offset: number;
|
offset: number
|
||||||
subsamples: any;
|
subsamples: any
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ExtractionOptions {
|
export interface ExtractionOptions {
|
||||||
nbSamples: number;
|
nbSamples: number
|
||||||
}
|
}
|
||||||
|
|
||||||
const BIG_ENDIAN: boolean;
|
const BIG_ENDIAN: boolean
|
||||||
const LITTLE_ENDIAN: boolean;
|
const LITTLE_ENDIAN: boolean
|
||||||
|
|
||||||
export class DataStream {
|
export class DataStream {
|
||||||
constructor(buffer?: ArrayBuffer, byteOffset?: number, littleEndian?: boolean);
|
constructor(
|
||||||
getPosition(): number;
|
buffer?: ArrayBuffer,
|
||||||
|
byteOffset?: number,
|
||||||
|
littleEndian?: boolean
|
||||||
|
)
|
||||||
|
getPosition(): number
|
||||||
|
|
||||||
get byteLength(): number;
|
get byteLength(): number
|
||||||
get buffer(): ArrayBuffer;
|
get buffer(): ArrayBuffer
|
||||||
set buffer(v: ArrayBuffer);
|
set buffer(v: ArrayBuffer)
|
||||||
get byteOffset(): number;
|
get byteOffset(): number
|
||||||
set byteOffset(v: number);
|
set byteOffset(v: number)
|
||||||
get dataView(): DataView;
|
get dataView(): DataView
|
||||||
set dataView(v: DataView);
|
set dataView(v: DataView)
|
||||||
|
|
||||||
seek(pos: number): void;
|
seek(pos: number): void
|
||||||
isEof(): boolean;
|
isEof(): boolean
|
||||||
|
|
||||||
mapUint8Array(length: number): Uint8Array;
|
mapUint8Array(length: number): Uint8Array
|
||||||
readInt32Array(length: number, littleEndian: boolean): Int32Array;
|
readInt32Array(length: number, littleEndian: boolean): Int32Array
|
||||||
readInt16Array(length: number, littleEndian: boolean): Int16Array;
|
readInt16Array(length: number, littleEndian: boolean): Int16Array
|
||||||
readInt8Array(length: number): Int8Array;
|
readInt8Array(length: number): Int8Array
|
||||||
readUint32Array(length: number, littleEndian: boolean): Uint32Array;
|
readUint32Array(length: number, littleEndian: boolean): Uint32Array
|
||||||
readUint16Array(length: number, littleEndian: boolean): Uint16Array;
|
readUint16Array(length: number, littleEndian: boolean): Uint16Array
|
||||||
readUint8Array(length: number): Uint8Array;
|
readUint8Array(length: number): Uint8Array
|
||||||
readFloat64Array(length: number, littleEndian: boolean): Float64Array;
|
readFloat64Array(length: number, littleEndian: boolean): Float64Array
|
||||||
readFloat32Array(length: number, littleEndian: boolean): Float32Array;
|
readFloat32Array(length: number, littleEndian: boolean): Float32Array
|
||||||
|
|
||||||
readInt32(littleEndian: boolean): number;
|
readInt32(littleEndian: boolean): number
|
||||||
readInt16(littleEndian: boolean): number;
|
readInt16(littleEndian: boolean): number
|
||||||
readInt8(): number;
|
readInt8(): number
|
||||||
readUint32(littleEndian: boolean): number;
|
readUint32(littleEndian: boolean): number
|
||||||
readUint16(littleEndian: boolean): number;
|
readUint16(littleEndian: boolean): number
|
||||||
readUint8(): number;
|
readUint8(): number
|
||||||
readFloat32(littleEndian: boolean): number;
|
readFloat32(littleEndian: boolean): number
|
||||||
readFloat64(littleEndian: boolean): number;
|
readFloat64(littleEndian: boolean): number
|
||||||
|
|
||||||
endianness: boolean;
|
endianness: boolean
|
||||||
|
|
||||||
memcpy(dst: ArrayBufferLike, dstOffset: number, src: ArrayBufferLike, srcOffset: number, byteLength: number): void;
|
memcpy(
|
||||||
|
dst: ArrayBufferLike,
|
||||||
|
dstOffset: number,
|
||||||
|
src: ArrayBufferLike,
|
||||||
|
srcOffset: number,
|
||||||
|
byteLength: number
|
||||||
|
): void
|
||||||
|
|
||||||
// TODO I got bored porting the remaining functions
|
// TODO I got bored porting the remaining functions
|
||||||
}
|
}
|
||||||
|
|
||||||
export class Box {
|
export class Box {
|
||||||
write(stream: DataStream): void;
|
write(stream: DataStream): void
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface TrackOptions {
|
export interface TrackOptions {
|
||||||
id?: number;
|
id?: number
|
||||||
type?: string;
|
type?: string
|
||||||
width?: number;
|
width?: number
|
||||||
height?: number;
|
height?: number
|
||||||
duration?: number;
|
duration?: number
|
||||||
layer?: number;
|
layer?: number
|
||||||
timescale?: number;
|
timescale?: number
|
||||||
media_duration?: number;
|
media_duration?: number
|
||||||
language?: string;
|
language?: string
|
||||||
hdlr?: string;
|
hdlr?: string
|
||||||
|
|
||||||
// video
|
// video
|
||||||
avcDecoderConfigRecord?: any;
|
avcDecoderConfigRecord?: any
|
||||||
|
|
||||||
// audio
|
// audio
|
||||||
balance?: number;
|
balance?: number
|
||||||
channel_count?: number;
|
channel_count?: number
|
||||||
samplesize?: number;
|
samplesize?: number
|
||||||
samplerate?: number;
|
samplerate?: number
|
||||||
|
|
||||||
//captions
|
//captions
|
||||||
namespace?: string;
|
namespace?: string
|
||||||
schema_location?: string;
|
schema_location?: string
|
||||||
auxiliary_mime_types?: string;
|
auxiliary_mime_types?: string
|
||||||
|
|
||||||
description?: any;
|
description?: any
|
||||||
description_boxes?: Box[];
|
description_boxes?: Box[]
|
||||||
|
|
||||||
default_sample_description_index_id?: number;
|
default_sample_description_index_id?: number
|
||||||
default_sample_duration?: number;
|
default_sample_duration?: number
|
||||||
default_sample_size?: number;
|
default_sample_size?: number
|
||||||
default_sample_flags?: number;
|
default_sample_flags?: number
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface FileOptions {
|
export interface FileOptions {
|
||||||
brands?: string[];
|
brands?: string[]
|
||||||
timescale?: number;
|
timescale?: number
|
||||||
rate?: number;
|
rate?: number
|
||||||
duration?: number;
|
duration?: number
|
||||||
width?: number;
|
width?: number
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface SampleOptions {
|
export interface SampleOptions {
|
||||||
sample_description_index?: number;
|
sample_description_index?: number
|
||||||
duration?: number;
|
duration?: number
|
||||||
cts?: number;
|
cts?: number
|
||||||
dts?: number;
|
dts?: number
|
||||||
is_sync?: boolean;
|
is_sync?: boolean
|
||||||
is_leading?: number;
|
is_leading?: number
|
||||||
depends_on?: number;
|
depends_on?: number
|
||||||
is_depended_on?: number;
|
is_depended_on?: number
|
||||||
has_redundancy?: number;
|
has_redundancy?: number
|
||||||
degradation_priority?: number;
|
degradation_priority?: number
|
||||||
subsamples?: any;
|
subsamples?: any
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO add the remaining functions
|
// TODO add the remaining functions
|
||||||
// TODO move to another module
|
// TODO move to another module
|
||||||
export class ISOFile {
|
export class ISOFile {
|
||||||
constructor(stream?: DataStream);
|
constructor(stream?: DataStream)
|
||||||
|
|
||||||
init(options?: FileOptions): ISOFile;
|
init(options?: FileOptions): ISOFile
|
||||||
addTrack(options?: TrackOptions): number;
|
addTrack(options?: TrackOptions): number
|
||||||
addSample(track: number, data: ArrayBuffer, options?: SampleOptions): Sample;
|
addSample(
|
||||||
|
track: number,
|
||||||
|
data: ArrayBuffer,
|
||||||
|
options?: SampleOptions
|
||||||
|
): Sample
|
||||||
|
|
||||||
createSingleSampleMoof(sample: Sample): Box;
|
createSingleSampleMoof(sample: Sample): Box
|
||||||
|
|
||||||
// helpers
|
// helpers
|
||||||
getTrackById(id: number): Box | undefined;
|
getTrackById(id: number): Box | undefined
|
||||||
getTrexById(id: number): Box | undefined;
|
getTrexById(id: number): Box | undefined
|
||||||
}
|
}
|
||||||
|
|
||||||
export { };
|
export {}
|
||||||
}
|
}
|
|
@ -1,12 +1,12 @@
|
||||||
import * as Message from "./message";
|
import * as Message from "./message"
|
||||||
import { Ring } from "./ring"
|
import { Ring } from "./ring"
|
||||||
|
|
||||||
export default class Audio {
|
export default class Audio {
|
||||||
ring?: Ring;
|
ring?: Ring
|
||||||
queue: Array<AudioData>;
|
queue: Array<AudioData>
|
||||||
|
|
||||||
render?: number; // non-zero if requestAnimationFrame has been called
|
render?: number // non-zero if requestAnimationFrame has been called
|
||||||
last?: number; // the timestamp of the last rendered frame, in microseconds
|
last?: number // the timestamp of the last rendered frame, in microseconds
|
||||||
|
|
||||||
constructor(_config: Message.Config) {
|
constructor(_config: Message.Config) {
|
||||||
this.queue = []
|
this.queue = []
|
||||||
|
@ -20,18 +20,21 @@ export default class Audio {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Insert the frame into the queue sorted by timestamp.
|
// Insert the frame into the queue sorted by timestamp.
|
||||||
if (this.queue.length > 0 && this.queue[this.queue.length - 1].timestamp <= frame.timestamp) {
|
if (
|
||||||
|
this.queue.length > 0 &&
|
||||||
|
this.queue[this.queue.length - 1].timestamp <= frame.timestamp
|
||||||
|
) {
|
||||||
// Fast path because we normally append to the end.
|
// Fast path because we normally append to the end.
|
||||||
this.queue.push(frame)
|
this.queue.push(frame)
|
||||||
} else {
|
} else {
|
||||||
// Do a full binary search
|
// Do a full binary search
|
||||||
let low = 0
|
let low = 0
|
||||||
let high = this.queue.length;
|
let high = this.queue.length
|
||||||
|
|
||||||
while (low < high) {
|
while (low < high) {
|
||||||
const mid = (low + high) >>> 1;
|
const mid = (low + high) >>> 1
|
||||||
if (this.queue[mid].timestamp < frame.timestamp) low = mid + 1;
|
if (this.queue[mid].timestamp < frame.timestamp) low = mid + 1
|
||||||
else high = mid;
|
else high = mid
|
||||||
}
|
}
|
||||||
|
|
||||||
this.queue.splice(low, 0, frame)
|
this.queue.splice(low, 0, frame)
|
||||||
|
@ -47,7 +50,7 @@ export default class Audio {
|
||||||
}
|
}
|
||||||
|
|
||||||
while (this.queue.length) {
|
while (this.queue.length) {
|
||||||
const frame = this.queue[0];
|
const frame = this.queue[0]
|
||||||
if (ring.size() + frame.numberOfFrames > ring.capacity) {
|
if (ring.size() + frame.numberOfFrames > ring.capacity) {
|
||||||
// Buffer is full
|
// Buffer is full
|
||||||
break
|
break
|
||||||
|
@ -72,7 +75,7 @@ export default class Audio {
|
||||||
const sampleRate = 44100 // TODO dynamic
|
const sampleRate = 44100 // TODO dynamic
|
||||||
|
|
||||||
// Refresh every half buffer
|
// Refresh every half buffer
|
||||||
const refresh = play.buffer.capacity / sampleRate * 1000 / 2
|
const refresh = ((play.buffer.capacity / sampleRate) * 1000) / 2
|
||||||
this.render = setInterval(this.emit.bind(this), refresh)
|
this.render = setInterval(this.emit.bind(this), refresh)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,22 +1,22 @@
|
||||||
import * as Message from "./message";
|
import * as Message from "./message"
|
||||||
import * as MP4 from "../mp4"
|
import * as MP4 from "../mp4"
|
||||||
import * as Stream from "../stream"
|
import * as Stream from "../stream"
|
||||||
|
|
||||||
import Renderer from "./renderer"
|
import Renderer from "./renderer"
|
||||||
|
|
||||||
export default class Decoder {
|
export default class Decoder {
|
||||||
init: MP4.InitParser;
|
init: MP4.InitParser
|
||||||
decoders: Map<number, AudioDecoder | VideoDecoder>;
|
decoders: Map<number, AudioDecoder | VideoDecoder>
|
||||||
renderer: Renderer;
|
renderer: Renderer
|
||||||
|
|
||||||
constructor(renderer: Renderer) {
|
constructor(renderer: Renderer) {
|
||||||
this.init = new MP4.InitParser();
|
this.init = new MP4.InitParser()
|
||||||
this.decoders = new Map();
|
this.decoders = new Map()
|
||||||
this.renderer = renderer;
|
this.renderer = renderer
|
||||||
}
|
}
|
||||||
|
|
||||||
async receiveInit(msg: Message.Init) {
|
async receiveInit(msg: Message.Init) {
|
||||||
const stream = new Stream.Reader(msg.reader, msg.buffer);
|
const stream = new Stream.Reader(msg.reader, msg.buffer)
|
||||||
for (;;) {
|
for (;;) {
|
||||||
const data = await stream.read()
|
const data = await stream.read()
|
||||||
if (!data) break
|
if (!data) break
|
||||||
|
@ -30,20 +30,20 @@ export default class Decoder {
|
||||||
async receiveSegment(msg: Message.Segment) {
|
async receiveSegment(msg: Message.Segment) {
|
||||||
// Wait for the init segment to be fully received and parsed
|
// Wait for the init segment to be fully received and parsed
|
||||||
const init = await this.init.info
|
const init = await this.init.info
|
||||||
const input = MP4.New();
|
const input = MP4.New()
|
||||||
|
|
||||||
input.onSamples = this.onSamples.bind(this);
|
input.onSamples = this.onSamples.bind(this)
|
||||||
input.onReady = (track: any) => {
|
input.onReady = (track: any) => {
|
||||||
// Extract all of the tracks, because we don't know if it's audio or video.
|
// Extract all of the tracks, because we don't know if it's audio or video.
|
||||||
for (const i of init.tracks) {
|
for (const i of init.tracks) {
|
||||||
input.setExtractionOptions(track.id, i, { nbSamples: 1 });
|
input.setExtractionOptions(track.id, i, { nbSamples: 1 })
|
||||||
}
|
}
|
||||||
|
|
||||||
input.start();
|
input.start()
|
||||||
}
|
}
|
||||||
|
|
||||||
// MP4box requires us to reparse the init segment unfortunately
|
// MP4box requires us to reparse the init segment unfortunately
|
||||||
let offset = 0;
|
let offset = 0
|
||||||
|
|
||||||
for (const raw of this.init.raw) {
|
for (const raw of this.init.raw) {
|
||||||
raw.fileStart = offset
|
raw.fileStart = offset
|
||||||
|
@ -53,15 +53,19 @@ export default class Decoder {
|
||||||
const stream = new Stream.Reader(msg.reader, msg.buffer)
|
const stream = new Stream.Reader(msg.reader, msg.buffer)
|
||||||
|
|
||||||
// For whatever reason, mp4box doesn't work until you read an atom at a time.
|
// For whatever reason, mp4box doesn't work until you read an atom at a time.
|
||||||
while (!await stream.done()) {
|
while (!(await stream.done())) {
|
||||||
const raw = await stream.peek(4)
|
const raw = await stream.peek(4)
|
||||||
|
|
||||||
// TODO this doesn't support when size = 0 (until EOF) or size = 1 (extended size)
|
// TODO this doesn't support when size = 0 (until EOF) or size = 1 (extended size)
|
||||||
const size = new DataView(raw.buffer, raw.byteOffset, raw.byteLength).getUint32(0)
|
const size = new DataView(
|
||||||
|
raw.buffer,
|
||||||
|
raw.byteOffset,
|
||||||
|
raw.byteLength
|
||||||
|
).getUint32(0)
|
||||||
const atom = await stream.bytes(size)
|
const atom = await stream.bytes(size)
|
||||||
|
|
||||||
// Make a copy of the atom because mp4box only accepts an ArrayBuffer unfortunately
|
// Make a copy of the atom because mp4box only accepts an ArrayBuffer unfortunately
|
||||||
const box = new Uint8Array(atom.byteLength);
|
const box = new Uint8Array(atom.byteLength)
|
||||||
box.set(atom)
|
box.set(atom)
|
||||||
|
|
||||||
// and for some reason we need to modify the underlying ArrayBuffer with offset
|
// and for some reason we need to modify the underlying ArrayBuffer with offset
|
||||||
|
@ -75,25 +79,29 @@ export default class Decoder {
|
||||||
}
|
}
|
||||||
|
|
||||||
onSamples(track_id: number, track: MP4.Track, samples: MP4.Sample[]) {
|
onSamples(track_id: number, track: MP4.Track, samples: MP4.Sample[]) {
|
||||||
let decoder = this.decoders.get(track_id);
|
let decoder = this.decoders.get(track_id)
|
||||||
|
|
||||||
if (!decoder) {
|
if (!decoder) {
|
||||||
// We need a sample to initalize the video decoder, because of mp4box limitations.
|
// We need a sample to initalize the video decoder, because of mp4box limitations.
|
||||||
const sample = samples[0];
|
const sample = samples[0]
|
||||||
|
|
||||||
if (isVideoTrack(track)) {
|
if (isVideoTrack(track)) {
|
||||||
// Configure the decoder using the AVC box for H.264
|
// Configure the decoder using the AVC box for H.264
|
||||||
// TODO it should be easy to support other codecs, just need to know the right boxes.
|
// TODO it should be easy to support other codecs, just need to know the right boxes.
|
||||||
const avcc = sample.description.avcC;
|
const avcc = sample.description.avcC
|
||||||
if (!avcc) throw new Error("TODO only h264 is supported");
|
if (!avcc) throw new Error("TODO only h264 is supported")
|
||||||
|
|
||||||
const description = new MP4.Stream(new Uint8Array(avcc.size), 0, false)
|
const description = new MP4.Stream(
|
||||||
|
new Uint8Array(avcc.size),
|
||||||
|
0,
|
||||||
|
false
|
||||||
|
)
|
||||||
avcc.write(description)
|
avcc.write(description)
|
||||||
|
|
||||||
const videoDecoder = new VideoDecoder({
|
const videoDecoder = new VideoDecoder({
|
||||||
output: this.renderer.push.bind(this.renderer),
|
output: this.renderer.push.bind(this.renderer),
|
||||||
error: console.warn,
|
error: console.warn,
|
||||||
});
|
})
|
||||||
|
|
||||||
videoDecoder.configure({
|
videoDecoder.configure({
|
||||||
codec: track.codec,
|
codec: track.codec,
|
||||||
|
@ -108,7 +116,7 @@ export default class Decoder {
|
||||||
const audioDecoder = new AudioDecoder({
|
const audioDecoder = new AudioDecoder({
|
||||||
output: this.renderer.push.bind(this.renderer),
|
output: this.renderer.push.bind(this.renderer),
|
||||||
error: console.warn,
|
error: console.warn,
|
||||||
});
|
})
|
||||||
|
|
||||||
audioDecoder.configure({
|
audioDecoder.configure({
|
||||||
codec: track.codec,
|
codec: track.codec,
|
||||||
|
@ -126,23 +134,27 @@ export default class Decoder {
|
||||||
|
|
||||||
for (const sample of samples) {
|
for (const sample of samples) {
|
||||||
// Convert to microseconds
|
// Convert to microseconds
|
||||||
const timestamp = 1000 * 1000 * sample.dts / sample.timescale
|
const timestamp = (1000 * 1000 * sample.dts) / sample.timescale
|
||||||
const duration = 1000 * 1000 * sample.duration / sample.timescale
|
const duration = (1000 * 1000 * sample.duration) / sample.timescale
|
||||||
|
|
||||||
if (isAudioDecoder(decoder)) {
|
if (isAudioDecoder(decoder)) {
|
||||||
decoder.decode(new EncodedAudioChunk({
|
decoder.decode(
|
||||||
|
new EncodedAudioChunk({
|
||||||
type: sample.is_sync ? "key" : "delta",
|
type: sample.is_sync ? "key" : "delta",
|
||||||
data: sample.data,
|
data: sample.data,
|
||||||
duration: duration,
|
duration: duration,
|
||||||
timestamp: timestamp,
|
timestamp: timestamp,
|
||||||
}))
|
})
|
||||||
|
)
|
||||||
} else if (isVideoDecoder(decoder)) {
|
} else if (isVideoDecoder(decoder)) {
|
||||||
decoder.decode(new EncodedVideoChunk({
|
decoder.decode(
|
||||||
|
new EncodedVideoChunk({
|
||||||
type: sample.is_sync ? "key" : "delta",
|
type: sample.is_sync ? "key" : "delta",
|
||||||
data: sample.data,
|
data: sample.data,
|
||||||
duration: duration,
|
duration: duration,
|
||||||
timestamp: timestamp,
|
timestamp: timestamp,
|
||||||
}))
|
})
|
||||||
|
)
|
||||||
} else {
|
} else {
|
||||||
throw new Error("unknown decoder type")
|
throw new Error("unknown decoder type")
|
||||||
}
|
}
|
||||||
|
@ -150,18 +162,22 @@ export default class Decoder {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function isAudioDecoder(decoder: AudioDecoder | VideoDecoder): decoder is AudioDecoder {
|
function isAudioDecoder(
|
||||||
|
decoder: AudioDecoder | VideoDecoder
|
||||||
|
): decoder is AudioDecoder {
|
||||||
return decoder instanceof AudioDecoder
|
return decoder instanceof AudioDecoder
|
||||||
}
|
}
|
||||||
|
|
||||||
function isVideoDecoder(decoder: AudioDecoder | VideoDecoder): decoder is VideoDecoder {
|
function isVideoDecoder(
|
||||||
|
decoder: AudioDecoder | VideoDecoder
|
||||||
|
): decoder is VideoDecoder {
|
||||||
return decoder instanceof VideoDecoder
|
return decoder instanceof VideoDecoder
|
||||||
}
|
}
|
||||||
|
|
||||||
function isAudioTrack(track: MP4.Track): track is MP4.AudioTrack {
|
function isAudioTrack(track: MP4.Track): track is MP4.AudioTrack {
|
||||||
return (track as MP4.AudioTrack).audio !== undefined;
|
return (track as MP4.AudioTrack).audio !== undefined
|
||||||
}
|
}
|
||||||
|
|
||||||
function isVideoTrack(track: MP4.Track): track is MP4.VideoTrack {
|
function isVideoTrack(track: MP4.Track): track is MP4.VideoTrack {
|
||||||
return (track as MP4.VideoTrack).video !== undefined;
|
return (track as MP4.VideoTrack).video !== undefined
|
||||||
}
|
}
|
|
@ -4,20 +4,20 @@ import Transport from "../transport"
|
||||||
|
|
||||||
export interface Config {
|
export interface Config {
|
||||||
transport: Transport
|
transport: Transport
|
||||||
canvas: OffscreenCanvas;
|
canvas: OffscreenCanvas
|
||||||
}
|
}
|
||||||
|
|
||||||
// This class must be created on the main thread due to AudioContext.
|
// This class must be created on the main thread due to AudioContext.
|
||||||
export default class Player {
|
export default class Player {
|
||||||
context: AudioContext;
|
context: AudioContext
|
||||||
worker: Worker;
|
worker: Worker
|
||||||
worklet: Promise<AudioWorkletNode>;
|
worklet: Promise<AudioWorkletNode>
|
||||||
|
|
||||||
transport: Transport
|
transport: Transport
|
||||||
|
|
||||||
constructor(config: Config) {
|
constructor(config: Config) {
|
||||||
this.transport = config.transport
|
this.transport = config.transport
|
||||||
this.transport.callback = this;
|
this.transport.callback = this
|
||||||
|
|
||||||
this.context = new AudioContext({
|
this.context = new AudioContext({
|
||||||
latencyHint: "interactive",
|
latencyHint: "interactive",
|
||||||
|
@ -29,7 +29,7 @@ export default class Player {
|
||||||
}
|
}
|
||||||
|
|
||||||
private setupWorker(config: Config): Worker {
|
private setupWorker(config: Config): Worker {
|
||||||
const url = new URL('worker.ts', import.meta.url)
|
const url = new URL("worker.ts", import.meta.url)
|
||||||
|
|
||||||
const worker = new Worker(url, {
|
const worker = new Worker(url, {
|
||||||
type: "module",
|
type: "module",
|
||||||
|
@ -47,17 +47,17 @@ export default class Player {
|
||||||
|
|
||||||
private async setupWorklet(_config: Config): Promise<AudioWorkletNode> {
|
private async setupWorklet(_config: Config): Promise<AudioWorkletNode> {
|
||||||
// Load the worklet source code.
|
// Load the worklet source code.
|
||||||
const url = new URL('worklet.ts', import.meta.url)
|
const url = new URL("worklet.ts", import.meta.url)
|
||||||
await this.context.audioWorklet.addModule(url)
|
await this.context.audioWorklet.addModule(url)
|
||||||
|
|
||||||
const volume = this.context.createGain()
|
const volume = this.context.createGain()
|
||||||
volume.gain.value = 2.0;
|
volume.gain.value = 2.0
|
||||||
|
|
||||||
// Create a worklet
|
// Create a worklet
|
||||||
const worklet = new AudioWorkletNode(this.context, 'renderer');
|
const worklet = new AudioWorkletNode(this.context, "renderer")
|
||||||
worklet.onprocessorerror = (e: Event) => {
|
worklet.onprocessorerror = (e: Event) => {
|
||||||
console.error("Audio worklet error:", e)
|
console.error("Audio worklet error:", e)
|
||||||
};
|
}
|
||||||
|
|
||||||
// Connect the worklet to the volume node and then to the speakers
|
// Connect the worklet to the volume node and then to the speakers
|
||||||
worklet.connect(volume)
|
worklet.connect(volume)
|
||||||
|
@ -71,7 +71,10 @@ export default class Player {
|
||||||
}
|
}
|
||||||
|
|
||||||
onSegment(segment: Message.Segment) {
|
onSegment(segment: Message.Segment) {
|
||||||
this.worker.postMessage({ segment }, [segment.buffer.buffer, segment.reader])
|
this.worker.postMessage({ segment }, [
|
||||||
|
segment.buffer.buffer,
|
||||||
|
segment.reader,
|
||||||
|
])
|
||||||
}
|
}
|
||||||
|
|
||||||
async play() {
|
async play() {
|
||||||
|
@ -81,7 +84,7 @@ export default class Player {
|
||||||
buffer: new Ring.Buffer(2, 44100 / 10), // 100ms of audio
|
buffer: new Ring.Buffer(2, 44100 / 10), // 100ms of audio
|
||||||
}
|
}
|
||||||
|
|
||||||
const worklet = await this.worklet;
|
const worklet = await this.worklet
|
||||||
worklet.port.postMessage({ play })
|
worklet.port.postMessage({ play })
|
||||||
this.worker.postMessage({ play })
|
this.worker.postMessage({ play })
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,20 +2,20 @@ import * as Ring from "./ring"
|
||||||
|
|
||||||
export interface Config {
|
export interface Config {
|
||||||
// video stuff
|
// video stuff
|
||||||
canvas: OffscreenCanvas;
|
canvas: OffscreenCanvas
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Init {
|
export interface Init {
|
||||||
buffer: Uint8Array; // unread buffered data
|
buffer: Uint8Array // unread buffered data
|
||||||
reader: ReadableStream; // unread unbuffered data
|
reader: ReadableStream // unread unbuffered data
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Segment {
|
export interface Segment {
|
||||||
buffer: Uint8Array; // unread buffered data
|
buffer: Uint8Array // unread buffered data
|
||||||
reader: ReadableStream; // unread unbuffered data
|
reader: ReadableStream // unread unbuffered data
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Play {
|
export interface Play {
|
||||||
timestamp?: number;
|
timestamp?: number
|
||||||
buffer: Ring.Buffer;
|
buffer: Ring.Buffer
|
||||||
}
|
}
|
|
@ -1,29 +1,29 @@
|
||||||
import * as Message from "./message";
|
import * as Message from "./message"
|
||||||
import Audio from "./audio"
|
import Audio from "./audio"
|
||||||
import Video from "./video"
|
import Video from "./video"
|
||||||
|
|
||||||
export default class Renderer {
|
export default class Renderer {
|
||||||
audio: Audio;
|
audio: Audio
|
||||||
video: Video;
|
video: Video
|
||||||
|
|
||||||
constructor(config: Message.Config) {
|
constructor(config: Message.Config) {
|
||||||
this.audio = new Audio(config);
|
this.audio = new Audio(config)
|
||||||
this.video = new Video(config);
|
this.video = new Video(config)
|
||||||
}
|
}
|
||||||
|
|
||||||
push(frame: AudioData | VideoFrame) {
|
push(frame: AudioData | VideoFrame) {
|
||||||
if (isAudioData(frame)) {
|
if (isAudioData(frame)) {
|
||||||
this.audio.push(frame);
|
this.audio.push(frame)
|
||||||
} else if (isVideoFrame(frame)) {
|
} else if (isVideoFrame(frame)) {
|
||||||
this.video.push(frame);
|
this.video.push(frame)
|
||||||
} else {
|
} else {
|
||||||
throw new Error("unknown frame type")
|
throw new Error("unknown frame type")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
play(play: Message.Play) {
|
play(play: Message.Play) {
|
||||||
this.audio.play(play);
|
this.audio.play(play)
|
||||||
this.video.play(play);
|
this.video.play(play)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -3,24 +3,28 @@
|
||||||
enum STATE {
|
enum STATE {
|
||||||
READ_POS = 0, // The current read position
|
READ_POS = 0, // The current read position
|
||||||
WRITE_POS, // The current write position
|
WRITE_POS, // The current write position
|
||||||
LENGTH // Clever way of saving the total number of enums values.
|
LENGTH, // Clever way of saving the total number of enums values.
|
||||||
}
|
}
|
||||||
|
|
||||||
// No prototype to make this easier to send via postMessage
|
// No prototype to make this easier to send via postMessage
|
||||||
export class Buffer {
|
export class Buffer {
|
||||||
state: SharedArrayBuffer;
|
state: SharedArrayBuffer
|
||||||
|
|
||||||
channels: SharedArrayBuffer[];
|
channels: SharedArrayBuffer[]
|
||||||
capacity: number;
|
capacity: number
|
||||||
|
|
||||||
constructor(channels: number, capacity: number) {
|
constructor(channels: number, capacity: number) {
|
||||||
// Store the current state in a separate ring buffer.
|
// Store the current state in a separate ring buffer.
|
||||||
this.state = new SharedArrayBuffer(STATE.LENGTH * Int32Array.BYTES_PER_ELEMENT)
|
this.state = new SharedArrayBuffer(
|
||||||
|
STATE.LENGTH * Int32Array.BYTES_PER_ELEMENT
|
||||||
|
)
|
||||||
|
|
||||||
// Create a buffer for each audio channel
|
// Create a buffer for each audio channel
|
||||||
this.channels = []
|
this.channels = []
|
||||||
for (let i = 0; i < channels; i += 1) {
|
for (let i = 0; i < channels; i += 1) {
|
||||||
const buffer = new SharedArrayBuffer(capacity * Float32Array.BYTES_PER_ELEMENT)
|
const buffer = new SharedArrayBuffer(
|
||||||
|
capacity * Float32Array.BYTES_PER_ELEMENT
|
||||||
|
)
|
||||||
this.channels.push(buffer)
|
this.channels.push(buffer)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -29,9 +33,9 @@ export class Buffer {
|
||||||
}
|
}
|
||||||
|
|
||||||
export class Ring {
|
export class Ring {
|
||||||
state: Int32Array;
|
state: Int32Array
|
||||||
channels: Float32Array[];
|
channels: Float32Array[]
|
||||||
capacity: number;
|
capacity: number
|
||||||
|
|
||||||
constructor(buffer: Buffer) {
|
constructor(buffer: Buffer) {
|
||||||
this.state = new Int32Array(buffer.state)
|
this.state = new Int32Array(buffer.state)
|
||||||
|
@ -50,7 +54,7 @@ export class Ring {
|
||||||
const writePos = Atomics.load(this.state, STATE.WRITE_POS)
|
const writePos = Atomics.load(this.state, STATE.WRITE_POS)
|
||||||
|
|
||||||
const startPos = writePos
|
const startPos = writePos
|
||||||
let endPos = writePos + frame.numberOfFrames;
|
let endPos = writePos + frame.numberOfFrames
|
||||||
|
|
||||||
if (endPos > readPos + this.capacity) {
|
if (endPos > readPos + this.capacity) {
|
||||||
endPos = readPos + this.capacity
|
endPos = readPos + this.capacity
|
||||||
|
@ -60,8 +64,8 @@ export class Ring {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const startIndex = startPos % this.capacity;
|
const startIndex = startPos % this.capacity
|
||||||
const endIndex = endPos % this.capacity;
|
const endIndex = endPos % this.capacity
|
||||||
|
|
||||||
// Loop over each channel
|
// Loop over each channel
|
||||||
for (let i = 0; i < this.channels.length; i += 1) {
|
for (let i = 0; i < this.channels.length; i += 1) {
|
||||||
|
@ -105,8 +109,8 @@ export class Ring {
|
||||||
const readPos = Atomics.load(this.state, STATE.READ_POS)
|
const readPos = Atomics.load(this.state, STATE.READ_POS)
|
||||||
const writePos = Atomics.load(this.state, STATE.WRITE_POS)
|
const writePos = Atomics.load(this.state, STATE.WRITE_POS)
|
||||||
|
|
||||||
const startPos = readPos;
|
const startPos = readPos
|
||||||
let endPos = startPos + dst[0].length;
|
let endPos = startPos + dst[0].length
|
||||||
|
|
||||||
if (endPos > writePos) {
|
if (endPos > writePos) {
|
||||||
endPos = writePos
|
endPos = writePos
|
||||||
|
@ -116,8 +120,8 @@ export class Ring {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const startIndex = startPos % this.capacity;
|
const startIndex = startPos % this.capacity
|
||||||
const endIndex = endPos % this.capacity;
|
const endIndex = endPos % this.capacity
|
||||||
|
|
||||||
// Loop over each channel
|
// Loop over each channel
|
||||||
for (let i = 0; i < dst.length; i += 1) {
|
for (let i = 0; i < dst.length; i += 1) {
|
||||||
|
|
|
@ -1,18 +1,18 @@
|
||||||
import * as Message from "./message";
|
import * as Message from "./message"
|
||||||
|
|
||||||
export default class Video {
|
export default class Video {
|
||||||
canvas: OffscreenCanvas;
|
canvas: OffscreenCanvas
|
||||||
queue: Array<VideoFrame>;
|
queue: Array<VideoFrame>
|
||||||
|
|
||||||
render: number; // non-zero if requestAnimationFrame has been called
|
render: number // non-zero if requestAnimationFrame has been called
|
||||||
sync?: number; // the wall clock value for timestamp 0, in microseconds
|
sync?: number // the wall clock value for timestamp 0, in microseconds
|
||||||
last?: number; // the timestamp of the last rendered frame, in microseconds
|
last?: number // the timestamp of the last rendered frame, in microseconds
|
||||||
|
|
||||||
constructor(config: Message.Config) {
|
constructor(config: Message.Config) {
|
||||||
this.canvas = config.canvas;
|
this.canvas = config.canvas
|
||||||
this.queue = [];
|
this.queue = []
|
||||||
|
|
||||||
this.render = 0;
|
this.render = 0
|
||||||
}
|
}
|
||||||
|
|
||||||
push(frame: VideoFrame) {
|
push(frame: VideoFrame) {
|
||||||
|
@ -23,18 +23,21 @@ export default class Video {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Insert the frame into the queue sorted by timestamp.
|
// Insert the frame into the queue sorted by timestamp.
|
||||||
if (this.queue.length > 0 && this.queue[this.queue.length - 1].timestamp <= frame.timestamp) {
|
if (
|
||||||
|
this.queue.length > 0 &&
|
||||||
|
this.queue[this.queue.length - 1].timestamp <= frame.timestamp
|
||||||
|
) {
|
||||||
// Fast path because we normally append to the end.
|
// Fast path because we normally append to the end.
|
||||||
this.queue.push(frame)
|
this.queue.push(frame)
|
||||||
} else {
|
} else {
|
||||||
// Do a full binary search
|
// Do a full binary search
|
||||||
let low = 0
|
let low = 0
|
||||||
let high = this.queue.length;
|
let high = this.queue.length
|
||||||
|
|
||||||
while (low < high) {
|
while (low < high) {
|
||||||
const mid = (low + high) >>> 1;
|
const mid = (low + high) >>> 1
|
||||||
if (this.queue[mid].timestamp < frame.timestamp) low = mid + 1;
|
if (this.queue[mid].timestamp < frame.timestamp) low = mid + 1
|
||||||
else high = mid;
|
else high = mid
|
||||||
}
|
}
|
||||||
|
|
||||||
this.queue.splice(low, 0, frame)
|
this.queue.splice(low, 0, frame)
|
||||||
|
@ -43,7 +46,7 @@ export default class Video {
|
||||||
|
|
||||||
draw(now: number) {
|
draw(now: number) {
|
||||||
// Draw and then queue up the next draw call.
|
// Draw and then queue up the next draw call.
|
||||||
this.drawOnce(now);
|
this.drawOnce(now)
|
||||||
|
|
||||||
// Queue up the new draw frame.
|
// Queue up the new draw frame.
|
||||||
this.render = self.requestAnimationFrame(this.draw.bind(this))
|
this.render = self.requestAnimationFrame(this.draw.bind(this))
|
||||||
|
@ -51,16 +54,16 @@ export default class Video {
|
||||||
|
|
||||||
drawOnce(now: number) {
|
drawOnce(now: number) {
|
||||||
// Convert to microseconds
|
// Convert to microseconds
|
||||||
now *= 1000;
|
now *= 1000
|
||||||
|
|
||||||
if (!this.queue.length) {
|
if (!this.queue.length) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
let frame = this.queue[0];
|
let frame = this.queue[0]
|
||||||
|
|
||||||
if (!this.sync) {
|
if (!this.sync) {
|
||||||
this.sync = now - frame.timestamp;
|
this.sync = now - frame.timestamp
|
||||||
}
|
}
|
||||||
|
|
||||||
// Determine the target timestamp.
|
// Determine the target timestamp.
|
||||||
|
@ -71,7 +74,7 @@ export default class Video {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
this.queue.shift();
|
this.queue.shift()
|
||||||
|
|
||||||
// Check if we should skip some frames
|
// Check if we should skip some frames
|
||||||
while (this.queue.length) {
|
while (this.queue.length) {
|
||||||
|
@ -79,13 +82,13 @@ export default class Video {
|
||||||
if (next.timestamp > target) break
|
if (next.timestamp > target) break
|
||||||
|
|
||||||
frame.close()
|
frame.close()
|
||||||
frame = this.queue.shift()!;
|
frame = this.queue.shift()!
|
||||||
}
|
}
|
||||||
|
|
||||||
const ctx = this.canvas.getContext("2d");
|
const ctx = this.canvas.getContext("2d")
|
||||||
ctx!.drawImage(frame, 0, 0, this.canvas.width, this.canvas.height) // TODO aspect ratio
|
ctx!.drawImage(frame, 0, 0, this.canvas.width, this.canvas.height) // TODO aspect ratio
|
||||||
|
|
||||||
this.last = frame.timestamp;
|
this.last = frame.timestamp
|
||||||
frame.close()
|
frame.close()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2,10 +2,10 @@ import Renderer from "./renderer"
|
||||||
import Decoder from "./decoder"
|
import Decoder from "./decoder"
|
||||||
import * as Message from "./message"
|
import * as Message from "./message"
|
||||||
|
|
||||||
let decoder: Decoder;
|
let decoder: Decoder
|
||||||
let renderer: Renderer;
|
let renderer: Renderer
|
||||||
|
|
||||||
self.addEventListener('message', async (e: MessageEvent) => {
|
self.addEventListener("message", async (e: MessageEvent) => {
|
||||||
if (e.data.config) {
|
if (e.data.config) {
|
||||||
const config = e.data.config as Message.Config
|
const config = e.data.config as Message.Config
|
||||||
|
|
||||||
|
@ -22,4 +22,3 @@ self.addEventListener('message', async (e: MessageEvent) => {
|
||||||
await renderer.play(play)
|
await renderer.play(play)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
@ -7,12 +7,12 @@ import * as Message from "./message"
|
||||||
import { Ring } from "./ring"
|
import { Ring } from "./ring"
|
||||||
|
|
||||||
class Renderer extends AudioWorkletProcessor {
|
class Renderer extends AudioWorkletProcessor {
|
||||||
ring?: Ring;
|
ring?: Ring
|
||||||
base: number;
|
base: number
|
||||||
|
|
||||||
constructor(_params: AudioWorkletNodeOptions) {
|
constructor(_params: AudioWorkletNodeOptions) {
|
||||||
// The super constructor call is required.
|
// The super constructor call is required.
|
||||||
super();
|
super()
|
||||||
|
|
||||||
this.base = 0
|
this.base = 0
|
||||||
this.port.onmessage = this.onMessage.bind(this)
|
this.port.onmessage = this.onMessage.bind(this)
|
||||||
|
@ -29,7 +29,11 @@ class Renderer extends AudioWorkletProcessor {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Inputs and outputs in groups of 128 samples.
|
// Inputs and outputs in groups of 128 samples.
|
||||||
process(inputs: Float32Array[][], outputs: Float32Array[][], _parameters: Record<string, Float32Array>): boolean {
|
process(
|
||||||
|
inputs: Float32Array[][],
|
||||||
|
outputs: Float32Array[][],
|
||||||
|
_parameters: Record<string, Float32Array>
|
||||||
|
): boolean {
|
||||||
if (!this.ring) {
|
if (!this.ring) {
|
||||||
// Paused
|
// Paused
|
||||||
return true
|
return true
|
||||||
|
@ -46,8 +50,8 @@ class Renderer extends AudioWorkletProcessor {
|
||||||
// TODO trigger rebuffering event
|
// TODO trigger rebuffering event
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
registerProcessor("renderer", Renderer);
|
registerProcessor("renderer", Renderer)
|
||||||
|
|
|
@ -1,18 +1,20 @@
|
||||||
// Reader wraps a stream and provides convience methods for reading pieces from a stream
|
// Reader wraps a stream and provides convience methods for reading pieces from a stream
|
||||||
export default class Reader {
|
export default class Reader {
|
||||||
reader: ReadableStream;
|
reader: ReadableStream
|
||||||
buffer: Uint8Array;
|
buffer: Uint8Array
|
||||||
|
|
||||||
constructor(reader: ReadableStream, buffer: Uint8Array = new Uint8Array(0)) {
|
constructor(
|
||||||
|
reader: ReadableStream,
|
||||||
|
buffer: Uint8Array = new Uint8Array(0)
|
||||||
|
) {
|
||||||
this.reader = reader
|
this.reader = reader
|
||||||
this.buffer = buffer
|
this.buffer = buffer
|
||||||
}
|
}
|
||||||
|
|
||||||
// Returns any number of bytes
|
// Returns any number of bytes
|
||||||
async read(): Promise<Uint8Array | undefined> {
|
async read(): Promise<Uint8Array | undefined> {
|
||||||
|
|
||||||
if (this.buffer.byteLength) {
|
if (this.buffer.byteLength) {
|
||||||
const buffer = this.buffer;
|
const buffer = this.buffer
|
||||||
this.buffer = new Uint8Array()
|
this.buffer = new Uint8Array()
|
||||||
return buffer
|
return buffer
|
||||||
}
|
}
|
||||||
|
@ -39,7 +41,9 @@ export default class Reader {
|
||||||
if (this.buffer.byteLength == 0) {
|
if (this.buffer.byteLength == 0) {
|
||||||
this.buffer = buffer
|
this.buffer = buffer
|
||||||
} else {
|
} else {
|
||||||
const temp = new Uint8Array(this.buffer.byteLength + buffer.byteLength)
|
const temp = new Uint8Array(
|
||||||
|
this.buffer.byteLength + buffer.byteLength
|
||||||
|
)
|
||||||
temp.set(this.buffer)
|
temp.set(this.buffer)
|
||||||
temp.set(buffer, this.buffer.byteLength)
|
temp.set(buffer, this.buffer.byteLength)
|
||||||
this.buffer = temp
|
this.buffer = temp
|
||||||
|
@ -68,15 +72,24 @@ export default class Reader {
|
||||||
if (this.buffer.byteLength == 0) {
|
if (this.buffer.byteLength == 0) {
|
||||||
this.buffer = buffer
|
this.buffer = buffer
|
||||||
} else {
|
} else {
|
||||||
const temp = new Uint8Array(this.buffer.byteLength + buffer.byteLength)
|
const temp = new Uint8Array(
|
||||||
|
this.buffer.byteLength + buffer.byteLength
|
||||||
|
)
|
||||||
temp.set(this.buffer)
|
temp.set(this.buffer)
|
||||||
temp.set(buffer, this.buffer.byteLength)
|
temp.set(buffer, this.buffer.byteLength)
|
||||||
this.buffer = temp
|
this.buffer = temp
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const result = new Uint8Array(this.buffer.buffer, this.buffer.byteOffset, size)
|
const result = new Uint8Array(
|
||||||
this.buffer = new Uint8Array(this.buffer.buffer, this.buffer.byteOffset + size)
|
this.buffer.buffer,
|
||||||
|
this.buffer.byteOffset,
|
||||||
|
size
|
||||||
|
)
|
||||||
|
this.buffer = new Uint8Array(
|
||||||
|
this.buffer.buffer,
|
||||||
|
this.buffer.byteOffset + size
|
||||||
|
)
|
||||||
|
|
||||||
r.releaseLock()
|
r.releaseLock()
|
||||||
|
|
||||||
|
@ -97,14 +110,20 @@ export default class Reader {
|
||||||
if (this.buffer.byteLength == 0) {
|
if (this.buffer.byteLength == 0) {
|
||||||
this.buffer = buffer
|
this.buffer = buffer
|
||||||
} else {
|
} else {
|
||||||
const temp = new Uint8Array(this.buffer.byteLength + buffer.byteLength)
|
const temp = new Uint8Array(
|
||||||
|
this.buffer.byteLength + buffer.byteLength
|
||||||
|
)
|
||||||
temp.set(this.buffer)
|
temp.set(this.buffer)
|
||||||
temp.set(buffer, this.buffer.byteLength)
|
temp.set(buffer, this.buffer.byteLength)
|
||||||
this.buffer = temp
|
this.buffer = temp
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const result = new Uint8Array(this.buffer.buffer, this.buffer.byteOffset, size)
|
const result = new Uint8Array(
|
||||||
|
this.buffer.buffer,
|
||||||
|
this.buffer.byteOffset,
|
||||||
|
size
|
||||||
|
)
|
||||||
|
|
||||||
r.releaseLock()
|
r.releaseLock()
|
||||||
|
|
||||||
|
@ -160,7 +179,11 @@ export default class Reader {
|
||||||
// NOTE: Returns a BigInt instead of a Number
|
// NOTE: Returns a BigInt instead of a Number
|
||||||
async vint64(): Promise<bigint> {
|
async vint64(): Promise<bigint> {
|
||||||
const peek = await this.peek(1)
|
const peek = await this.peek(1)
|
||||||
const first = new DataView(peek.buffer, peek.byteOffset, peek.byteLength).getUint8(0)
|
const first = new DataView(
|
||||||
|
peek.buffer,
|
||||||
|
peek.byteOffset,
|
||||||
|
peek.byteLength
|
||||||
|
).getUint8(0)
|
||||||
const size = (first & 0xc0) >> 6
|
const size = (first & 0xc0) >> 6
|
||||||
|
|
||||||
switch (size) {
|
switch (size) {
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
// Writer wraps a stream and writes chunks of data
|
// Writer wraps a stream and writes chunks of data
|
||||||
export default class Writer {
|
export default class Writer {
|
||||||
buffer: ArrayBuffer;
|
buffer: ArrayBuffer
|
||||||
writer: WritableStreamDefaultWriter;
|
writer: WritableStreamDefaultWriter
|
||||||
|
|
||||||
constructor(stream: WritableStream) {
|
constructor(stream: WritableStream) {
|
||||||
this.buffer = new ArrayBuffer(8)
|
this.buffer = new ArrayBuffer(8)
|
||||||
|
@ -31,7 +31,7 @@ export default class Writer {
|
||||||
async uint24(v: number) {
|
async uint24(v: number) {
|
||||||
const v1 = (v >> 16) & 0xff
|
const v1 = (v >> 16) & 0xff
|
||||||
const v2 = (v >> 8) & 0xff
|
const v2 = (v >> 8) & 0xff
|
||||||
const v3 = (v) & 0xff
|
const v3 = v & 0xff
|
||||||
|
|
||||||
const view = new DataView(this.buffer, 0, 3)
|
const view = new DataView(this.buffer, 0, 3)
|
||||||
view.setUint8(0, v1)
|
view.setUint8(0, v1)
|
||||||
|
@ -60,12 +60,12 @@ export default class Writer {
|
||||||
throw "value too large"
|
throw "value too large"
|
||||||
}
|
}
|
||||||
|
|
||||||
if (v < (1 << 6)) {
|
if (v < 1 << 6) {
|
||||||
return this.uint8(v)
|
return this.uint8(v)
|
||||||
} else if (v < (1 << 14)) {
|
} else if (v < 1 << 14) {
|
||||||
return this.uint16(v|0x4000)
|
return this.uint16(v | 0x4000)
|
||||||
} else if (v < (1 << 30)) {
|
} else if (v < 1 << 30) {
|
||||||
return this.uint32(v|0x80000000)
|
return this.uint32(v | 0x80000000)
|
||||||
} else {
|
} else {
|
||||||
return this.uint64(BigInt(v) | 0xc000000000000000n)
|
return this.uint64(BigInt(v) | 0xc000000000000000n)
|
||||||
}
|
}
|
||||||
|
@ -78,12 +78,12 @@ export default class Writer {
|
||||||
}
|
}
|
||||||
|
|
||||||
async vint64(v: bigint) {
|
async vint64(v: bigint) {
|
||||||
if (v < (1 << 6)) {
|
if (v < 1 << 6) {
|
||||||
return this.uint8(Number(v))
|
return this.uint8(Number(v))
|
||||||
} else if (v < (1 << 14)) {
|
} else if (v < 1 << 14) {
|
||||||
return this.uint16(Number(v)|0x4000)
|
return this.uint16(Number(v) | 0x4000)
|
||||||
} else if (v < (1 << 30)) {
|
} else if (v < 1 << 30) {
|
||||||
return this.uint32(Number(v)|0x80000000)
|
return this.uint32(Number(v) | 0x80000000)
|
||||||
} else {
|
} else {
|
||||||
return this.uint64(v | 0xc000000000000000n)
|
return this.uint64(v | 0xc000000000000000n)
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,14 +2,14 @@ import * as Stream from "../stream"
|
||||||
import * as Interface from "./interface"
|
import * as Interface from "./interface"
|
||||||
|
|
||||||
export interface Config {
|
export interface Config {
|
||||||
url: string;
|
url: string
|
||||||
fingerprint?: WebTransportHash; // the certificate fingerprint, temporarily needed for local development
|
fingerprint?: WebTransportHash // the certificate fingerprint, temporarily needed for local development
|
||||||
}
|
}
|
||||||
|
|
||||||
export default class Transport {
|
export default class Transport {
|
||||||
quic: Promise<WebTransport>;
|
quic: Promise<WebTransport>
|
||||||
api: Promise<WritableStream>;
|
api: Promise<WritableStream>
|
||||||
callback?: Interface.Callback;
|
callback?: Interface.Callback
|
||||||
|
|
||||||
constructor(config: Config) {
|
constructor(config: Config) {
|
||||||
this.quic = this.connect(config)
|
this.quic = this.connect(config)
|
||||||
|
@ -24,14 +24,14 @@ export default class Transport {
|
||||||
}
|
}
|
||||||
|
|
||||||
async close() {
|
async close() {
|
||||||
(await this.quic).close()
|
;(await this.quic).close()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Helper function to make creating a promise easier
|
// Helper function to make creating a promise easier
|
||||||
private async connect(config: Config): Promise<WebTransport> {
|
private async connect(config: Config): Promise<WebTransport> {
|
||||||
const options: WebTransportOptions = {};
|
const options: WebTransportOptions = {}
|
||||||
if (config.fingerprint) {
|
if (config.fingerprint) {
|
||||||
options.serverCertificateHashes = [ config.fingerprint ]
|
options.serverCertificateHashes = [config.fingerprint]
|
||||||
}
|
}
|
||||||
|
|
||||||
const quic = new WebTransport(config.url, options)
|
const quic = new WebTransport(config.url, options)
|
||||||
|
@ -68,14 +68,16 @@ export default class Transport {
|
||||||
async handleStream(stream: ReadableStream) {
|
async handleStream(stream: ReadableStream) {
|
||||||
const r = new Stream.Reader(stream)
|
const r = new Stream.Reader(stream)
|
||||||
|
|
||||||
while (!await r.done()) {
|
while (!(await r.done())) {
|
||||||
const size = await r.uint32();
|
const size = await r.uint32()
|
||||||
const typ = new TextDecoder('utf-8').decode(await r.bytes(4));
|
const typ = new TextDecoder("utf-8").decode(await r.bytes(4))
|
||||||
|
|
||||||
if (typ != "warp") throw "expected warp atom"
|
if (typ != "warp") throw "expected warp atom"
|
||||||
if (size < 8) throw "atom too small"
|
if (size < 8) throw "atom too small"
|
||||||
|
|
||||||
const payload = new TextDecoder('utf-8').decode(await r.bytes(size - 8));
|
const payload = new TextDecoder("utf-8").decode(
|
||||||
|
await r.bytes(size - 8)
|
||||||
|
)
|
||||||
const msg = JSON.parse(payload)
|
const msg = JSON.parse(payload)
|
||||||
|
|
||||||
if (msg.init) {
|
if (msg.init) {
|
||||||
|
@ -89,7 +91,7 @@ export default class Transport {
|
||||||
reader: r.reader,
|
reader: r.reader,
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
console.warn("unknown message", msg);
|
console.warn("unknown message", msg)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,11 +4,11 @@ export interface Callback {
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Init {
|
export interface Init {
|
||||||
buffer: Uint8Array; // unread buffered data
|
buffer: Uint8Array // unread buffered data
|
||||||
reader: ReadableStream; // unread unbuffered data
|
reader: ReadableStream // unread unbuffered data
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Segment {
|
export interface Segment {
|
||||||
buffer: Uint8Array; // unread buffered data
|
buffer: Uint8Array // unread buffered data
|
||||||
reader: ReadableStream; // unread unbuffered data
|
reader: ReadableStream // unread unbuffered data
|
||||||
}
|
}
|
|
@ -8,77 +8,77 @@ declare module "webtransport"
|
||||||
*/
|
*/
|
||||||
|
|
||||||
interface WebTransportDatagramDuplexStream {
|
interface WebTransportDatagramDuplexStream {
|
||||||
readonly readable: ReadableStream;
|
readonly readable: ReadableStream
|
||||||
readonly writable: WritableStream;
|
readonly writable: WritableStream
|
||||||
readonly maxDatagramSize: number;
|
readonly maxDatagramSize: number
|
||||||
incomingMaxAge: number;
|
incomingMaxAge: number
|
||||||
outgoingMaxAge: number;
|
outgoingMaxAge: number
|
||||||
incomingHighWaterMark: number;
|
incomingHighWaterMark: number
|
||||||
outgoingHighWaterMark: number;
|
outgoingHighWaterMark: number
|
||||||
}
|
}
|
||||||
|
|
||||||
interface WebTransport {
|
interface WebTransport {
|
||||||
getStats(): Promise<WebTransportStats>;
|
getStats(): Promise<WebTransportStats>
|
||||||
readonly ready: Promise<undefined>;
|
readonly ready: Promise<undefined>
|
||||||
readonly closed: Promise<WebTransportCloseInfo>;
|
readonly closed: Promise<WebTransportCloseInfo>
|
||||||
close(closeInfo?: WebTransportCloseInfo): undefined;
|
close(closeInfo?: WebTransportCloseInfo): undefined
|
||||||
readonly datagrams: WebTransportDatagramDuplexStream;
|
readonly datagrams: WebTransportDatagramDuplexStream
|
||||||
createBidirectionalStream(): Promise<WebTransportBidirectionalStream>;
|
createBidirectionalStream(): Promise<WebTransportBidirectionalStream>
|
||||||
readonly incomingBidirectionalStreams: ReadableStream;
|
readonly incomingBidirectionalStreams: ReadableStream
|
||||||
createUnidirectionalStream(): Promise<WritableStream>;
|
createUnidirectionalStream(): Promise<WritableStream>
|
||||||
readonly incomingUnidirectionalStreams: ReadableStream;
|
readonly incomingUnidirectionalStreams: ReadableStream
|
||||||
}
|
}
|
||||||
|
|
||||||
declare const WebTransport: {
|
declare const WebTransport: {
|
||||||
prototype: WebTransport;
|
prototype: WebTransport
|
||||||
new(url: string, options?: WebTransportOptions): WebTransport;
|
new (url: string, options?: WebTransportOptions): WebTransport
|
||||||
};
|
}
|
||||||
|
|
||||||
interface WebTransportHash {
|
interface WebTransportHash {
|
||||||
algorithm?: string;
|
algorithm?: string
|
||||||
value?: BufferSource;
|
value?: BufferSource
|
||||||
}
|
}
|
||||||
|
|
||||||
interface WebTransportOptions {
|
interface WebTransportOptions {
|
||||||
allowPooling?: boolean;
|
allowPooling?: boolean
|
||||||
serverCertificateHashes?: Array<WebTransportHash>;
|
serverCertificateHashes?: Array<WebTransportHash>
|
||||||
}
|
}
|
||||||
|
|
||||||
interface WebTransportCloseInfo {
|
interface WebTransportCloseInfo {
|
||||||
closeCode?: number;
|
closeCode?: number
|
||||||
reason?: string;
|
reason?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
interface WebTransportStats {
|
interface WebTransportStats {
|
||||||
timestamp?: DOMHighResTimeStamp;
|
timestamp?: DOMHighResTimeStamp
|
||||||
bytesSent?: number;
|
bytesSent?: number
|
||||||
packetsSent?: number;
|
packetsSent?: number
|
||||||
numOutgoingStreamsCreated?: number;
|
numOutgoingStreamsCreated?: number
|
||||||
numIncomingStreamsCreated?: number;
|
numIncomingStreamsCreated?: number
|
||||||
bytesReceived?: number;
|
bytesReceived?: number
|
||||||
packetsReceived?: number;
|
packetsReceived?: number
|
||||||
minRtt?: DOMHighResTimeStamp;
|
minRtt?: DOMHighResTimeStamp
|
||||||
numReceivedDatagramsDropped?: number;
|
numReceivedDatagramsDropped?: number
|
||||||
}
|
}
|
||||||
|
|
||||||
interface WebTransportBidirectionalStream {
|
interface WebTransportBidirectionalStream {
|
||||||
readonly readable: ReadableStream;
|
readonly readable: ReadableStream
|
||||||
readonly writable: WritableStream;
|
readonly writable: WritableStream
|
||||||
}
|
}
|
||||||
|
|
||||||
interface WebTransportError extends DOMException {
|
interface WebTransportError extends DOMException {
|
||||||
readonly source: WebTransportErrorSource;
|
readonly source: WebTransportErrorSource
|
||||||
readonly streamErrorCode: number;
|
readonly streamErrorCode: number
|
||||||
}
|
}
|
||||||
|
|
||||||
declare const WebTransportError: {
|
declare const WebTransportError: {
|
||||||
prototype: WebTransportError;
|
prototype: WebTransportError
|
||||||
new(init?: WebTransportErrorInit): WebTransportError;
|
new (init?: WebTransportErrorInit): WebTransportError
|
||||||
};
|
|
||||||
|
|
||||||
interface WebTransportErrorInit {
|
|
||||||
streamErrorCode?: number;
|
|
||||||
message?: string;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type WebTransportErrorSource = "stream" | "session";
|
interface WebTransportErrorInit {
|
||||||
|
streamErrorCode?: number
|
||||||
|
message?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
type WebTransportErrorSource = "stream" | "session"
|
||||||
|
|
|
@ -5,12 +5,16 @@ export default class Deferred<T> {
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
// Set initial values so TS stops being annoying.
|
// Set initial values so TS stops being annoying.
|
||||||
this.resolve = (_value: T | PromiseLike<T>) => { /* noop */ };
|
this.resolve = (_value: T | PromiseLike<T>) => {
|
||||||
this.reject = (_value: T | PromiseLike<T>) => { /* noop */ };
|
/* noop */
|
||||||
|
}
|
||||||
|
this.reject = (_value: T | PromiseLike<T>) => {
|
||||||
|
/* noop */
|
||||||
|
}
|
||||||
|
|
||||||
this.promise = new Promise((resolve, reject) => {
|
this.promise = new Promise((resolve, reject) => {
|
||||||
this.resolve = resolve
|
this.resolve = resolve
|
||||||
this.reject = reject
|
this.reject = reject
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,11 +1,9 @@
|
||||||
{
|
{
|
||||||
"include": [
|
"include": ["src/**/*"],
|
||||||
"src/**/*"
|
|
||||||
],
|
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"target": "es2022",
|
"target": "es2022",
|
||||||
"module": "es2022",
|
"module": "es2022",
|
||||||
"moduleResolution": "node",
|
"moduleResolution": "node",
|
||||||
"strict": true,
|
"strict": true
|
||||||
}
|
}
|
||||||
}
|
}
|
150
web/yarn.lock
150
web/yarn.lock
|
@ -101,7 +101,7 @@
|
||||||
"@jridgewell/gen-mapping" "^0.3.0"
|
"@jridgewell/gen-mapping" "^0.3.0"
|
||||||
"@jridgewell/trace-mapping" "^0.3.9"
|
"@jridgewell/trace-mapping" "^0.3.9"
|
||||||
|
|
||||||
"@jridgewell/sourcemap-codec@^1.4.10", "@jridgewell/sourcemap-codec@1.4.14":
|
"@jridgewell/sourcemap-codec@1.4.14", "@jridgewell/sourcemap-codec@^1.4.10":
|
||||||
version "1.4.14"
|
version "1.4.14"
|
||||||
resolved "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz"
|
resolved "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz"
|
||||||
integrity sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==
|
integrity sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==
|
||||||
|
@ -131,6 +131,31 @@
|
||||||
resolved "https://registry.npmjs.org/@lmdb/lmdb-darwin-arm64/-/lmdb-darwin-arm64-2.5.2.tgz"
|
resolved "https://registry.npmjs.org/@lmdb/lmdb-darwin-arm64/-/lmdb-darwin-arm64-2.5.2.tgz"
|
||||||
integrity sha512-+F8ioQIUN68B4UFiIBYu0QQvgb9FmlKw2ctQMSBfW2QBrZIxz9vD9jCGqTCPqZBRbPHAS/vG1zSXnKqnS2ch/A==
|
integrity sha512-+F8ioQIUN68B4UFiIBYu0QQvgb9FmlKw2ctQMSBfW2QBrZIxz9vD9jCGqTCPqZBRbPHAS/vG1zSXnKqnS2ch/A==
|
||||||
|
|
||||||
|
"@lmdb/lmdb-darwin-x64@2.5.2":
|
||||||
|
version "2.5.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/@lmdb/lmdb-darwin-x64/-/lmdb-darwin-x64-2.5.2.tgz#89d8390041bce6bab24a82a20392be22faf54ffc"
|
||||||
|
integrity sha512-KvPH56KRLLx4KSfKBx0m1r7GGGUMXm0jrKmNE7plbHlesZMuPJICtn07HYgQhj1LNsK7Yqwuvnqh1QxhJnF1EA==
|
||||||
|
|
||||||
|
"@lmdb/lmdb-linux-arm64@2.5.2":
|
||||||
|
version "2.5.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/@lmdb/lmdb-linux-arm64/-/lmdb-linux-arm64-2.5.2.tgz#14fe4c96c2bb1285f93797f45915fa35ee047268"
|
||||||
|
integrity sha512-aLl89VHL/wjhievEOlPocoefUyWdvzVrcQ/MHQYZm2JfV1jUsrbr/ZfkPPUFvZBf+VSE+Q0clWs9l29PCX1hTQ==
|
||||||
|
|
||||||
|
"@lmdb/lmdb-linux-arm@2.5.2":
|
||||||
|
version "2.5.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/@lmdb/lmdb-linux-arm/-/lmdb-linux-arm-2.5.2.tgz#05bde4573ab10cf21827339fe687148f2590cfa1"
|
||||||
|
integrity sha512-5kQAP21hAkfW5Bl+e0P57dV4dGYnkNIpR7f/GAh6QHlgXx+vp/teVj4PGRZaKAvt0GX6++N6hF8NnGElLDuIDw==
|
||||||
|
|
||||||
|
"@lmdb/lmdb-linux-x64@2.5.2":
|
||||||
|
version "2.5.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/@lmdb/lmdb-linux-x64/-/lmdb-linux-x64-2.5.2.tgz#d2f85afd857d2c33d2caa5b057944574edafcfee"
|
||||||
|
integrity sha512-xUdUfwDJLGjOUPH3BuPBt0NlIrR7f/QHKgu3GZIXswMMIihAekj2i97oI0iWG5Bok/b+OBjHPfa8IU9velnP/Q==
|
||||||
|
|
||||||
|
"@lmdb/lmdb-win32-x64@2.5.2":
|
||||||
|
version "2.5.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/@lmdb/lmdb-win32-x64/-/lmdb-win32-x64-2.5.2.tgz#28f643fbc0bec30b07fbe95b137879b6b4d1c9c5"
|
||||||
|
integrity sha512-zrBczSbXKxEyK2ijtbRdICDygRqWSRPpZMN5dD1T8VMEW5RIhIbwFWw2phDRXuBQdVDpSjalCIUMWMV2h3JaZA==
|
||||||
|
|
||||||
"@mischnic/json-sourcemap@^0.1.0":
|
"@mischnic/json-sourcemap@^0.1.0":
|
||||||
version "0.1.0"
|
version "0.1.0"
|
||||||
resolved "https://registry.npmjs.org/@mischnic/json-sourcemap/-/json-sourcemap-0.1.0.tgz"
|
resolved "https://registry.npmjs.org/@mischnic/json-sourcemap/-/json-sourcemap-0.1.0.tgz"
|
||||||
|
@ -145,6 +170,31 @@
|
||||||
resolved "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-darwin-arm64/-/msgpackr-extract-darwin-arm64-3.0.2.tgz"
|
resolved "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-darwin-arm64/-/msgpackr-extract-darwin-arm64-3.0.2.tgz"
|
||||||
integrity sha512-9bfjwDxIDWmmOKusUcqdS4Rw+SETlp9Dy39Xui9BEGEk19dDwH0jhipwFzEff/pFg95NKymc6TOTbRKcWeRqyQ==
|
integrity sha512-9bfjwDxIDWmmOKusUcqdS4Rw+SETlp9Dy39Xui9BEGEk19dDwH0jhipwFzEff/pFg95NKymc6TOTbRKcWeRqyQ==
|
||||||
|
|
||||||
|
"@msgpackr-extract/msgpackr-extract-darwin-x64@3.0.2":
|
||||||
|
version "3.0.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/@msgpackr-extract/msgpackr-extract-darwin-x64/-/msgpackr-extract-darwin-x64-3.0.2.tgz#f954f34355712212a8e06c465bc06c40852c6bb3"
|
||||||
|
integrity sha512-lwriRAHm1Yg4iDf23Oxm9n/t5Zpw1lVnxYU3HnJPTi2lJRkKTrps1KVgvL6m7WvmhYVt/FIsssWay+k45QHeuw==
|
||||||
|
|
||||||
|
"@msgpackr-extract/msgpackr-extract-linux-arm64@3.0.2":
|
||||||
|
version "3.0.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/@msgpackr-extract/msgpackr-extract-linux-arm64/-/msgpackr-extract-linux-arm64-3.0.2.tgz#45c63037f045c2b15c44f80f0393fa24f9655367"
|
||||||
|
integrity sha512-FU20Bo66/f7He9Fp9sP2zaJ1Q8L9uLPZQDub/WlUip78JlPeMbVL8546HbZfcW9LNciEXc8d+tThSJjSC+tmsg==
|
||||||
|
|
||||||
|
"@msgpackr-extract/msgpackr-extract-linux-arm@3.0.2":
|
||||||
|
version "3.0.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/@msgpackr-extract/msgpackr-extract-linux-arm/-/msgpackr-extract-linux-arm-3.0.2.tgz#35707efeafe6d22b3f373caf9e8775e8920d1399"
|
||||||
|
integrity sha512-MOI9Dlfrpi2Cuc7i5dXdxPbFIgbDBGgKR5F2yWEa6FVEtSWncfVNKW5AKjImAQ6CZlBK9tympdsZJ2xThBiWWA==
|
||||||
|
|
||||||
|
"@msgpackr-extract/msgpackr-extract-linux-x64@3.0.2":
|
||||||
|
version "3.0.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/@msgpackr-extract/msgpackr-extract-linux-x64/-/msgpackr-extract-linux-x64-3.0.2.tgz#091b1218b66c341f532611477ef89e83f25fae4f"
|
||||||
|
integrity sha512-gsWNDCklNy7Ajk0vBBf9jEx04RUxuDQfBse918Ww+Qb9HCPoGzS+XJTLe96iN3BVK7grnLiYghP/M4L8VsaHeA==
|
||||||
|
|
||||||
|
"@msgpackr-extract/msgpackr-extract-win32-x64@3.0.2":
|
||||||
|
version "3.0.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/@msgpackr-extract/msgpackr-extract-win32-x64/-/msgpackr-extract-win32-x64-3.0.2.tgz#0f164b726869f71da3c594171df5ebc1c4b0a407"
|
||||||
|
integrity sha512-O+6Gs8UeDbyFpbSh2CPEz/UOrrdWPTBYNblZK5CxxLisYt4kGX3Sc+czffFonyjiGSq3jWLwJS/CCJc7tBr4sQ==
|
||||||
|
|
||||||
"@nodelib/fs.scandir@2.1.5":
|
"@nodelib/fs.scandir@2.1.5":
|
||||||
version "2.1.5"
|
version "2.1.5"
|
||||||
resolved "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz"
|
resolved "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz"
|
||||||
|
@ -153,7 +203,7 @@
|
||||||
"@nodelib/fs.stat" "2.0.5"
|
"@nodelib/fs.stat" "2.0.5"
|
||||||
run-parallel "^1.1.9"
|
run-parallel "^1.1.9"
|
||||||
|
|
||||||
"@nodelib/fs.stat@^2.0.2", "@nodelib/fs.stat@2.0.5":
|
"@nodelib/fs.stat@2.0.5", "@nodelib/fs.stat@^2.0.2":
|
||||||
version "2.0.5"
|
version "2.0.5"
|
||||||
resolved "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz"
|
resolved "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz"
|
||||||
integrity sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==
|
integrity sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==
|
||||||
|
@ -238,7 +288,7 @@
|
||||||
"@parcel/transformer-react-refresh-wrap" "2.8.3"
|
"@parcel/transformer-react-refresh-wrap" "2.8.3"
|
||||||
"@parcel/transformer-svg" "2.8.3"
|
"@parcel/transformer-svg" "2.8.3"
|
||||||
|
|
||||||
"@parcel/core@^2.8.3", "@parcel/core@2.8.3":
|
"@parcel/core@2.8.3":
|
||||||
version "2.8.3"
|
version "2.8.3"
|
||||||
resolved "https://registry.npmjs.org/@parcel/core/-/core-2.8.3.tgz"
|
resolved "https://registry.npmjs.org/@parcel/core/-/core-2.8.3.tgz"
|
||||||
integrity sha512-Euf/un4ZAiClnlUXqPB9phQlKbveU+2CotZv7m7i+qkgvFn5nAGnrV4h1OzQU42j9dpgOxWi7AttUDMrvkbhCQ==
|
integrity sha512-Euf/un4ZAiClnlUXqPB9phQlKbveU+2CotZv7m7i+qkgvFn5nAGnrV4h1OzQU42j9dpgOxWi7AttUDMrvkbhCQ==
|
||||||
|
@ -806,7 +856,7 @@
|
||||||
semver "^7.3.7"
|
semver "^7.3.7"
|
||||||
tsutils "^3.21.0"
|
tsutils "^3.21.0"
|
||||||
|
|
||||||
"@typescript-eslint/parser@^5.0.0", "@typescript-eslint/parser@^5.59.7":
|
"@typescript-eslint/parser@^5.59.7":
|
||||||
version "5.59.7"
|
version "5.59.7"
|
||||||
resolved "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.59.7.tgz"
|
resolved "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.59.7.tgz"
|
||||||
integrity sha512-VhpsIEuq/8i5SF+mPg9jSdIwgMBBp0z9XqjiEay+81PYLJuroN+ET1hM5IhkiYMJd9MkTz8iJLt7aaGAgzWUbQ==
|
integrity sha512-VhpsIEuq/8i5SF+mPg9jSdIwgMBBp0z9XqjiEay+81PYLJuroN+ET1hM5IhkiYMJd9MkTz8iJLt7aaGAgzWUbQ==
|
||||||
|
@ -884,7 +934,7 @@ acorn-jsx@^5.3.2:
|
||||||
resolved "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz"
|
resolved "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz"
|
||||||
integrity sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==
|
integrity sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==
|
||||||
|
|
||||||
"acorn@^6.0.0 || ^7.0.0 || ^8.0.0", acorn@^8.5.0, acorn@^8.8.0:
|
acorn@^8.5.0, acorn@^8.8.0:
|
||||||
version "8.8.2"
|
version "8.8.2"
|
||||||
resolved "https://registry.npmjs.org/acorn/-/acorn-8.8.2.tgz"
|
resolved "https://registry.npmjs.org/acorn/-/acorn-8.8.2.tgz"
|
||||||
integrity sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw==
|
integrity sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw==
|
||||||
|
@ -960,7 +1010,7 @@ braces@^3.0.2:
|
||||||
dependencies:
|
dependencies:
|
||||||
fill-range "^7.0.1"
|
fill-range "^7.0.1"
|
||||||
|
|
||||||
browserslist@^4.6.6, "browserslist@>= 4.21.0":
|
browserslist@^4.6.6:
|
||||||
version "4.21.5"
|
version "4.21.5"
|
||||||
resolved "https://registry.npmjs.org/browserslist/-/browserslist-4.21.5.tgz"
|
resolved "https://registry.npmjs.org/browserslist/-/browserslist-4.21.5.tgz"
|
||||||
integrity sha512-tUkiguQGW7S3IhB7N+c2MV/HZPSCPAAiYBZXLsBhFB/PCy6ZKKsZrmBayHV9fdGV/ARIfJ14NkxKzRDjvp7L6w==
|
integrity sha512-tUkiguQGW7S3IhB7N+c2MV/HZPSCPAAiYBZXLsBhFB/PCy6ZKKsZrmBayHV9fdGV/ARIfJ14NkxKzRDjvp7L6w==
|
||||||
|
@ -1026,16 +1076,16 @@ color-convert@^2.0.1:
|
||||||
dependencies:
|
dependencies:
|
||||||
color-name "~1.1.4"
|
color-name "~1.1.4"
|
||||||
|
|
||||||
color-name@~1.1.4:
|
|
||||||
version "1.1.4"
|
|
||||||
resolved "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz"
|
|
||||||
integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==
|
|
||||||
|
|
||||||
color-name@1.1.3:
|
color-name@1.1.3:
|
||||||
version "1.1.3"
|
version "1.1.3"
|
||||||
resolved "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz"
|
resolved "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz"
|
||||||
integrity sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==
|
integrity sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==
|
||||||
|
|
||||||
|
color-name@~1.1.4:
|
||||||
|
version "1.1.4"
|
||||||
|
resolved "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz"
|
||||||
|
integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==
|
||||||
|
|
||||||
commander@^2.20.0:
|
commander@^2.20.0:
|
||||||
version "2.20.3"
|
version "2.20.3"
|
||||||
resolved "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz"
|
resolved "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz"
|
||||||
|
@ -1102,21 +1152,7 @@ csso@^4.2.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
css-tree "^1.1.2"
|
css-tree "^1.1.2"
|
||||||
|
|
||||||
debug@^4.1.1:
|
debug@^4.1.1, debug@^4.3.2, debug@^4.3.4:
|
||||||
version "4.3.4"
|
|
||||||
resolved "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz"
|
|
||||||
integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==
|
|
||||||
dependencies:
|
|
||||||
ms "2.1.2"
|
|
||||||
|
|
||||||
debug@^4.3.2:
|
|
||||||
version "4.3.4"
|
|
||||||
resolved "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz"
|
|
||||||
integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==
|
|
||||||
dependencies:
|
|
||||||
ms "2.1.2"
|
|
||||||
|
|
||||||
debug@^4.3.4:
|
|
||||||
version "4.3.4"
|
version "4.3.4"
|
||||||
resolved "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz"
|
resolved "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz"
|
||||||
integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==
|
integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==
|
||||||
|
@ -1224,6 +1260,11 @@ escape-string-regexp@^4.0.0:
|
||||||
resolved "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz"
|
resolved "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz"
|
||||||
integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==
|
integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==
|
||||||
|
|
||||||
|
eslint-config-prettier@^8.8.0:
|
||||||
|
version "8.8.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/eslint-config-prettier/-/eslint-config-prettier-8.8.0.tgz#bfda738d412adc917fd7b038857110efe98c9348"
|
||||||
|
integrity sha512-wLbQiFre3tdGgpDv67NQKnJuTlcUVYHas3k+DZCc2U2BadthoEY4B7hLPvAxaqdyOGCzuLfii2fqGph10va7oA==
|
||||||
|
|
||||||
eslint-scope@^5.1.1:
|
eslint-scope@^5.1.1:
|
||||||
version "5.1.1"
|
version "5.1.1"
|
||||||
resolved "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz"
|
resolved "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz"
|
||||||
|
@ -1245,7 +1286,7 @@ eslint-visitor-keys@^3.3.0, eslint-visitor-keys@^3.4.1:
|
||||||
resolved "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.1.tgz"
|
resolved "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.1.tgz"
|
||||||
integrity sha512-pZnmmLwYzf+kWaM/Qgrvpen51upAktaaiI01nsJD/Yr3lMOdNtq0cxkrrg16w64VtisN6okbs7Q8AfGqj4c9fA==
|
integrity sha512-pZnmmLwYzf+kWaM/Qgrvpen51upAktaaiI01nsJD/Yr3lMOdNtq0cxkrrg16w64VtisN6okbs7Q8AfGqj4c9fA==
|
||||||
|
|
||||||
eslint@*, "eslint@^6.0.0 || ^7.0.0 || ^8.0.0", "eslint@^6.0.0 || ^7.0.0 || >=8.0.0", eslint@^8.41.0:
|
eslint@^8.41.0:
|
||||||
version "8.41.0"
|
version "8.41.0"
|
||||||
resolved "https://registry.npmjs.org/eslint/-/eslint-8.41.0.tgz"
|
resolved "https://registry.npmjs.org/eslint/-/eslint-8.41.0.tgz"
|
||||||
integrity sha512-WQDQpzGBOP5IrXPo4Hc0814r4/v2rrIsB0rhT7jtunIalgg6gYXWhRMOejVO8yH21T/FGaxjmFjBMNqcIlmH1Q==
|
integrity sha512-WQDQpzGBOP5IrXPo4Hc0814r4/v2rrIsB0rhT7jtunIalgg6gYXWhRMOejVO8yH21T/FGaxjmFjBMNqcIlmH1Q==
|
||||||
|
@ -1318,12 +1359,7 @@ estraverse@^4.1.1:
|
||||||
resolved "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz"
|
resolved "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz"
|
||||||
integrity sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==
|
integrity sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==
|
||||||
|
|
||||||
estraverse@^5.1.0:
|
estraverse@^5.1.0, estraverse@^5.2.0:
|
||||||
version "5.3.0"
|
|
||||||
resolved "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz"
|
|
||||||
integrity sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==
|
|
||||||
|
|
||||||
estraverse@^5.2.0:
|
|
||||||
version "5.3.0"
|
version "5.3.0"
|
||||||
resolved "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz"
|
resolved "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz"
|
||||||
integrity sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==
|
integrity sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==
|
||||||
|
@ -1608,6 +1644,41 @@ lightningcss-darwin-arm64@1.19.0:
|
||||||
resolved "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.19.0.tgz"
|
resolved "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.19.0.tgz"
|
||||||
integrity sha512-wIJmFtYX0rXHsXHSr4+sC5clwblEMji7HHQ4Ub1/CznVRxtCFha6JIt5JZaNf8vQrfdZnBxLLC6R8pC818jXqg==
|
integrity sha512-wIJmFtYX0rXHsXHSr4+sC5clwblEMji7HHQ4Ub1/CznVRxtCFha6JIt5JZaNf8vQrfdZnBxLLC6R8pC818jXqg==
|
||||||
|
|
||||||
|
lightningcss-darwin-x64@1.19.0:
|
||||||
|
version "1.19.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.19.0.tgz#c867308b88859ba61a2c46c82b1ca52ff73a1bd0"
|
||||||
|
integrity sha512-Lif1wD6P4poaw9c/4Uh2z+gmrWhw/HtXFoeZ3bEsv6Ia4tt8rOJBdkfVaUJ6VXmpKHALve+iTyP2+50xY1wKPw==
|
||||||
|
|
||||||
|
lightningcss-linux-arm-gnueabihf@1.19.0:
|
||||||
|
version "1.19.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.19.0.tgz#0f921dc45f2e5c3aea70fab98844ac0e5f2f81be"
|
||||||
|
integrity sha512-P15VXY5682mTXaiDtbnLYQflc8BYb774j2R84FgDLJTN6Qp0ZjWEFyN1SPqyfTj2B2TFjRHRUvQSSZ7qN4Weig==
|
||||||
|
|
||||||
|
lightningcss-linux-arm64-gnu@1.19.0:
|
||||||
|
version "1.19.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.19.0.tgz#027f9df9c7f4ffa127c37a71726245a5794d7ba2"
|
||||||
|
integrity sha512-zwXRjWqpev8wqO0sv0M1aM1PpjHz6RVIsBcxKszIG83Befuh4yNysjgHVplF9RTU7eozGe3Ts7r6we1+Qkqsww==
|
||||||
|
|
||||||
|
lightningcss-linux-arm64-musl@1.19.0:
|
||||||
|
version "1.19.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.19.0.tgz#85ea987da868524eac6db94f8e1eaa23d0b688a3"
|
||||||
|
integrity sha512-vSCKO7SDnZaFN9zEloKSZM5/kC5gbzUjoJQ43BvUpyTFUX7ACs/mDfl2Eq6fdz2+uWhUh7vf92c4EaaP4udEtA==
|
||||||
|
|
||||||
|
lightningcss-linux-x64-gnu@1.19.0:
|
||||||
|
version "1.19.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.19.0.tgz#02bec89579ab4153dccc0def755d1fd9e3ee7f3c"
|
||||||
|
integrity sha512-0AFQKvVzXf9byrXUq9z0anMGLdZJS+XSDqidyijI5njIwj6MdbvX2UZK/c4FfNmeRa2N/8ngTffoIuOUit5eIQ==
|
||||||
|
|
||||||
|
lightningcss-linux-x64-musl@1.19.0:
|
||||||
|
version "1.19.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.19.0.tgz#e36a5df8193ae961d22974635e4c100a1823bb8c"
|
||||||
|
integrity sha512-SJoM8CLPt6ECCgSuWe+g0qo8dqQYVcPiW2s19dxkmSI5+Uu1GIRzyKA0b7QqmEXolA+oSJhQqCmJpzjY4CuZAg==
|
||||||
|
|
||||||
|
lightningcss-win32-x64-msvc@1.19.0:
|
||||||
|
version "1.19.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.19.0.tgz#0854dbd153035eca1396e2227c708ad43655a61c"
|
||||||
|
integrity sha512-C+VuUTeSUOAaBZZOPT7Etn/agx/MatzJzGRkeV+zEABmPuntv1zihncsi+AyGmjkkzq3wVedEy7h0/4S84mUtg==
|
||||||
|
|
||||||
lightningcss@^1.16.1:
|
lightningcss@^1.16.1:
|
||||||
version "1.19.0"
|
version "1.19.0"
|
||||||
resolved "https://registry.npmjs.org/lightningcss/-/lightningcss-1.19.0.tgz"
|
resolved "https://registry.npmjs.org/lightningcss/-/lightningcss-1.19.0.tgz"
|
||||||
|
@ -1918,6 +1989,11 @@ prelude-ls@^1.2.1:
|
||||||
resolved "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz"
|
resolved "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz"
|
||||||
integrity sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==
|
integrity sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==
|
||||||
|
|
||||||
|
prettier@^2.8.8:
|
||||||
|
version "2.8.8"
|
||||||
|
resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.8.8.tgz#e8c5d7e98a4305ffe3de2e1fc4aca1a71c28b1da"
|
||||||
|
integrity sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==
|
||||||
|
|
||||||
punycode@^2.1.0:
|
punycode@^2.1.0:
|
||||||
version "2.3.0"
|
version "2.3.0"
|
||||||
resolved "https://registry.npmjs.org/punycode/-/punycode-2.3.0.tgz"
|
resolved "https://registry.npmjs.org/punycode/-/punycode-2.3.0.tgz"
|
||||||
|
@ -2014,7 +2090,7 @@ source-map@^0.6.0, source-map@^0.6.1:
|
||||||
resolved "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz"
|
resolved "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz"
|
||||||
integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==
|
integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==
|
||||||
|
|
||||||
srcset@4, srcset@4.0.0:
|
srcset@4:
|
||||||
version "4.0.0"
|
version "4.0.0"
|
||||||
resolved "https://registry.npmjs.org/srcset/-/srcset-4.0.0.tgz"
|
resolved "https://registry.npmjs.org/srcset/-/srcset-4.0.0.tgz"
|
||||||
integrity sha512-wvLeHgcVHKO8Sc/H/5lkGreJQVeYMm9rlmt8PuR1xE31rIuXhuzznUUqAt8MqLhB3MqJdFzlNAfpcWnxiFUcPw==
|
integrity sha512-wvLeHgcVHKO8Sc/H/5lkGreJQVeYMm9rlmt8PuR1xE31rIuXhuzznUUqAt8MqLhB3MqJdFzlNAfpcWnxiFUcPw==
|
||||||
|
@ -2050,7 +2126,7 @@ supports-color@^7.1.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
has-flag "^4.0.0"
|
has-flag "^4.0.0"
|
||||||
|
|
||||||
svgo@^2.4.0, svgo@^2.8.0:
|
svgo@^2.4.0:
|
||||||
version "2.8.0"
|
version "2.8.0"
|
||||||
resolved "https://registry.npmjs.org/svgo/-/svgo-2.8.0.tgz"
|
resolved "https://registry.npmjs.org/svgo/-/svgo-2.8.0.tgz"
|
||||||
integrity sha512-+N/Q9kV1+F+UeWYoSiULYo4xYSDQlTgb+ayMobAXPwMnLvop7oxKMo9OzIrX5x3eS4L4f2UHhc9axXwY8DpChg==
|
integrity sha512-+N/Q9kV1+F+UeWYoSiULYo4xYSDQlTgb+ayMobAXPwMnLvop7oxKMo9OzIrX5x3eS4L4f2UHhc9axXwY8DpChg==
|
||||||
|
@ -2068,7 +2144,7 @@ term-size@^2.2.1:
|
||||||
resolved "https://registry.npmjs.org/term-size/-/term-size-2.2.1.tgz"
|
resolved "https://registry.npmjs.org/term-size/-/term-size-2.2.1.tgz"
|
||||||
integrity sha512-wK0Ri4fOGjv/XPy8SBHZChl8CM7uMc5VML7SqiQ0zG7+J5Vr+RMQDoHa2CNT6KHUnTGIXH34UDMkPzAUyapBZg==
|
integrity sha512-wK0Ri4fOGjv/XPy8SBHZChl8CM7uMc5VML7SqiQ0zG7+J5Vr+RMQDoHa2CNT6KHUnTGIXH34UDMkPzAUyapBZg==
|
||||||
|
|
||||||
terser@^5.10.0, terser@^5.2.0:
|
terser@^5.2.0:
|
||||||
version "5.16.8"
|
version "5.16.8"
|
||||||
resolved "https://registry.npmjs.org/terser/-/terser-5.16.8.tgz"
|
resolved "https://registry.npmjs.org/terser/-/terser-5.16.8.tgz"
|
||||||
integrity sha512-QI5g1E/ef7d+PsDifb+a6nnVgC4F22Bg6T0xrBrz6iloVB4PUkkunp6V8nzoOOZJIzjWVdAGqCdlKlhLq/TbIA==
|
integrity sha512-QI5g1E/ef7d+PsDifb+a6nnVgC4F22Bg6T0xrBrz6iloVB4PUkkunp6V8nzoOOZJIzjWVdAGqCdlKlhLq/TbIA==
|
||||||
|
@ -2124,7 +2200,7 @@ type-fest@^0.20.2:
|
||||||
resolved "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz"
|
resolved "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz"
|
||||||
integrity sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==
|
integrity sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==
|
||||||
|
|
||||||
typescript@^5.0.4, "typescript@>=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta", typescript@>=3.0.0:
|
typescript@^5.0.4:
|
||||||
version "5.0.4"
|
version "5.0.4"
|
||||||
resolved "https://registry.npmjs.org/typescript/-/typescript-5.0.4.tgz"
|
resolved "https://registry.npmjs.org/typescript/-/typescript-5.0.4.tgz"
|
||||||
integrity sha512-cW9T5W9xY37cc+jfEnaUvX91foxtHkza3Nw3wkoF4sSlKn0MONdkdEndig/qPBWXNkmplh3NzayQzCiHM4/hqw==
|
integrity sha512-cW9T5W9xY37cc+jfEnaUvX91foxtHkza3Nw3wkoF4sSlKn0MONdkdEndig/qPBWXNkmplh3NzayQzCiHM4/hqw==
|
||||||
|
|
Loading…
Reference in New Issue