sounds, better files

This commit is contained in:
2024-08-18 19:11:41 +00:00
parent 9ed2f70701
commit 08489ed67b
86 changed files with 4226 additions and 86 deletions

View File

@@ -0,0 +1,172 @@
import { VirtualFile } from "../../FileSystem/types";
import { RemoteDeviceManagerCtx } from "../RemoteDeviceManager";
import { JsonChannelCaster, JsonChannelSummon, Message, MessageEmitter } from "./type";
export class WebRtcChannelCaster extends MessageEmitter implements JsonChannelCaster {
private peer = new RTCPeerConnection()
private offerSent = false;
private dataChannel = this.peer.createDataChannel('json');
public readonly sink: MediaStreamAudioDestinationNode;
constructor(
private channel: JsonChannelCaster,
{onOpen}:Partial<{onOpen: Function}> = {},
private ctx: RemoteDeviceManagerCtx
) {
super();
this.sink = ctx.audioContext.createMediaStreamDestination();
this.channel.on(this.onJsonMessage);
this.dataChannel.addEventListener('close', () => {
this.emit({ type: 'disconnected' });
});
this.dataChannel.addEventListener('open', () => {
onOpen?.();
this.emit({ type: 'connected' });
});
this.dataChannel.addEventListener('message', this.onMessage);
this.peer.addTrack(this.sink.stream.getAudioTracks()[0], this.sink.stream);
this.peer.onicecandidate = (event) => {
if (event.candidate) {
this.channel.send({
type: 'RtcIce',
candidate: event.candidate
});
}
}
}
private onMessage = (event: MessageEvent) => {
const data = JSON.parse(event.data) as Message;
this.emit(data);
}
private onJsonMessage = (data: Message) => {
if (data.type === 'summoned') {
if (!this.offerSent) {
this.peer.createOffer().then(offer => {
this.peer.setLocalDescription(offer);
this.channel.send({
type: 'RtcOffer',
offer
});
});
this.offerSent = true;
}
} else if (data.type === 'RtcAnswer') {
this.peer.setRemoteDescription(new RTCSessionDescription(data.answer));
} else if (data.type === 'RtcIce') {
this.peer.addIceCandidate(new RTCIceCandidate(data.candidate));
}
}
async sendBinary(file: VirtualFile, data: Blob) {
const channelName = `file:${Math.random()}`;
this.dataChannel.send(JSON.stringify({
type: 'fileTransfer',
channel: channelName,
size: data.size,
file,
}));
const channel = this.peer.createDataChannel(channelName);
channel.binaryType = 'arraybuffer';
const buffer = await data.arrayBuffer();
const chunkSize = 16384;
for (let i = 0; i < buffer.byteLength; i += chunkSize) {
const chunk = buffer.slice(i, i + chunkSize);
channel.send(chunk);
}
channel.close();
await new Promise((resolve) => channel.addEventListener('close', resolve, { once: true }));
}
send(data: Message) {
this.dataChannel.send(JSON.stringify(data));
}
disconnect() {
super.disconnect();
}
}
export class WebRtcChannelSummon extends MessageEmitter implements JsonChannelSummon {
private peer = new RTCPeerConnection()
private dataChannel?: RTCDataChannel;
private transferedFilesMeta = new Map<string, VirtualFile>();
constructor(private channel: JsonChannelCaster) {
super();
this.channel.on(this.onJsonMessage);
this.peer.onicecandidate = (event) => {
if (event.candidate) {
this.channel.send({
type: 'RtcIce',
candidate: event.candidate
});
}
}
this.peer.ontrack = (event) => {
console.log('ontrack', event);
const audioElement = document.createElement('audio');
audioElement.srcObject = event.streams[0]; // Attach the received stream
audioElement.play();
}
this.peer.ondatachannel = (event) => {
if (event.channel.label === 'json') {
this.dataChannel = event.channel;
this.dataChannel.addEventListener('message', this.onRtcMessage);
this.emit({ type: 'connected' });
} else {
const channel = event.channel;
const chunks:ArrayBuffer[] = [];
channel.addEventListener('message', (event: MessageEvent<ArrayBuffer>) => {
console.log('>', channel.label, event.data);
chunks.push(event.data);
});
channel.addEventListener('close', () => {
console.log('>', channel.label, 'closed');
const meta = this.transferedFilesMeta.get(channel.label)!;
// TODO: handle data before meta?
const blob = new Blob(chunks, { type: meta?.mimeType ?? 'application/octet-stream' });
console.log(meta, blob);
this.emit({
type: 'fileTransfered',
meta,
blob,
})
});
}
}
}
private onRtcMessage = (event: MessageEvent) => {
const msg = JSON.parse(event.data) as Message;
if (msg.type === 'fileTransfer') {
this.transferedFilesMeta.set(msg.channel, msg.file);
} else {
this.emit(msg);
}
};
private onJsonMessage = (data: Message) => {
if (data.type === 'RtcOffer') {
this.peer.setRemoteDescription(new RTCSessionDescription(data.offer));
this.peer.createAnswer().then(answer => {
this.peer.setLocalDescription(answer);
this.channel.send({
type: 'RtcAnswer',
answer
});
});
} else if (data.type === 'RtcIce') {
this.peer.addIceCandidate(new RTCIceCandidate(data.candidate));
}
}
send(data: Message) {
this.dataChannel?.send(JSON.stringify(data));
}
}