UNPKG

@tf2pickup-org/mumble-client

Version:
217 lines 7.68 kB
import { Subject } from 'rxjs'; import { packetForType, packetType } from './packet-type-registry.js'; import { UDPTunnel } from '@tf2pickup-org/mumble-protocol'; import { readVarint } from './read-varint.js'; import { writeVarint } from './write-varint.js'; export var AudioCodec; (function (AudioCodec) { AudioCodec[AudioCodec["CELTAlpha"] = 0] = "CELTAlpha"; AudioCodec[AudioCodec["Ping"] = 1] = "Ping"; AudioCodec[AudioCodec["Speex"] = 2] = "Speex"; AudioCodec[AudioCodec["CELTBeta"] = 3] = "CELTBeta"; AudioCodec[AudioCodec["Opus"] = 4] = "Opus"; })(AudioCodec || (AudioCodec = {})); export class MumbleSocket { socket; _packet = new Subject(); _audioPacket = new Subject(); _fullAudioPacket = new Subject(); buffers = []; length = 0; readers = []; audioSequence = 0; constructor(socket) { this.socket = socket; this.socket.on('data', (data) => { this.receiveData(data); }); this.readPrefix(); } get packet() { return this._packet.asObservable(); } get audioPacket() { return this._audioPacket.asObservable(); } get fullAudioPacket() { return this._fullAudioPacket.asObservable(); } read(length, callback) { this.readers.push({ length, callback }); if (this.readers.length === 1) { this.flushReaders(); } } async send(message, payload) { const typeNumber = packetType(message); if (typeNumber === undefined) { throw new Error(`unknown message type (${message.typeName})`); } const encoded = message.toBinary(payload); const prefix = Buffer.alloc(6); prefix.writeUint16BE(typeNumber, 0); prefix.writeUint32BE(encoded.length, 2); await this.write(Buffer.concat([prefix, encoded])); } write(buffer) { return new Promise((resolve, reject) => { if (this.socket.writable) { this.socket.write(buffer, err => { if (err) { reject(err); } else { resolve(); } }); } else { reject(new Error('socket not writable')); } }); } end() { this.socket.end(); } async sendAudio({ data, codec = AudioCodec.Opus, target = 0, isTerminator = false, }) { const header = Buffer.alloc(1); header[0] = ((codec & 0b111) << 5) | (target & 0b00011111); const sequence = writeVarint(this.audioSequence++); let frameHeader; if (codec === AudioCodec.Opus) { const frameHeaderValue = (data.length & 0x1fff) | (isTerminator ? 0x2000 : 0); frameHeader = writeVarint(frameHeaderValue); } else { const continuationBit = isTerminator ? 0 : 0x80; frameHeader = Buffer.from([(data.length & 0x7f) | continuationBit]); } const packet = Buffer.concat([header, sequence, frameHeader, data]); const typeNumber = packetType(UDPTunnel); if (typeNumber === undefined) { throw new Error('UDPTunnel packet type not found'); } const prefix = Buffer.alloc(6); prefix.writeUint16BE(typeNumber, 0); prefix.writeUint32BE(packet.length, 2); await this.write(Buffer.concat([prefix, packet])); } receiveData(data) { this.buffers.push(data); this.length += data.length; this.flushReaders(); } flushReaders() { if (this.readers.length === 0) { return; } const reader = this.readers[0]; if (this.length < reader.length) { return; } const buffer = Buffer.alloc(reader.length); let written = 0; while (written < reader.length) { const received = this.buffers[0]; const remaining = reader.length - written; if (received.length <= remaining) { received.copy(buffer, written); written += received.length; this.buffers.splice(0, 1); this.length -= received.length; } else { received.copy(buffer, written, 0, remaining); written += remaining; this.buffers[0] = received.subarray(remaining); this.length -= remaining; } } this.readers.splice(0, 1); reader.callback(buffer); } readPrefix() { this.read(6, prefix => { const type = prefix.readUint16BE(0); const length = prefix.readUint32BE(2); this.readPacket(type, length); }); } readPacket(type, length) { this.read(length, data => { const message = packetForType(type); if (message) { switch (message.typeName) { case UDPTunnel.typeName: this.decodeAudio(data); break; default: this._packet.next({ type, typeName: message.typeName, payload: message.fromBinary(data), }); } } else { console.error(`Unrecognized packet type (${type})`); } this.readPrefix(); }); } decodeAudio(packet) { if (packet.length < 1) { return; } const header = packet[0]; const codec = (header >> 5) & 0b111; const target = header & 0b00011111; if (codec === AudioCodec.Ping) { return; } let offset = 1; const sessionResult = readVarint(packet.subarray(offset)); const source = sessionResult.value; offset += sessionResult.length; this._audioPacket.next({ source }); if (target !== 0) { return; } const seqResult = readVarint(packet.subarray(offset)); const sequence = seqResult.value; offset += seqResult.length; let audioData; let hasTerminator = false; if (codec === AudioCodec.Opus) { const frameHeaderResult = readVarint(packet.subarray(offset)); const frameHeader = frameHeaderResult.value; offset += frameHeaderResult.length; const audioLength = frameHeader & 0x1fff; hasTerminator = (frameHeader & 0x2000) !== 0; if (offset + audioLength > packet.length) { return; } audioData = packet.subarray(offset, offset + audioLength); } else { const frames = []; while (offset < packet.length) { const frameHeader = packet[offset]; offset++; const frameLength = frameHeader & 0x7f; const continuation = (frameHeader & 0x80) !== 0; if (frameLength > 0 && offset + frameLength <= packet.length) { frames.push(packet.subarray(offset, offset + frameLength)); offset += frameLength; } if (!continuation) { hasTerminator = true; break; } } audioData = Buffer.concat(frames); } this._fullAudioPacket.next({ source, target, codec, sequence, audioData, hasTerminator }); } } //# sourceMappingURL=mumble-socket.js.map