121 lines
4.2 KiB
TypeScript
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.08);
|
|
_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, _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"] = {}), audio); |