import { createRenderer } from '@/utils'; import type { YoutubePlayer } from '@/types/youtube-player'; import type { RendererContext } from '@/types/contexts'; import type { CustomOutputPluginConfig } from './index'; const updateDeviceList = async ( context: RendererContext, ) => { const newDevices: Record = {}; const devices = await navigator.mediaDevices .enumerateDevices() .then((devices) => devices.filter((device) => device.kind === 'audiooutput'), ); for (const device of devices) { newDevices[device.deviceId] = device.label; } const options = await context.getConfig(); options.devices = newDevices; context.setConfig(options); }; const updateSinkId = async ( audioContext?: AudioContext & { setSinkId?: (sinkId: string) => Promise; }, sinkId?: string, ) => { if (!audioContext || !sinkId) return; if (!('setSinkId' in audioContext)) return; if (typeof audioContext.setSinkId === 'function') { await audioContext.setSinkId(sinkId); } }; export const renderer = createRenderer< { options?: CustomOutputPluginConfig; audioContext?: AudioContext; audioCanPlayHandler: (event: CustomEvent) => Promise; }, CustomOutputPluginConfig >({ async audioCanPlayHandler({ detail: { audioContext } }) { this.audioContext = audioContext; await updateSinkId(audioContext, this.options!.output); }, async onPlayerApiReady(_: YoutubePlayer, context) { this.options = await context.getConfig(); await navigator.mediaDevices.getUserMedia({ audio: true, video: false }); navigator.mediaDevices.ondevicechange = async () => await updateDeviceList(context); document.addEventListener('ytmd:audio-can-play', this.audioCanPlayHandler, { once: true, passive: true, }); await updateDeviceList(context); }, stop() { document.removeEventListener( 'ytmd:audio-can-play', this.audioCanPlayHandler, ); navigator.mediaDevices.ondevicechange = null; }, async onConfigChange(config) { this.options = config; await updateSinkId(this.audioContext, config.output); }, });