TeaSpeak-Client/modules/renderer/audio/AudioPlayer.ts

121 lines
4.2 KiB
TypeScript

window["require_setup"](module);
import {audio as naudio} from "teaclient_connection";
namespace audio.player {
export interface Device {
device_id: string;
name: string;
}
interface Navigator {
mozGetUserMedia(constraints: MediaStreamConstraints, successCallback: NavigatorUserMediaSuccessCallback, errorCallback: NavigatorUserMediaErrorCallback): void;
webkitGetUserMedia(constraints: MediaStreamConstraints, successCallback: NavigatorUserMediaSuccessCallback, errorCallback: NavigatorUserMediaErrorCallback): void;
}
let _initialized_callbacks: (() => any)[] = [];
export let _initialized = false;
export let _audioContext: AudioContext;
export let _processor: ScriptProcessorNode;
export let _output_stream: naudio.playback.OwnedAudioOutputStream;
export let _current_device: naudio.AudioDevice;
export function initialized() : boolean {
return _initialized;
}
export function context() : AudioContext {
if(!_audioContext) throw "Initialize first!";
return _audioContext;
}
export function destination() : AudioNode {
if(!_initialized)
throw "Audio player hasn't yet be initialized";
return _processor || _audioContext.destination;
}
export function on_ready(cb: () => any) {
if(_initialized)
cb();
else
_initialized_callbacks.push(cb);
}
export function initialize() {
_output_stream = naudio.playback.create_stream();
_output_stream.set_buffer_max_latency(0.4);
_output_stream.set_buffer_latency(0.02);
_output_stream.callback_overflow = () => {
console.warn("Main audio overflow");
_output_stream.clear();
};
_output_stream.callback_underflow = () => {
console.warn("Main audio underflow");
};
_audioContext = new AudioContext();
_processor = _audioContext.createScriptProcessor(1024 * 8, _output_stream.channels, _output_stream.channels);
_processor.onaudioprocess = function(event) {
const buffer = event.inputBuffer;
//console.log("Received %d channels of %d with a rate of %d", buffer.numberOfChannels, buffer.length, buffer.sampleRate);
const target_buffer = new Float32Array(buffer.numberOfChannels * buffer.length);
for(let channel = 0; channel < buffer.numberOfChannels; channel++) {
const channel_data = buffer.getChannelData(channel);
target_buffer.set(channel_data, channel * buffer.length);
}
_output_stream.write_data_rated(target_buffer.buffer, false, buffer.sampleRate);
};
_processor.connect(_audioContext.destination);
_initialized = true;
for(const callback of _initialized_callbacks)
callback();
_initialized_callbacks = [];
return true;
}
export async function available_devices() : Promise<Device[]> {
return naudio.available_devices().filter(e => e.output_supported || e.output_default).map(e => {
return {
device_id: e.device_id,
name: e.name
}
});
}
export async function set_device(device_id?: string) : Promise<void> {
const dev = naudio.available_devices().filter(e => e.device_id == device_id);
if(dev.length == 0) {
console.warn("Missing audio device with is %s", device_id)
throw "invalid device id";
}
await naudio.playback.set_device(dev[0].device_index);
_current_device = dev[0];
}
export function current_device() : Device {
if(_current_device)
return _current_device;
const dev = naudio.available_devices().filter(e => e.output_default);
if(dev.length > 0)
return dev[0];
return {device_id: "default", name: "default"} as Device;
}
export function get_master_volume() : number {
return naudio.playback.get_master_volume();
}
export function set_master_volume(volume: number) {
naudio.playback.set_master_volume(volume);
}
}
Object.assign(window["audio"] || (window["audio"] = {} as any), audio);