import * as native from "tc-native/connection"; import {AudioBackend, OutputDevice} from "tc-shared/audio/Player"; //FIXME: Native audio initialize handle! export interface Device { device_id: string; name: string; } let _initialized_callbacks: (() => any)[] = []; export let _initialized = false; export let _initializing = false; export let _current_device: native.audio.AudioDevice; export function initialized() : boolean { return _initialized; } export function on_ready(cb: () => any) { if(_initialized) cb(); else _initialized_callbacks.push(cb); } export function initialize() { if(_initializing) return; _initializing = true; native.audio.initialize(() => { _initialized = true; for(const callback of _initialized_callbacks) callback(); _initialized_callbacks = []; }); return true; } export async function available_devices() : Promise { return native.audio.available_devices().filter(e => e.output_supported || e.output_default); } export async function set_device(device_id?: string) : Promise { const dev = native.audio.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 { native.audio.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 = native.audio.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 native.audio.playback.get_master_volume(); } export function set_master_volume(volume: number) { native.audio.playback.set_master_volume(volume); } export class NativeAudioPlayer implements AudioBackend { private readonly audioContext: AudioContext; private initializedPromises: (() => void)[]; private currentDevice: native.audio.AudioDevice; constructor() { this.audioContext = new AudioContext(); this.initializedPromises = []; native.audio.initialize(() => { const promises = this.initializedPromises; this.initializedPromises = undefined; promises.forEach(callback => callback()); }); } executeWhenInitialized(callback: () => void): any { if(this.initializedPromises) { this.initializedPromises.push(callback); } else { callback(); } } getMasterVolume(): number { return native.audio.playback.get_master_volume(); } setMasterVolume(volume: number): any { native.audio.playback.set_master_volume(volume); } getAudioContext(): AudioContext | undefined { return this.audioContext; } async getAvailableDevices(): Promise { return native.audio.available_devices().filter(e => e.output_supported || e.output_default).map(entry => ({ device_id: entry.device_id, driver: entry.driver, name: entry.name })); } getCurrentDevice(): OutputDevice { if(this.currentDevice) { return this.currentDevice; } const defaultDevice = native.audio.available_devices().find(entry => entry.output_default); if(defaultDevice) { return { name: defaultDevice.name, driver: defaultDevice.driver, device_id: defaultDevice.device_id }; } return { name: "Default device", device_id: "default", driver: "default driver" }; } async setCurrentDevice(targetId: string | undefined): Promise { const device = native.audio.available_devices().find(e => e.device_id == targetId); if(!device) { console.warn("Missing audio device with is %s", targetId); throw "invalid device id"; } try { native.audio.playback.set_device(device.device_id); } catch(error) { if(error instanceof Error) { throw error.message; } throw error; } this.currentDevice = device; } getDefaultDeviceId(): string { return native.audio.available_devices().find(entry => entry.output_default).device_id; } isDeviceRefreshAvailable(): boolean { return false; } isInitialized(): boolean { return !this.initializedPromises; } refreshDevices(): Promise { return Promise.resolve(undefined); } }