commit
f05bd5a0ac
|
@ -1,4 +1,4 @@
|
|||
name: Rust
|
||||
name: server
|
||||
|
||||
on:
|
||||
push:
|
||||
|
@ -10,14 +10,14 @@ env:
|
|||
CARGO_TERM_COLOR: always
|
||||
|
||||
jobs:
|
||||
build:
|
||||
check:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Build
|
||||
- name: build
|
||||
working-directory: server
|
||||
run: cargo build --verbose
|
||||
- name: Run tests
|
||||
- name: test
|
||||
working-directory: server
|
||||
run: cargo test --verbose
|
||||
|
|
|
@ -0,0 +1,26 @@
|
|||
name: web
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ "main" ]
|
||||
pull_request:
|
||||
branches: [ "main" ]
|
||||
|
||||
jobs:
|
||||
check:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: install
|
||||
working-directory: web
|
||||
run: yarn install
|
||||
- name: cert
|
||||
working-directory: cert
|
||||
run: ./generate
|
||||
- name: build
|
||||
working-directory: web
|
||||
run: yarn build
|
||||
- name: lint
|
||||
working-directory: web
|
||||
run: yarn lint
|
|
@ -9,5 +9,14 @@ module.exports = {
|
|||
"@typescript-eslint/ban-ts-comment": "off",
|
||||
"@typescript-eslint/no-non-null-assertion": "off",
|
||||
"@typescript-eslint/no-explicit-any": "off",
|
||||
"no-unused-vars": "off", // note you must disable the base rule as it can report incorrect errors
|
||||
"@typescript-eslint/no-unused-vars": [
|
||||
"warn", // or "error"
|
||||
{
|
||||
"argsIgnorePattern": "^_",
|
||||
"varsIgnorePattern": "^_",
|
||||
"caughtErrorsIgnorePattern": "^_"
|
||||
}
|
||||
],
|
||||
}
|
||||
};
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -4,15 +4,19 @@
|
|||
"scripts": {
|
||||
"serve": "parcel serve --https --cert ../cert/localhost.crt --key ../cert/localhost.key --port 4444 --open",
|
||||
"build": "parcel build",
|
||||
"check": "tsc --noEmit"
|
||||
"check": "tsc --noEmit",
|
||||
"lint": "eslint ."
|
||||
},
|
||||
"devDependencies": {
|
||||
"@parcel/transformer-inline-string": "2.8.3",
|
||||
"@parcel/validator-typescript": "^2.6.0",
|
||||
"@types/audioworklet": "^0.0.41",
|
||||
"@types/dom-webcodecs": "^0.1.6",
|
||||
"@typescript-eslint/eslint-plugin": "^5.59.7",
|
||||
"@typescript-eslint/parser": "^5.59.7",
|
||||
"eslint": "^8.41.0",
|
||||
"parcel": "^2.8.0",
|
||||
"typescript": ">=3.0.0"
|
||||
"typescript": "^5.0.4"
|
||||
},
|
||||
"dependencies": {
|
||||
"mp4box": "^0.5.2"
|
||||
|
|
|
@ -69,7 +69,7 @@ export class Encoder {
|
|||
dts: frame.timestamp,
|
||||
});
|
||||
|
||||
const stream = this.container.createSingleSampleMoof(sample);
|
||||
const _stream = this.container.createSingleSampleMoof(sample);
|
||||
}
|
||||
|
||||
onVideo(frame: EncodedVideoChunk, metadata?: EncodedVideoChunkMetadata) {
|
||||
|
@ -99,6 +99,6 @@ export class Encoder {
|
|||
dts: frame.timestamp,
|
||||
});
|
||||
|
||||
const stream = this.container.createSingleSampleMoof(sample);
|
||||
const _stream = this.container.createSingleSampleMoof(sample);
|
||||
}
|
||||
}
|
|
@ -1,4 +1,5 @@
|
|||
export default class Broadcaster {
|
||||
constructor() {
|
||||
// TODO
|
||||
}
|
||||
}
|
|
@ -5,7 +5,7 @@ import Transport from "./transport"
|
|||
import fingerprintHex from 'bundle-text:../fingerprint.hex';
|
||||
|
||||
// Convert the hex to binary.
|
||||
let fingerprint = [];
|
||||
const fingerprint = [];
|
||||
for (let c = 0; c < fingerprintHex.length - 1; c += 2) {
|
||||
fingerprint.push(parseInt(fingerprintHex.substring(c, c + 2), 16));
|
||||
}
|
||||
|
|
|
@ -26,11 +26,11 @@ export class InitParser {
|
|||
|
||||
push(data: Uint8Array) {
|
||||
// Make a copy of the atom because mp4box only accepts an ArrayBuffer unfortunately
|
||||
let box = new Uint8Array(data.byteLength);
|
||||
const box = new Uint8Array(data.byteLength);
|
||||
box.set(data)
|
||||
|
||||
// and for some reason we need to modify the underlying ArrayBuffer with fileStart
|
||||
let buffer = box.buffer as MP4.ArrayBuffer
|
||||
const buffer = box.buffer as MP4.ArrayBuffer
|
||||
buffer.fileStart = this.offset
|
||||
|
||||
// Parse the data
|
||||
|
|
|
@ -121,10 +121,10 @@ declare module "mp4box" {
|
|||
mapUint8Array(length: number): Uint8Array;
|
||||
readInt32Array(length: number, littleEndian: boolean): Int32Array;
|
||||
readInt16Array(length: number, littleEndian: boolean): Int16Array;
|
||||
readInt8(length: number): Int8Array;
|
||||
readInt8Array(length: number): Int8Array;
|
||||
readUint32Array(length: number, littleEndian: boolean): Uint32Array;
|
||||
readUint16Array(length: number, littleEndian: boolean): Uint16Array;
|
||||
readUint8(length: number): Uint8Array;
|
||||
readUint8Array(length: number): Uint8Array;
|
||||
readFloat64Array(length: number, littleEndian: boolean): Float64Array;
|
||||
readFloat32Array(length: number, littleEndian: boolean): Float32Array;
|
||||
|
||||
|
|
|
@ -8,7 +8,7 @@ export default class Audio {
|
|||
render?: number; // non-zero if requestAnimationFrame has been called
|
||||
last?: number; // the timestamp of the last rendered frame, in microseconds
|
||||
|
||||
constructor(config: Message.Config) {
|
||||
constructor(_config: Message.Config) {
|
||||
this.queue = []
|
||||
}
|
||||
|
||||
|
@ -47,7 +47,7 @@ export default class Audio {
|
|||
}
|
||||
|
||||
while (this.queue.length) {
|
||||
let frame = this.queue[0];
|
||||
const frame = this.queue[0];
|
||||
if (ring.size() + frame.numberOfFrames > ring.capacity) {
|
||||
// Buffer is full
|
||||
break
|
||||
|
|
|
@ -16,8 +16,8 @@ export default class Decoder {
|
|||
}
|
||||
|
||||
async receiveInit(msg: Message.Init) {
|
||||
let stream = new Stream.Reader(msg.reader, msg.buffer);
|
||||
while (1) {
|
||||
const stream = new Stream.Reader(msg.reader, msg.buffer);
|
||||
for (;;) {
|
||||
const data = await stream.read()
|
||||
if (!data) break
|
||||
|
||||
|
@ -29,14 +29,14 @@ export default class Decoder {
|
|||
|
||||
async receiveSegment(msg: Message.Segment) {
|
||||
// Wait for the init segment to be fully received and parsed
|
||||
const info = await this.init.info
|
||||
const init = await this.init.info
|
||||
const input = MP4.New();
|
||||
|
||||
input.onSamples = this.onSamples.bind(this);
|
||||
input.onReady = (info: any) => {
|
||||
input.onReady = (track: any) => {
|
||||
// Extract all of the tracks, because we don't know if it's audio or video.
|
||||
for (let track of info.tracks) {
|
||||
input.setExtractionOptions(track.id, track, { nbSamples: 1 });
|
||||
for (const i of init.tracks) {
|
||||
input.setExtractionOptions(track.id, i, { nbSamples: 1 });
|
||||
}
|
||||
|
||||
input.start();
|
||||
|
@ -45,7 +45,7 @@ export default class Decoder {
|
|||
// MP4box requires us to reparse the init segment unfortunately
|
||||
let offset = 0;
|
||||
|
||||
for (let raw of this.init.raw) {
|
||||
for (const raw of this.init.raw) {
|
||||
raw.fileStart = offset
|
||||
offset = input.appendBuffer(raw)
|
||||
}
|
||||
|
@ -61,11 +61,11 @@ export default class Decoder {
|
|||
const atom = await stream.bytes(size)
|
||||
|
||||
// Make a copy of the atom because mp4box only accepts an ArrayBuffer unfortunately
|
||||
let box = new Uint8Array(atom.byteLength);
|
||||
const box = new Uint8Array(atom.byteLength);
|
||||
box.set(atom)
|
||||
|
||||
// and for some reason we need to modify the underlying ArrayBuffer with offset
|
||||
let buffer = box.buffer as MP4.ArrayBuffer
|
||||
const buffer = box.buffer as MP4.ArrayBuffer
|
||||
buffer.fileStart = offset
|
||||
|
||||
// Parse the data
|
||||
|
@ -79,7 +79,7 @@ export default class Decoder {
|
|||
|
||||
if (!decoder) {
|
||||
// We need a sample to initalize the video decoder, because of mp4box limitations.
|
||||
let sample = samples[0];
|
||||
const sample = samples[0];
|
||||
|
||||
if (isVideoTrack(track)) {
|
||||
// Configure the decoder using the AVC box for H.264
|
||||
|
@ -124,7 +124,7 @@ export default class Decoder {
|
|||
this.decoders.set(track_id, decoder)
|
||||
}
|
||||
|
||||
for (let sample of samples) {
|
||||
for (const sample of samples) {
|
||||
// Convert to microseconds
|
||||
const timestamp = 1000 * 1000 * sample.dts / sample.timescale
|
||||
const duration = 1000 * 1000 * sample.duration / sample.timescale
|
||||
|
|
|
@ -45,7 +45,7 @@ export default class Player {
|
|||
return worker
|
||||
}
|
||||
|
||||
private async setupWorklet(config: Config): Promise<AudioWorkletNode> {
|
||||
private async setupWorklet(_config: Config): Promise<AudioWorkletNode> {
|
||||
// Load the worklet source code.
|
||||
const url = new URL('worklet.ts', import.meta.url)
|
||||
await this.context.audioWorklet.addModule(url)
|
||||
|
|
|
@ -37,7 +37,7 @@ export class Ring {
|
|||
this.state = new Int32Array(buffer.state)
|
||||
|
||||
this.channels = []
|
||||
for (let channel of buffer.channels) {
|
||||
for (const channel of buffer.channels) {
|
||||
this.channels.push(new Float32Array(channel))
|
||||
}
|
||||
|
||||
|
@ -46,8 +46,8 @@ export class Ring {
|
|||
|
||||
// Write samples for single audio frame, returning the total number written.
|
||||
write(frame: AudioData): number {
|
||||
let readPos = Atomics.load(this.state, STATE.READ_POS)
|
||||
let writePos = Atomics.load(this.state, STATE.WRITE_POS)
|
||||
const readPos = Atomics.load(this.state, STATE.READ_POS)
|
||||
const writePos = Atomics.load(this.state, STATE.WRITE_POS)
|
||||
|
||||
const startPos = writePos
|
||||
let endPos = writePos + frame.numberOfFrames;
|
||||
|
@ -60,8 +60,8 @@ export class Ring {
|
|||
}
|
||||
}
|
||||
|
||||
let startIndex = startPos % this.capacity;
|
||||
let endIndex = endPos % this.capacity;
|
||||
const startIndex = startPos % this.capacity;
|
||||
const endIndex = endPos % this.capacity;
|
||||
|
||||
// Loop over each channel
|
||||
for (let i = 0; i < this.channels.length; i += 1) {
|
||||
|
@ -102,10 +102,10 @@ export class Ring {
|
|||
}
|
||||
|
||||
read(dst: Float32Array[]): number {
|
||||
let readPos = Atomics.load(this.state, STATE.READ_POS)
|
||||
let writePos = Atomics.load(this.state, STATE.WRITE_POS)
|
||||
const readPos = Atomics.load(this.state, STATE.READ_POS)
|
||||
const writePos = Atomics.load(this.state, STATE.WRITE_POS)
|
||||
|
||||
let startPos = readPos;
|
||||
const startPos = readPos;
|
||||
let endPos = startPos + dst[0].length;
|
||||
|
||||
if (endPos > writePos) {
|
||||
|
@ -116,8 +116,8 @@ export class Ring {
|
|||
}
|
||||
}
|
||||
|
||||
let startIndex = startPos % this.capacity;
|
||||
let endIndex = endPos % this.capacity;
|
||||
const startIndex = startPos % this.capacity;
|
||||
const endIndex = endPos % this.capacity;
|
||||
|
||||
// Loop over each channel
|
||||
for (let i = 0; i < dst.length; i += 1) {
|
||||
|
@ -147,8 +147,8 @@ export class Ring {
|
|||
|
||||
size() {
|
||||
// TODO is this thread safe?
|
||||
let readPos = Atomics.load(this.state, STATE.READ_POS)
|
||||
let writePos = Atomics.load(this.state, STATE.WRITE_POS)
|
||||
const readPos = Atomics.load(this.state, STATE.READ_POS)
|
||||
const writePos = Atomics.load(this.state, STATE.WRITE_POS)
|
||||
|
||||
return writePos - readPos
|
||||
}
|
||||
|
|
|
@ -89,7 +89,7 @@ export default class Video {
|
|||
frame.close()
|
||||
}
|
||||
|
||||
play(play: Message.Play) {
|
||||
play(_play: Message.Play) {
|
||||
// Queue up to render the next frame.
|
||||
if (!this.render) {
|
||||
this.render = self.requestAnimationFrame(this.draw.bind(this))
|
||||
|
|
|
@ -10,7 +10,7 @@ class Renderer extends AudioWorkletProcessor {
|
|||
ring?: Ring;
|
||||
base: number;
|
||||
|
||||
constructor(params: AudioWorkletNodeOptions) {
|
||||
constructor(_params: AudioWorkletNodeOptions) {
|
||||
// The super constructor call is required.
|
||||
super();
|
||||
|
||||
|
@ -29,7 +29,7 @@ class Renderer extends AudioWorkletProcessor {
|
|||
}
|
||||
|
||||
// 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) {
|
||||
// Paused
|
||||
return true
|
||||
|
|
|
@ -28,7 +28,7 @@ export default class Reader {
|
|||
async readAll(): Promise<Uint8Array> {
|
||||
const r = this.reader.getReader()
|
||||
|
||||
while (1) {
|
||||
for (;;) {
|
||||
const result = await r.read()
|
||||
if (result.done) {
|
||||
break
|
||||
|
@ -164,18 +164,22 @@ export default class Reader {
|
|||
const size = (first & 0xc0) >> 6
|
||||
|
||||
switch (size) {
|
||||
case 0:
|
||||
const v0 = await this.uint8()
|
||||
return BigInt(v0) & 0x3fn
|
||||
case 1:
|
||||
const v1 = await this.uint16()
|
||||
return BigInt(v1) & 0x3fffn
|
||||
case 2:
|
||||
const v2 = await this.uint32()
|
||||
return BigInt(v2) & 0x3fffffffn
|
||||
case 3:
|
||||
const v3 = await this.uint64()
|
||||
return v3 & 0x3fffffffffffffffn
|
||||
case 0: {
|
||||
const v = await this.uint8()
|
||||
return BigInt(v) & 0x3fn
|
||||
}
|
||||
case 1: {
|
||||
const v = await this.uint16()
|
||||
return BigInt(v) & 0x3fffn
|
||||
}
|
||||
case 2: {
|
||||
const v = await this.uint32()
|
||||
return BigInt(v) & 0x3fffffffn
|
||||
}
|
||||
case 3: {
|
||||
const v = await this.uint64()
|
||||
return v & 0x3fffffffffffffffn
|
||||
}
|
||||
default:
|
||||
throw "impossible"
|
||||
}
|
||||
|
@ -183,7 +187,7 @@ export default class Reader {
|
|||
|
||||
async done(): Promise<boolean> {
|
||||
try {
|
||||
const peek = await this.peek(1)
|
||||
await this.peek(1)
|
||||
return false
|
||||
} catch (err) {
|
||||
return true // Assume EOF
|
||||
|
|
|
@ -29,7 +29,7 @@ export default class Transport {
|
|||
|
||||
// Helper function to make creating a promise easier
|
||||
private async connect(config: Config): Promise<WebTransport> {
|
||||
let options: WebTransportOptions = {};
|
||||
const options: WebTransportOptions = {};
|
||||
if (config.fingerprint) {
|
||||
options.serverCertificateHashes = [ config.fingerprint ]
|
||||
}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
export interface Init {}
|
||||
export interface Segment {}
|
||||
// TODO fill in required fields
|
||||
export type Init = any
|
||||
export type Segment = any
|
||||
|
||||
export interface Debug {
|
||||
max_bitrate: number
|
||||
|
|
|
@ -29,7 +29,7 @@ interface WebTransport {
|
|||
readonly incomingUnidirectionalStreams: ReadableStream;
|
||||
}
|
||||
|
||||
declare var WebTransport: {
|
||||
declare const WebTransport: {
|
||||
prototype: WebTransport;
|
||||
new(url: string, options?: WebTransportOptions): WebTransport;
|
||||
};
|
||||
|
@ -71,7 +71,7 @@ interface WebTransportError extends DOMException {
|
|||
readonly streamErrorCode: number;
|
||||
}
|
||||
|
||||
declare var WebTransportError: {
|
||||
declare const WebTransportError: {
|
||||
prototype: WebTransportError;
|
||||
new(init?: WebTransportErrorInit): WebTransportError;
|
||||
};
|
||||
|
|
|
@ -5,8 +5,8 @@ export default class Deferred<T> {
|
|||
|
||||
constructor() {
|
||||
// Set initial values so TS stops being annoying.
|
||||
this.resolve = (value: T | PromiseLike<T>) => {};
|
||||
this.reject = (value: T | PromiseLike<T>) => {};
|
||||
this.resolve = (_value: T | PromiseLike<T>) => { /* noop */ };
|
||||
this.reject = (_value: T | PromiseLike<T>) => { /* noop */ };
|
||||
|
||||
this.promise = new Promise((resolve, reject) => {
|
||||
this.resolve = resolve
|
||||
|
|
1254
web/yarn.lock
1254
web/yarn.lock
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue