102 lines
3.3 KiB
JavaScript
102 lines
3.3 KiB
JavaScript
const atob = globalThis.atob;
|
|
const btoa = globalThis.btoa;
|
|
|
|
/**
|
|
* Basic utilities for the RealtimeAPI
|
|
* @class
|
|
*/
|
|
export class RealtimeUtils {
|
|
/**
|
|
* Converts Float32Array of amplitude data to ArrayBuffer in Int16Array format
|
|
* @param {Float32Array} float32Array
|
|
* @returns {ArrayBuffer}
|
|
*/
|
|
static floatTo16BitPCM(float32Array) {
|
|
const buffer = new ArrayBuffer(float32Array.length * 2);
|
|
const view = new DataView(buffer);
|
|
let offset = 0;
|
|
for (let i = 0; i < float32Array.length; i++, offset += 2) {
|
|
let s = Math.max(-1, Math.min(1, float32Array[i]));
|
|
view.setInt16(offset, s < 0 ? s * 0x8000 : s * 0x7fff, true);
|
|
}
|
|
return buffer;
|
|
}
|
|
|
|
/**
|
|
* Converts a base64 string to an ArrayBuffer
|
|
* @param {string} base64
|
|
* @returns {ArrayBuffer}
|
|
*/
|
|
static base64ToArrayBuffer(base64) {
|
|
const binaryString = atob(base64);
|
|
const len = binaryString.length;
|
|
const bytes = new Uint8Array(len);
|
|
for (let i = 0; i < len; i++) {
|
|
bytes[i] = binaryString.charCodeAt(i);
|
|
}
|
|
return bytes.buffer;
|
|
}
|
|
|
|
/**
|
|
* Converts an ArrayBuffer, Int16Array or Float32Array to a base64 string
|
|
* @param {ArrayBuffer|Int16Array|Float32Array} arrayBuffer
|
|
* @returns {string}
|
|
*/
|
|
static arrayBufferToBase64(arrayBuffer) {
|
|
if (arrayBuffer instanceof Float32Array) {
|
|
arrayBuffer = this.floatTo16BitPCM(arrayBuffer);
|
|
} else if (arrayBuffer instanceof Int16Array) {
|
|
arrayBuffer = arrayBuffer.buffer;
|
|
}
|
|
let binary = '';
|
|
let bytes = new Uint8Array(arrayBuffer);
|
|
const chunkSize = 0x8000; // 32KB chunk size
|
|
for (let i = 0; i < bytes.length; i += chunkSize) {
|
|
let chunk = bytes.subarray(i, i + chunkSize);
|
|
binary += String.fromCharCode.apply(null, chunk);
|
|
}
|
|
return btoa(binary);
|
|
}
|
|
|
|
/**
|
|
* Merge two Int16Arrays from Int16Arrays or ArrayBuffers
|
|
* @param {ArrayBuffer|Int16Array} left
|
|
* @param {ArrayBuffer|Int16Array} right
|
|
* @returns {Int16Array}
|
|
*/
|
|
static mergeInt16Arrays(left, right) {
|
|
if (left instanceof ArrayBuffer) {
|
|
left = new Int16Array(left);
|
|
}
|
|
if (right instanceof ArrayBuffer) {
|
|
right = new Int16Array(right);
|
|
}
|
|
if (!(left instanceof Int16Array) || !(right instanceof Int16Array)) {
|
|
throw new Error(`Both items must be Int16Array`);
|
|
}
|
|
const newValues = new Int16Array(left.length + right.length);
|
|
for (let i = 0; i < left.length; i++) {
|
|
newValues[i] = left[i];
|
|
}
|
|
for (let j = 0; j < right.length; j++) {
|
|
newValues[left.length + j] = right[j];
|
|
}
|
|
return newValues;
|
|
}
|
|
|
|
/**
|
|
* Generates an id to send with events and messages
|
|
* @param {string} prefix
|
|
* @param {number} [length]
|
|
* @returns {string}
|
|
*/
|
|
static generateId(prefix, length = 21) {
|
|
// base58; non-repeating chars
|
|
const chars = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz';
|
|
const str = Array(length - prefix.length)
|
|
.fill(0)
|
|
.map((_) => chars[Math.floor(Math.random() * chars.length)])
|
|
.join('');
|
|
return `${prefix}${str}`;
|
|
}
|
|
}
|