it's working chat

This commit is contained in:
Aubrey 2024-12-20 04:33:50 -06:00
parent dcf885a7b0
commit a1dbfadfb0
No known key found for this signature in database
13 changed files with 526 additions and 229 deletions

View file

@ -20,6 +20,6 @@
"vite": "^6.0.0" "vite": "^6.0.0"
}, },
"dependencies": { "dependencies": {
"peerjs": "^1.5.4" "uuid": "^11.0.3"
} }
} }

View file

@ -8,9 +8,9 @@ importers:
.: .:
dependencies: dependencies:
peerjs: uuid:
specifier: ^1.5.4 specifier: ^11.0.3
version: 1.5.4 version: 11.0.3
devDependencies: devDependencies:
'@sveltejs/adapter-node': '@sveltejs/adapter-node':
specifier: ^5.2.0 specifier: ^5.2.0
@ -205,10 +205,6 @@ packages:
'@jridgewell/trace-mapping@0.3.25': '@jridgewell/trace-mapping@0.3.25':
resolution: {integrity: sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==} resolution: {integrity: sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==}
'@msgpack/msgpack@2.8.0':
resolution: {integrity: sha512-h9u4u/jiIRKbq25PM+zymTyW6bhTzELvOoUd+AvYriWOAKpLGnIamaET3pnHYoI5iYphAHBI4ayx0MehR+VVPQ==}
engines: {node: '>= 10'}
'@polka/url@1.0.0-next.28': '@polka/url@1.0.0-next.28':
resolution: {integrity: sha512-8LduaNlMZGwdZ6qWrKlfa+2M4gahzFkprZiAt2TF8uS0qQgBizKXpXURqvTJ4WtmupWxaLqjRb2UCTe72mu+Aw==} resolution: {integrity: sha512-8LduaNlMZGwdZ6qWrKlfa+2M4gahzFkprZiAt2TF8uS0qQgBizKXpXURqvTJ4WtmupWxaLqjRb2UCTe72mu+Aw==}
@ -446,9 +442,6 @@ packages:
estree-walker@2.0.2: estree-walker@2.0.2:
resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==} resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==}
eventemitter3@4.0.7:
resolution: {integrity: sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==}
fdir@6.4.2: fdir@6.4.2:
resolution: {integrity: sha512-KnhMXsKSPZlAhp7+IjUkRZKPb4fUyccpDrdFXbi4QL1qkmFh9kVY09Yox+n4MaOb3lHZ1Tv829C3oaaXoMYPDQ==} resolution: {integrity: sha512-KnhMXsKSPZlAhp7+IjUkRZKPb4fUyccpDrdFXbi4QL1qkmFh9kVY09Yox+n4MaOb3lHZ1Tv829C3oaaXoMYPDQ==}
peerDependencies: peerDependencies:
@ -520,14 +513,6 @@ packages:
path-parse@1.0.7: path-parse@1.0.7:
resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==}
peerjs-js-binarypack@2.1.0:
resolution: {integrity: sha512-YIwCC+pTzp3Bi8jPI9UFKO0t0SLo6xALnHkiNt/iUFmUUZG0fEEmEyFKvjsDKweiFitzHRyhuh6NvyJZ4nNxMg==}
engines: {node: '>= 14.0.0'}
peerjs@1.5.4:
resolution: {integrity: sha512-yFsoLMnurJKlQbx6kVSBpOp+AlNldY1JQS2BrSsHLKCZnq6t7saHleuHM5svuLNbQkUJXHLF3sKOJB1K0xulOw==}
engines: {node: '>= 14'}
picocolors@1.1.1: picocolors@1.1.1:
resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==}
@ -556,9 +541,6 @@ packages:
resolution: {integrity: sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A==} resolution: {integrity: sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A==}
engines: {node: '>=6'} engines: {node: '>=6'}
sdp@3.2.0:
resolution: {integrity: sha512-d7wDPgDV3DDiqulJjKiV2865wKsJ34YI+NDREbm+FySq6WuKOikwyNQcm+doLAZ1O6ltdO0SeKle2xMpN3Brgw==}
set-cookie-parser@2.7.1: set-cookie-parser@2.7.1:
resolution: {integrity: sha512-IOc8uWeOZgnb3ptbCURJWNjWUPcO3ZnTTdzsurqERrP6nPyv+paC55vJM0LpOlT2ne+Ix+9+CRG1MNLlyZ4GjQ==} resolution: {integrity: sha512-IOc8uWeOZgnb3ptbCURJWNjWUPcO3ZnTTdzsurqERrP6nPyv+paC55vJM0LpOlT2ne+Ix+9+CRG1MNLlyZ4GjQ==}
@ -598,6 +580,10 @@ packages:
engines: {node: '>=14.17'} engines: {node: '>=14.17'}
hasBin: true hasBin: true
uuid@11.0.3:
resolution: {integrity: sha512-d0z310fCWv5dJwnX1Y/MncBAqGMKEzlBb1AOf7z9K8ALnd0utBX/msg/fA0+sbyN1ihbMsLhrBlnl1ak7Wa0rg==}
hasBin: true
vite@6.0.3: vite@6.0.3:
resolution: {integrity: sha512-Cmuo5P0ENTN6HxLSo6IHsjCLn/81Vgrp81oaiFFMRa8gGDj5xEjIcEpf2ZymZtZR8oU0P2JX5WuUp/rlXcHkAw==} resolution: {integrity: sha512-Cmuo5P0ENTN6HxLSo6IHsjCLn/81Vgrp81oaiFFMRa8gGDj5xEjIcEpf2ZymZtZR8oU0P2JX5WuUp/rlXcHkAw==}
engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0}
@ -646,10 +632,6 @@ packages:
vite: vite:
optional: true optional: true
webrtc-adapter@9.0.1:
resolution: {integrity: sha512-1AQO+d4ElfVSXyzNVTOewgGT/tAomwwztX/6e3totvyyzXPvXIIuUUjAmyZGbKBKbZOXauuJooZm3g6IuFuiNQ==}
engines: {node: '>=6.0.0', npm: '>=3.10.0'}
zimmerframe@1.1.2: zimmerframe@1.1.2:
resolution: {integrity: sha512-rAbqEGa8ovJy4pyBxZM70hg4pE6gDgaQ0Sl9M3enG3I0d6H4XSAM3GeNGLKnsBpuijUow064sf7ww1nutC5/3w==} resolution: {integrity: sha512-rAbqEGa8ovJy4pyBxZM70hg4pE6gDgaQ0Sl9M3enG3I0d6H4XSAM3GeNGLKnsBpuijUow064sf7ww1nutC5/3w==}
@ -749,8 +731,6 @@ snapshots:
'@jridgewell/resolve-uri': 3.1.2 '@jridgewell/resolve-uri': 3.1.2
'@jridgewell/sourcemap-codec': 1.5.0 '@jridgewell/sourcemap-codec': 1.5.0
'@msgpack/msgpack@2.8.0': {}
'@polka/url@1.0.0-next.28': {} '@polka/url@1.0.0-next.28': {}
'@rollup/plugin-commonjs@28.0.2(rollup@4.28.1)': '@rollup/plugin-commonjs@28.0.2(rollup@4.28.1)':
@ -966,8 +946,6 @@ snapshots:
estree-walker@2.0.2: {} estree-walker@2.0.2: {}
eventemitter3@4.0.7: {}
fdir@6.4.2(picomatch@4.0.2): fdir@6.4.2(picomatch@4.0.2):
optionalDependencies: optionalDependencies:
picomatch: 4.0.2 picomatch: 4.0.2
@ -1019,15 +997,6 @@ snapshots:
path-parse@1.0.7: {} path-parse@1.0.7: {}
peerjs-js-binarypack@2.1.0: {}
peerjs@1.5.4:
dependencies:
'@msgpack/msgpack': 2.8.0
eventemitter3: 4.0.7
peerjs-js-binarypack: 2.1.0
webrtc-adapter: 9.0.1
picocolors@1.1.1: {} picocolors@1.1.1: {}
picomatch@4.0.2: {} picomatch@4.0.2: {}
@ -1075,8 +1044,6 @@ snapshots:
dependencies: dependencies:
mri: 1.2.0 mri: 1.2.0
sdp@3.2.0: {}
set-cookie-parser@2.7.1: {} set-cookie-parser@2.7.1: {}
sirv@3.0.0: sirv@3.0.0:
@ -1126,6 +1093,8 @@ snapshots:
typescript@5.7.2: {} typescript@5.7.2: {}
uuid@11.0.3: {}
vite@6.0.3: vite@6.0.3:
dependencies: dependencies:
esbuild: 0.24.0 esbuild: 0.24.0
@ -1138,8 +1107,4 @@ snapshots:
optionalDependencies: optionalDependencies:
vite: 6.0.3 vite: 6.0.3
webrtc-adapter@9.0.1:
dependencies:
sdp: 3.2.0
zimmerframe@1.1.2: {} zimmerframe@1.1.2: {}

34
src/lib/audio.svelte.ts Normal file
View file

@ -0,0 +1,34 @@
export class AudioManager {
globalGain = $state(1.0);
useMusic: boolean;
ac: AudioContext;
dest: MediaStreamAudioDestinationNode;
gain: GainNode;
source: AudioNode;
public readonly output: MediaStream;
constructor(media: HTMLAudioElement | MediaStream) {
this.ac = new AudioContext();
this.dest = this.ac.createMediaStreamDestination();
this.gain = this.ac.createGain();
globalThis.audioPlay = this;
if (media instanceof HTMLAudioElement) {
this.source = this.ac.createMediaElementSource(media);
media.autoplay = true;
media.controls = true;
media.play();
this.useMusic = true;
} else {
this.source = this.ac.createMediaStreamSource(media);
this.useMusic = false;
}
// .connect(this.gain)
// this.source.connect(this.dest);
this.gain.gain.value /= 32;
this.source.connect(this.gain).connect(this.dest)
this.output = this.dest.stream;
// this.output = media;
console.log("epic", this.dest.stream, this.output);
}
}

View file

@ -1,149 +1,127 @@
// import { BinaryStreamReader } from "./readerStream"; import { ReadMethod } from "./types";
// import { AsyncReadMethod, AsyncReadMethodReturnValue, ObjectValue, ObjectValueType, ReadMethod, ReadMethodReturnValue } from "./types";
// export class BinaryReader { export class BinaryReader {
// protected index: number = 0; protected index: number = 0;
// constructor( constructor(
// protected readonly buffer: DataView, protected readonly buffer: DataView,
// ) {} ) { }
// readBoolean(): boolean { readBoolean(): boolean {
// return this.readUInt8() != 0; return this.readUInt8() != 0;
// } }
// readSInt8(): number { readSInt8(): number {
// const value = this.buffer.getInt8(this.index); const value = this.buffer.getInt8(this.index);
// this.index++; this.index++;
// return value; return value;
// } }
// readUInt8(): number { readUInt8(): number {
// const value = this.buffer.getUint8(this.index); const value = this.buffer.getUint8(this.index);
// this.index++; this.index++;
// return value; return value;
// } }
// readSInt16(): number { readSInt16(): number {
// const value = this.buffer.getInt16(this.index, true); const value = this.buffer.getInt16(this.index, true);
// this.index += 2; this.index += 2;
// return value; return value;
// } }
// readUInt16(): number { readUInt16(): number {
// const value = this.buffer.getUint16(this.index, true); const value = this.buffer.getUint16(this.index, true);
// this.index += 2; this.index += 2;
// return value; return value;
// } }
// readSInt32(): number { readSInt32(): number {
// const value = this.buffer.getInt32(this.index, true); const value = this.buffer.getInt32(this.index, true);
// this.index += 4; this.index += 4;
// return value; return value;
// } }
// readUInt32(): number { readUInt32(): number {
// const value = this.buffer.getUint32(this.index, true); const value = this.buffer.getUint32(this.index, true);
// this.index += 4; this.index += 4;
// return value; return value;
// } }
// readSInt64(): bigint { readSInt64(): bigint {
// const value = this.buffer.getBigInt64(this.index, true); const value = this.buffer.getBigInt64(this.index, true);
// this.index += 8; this.index += 8;
// return value; return value;
// } }
// readUInt64(): bigint { readUInt64(): bigint {
// const value = this.buffer.getBigUint64(this.index, true); const value = this.buffer.getBigUint64(this.index, true);
// this.index += 8; this.index += 8;
// return value; return value;
// } }
// readFloat32(): number { readFloat32(): number {
// const value = this.buffer.getFloat32(this.index, true); const value = this.buffer.getFloat32(this.index, true);
// this.index += 4; this.index += 4;
// return value; return value;
// } }
// readFloat64(): number { readFloat64(): number {
// const value = this.buffer.getFloat64(this.index, true); const value = this.buffer.getFloat64(this.index, true);
// this.index += 8; this.index += 8;
// return value; return value;
// } }
// readString(length: number, encoding?: string): string { readString(length: number, encoding?: string): string {
// const decoder = new TextDecoder(encoding); const decoder = new TextDecoder(encoding);
// return decoder.decode(this.buffer.buffer.slice(this.index, this.index += length)); return decoder.decode(this.buffer.buffer.slice(this.index, this.index += length));
// } }
// async readArray<T>(size: number, readMethod: ReadMethod<T> | AsyncReadMethod<T>): Promise<T[]> { async readFixedString(length: number, encoding?: string): Promise<string> {
// const array = new Array<T>(size); const decoder = new TextDecoder(encoding);
// for (let i = 0; i < size; i++) { const slice = new Uint8Array(this.buffer.buffer.slice(this.index, this.index += length));
// array[i] = await this.read(readMethod); const foundIndex = slice.findIndex(byte => byte == 0);
// } const end = foundIndex === -1 ? this.buffer.byteLength : foundIndex
// return array; return decoder.decode(slice.slice(0, end));
// } }
// async readObject(): Promise<ObjectValue> { async readArray<T>(size: number, readMethod: ReadMethod<T, BinaryReader>): Promise<T[]> {
// return await this.readMappedShortEnum({ const array = new Array<T>(size);
// [ObjectValueType.Object]: async (r: BinaryReader | BinaryStreamReader) => Object.fromEntries(await r.readArray(await r.readUInt32(), async r2 => [ await r2.readString(await r2.readUInt32()), await r2.readObject() ])),
// [ObjectValueType.Array]: async (r: BinaryReader | BinaryStreamReader) => await r.readArray(await r.readUInt32(), async (r2: BinaryReader | BinaryStreamReader) => await r2.readObject()),
// [ObjectValueType.Boolean]: async (r: BinaryReader | BinaryStreamReader) => await r.readBoolean(),
// [ObjectValueType.Null]: () => null,
// [ObjectValueType.Number]: async (r: BinaryReader | BinaryStreamReader) => await r.readFloat64(),
// [ObjectValueType.String]: async (r: BinaryReader | BinaryStreamReader) => await r.readString(await r.readUInt32()),
// })
// }
// async read<T>(readMethod: ReadMethod<T> | AsyncReadMethod<T>): Promise<T> { for (let i = 0; i < size; i++) {
// if ("deserialize" in readMethod) { array[i] = await this.read(readMethod);
// return await readMethod.deserialize(this); }
// }
// return await readMethod(this); return array;
// } }
// readShortEnum<T extends number>(): T { async read<T>(readMethod: ReadMethod<T, BinaryReader>): Promise<T> {
// return this.readUInt8() as T; if ("deserialize" in readMethod) {
// } return await readMethod.deserialize(this);
}
// readEnum<T extends number>(): T { return await readMethod(this);
// return this.readUInt16() as T; }
// } }
// readDate(): Date {
// return new Date(Number(this.readUInt64()));
// }
// async readMappedEnum<T extends number, R extends Record<T, ReadMethod<any> | AsyncReadMethod<T>>>(mappedType: R): Promise<ReadMethodReturnValue<R[keyof R]> | AsyncReadMethodReturnValue<R[keyof R]>> {
// return await this.read(mappedType[this.readEnum<T>()]);
// }
// async readMappedShortEnum<T extends number, R extends Record<T, ReadMethod<any> | AsyncReadMethod<T>>>(mappedType: R): Promise<ReadMethodReturnValue<R[keyof R]> | AsyncReadMethodReturnValue<R[keyof R]>> {
// return await this.read(mappedType[this.readShortEnum<T>()]);
// }
// }

View file

@ -24,7 +24,6 @@ export class BinaryStreamReader {
(async () => { (async () => {
while (true) { while (true) {
const data = await reader.read(); const data = await reader.read();
console.log("got data", data);
streamReader.push(data.value); streamReader.push(data.value);
} }
})(); })();
@ -94,35 +93,35 @@ export class BinaryStreamReader {
} }
async readSInt16(): Promise<number> { async readSInt16(): Promise<number> {
return (await this.bytes(2)).getInt16(0); return (await this.bytes(2)).getInt16(0, true);
} }
async readUInt16(): Promise<number> { async readUInt16(): Promise<number> {
return (await this.bytes(2)).getUint16(0); return (await this.bytes(2)).getUint16(0, true);
} }
async readSInt32(): Promise<number> { async readSInt32(): Promise<number> {
return (await this.bytes(4)).getInt32(0); return (await this.bytes(4)).getInt32(0, true);
} }
async readUInt32(): Promise<number> { async readUInt32(): Promise<number> {
return (await this.bytes(4)).getUint32(0); return (await this.bytes(4)).getUint32(0, true);
} }
async readSInt64(): Promise<bigint> { async readSInt64(): Promise<bigint> {
return (await this.bytes(8)).getBigInt64(0); return (await this.bytes(8)).getBigInt64(0, true);
} }
async readUInt64(): Promise<bigint> { async readUInt64(): Promise<bigint> {
return (await this.bytes(8)).getBigUint64(0); return (await this.bytes(8)).getBigUint64(0, true);
} }
async readFloat32(): Promise<number> { async readFloat32(): Promise<number> {
return (await this.bytes(4)).getFloat32(0); return (await this.bytes(4)).getFloat32(0, true);
} }
async readFloat64(): Promise<number> { async readFloat64(): Promise<number> {
return (await this.bytes(8)).getFloat64(0); return (await this.bytes(8)).getFloat64(0, true);
} }
async readString(length: number, encoding?: string): Promise<string> { async readString(length: number, encoding?: string): Promise<string> {
@ -136,10 +135,10 @@ export class BinaryStreamReader {
const bytes = await this.bytes(length); const bytes = await this.bytes(length);
const slice = new Uint8Array(bytes.buffer.slice(bytes.byteOffset, bytes.byteOffset + bytes.byteLength)); const slice = new Uint8Array(bytes.buffer.slice(bytes.byteOffset, bytes.byteOffset + bytes.byteLength));
const end = slice.findIndex(byte => byte == 0); const foundIndex = slice.findIndex(byte => byte == 0);
console.log(bytes.byteOffset, bytes.byteOffset + end, end, bytes.byteLength); const end = foundIndex === -1 ? slice.byteLength : foundIndex;
return decoder.decode(bytes.buffer.slice(bytes.byteOffset, bytes.byteOffset + end)); return decoder.decode(slice.slice(0, end));
} }
async readArray<T>(size: number, readMethod: AsyncReadMethod<T, this>): Promise<T[]> { async readArray<T>(size: number, readMethod: AsyncReadMethod<T, this>): Promise<T[]> {

View file

@ -1,9 +1,9 @@
type Method<T, R, F extends string> = ((reader: R) => T) | { [key in F]: (reader: R) => T }; type Method<T, R, F extends string> = ((reader: R) => T) | { [key in F]: (reader: R) => T };
export type ReadMethod<T, R> = Method<T, R, "deserialize">; export type ReadMethod<T, R> = Method<T, R, "deserialize">;
export type WriteMethod<W> = Method<void, W, "serialize">;
export type AsyncReadMethod<T, R> = Method<T | Promise<T>, R, "deserialize">; export type AsyncReadMethod<T, R> = Method<T | Promise<T>, R, "deserialize">;
export type WriteMethod<T, W> = ((writer: W, value: T) => void) | { serialize: (writer: W, value: T) => void };
export type ReadMethodReturnValue<X> = X extends ReadMethod<infer Y, any> ? (Y extends Promise<infer Z> ? Z : Y) : never; export type ReadMethodReturnValue<X> = X extends ReadMethod<infer Y, any> ? (Y extends Promise<infer Z> ? Z : Y) : never;
export enum ObjectValueType { export enum ObjectValueType {

View file

@ -43,7 +43,7 @@ export class BinaryWriter {
writeSInt16(value: number): void { writeSInt16(value: number): void {
this.request(2); this.request(2);
this.buffer.setInt16(this.index, value); this.buffer.setInt16(this.index, value, true);
this.index += 2; this.index += 2;
} }
@ -51,7 +51,7 @@ export class BinaryWriter {
writeUInt16(value: number): void { writeUInt16(value: number): void {
this.request(2); this.request(2);
this.buffer.setUint16(this.index, value); this.buffer.setUint16(this.index, value, true);
this.index += 2; this.index += 2;
} }
@ -59,7 +59,7 @@ export class BinaryWriter {
writeSInt32(value: number): void { writeSInt32(value: number): void {
this.request(4); this.request(4);
this.buffer.setInt32(this.index, value); this.buffer.setInt32(this.index, value, true);
this.index += 4; this.index += 4;
} }
@ -67,7 +67,7 @@ export class BinaryWriter {
writeUInt32(value: number): void { writeUInt32(value: number): void {
this.request(4); this.request(4);
this.buffer.setUint32(this.index, value); this.buffer.setUint32(this.index, value, true);
this.index += 4; this.index += 4;
} }
@ -75,7 +75,7 @@ export class BinaryWriter {
writeSInt64(value: bigint): void { writeSInt64(value: bigint): void {
this.request(8); this.request(8);
this.buffer.setBigInt64(this.index, value); this.buffer.setBigInt64(this.index, value, true);
this.index += 8; this.index += 8;
} }
@ -83,7 +83,7 @@ export class BinaryWriter {
writeUInt64(value: bigint): void { writeUInt64(value: bigint): void {
this.request(8); this.request(8);
this.buffer.setBigUint64(this.index, value); this.buffer.setBigUint64(this.index, value, true);
this.index += 8; this.index += 8;
} }
@ -91,7 +91,7 @@ export class BinaryWriter {
writeFloat32(value: number): void { writeFloat32(value: number): void {
this.request(4); this.request(4);
this.buffer.setFloat32(this.index, value); this.buffer.setFloat32(this.index, value, true);
this.index += 4; this.index += 4;
} }
@ -99,7 +99,7 @@ export class BinaryWriter {
writeFloat64(value: number): void { writeFloat64(value: number): void {
this.request(8); this.request(8);
this.buffer.setFloat64(this.index, value); this.buffer.setFloat64(this.index, value, true);
this.index += 8; this.index += 8;
} }
@ -114,17 +114,41 @@ export class BinaryWriter {
this.index += encoded.byteLength; this.index += encoded.byteLength;
} }
writeArray(array: WriteMethod<BinaryWriter>[]): void { writePrefixedString(string: string): void {
const encoder = new TextEncoder();
const encoded = encoder.encode(string);
this.writeUInt32(encoded.byteLength);
this.request(encoded.byteLength);
new Uint8Array(this.buffer.buffer).set(encoded, this.index);
this.index += encoded.byteLength;
}
writeFixedString(size: number, string: string): void {
this.request(size);
const encoder = new TextEncoder();
encoder.encodeInto(string, new Uint8Array(this.buffer.buffer, this.index, size));
this.index += size;
}
writeArray<T>(array: T[], method: WriteMethod<T, BinaryWriter>): void {
for (let i = 0; i < array.length; i++) { for (let i = 0; i < array.length; i++) {
this.write(array[i]); this.write(array[i], method);
} }
} }
write(writeMethod: WriteMethod<BinaryWriter>): void { write<T>(value: T, writeMethod: WriteMethod<T, BinaryWriter>): void {
if ("serialize" in writeMethod) { if ("serialize" in writeMethod) {
writeMethod.serialize(this); writeMethod.serialize(this, value);
return; return;
} }
writeMethod(this); writeMethod(this, value);
}
toBytes(): Uint8Array {
return new Uint8Array(this.buffer.buffer.slice(this.buffer.byteOffset, this.buffer.byteOffset + this.index));
} }
} }

View file

@ -1,49 +1,213 @@
import Peer from "peerjs"; import { v4 } from "uuid";
import { readHello } from "./protocol/hello"; import { readHello } from "./protocol/hello";
import { BinaryStreamReader } from "./binary/readerStream"; import { BinaryStreamReader } from "./binary/readerStream";
import { ReadKind, readEvent } from "./protocol/serverEvent";
import { AudioManager } from "./audio.svelte";
import { type WriteEvent, writeEvent, WriteKind } from "./protocol/clientEvent";
import { BinaryWriter } from "./binary/writer";
export class Client { export class Client {
static async connect() { static async connect(audioManager: AudioManager, outputAudio: HTMLDivElement) {
const transport = new WebTransport(`https://${location.hostname}:4433`, { allowPooling: false }); const transport = new WebTransport(`https://${location.hostname}:4433`, { allowPooling: false });
await transport.ready; await transport.ready;
console.log("ready!");
const stream = await transport.createBidirectionalStream(); const stream = await transport.createBidirectionalStream();
const peer = new Peer(); const uuid = v4();
await new Promise((res) => { const writer = stream.writable.getWriter();
peer.on("open", () => { await writer.write(new TextEncoder().encode(uuid));
console.log("open!", peer.id)
res(void 0);
});
});
await stream.writable.getWriter().write(new TextEncoder().encode(peer.id));
const reader = BinaryStreamReader.fromReader(stream.readable.getReader()); const reader = BinaryStreamReader.fromReader(stream.readable.getReader());
const players = $state(await readHello(reader)); const { players, peers } = await readHello(reader);
console.log("done reading hello");
return new Client(players, peer, transport, stream); const playersBind = $state(players);
const client = new Client(audioManager, outputAudio, playersBind, uuid, transport, stream, writer);
for (const id of peers) {
client.getPeer(id).offer();
}
// peer.on("call", (conn) => {
// conn.answer(audioManager.output);
// client.peers[conn.connectionId] = new Peer(conn.connectionId, outputAudio, conn)
// });
console.log("done setup, calling handlers");
client.datagramEventHandler();
client.streamEventHandler(reader);
client.closeHandler();
return client;
} }
private async closeHandler() {
await this.transport.closed;
console.error("closed");
}
private async datagramEventHandler() {
const dgramReader = this.transport.datagrams.readable.getReader();
this.eventHandler(async () => {
const dgram = await dgramReader.read();
console.log(dgram);
throw dgram;
})
}
private getPeer(id: string) {
if (this.peers[id] !== undefined) {
return this.peers[id];
}
return this.peers[id] = new Peer(id, this);
}
async writeEvent(event: WriteEvent) {
const binaryWriter = new BinaryWriter(0x100);
binaryWriter.write(event, writeEvent);
await this.writer.write(binaryWriter.toBytes());
}
private async streamEventHandler(reader: BinaryStreamReader) {
this.eventHandler(() => readEvent(reader))
}
private async eventHandler(eventSource: () => ReturnType<typeof readEvent>) {
while (true) {
const event = await eventSource();
console.log("got event", event);
switch (event.kind) {
case ReadKind.Connected: {
console.log("hello", event.name);
this.players[event.id] = new Player(event.name, event.id)
break;
}
case ReadKind.Disconnected: {
console.log("goodbye", event.id);
delete this.players[event.id];
break;
}
case ReadKind.Moved: {
if (this.players[event.id] !== undefined)
this.players[event.id].position = event.position;
break;
}
case ReadKind.StageChanged: {
console.log("goodbye", event.id);
break;
}
case ReadKind.PeerConnectionChanged: {
console.log("peer changed", event.id, event.connected);
// if (event.connected) {
// this.getPeer(event.id)
// }
break;
}
case ReadKind.Offer: {
this.getPeer(event.id).gotOffer(event.offerSdp);
break;
}
case ReadKind.Answer: {
this.getPeer(event.id).gotAnswer(event.answerSdp);
break;
}
case ReadKind.IceCandidate: {
this.getPeer(event.id).gotCandidate(event.candidate);
break;
}
}
}
}
private peers: Record<string, Peer> = $state({});
private constructor( private constructor(
public players: Player[], public audioManager: AudioManager,
private peer: Peer, public outputAudio: HTMLDivElement,
private transport: WebTransport, public players: Record<number, Player>,
private stream: WebTransportBidirectionalStream public uuid: string,
public transport: WebTransport,
public stream: WebTransportBidirectionalStream,
public writer: WritableStreamDefaultWriter,
) { ) {
}
get id() {
return this.peer.id;
} }
closed() { closed() {
// return transport.closed; return this.transport.closed;
} }
} }
export class Player { export class Player {
constructor(public name: string, public id: number, public position: number[], public stage: string) { } constructor(public name: string, public id: number, public position: number[] = [0, 0, 0], public stage: string = "nostage" + Math.random()) { }
}
export class Peer {
private connection: RTCPeerConnection;
private audel: HTMLAudioElement;
private state: string = $state("not connected");
constructor(public peerId: string, public client: Client) {
this.connection = new RTCPeerConnection({
iceServers: [
{
urls: ["stun:stun.l.google.com:19302"],
}
]
});
for (const track of this.client.audioManager.output.getAudioTracks()) {
this.connection.addTrack(track, this.client.audioManager.output);
}
this.connection.addEventListener("iceconnectionstatechange", (state) => {
console.log(this.connection.iceConnectionState);
this.state = this.connection.iceConnectionState;
});
this.connection.addEventListener("icecandidate", (event) => {
if (event.candidate)
this.client.writeEvent({ kind: WriteKind.IceCandidate, to: this.peerId, candidate: event.candidate });
else
console.log(this.peerId, "finished candidate gathering")
});
this.audel = document.createElement("audio");
this.connection.addEventListener("track", (ev) => {
this.audel.srcObject = ev.streams[0];
this.audel.autoplay = true;
this.audel.controls = true;
this.client.outputAudio.appendChild(this.audel);
console.log('got track :)');
});
}
public async offer() {
console.log('offering!');
const offer = await this.connection.createOffer({ offerToReceiveAudio: true });
await this.connection.setLocalDescription(offer);
this.client.writeEvent({
kind: WriteKind.Offer,
to: this.peerId,
offerSdp: offer.sdp ?? throwExpr("it didn't create an offer???"),
});
}
public async gotOffer(offer: string) {
console.log('got offer!');
await this.connection.setRemoteDescription(new RTCSessionDescription({ type: "offer", sdp: offer }));
const answer = await this.connection.createAnswer({ offerToReceiveAudio: true });
await this.connection.setLocalDescription(answer);
this.client.writeEvent({
kind: WriteKind.Answer,
to: this.peerId,
answerSdp: answer.sdp ?? throwExpr("it didn't create an offer???"),
});
}
public async gotAnswer(answer: string) {
console.log('got answer!');
await this.connection.setRemoteDescription(new RTCSessionDescription({ type: "answer", sdp: answer }));
}
public async gotCandidate(candidate: RTCIceCandidateInit) {
console.log('got candidate...');
await this.connection.addIceCandidate(candidate)
}
}
function throwExpr(...args: Parameters<ErrorConstructor>): never {
throw new Error(...args);
} }

View file

@ -0,0 +1,30 @@
import { BinaryWriter } from "../binary/writer";
export enum WriteKind {
Offer,
Answer,
IceCandidate,
}
export type WriteEvent = {
kind: WriteKind.IceCandidate,
to: string,
candidate: RTCIceCandidate
} | {
kind: WriteKind.Offer,
to: string,
offerSdp: string
} | {
kind: WriteKind.Answer,
to: string,
answerSdp: string
}
export function writeEvent(writer: BinaryWriter, event: WriteEvent) {
writer.writeUInt8(event.kind);
writer.writeFixedString(36, event.to);
switch (event.kind) {
case WriteKind.Offer: writer.writePrefixedString(event.offerSdp); return;
case WriteKind.Answer: writer.writePrefixedString(event.answerSdp); return;
case WriteKind.IceCandidate: writer.writePrefixedString(JSON.stringify(event.candidate.toJSON())); return;
default: throw new Error("kind not good");
}
}

View file

@ -2,23 +2,24 @@ import { BinaryStreamReader } from "../binary/readerStream";
import { Player } from "../client.svelte"; import { Player } from "../client.svelte";
export async function readHello(reader: BinaryStreamReader) { export async function readHello(reader: BinaryStreamReader) {
console.log("reading");
const count = await reader.readUInt8(); const count = await reader.readUInt8();
console.log("reading", count); const players: Record<number, Player> = {};
const players = await reader.readArray(count, async (reader) => { for (let i = 0; i < count; i++) {
const name = await reader.readFixedString(0x20);
console.log(name);
const id = await reader.readUInt32(); const id = await reader.readUInt32();
const position = await reader.readArray(3, async reader => await reader.readFloat32()); const name = await reader.readFixedString(0x20);
const stage = await reader.readFixedString(0x40); const stage = await reader.readFixedString(0x40);
const position = await reader.readArray(3, async reader => await reader.readFloat32());
return new Player( players[id] = new Player(
name, name,
id, id,
position, position,
stage, stage,
); );
};
const peers = await reader.readArray(await reader.readUInt32(), async (reader) => {
return await reader.readFixedString(36);
}); });
return players; return { players, peers };
} }

View file

@ -0,0 +1,59 @@
import { BinaryStreamReader } from "../binary/readerStream";
export enum ReadKind {
Connected = 0,
Disconnected = 1,
Moved = 2,
StageChanged = 3,
PeerConnectionChanged = 4,
Offer = 5,
Answer = 6,
IceCandidate = 7,
}
export async function readEvent(reader: BinaryStreamReader) {
const kind = await reader.readUInt8();
switch (kind) {
case ReadKind.Connected: {
const id = await reader.readUInt32();
const name = await reader.readFixedString(0x20);
return { kind, id, name };
}
case ReadKind.Disconnected: {
const id = await reader.readUInt32();
return { kind, id };
}
case ReadKind.Moved: {
const id = await reader.readUInt32();
const position = await reader.readArray(3, async reader => reader.readFloat32());
return { kind, id, position };
}
case ReadKind.StageChanged: {
const id = await reader.readUInt32();
const stage = await reader.readFixedString(0x40);
return { kind, id, stage };
}
case ReadKind.PeerConnectionChanged: {
const id = await reader.readFixedString(36);
const connected = await reader.readBoolean();
return { kind, id, connected };
}
case ReadKind.Offer: {
const id = await reader.readFixedString(36);
const offerSdp = await reader.readString(await reader.readUInt32());
return { kind, id, offerSdp }
}
case ReadKind.Answer: {
const id = await reader.readFixedString(36);
const answerSdp = await reader.readString(await reader.readUInt32());
return { kind, id, answerSdp }
}
case ReadKind.IceCandidate: {
const id = await reader.readFixedString(36);
const candidate: RTCIceCandidateInit = JSON.parse(await reader.readString(await reader.readUInt32()));
return { kind, id, candidate }
}
default:
throw new Error(`unsupported kind ${kind}`)
}
}

View file

@ -1,33 +1,76 @@
<script lang="ts"> <script lang="ts">
import { onMount } from "svelte"; import { onMount } from "svelte";
import { Client } from "$lib/client.svelte.ts"; import { Client } from "$lib/client.svelte.ts";
import { AudioManager } from "$lib/audio.svelte.ts";
import { BinaryStreamReader } from "$lib/binary/readerStream"; import { BinaryStreamReader } from "$lib/binary/readerStream";
let name = $state("none"); let name = $state("");
let client: Client = undefined as any; let useMusic = $state(false);
let players = $derived(client?.players ?? []); let client: Client = $state(undefined as any);
let players = $derived.by(() => {
if (client === undefined) return {};
return client.players;
});
let ac: AudioManager;
let wyrm: HTMLAudioElement;
let outputAudio: HTMLDivElement;
async function a() { async function a() {
client = await Client.connect(); name = localStorage.getItem("name") ?? "";
useMusic = localStorage.getItem("useMusic") == "true";
}
async function audioPrep() {
let media = useMusic
? wyrm
: await navigator.mediaDevices.getUserMedia({
audio: true,
video: false,
});
ac = new AudioManager(media);
}
async function connect() {
await audioPrep();
client = await Client.connect(ac, outputAudio);
console.log(client); console.log(client);
} }
onMount(a); onMount(a);
</script> </script>
<div bind:this={outputAudio} />
<div> <div>
I am <input type="text" maxlength="32" list="connectedPlayers" /> I am <input
</div> type="text"
<datalist> maxlength="32"
{#each players as player} list="connectedPlayers"
<option>{player.name}</option> bind:value={name}
{/each} onchange={() => localStorage.setItem("name", name)}
</datalist> />
<div> <label for="useMusic">Use Music</label>
Connected players - {players.length}<br /> <input
{#each players as player} id="useMusic"
<pre class="name">{player.name}</pre> type="checkbox"
{/each} bind:checked={useMusic}
onchange={() => localStorage.setItem("useMusic", useMusic)}
/>
<button onclick={() => connect()}>Connect</button>
<audio bind:this={wyrm} src="/wyrmjewelbox.wav" autoplay={false} controls
>audio of the wyrm's houseki box</audio
>
</div> </div>
{#if client !== undefined}
<datalist>
{#each players as player}
<option>{player.name}</option>
{/each}
</datalist>
<div>
Connected players - {players.length}<br />
{#each players as player}
<pre class="name">{player.name}</pre>
{/each}
</div>
{/if}
<style> <style>
.name { .name {

BIN
static/wyrmjewelbox.wav Executable file

Binary file not shown.