window["require_setup"](module); import {audio as naudio} from "teaclient_connection"; namespace audio.player { //FIXME: Native audio initialize handle! 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 _initializing = 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() { if(_initializing) return; _initializing = true; naudio.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 { return naudio.available_devices().filter(e => e.output_supported || e.output_default); } export async function set_device(device_id?: string) : Promise { 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"; } try { naudio.playback.set_device(dev[0].device_id); } catch(error) { if(error instanceof Error) throw error.message; throw error; } _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);