UNPKG

desktop-audio-proxy

Version:

A comprehensive audio streaming solution for Tauri and Electron apps that bypasses CORS and WebKit codec issues

1 lines 59.7 kB
{"version":3,"file":"server.cjs","sources":["../src/client.ts","../src/tauri-service.ts","../src/electron-service.ts","../src/server-impl.ts"],"sourcesContent":["import { AudioProxyOptions, StreamInfo, Environment } from './types';\n\n// Type declarations for window objects\ndeclare global {\n interface Window {\n __TAURI__?: {\n tauri: {\n convertFileSrc: (_filePath: string) => string;\n invoke?: (\n _command: string,\n _args?: Record<string, unknown>\n ) => Promise<unknown>;\n };\n };\n electronAPI?: unknown;\n }\n}\n\ninterface ProcessVersions {\n electron?: string;\n [key: string]: string | undefined;\n}\n\ndeclare const process:\n | {\n versions?: ProcessVersions;\n }\n | undefined;\n\nexport class AudioProxyClient {\n private options: Required<AudioProxyOptions>;\n private environment: Environment;\n\n constructor(options: AudioProxyOptions = {}) {\n this.options = {\n proxyUrl: options.proxyUrl || 'http://localhost:3002',\n autoDetect: options.autoDetect ?? true,\n fallbackToOriginal: options.fallbackToOriginal ?? true,\n retryAttempts: options.retryAttempts || 3,\n retryDelay: options.retryDelay || 1000,\n proxyConfig: options.proxyConfig || {},\n };\n\n this.environment = this.detectEnvironment();\n }\n\n private detectEnvironment(): Environment {\n if (typeof window === 'undefined') {\n return 'unknown';\n }\n\n if (window.__TAURI__) {\n return 'tauri';\n }\n\n if (\n window.electronAPI ||\n (typeof process !== 'undefined' &&\n process?.versions &&\n process.versions.electron)\n ) {\n return 'electron';\n }\n\n return 'web';\n }\n\n public getEnvironment(): Environment {\n return this.environment;\n }\n\n public async isProxyAvailable(): Promise<boolean> {\n try {\n const controller = new AbortController();\n const timeoutId = setTimeout(() => controller.abort(), 5000);\n\n const response = await fetch(`${this.options.proxyUrl}/health`, {\n signal: controller.signal,\n method: 'GET',\n cache: 'no-cache',\n });\n\n clearTimeout(timeoutId);\n\n if (response.ok) {\n const data = await response.json();\n console.log('[AudioProxyClient] Proxy server available:', data);\n return true;\n }\n return false;\n } catch (error: unknown) {\n const errorMessage =\n error instanceof Error ? error.message : 'Unknown error';\n console.warn(\n '[AudioProxyClient] Proxy server unavailable:',\n errorMessage\n );\n return false;\n }\n }\n\n public async canPlayUrl(url: string): Promise<StreamInfo> {\n console.log('[AudioProxyClient] Processing URL:', url);\n\n // Check if it's a local file\n if (this.isLocalFile(url)) {\n console.log('[AudioProxyClient] Using local file handler');\n return {\n url,\n status: 200,\n headers: {},\n canPlay: true,\n requiresProxy: false,\n };\n }\n\n // Check if proxy is available\n const proxyAvailable = await this.isProxyAvailable();\n\n if (proxyAvailable) {\n try {\n const infoUrl = `${this.options.proxyUrl}/info?url=${encodeURIComponent(url)}`;\n const response = await fetch(infoUrl);\n\n if (response.ok) {\n const data = await response.json();\n const streamInfo: StreamInfo = {\n url: data.url,\n status: data.status,\n headers: data.headers || {},\n canPlay: true,\n requiresProxy: true,\n contentType: data.contentType,\n contentLength: data.contentLength,\n acceptRanges: data.acceptRanges,\n lastModified: data.lastModified,\n };\n console.log('[AudioProxyClient] Stream info:', streamInfo);\n return streamInfo;\n }\n } catch (error) {\n console.warn(\n '[AudioProxyClient] Failed to get stream info via proxy:',\n error\n );\n }\n }\n\n // Fallback: assume it needs proxy\n const streamInfo: StreamInfo = {\n url,\n status: 0,\n headers: {},\n canPlay: false,\n requiresProxy: true,\n };\n console.log('[AudioProxyClient] Stream info:', streamInfo);\n return streamInfo;\n }\n\n public async getPlayableUrl(url: string): Promise<string> {\n console.log('[AudioProxyClient] Processing URL:', url);\n\n // Handle local files\n if (this.isLocalFile(url)) {\n console.log('[AudioProxyClient] Using local file handler');\n return this.handleLocalFile(url);\n }\n\n // Check stream info\n const streamInfo = await this.canPlayUrl(url);\n\n if (streamInfo.requiresProxy) {\n console.log(\n '[AudioProxyClient] Proxy required, checking availability...'\n );\n\n // Try proxy with retries\n for (let attempt = 1; attempt <= this.options.retryAttempts; attempt++) {\n const proxyAvailable = await this.isProxyAvailable();\n\n if (proxyAvailable) {\n console.log(\n '[AudioProxyClient] Generated proxy URL:',\n `${this.options.proxyUrl}/proxy?url=${encodeURIComponent(url)}`\n );\n return `${this.options.proxyUrl}/proxy?url=${encodeURIComponent(url)}`;\n }\n\n if (attempt < this.options.retryAttempts) {\n console.log(\n `[AudioProxyClient] Proxy not available on attempt ${attempt}`\n );\n await this.delay(this.options.retryDelay);\n }\n }\n\n // Proxy failed, fallback if enabled\n if (this.options.fallbackToOriginal) {\n console.log(\n '[AudioProxyClient] Falling back to original URL (may have CORS issues)'\n );\n return url;\n } else {\n throw new Error('Proxy server unavailable and fallback disabled');\n }\n }\n\n return url;\n }\n\n private isLocalFile(url: string): boolean {\n return (\n url.startsWith('/') ||\n url.startsWith('./') ||\n url.startsWith('../') ||\n url.startsWith('file://') ||\n url.startsWith('blob:') ||\n url.startsWith('data:') ||\n !!url.match(/^[a-zA-Z]:\\\\/)\n ); // Windows path\n }\n\n private handleLocalFile(url: string): string {\n // Handle data: and blob: URLs directly - no conversion needed\n if (url.startsWith('data:') || url.startsWith('blob:')) {\n return url;\n }\n\n // In Tauri, use convertFileSrc for file:// URLs\n if (this.environment === 'tauri' && window.__TAURI__) {\n try {\n const { convertFileSrc } = window.__TAURI__.tauri;\n if (\n url.startsWith('file://') ||\n url.startsWith('/') ||\n url.match(/^[a-zA-Z]:\\\\/)\n ) {\n return convertFileSrc(url);\n }\n } catch (error) {\n console.warn(\n '[AudioProxyClient] Failed to convert file source with Tauri:',\n error\n );\n // Fallback to original URL if conversion fails\n }\n }\n\n // For other environments or fallback, return as-is\n return url;\n }\n\n private delay(ms: number): Promise<void> {\n return new Promise(resolve => setTimeout(resolve, ms));\n }\n}\n\nexport function createAudioClient(\n options?: AudioProxyOptions\n): AudioProxyClient {\n return new AudioProxyClient(options);\n}\n","import { AudioProxyClient } from './client';\nimport { AudioServiceOptions } from './types';\n\n// Type declarations for Tauri API that extends client.ts declarations\n\nexport class TauriAudioService {\n private audioClient: AudioProxyClient;\n\n constructor(options: AudioServiceOptions = {}) {\n // Use audioOptions if provided, otherwise use the options directly\n const clientOptions = options.audioOptions || options;\n this.audioClient = new AudioProxyClient(clientOptions);\n }\n\n public async getStreamableUrl(url: string): Promise<string> {\n return await this.audioClient.getPlayableUrl(url);\n }\n\n public async canPlayStream(url: string) {\n return await this.audioClient.canPlayUrl(url);\n }\n\n public getEnvironment() {\n return this.audioClient.getEnvironment();\n }\n\n public async isProxyAvailable(): Promise<boolean> {\n return await this.audioClient.isProxyAvailable();\n }\n\n public async checkSystemCodecs(): Promise<{\n supportedFormats: string[];\n missingCodecs: string[];\n capabilities: Record<string, string>;\n }> {\n const audio = new Audio();\n const formats = [\n { name: 'MP3', mime: 'audio/mpeg', codecs: ['mp3'] },\n { name: 'OGG', mime: 'audio/ogg', codecs: ['vorbis', 'opus'] },\n { name: 'WAV', mime: 'audio/wav', codecs: ['pcm'] },\n { name: 'AAC', mime: 'audio/aac', codecs: ['mp4a.40.2'] },\n { name: 'FLAC', mime: 'audio/flac', codecs: ['flac'] },\n { name: 'WEBM', mime: 'audio/webm', codecs: ['vorbis', 'opus'] },\n { name: 'M4A', mime: 'audio/mp4', codecs: ['mp4a.40.2'] },\n ];\n\n const supportedFormats: string[] = [];\n const missingCodecs: string[] = [];\n const capabilities: Record<string, string> = {};\n\n for (const format of formats) {\n let bestSupport = '';\n let isSupported = false;\n\n // Test basic MIME type\n const basicSupport = audio.canPlayType(format.mime);\n capabilities[format.name + '_basic'] = basicSupport;\n\n if (basicSupport === 'probably' || basicSupport === 'maybe') {\n bestSupport = basicSupport;\n isSupported = true;\n }\n\n // Test with codecs\n for (const codec of format.codecs) {\n const codecSupport = audio.canPlayType(\n `${format.mime}; codecs=\"${codec}\"`\n );\n capabilities[format.name + '_' + codec] = codecSupport;\n\n if (codecSupport === 'probably') {\n bestSupport = 'probably';\n isSupported = true;\n } else if (codecSupport === 'maybe' && bestSupport !== 'probably') {\n bestSupport = 'maybe';\n isSupported = true;\n }\n }\n\n capabilities[format.name] = bestSupport;\n\n if (isSupported) {\n supportedFormats.push(format.name);\n } else {\n missingCodecs.push(format.name);\n }\n }\n\n // Check for additional Tauri-specific audio capabilities if available\n if (this.getEnvironment() === 'tauri' && window.__TAURI__?.tauri?.invoke) {\n try {\n const { invoke } = window.__TAURI__.tauri;\n\n // Try to get system audio info from Tauri backend\n const systemAudioInfo = await invoke('get_system_audio_info').catch(\n () => null\n );\n if (systemAudioInfo && typeof systemAudioInfo === 'object') {\n capabilities['tauri_system_info'] = JSON.stringify(systemAudioInfo);\n\n // Enhanced format support based on system capabilities\n const audioInfo = systemAudioInfo as { supportedFormats?: string[] };\n if (audioInfo.supportedFormats) {\n audioInfo.supportedFormats.forEach((format: string) => {\n if (!supportedFormats.includes(format)) {\n supportedFormats.push(format);\n // Remove from missing codecs if it was there\n const missingIndex = missingCodecs.indexOf(format);\n if (missingIndex > -1) {\n missingCodecs.splice(missingIndex, 1);\n }\n }\n });\n }\n }\n } catch (error) {\n console.warn(\n '[TauriAudioService] Could not access Tauri backend for codec detection:',\n error\n );\n }\n }\n\n return { supportedFormats, missingCodecs, capabilities };\n }\n\n // Tauri-specific method to get audio file metadata\n public async getAudioMetadata(filePath: string): Promise<{\n duration?: number;\n bitrate?: number;\n sampleRate?: number;\n channels?: number;\n format?: string;\n } | null> {\n if (this.getEnvironment() !== 'tauri' || !window.__TAURI__?.tauri?.invoke) {\n return null;\n }\n\n try {\n const { invoke } = window.__TAURI__.tauri;\n const result = await invoke('get_audio_metadata', { path: filePath });\n return result as {\n duration?: number;\n bitrate?: number;\n sampleRate?: number;\n channels?: number;\n format?: string;\n } | null;\n } catch (error) {\n console.warn('[TauriAudioService] Failed to get audio metadata:', error);\n return null;\n }\n }\n\n // Tauri-specific method to enumerate audio devices\n public async getAudioDevices(): Promise<{\n inputDevices: Array<{ id: string; name: string }>;\n outputDevices: Array<{ id: string; name: string }>;\n } | null> {\n if (this.getEnvironment() !== 'tauri' || !window.__TAURI__?.tauri?.invoke) {\n return null;\n }\n\n try {\n const { invoke } = window.__TAURI__.tauri;\n const result = await invoke('get_audio_devices');\n return result as {\n inputDevices: Array<{ id: string; name: string }>;\n outputDevices: Array<{ id: string; name: string }>;\n } | null;\n } catch (error) {\n console.warn('[TauriAudioService] Failed to get audio devices:', error);\n return null;\n }\n }\n}\n","import { AudioProxyClient } from './client';\nimport { AudioServiceOptions } from './types';\n\n// Type declarations for Electron API\ninterface ElectronAPI {\n getSystemAudioInfo?: () => Promise<{\n supportedFormats?: string[];\n systemInfo?: string;\n }>;\n getAudioMetadata?: (_filePath: string) => Promise<{\n duration?: number;\n bitrate?: number;\n sampleRate?: number;\n channels?: number;\n format?: string;\n }>;\n getAudioDevices?: () => Promise<{\n inputDevices: Array<{ id: string; name: string }>;\n outputDevices: Array<{ id: string; name: string }>;\n }>;\n getSystemAudioSettings?: () => Promise<{\n defaultInputDevice?: string;\n defaultOutputDevice?: string;\n masterVolume?: number;\n }>;\n}\n\n// Extend the existing Window interface from client.ts - don't redeclare electronAPI\n\nexport class ElectronAudioService {\n private audioClient: AudioProxyClient;\n\n constructor(options: AudioServiceOptions = {}) {\n // Use audioOptions if provided, otherwise use the options directly\n const clientOptions = options.audioOptions || options;\n this.audioClient = new AudioProxyClient(clientOptions);\n }\n\n public async getStreamableUrl(url: string): Promise<string> {\n return await this.audioClient.getPlayableUrl(url);\n }\n\n public async canPlayStream(url: string) {\n return await this.audioClient.canPlayUrl(url);\n }\n\n public getEnvironment() {\n return this.audioClient.getEnvironment();\n }\n\n public async isProxyAvailable(): Promise<boolean> {\n return await this.audioClient.isProxyAvailable();\n }\n\n public async checkSystemCodecs(): Promise<{\n supportedFormats: string[];\n missingCodecs: string[];\n capabilities: Record<string, string>;\n electronVersion?: string;\n chromiumVersion?: string;\n }> {\n const audio = new Audio();\n const formats = [\n { name: 'MP3', mime: 'audio/mpeg', codecs: ['mp3'] },\n { name: 'OGG', mime: 'audio/ogg', codecs: ['vorbis', 'opus'] },\n { name: 'WAV', mime: 'audio/wav', codecs: ['pcm'] },\n { name: 'AAC', mime: 'audio/aac', codecs: ['mp4a.40.2'] },\n { name: 'FLAC', mime: 'audio/flac', codecs: ['flac'] },\n { name: 'WEBM', mime: 'audio/webm', codecs: ['vorbis', 'opus'] },\n { name: 'M4A', mime: 'audio/mp4', codecs: ['mp4a.40.2'] },\n ];\n\n const supportedFormats: string[] = [];\n const missingCodecs: string[] = [];\n const capabilities: Record<string, string> = {};\n\n for (const format of formats) {\n let bestSupport = '';\n let isSupported = false;\n\n // Test basic MIME type\n const basicSupport = audio.canPlayType(format.mime);\n capabilities[format.name + '_basic'] = basicSupport;\n\n if (basicSupport === 'probably' || basicSupport === 'maybe') {\n bestSupport = basicSupport;\n isSupported = true;\n }\n\n // Test with codecs\n for (const codec of format.codecs) {\n const codecSupport = audio.canPlayType(\n `${format.mime}; codecs=\"${codec}\"`\n );\n capabilities[format.name + '_' + codec] = codecSupport;\n\n if (codecSupport === 'probably') {\n bestSupport = 'probably';\n isSupported = true;\n } else if (codecSupport === 'maybe' && bestSupport !== 'probably') {\n bestSupport = 'maybe';\n isSupported = true;\n }\n }\n\n capabilities[format.name] = bestSupport;\n\n if (isSupported) {\n supportedFormats.push(format.name);\n } else {\n missingCodecs.push(format.name);\n }\n }\n\n const result: {\n supportedFormats: string[];\n missingCodecs: string[];\n capabilities: Record<string, string>;\n electronVersion?: string;\n chromiumVersion?: string;\n } = { supportedFormats, missingCodecs, capabilities };\n\n // Add Electron version info if available\n if (this.getEnvironment() === 'electron') {\n try {\n if (typeof process !== 'undefined' && process.versions) {\n result.electronVersion = process.versions.electron;\n result.chromiumVersion = process.versions.chrome;\n }\n\n // Integrate with Electron main process for system codec detection\n const electronAPI = window.electronAPI as ElectronAPI | undefined;\n if (electronAPI?.getSystemAudioInfo) {\n try {\n const systemAudioInfo = await electronAPI.getSystemAudioInfo();\n if (systemAudioInfo) {\n capabilities['electron_system_info'] =\n JSON.stringify(systemAudioInfo);\n\n // Enhanced format support based on system capabilities\n if (systemAudioInfo.supportedFormats) {\n systemAudioInfo.supportedFormats.forEach((format: string) => {\n if (!supportedFormats.includes(format)) {\n supportedFormats.push(format);\n // Remove from missing codecs if it was there\n const missingIndex = missingCodecs.indexOf(format);\n if (missingIndex > -1) {\n missingCodecs.splice(missingIndex, 1);\n }\n }\n });\n }\n }\n } catch (error) {\n console.warn(\n '[ElectronAudioService] Failed to get system audio info via IPC:',\n error\n );\n }\n }\n } catch (error) {\n console.warn(\n '[ElectronAudioService] Could not access Electron version info:',\n error\n );\n }\n }\n\n return result;\n }\n\n // Electron-specific method to get audio file metadata via main process\n public async getAudioMetadata(filePath: string): Promise<{\n duration?: number;\n bitrate?: number;\n sampleRate?: number;\n channels?: number;\n format?: string;\n } | null> {\n const electronAPI = window.electronAPI as ElectronAPI | undefined;\n if (\n this.getEnvironment() !== 'electron' ||\n !electronAPI?.getAudioMetadata\n ) {\n return null;\n }\n\n try {\n return await electronAPI.getAudioMetadata(filePath);\n } catch (error) {\n console.warn(\n '[ElectronAudioService] Failed to get audio metadata:',\n error\n );\n return null;\n }\n }\n\n // Electron-specific method to enumerate audio devices via main process\n public async getAudioDevices(): Promise<{\n inputDevices: Array<{ id: string; name: string }>;\n outputDevices: Array<{ id: string; name: string }>;\n } | null> {\n const electronAPI = window.electronAPI as ElectronAPI | undefined;\n if (this.getEnvironment() !== 'electron' || !electronAPI?.getAudioDevices) {\n return null;\n }\n\n try {\n return await electronAPI.getAudioDevices();\n } catch (error) {\n console.warn(\n '[ElectronAudioService] Failed to get audio devices:',\n error\n );\n return null;\n }\n }\n\n // Electron-specific method to get system audio settings\n public async getSystemAudioSettings(): Promise<{\n defaultInputDevice?: string;\n defaultOutputDevice?: string;\n masterVolume?: number;\n } | null> {\n const electronAPI = window.electronAPI as ElectronAPI | undefined;\n if (\n this.getEnvironment() !== 'electron' ||\n !electronAPI?.getSystemAudioSettings\n ) {\n return null;\n }\n\n try {\n return await electronAPI.getSystemAudioSettings();\n } catch (error) {\n console.warn(\n '[ElectronAudioService] Failed to get system audio settings:',\n error\n );\n return null;\n }\n }\n}\n","import express, { Request, Response } from 'express';\nimport cors from 'cors';\nimport axios, { AxiosResponse, AxiosError } from 'axios';\nimport { Readable } from 'stream';\nimport { createServer } from 'net';\nimport { Server as HttpServer } from 'http';\nimport { ProxyConfig } from './types';\n\n// Utility function to check if a port is available\nasync function isPortAvailable(\n port: number,\n host: string = 'localhost'\n): Promise<boolean> {\n return new Promise(resolve => {\n const server = createServer();\n\n server.listen(port, host, () => {\n server.close(() => {\n resolve(true);\n });\n });\n\n server.on('error', () => {\n resolve(false);\n });\n });\n}\n\n// Find the next available port starting from the given port\nasync function findAvailablePort(\n startPort: number,\n host: string = 'localhost',\n maxAttempts: number = 10\n): Promise<number> {\n for (let i = 0; i < maxAttempts; i++) {\n const port = startPort + i;\n const available = await isPortAvailable(port, host);\n if (available) {\n return port;\n }\n }\n throw new Error(\n `No available port found in range ${startPort}-${startPort + maxAttempts - 1}`\n );\n}\n\nexport class AudioProxyServer {\n private app: express.Application;\n private server: HttpServer | null = null;\n private config: Required<ProxyConfig>;\n private actualPort: number = 0;\n\n constructor(config: ProxyConfig = {}) {\n this.config = {\n port: config.port || 3002,\n host: config.host || 'localhost',\n corsOrigins: config.corsOrigins || '*',\n timeout: config.timeout || 60000,\n maxRedirects: config.maxRedirects || 10,\n userAgent: config.userAgent || 'AudioProxy/1.0',\n enableLogging: config.enableLogging ?? true,\n enableTranscoding: config.enableTranscoding ?? false,\n cacheEnabled: config.cacheEnabled ?? true,\n cacheTTL: config.cacheTTL || 3600,\n };\n\n this.app = express();\n this.setupMiddleware();\n this.setupRoutes();\n }\n\n private setupMiddleware(): void {\n // CORS middleware\n this.app.use(\n cors({\n origin: this.config.corsOrigins,\n credentials: true,\n exposedHeaders: ['Content-Length', 'Content-Range', 'Accept-Ranges'],\n methods: ['GET', 'OPTIONS', 'HEAD'],\n allowedHeaders: ['Content-Type', 'Range', 'Accept-Encoding'],\n })\n );\n\n // Logging middleware\n if (this.config.enableLogging) {\n this.app.use((req: Request, res: Response, next) => {\n console.log(`[AudioProxy] ${req.method} ${req.path}`);\n next();\n });\n }\n }\n\n private setupRoutes(): void {\n // Handle CORS preflight for all routes\n this.app.options('*', (req: Request, res: Response) => {\n res.set({\n 'Access-Control-Allow-Origin': this.config.corsOrigins,\n 'Access-Control-Allow-Methods': 'GET, HEAD, OPTIONS',\n 'Access-Control-Allow-Headers':\n 'Content-Type, Range, Accept-Encoding, User-Agent',\n 'Access-Control-Allow-Credentials': 'true',\n 'Access-Control-Max-Age': '86400', // 24 hours\n });\n res.status(204).end();\n });\n\n // Health check endpoint\n this.app.get('/health', (req: Request, res: Response) => {\n res.json({\n status: 'ok',\n version: '1.1.1',\n uptime: process.uptime(),\n config: {\n port: this.actualPort || this.config.port,\n configuredPort: this.config.port,\n enableTranscoding: this.config.enableTranscoding,\n cacheEnabled: this.config.cacheEnabled,\n },\n });\n });\n\n // Info endpoint\n this.app.get('/info', async (req: Request, res: Response) => {\n const url = req.query.url as string;\n if (!url) {\n return res.status(400).json({ error: 'URL parameter required' });\n }\n\n try {\n // Get stream info without downloading\n const response = await axios({\n method: 'HEAD',\n url: url,\n headers: {\n 'User-Agent': this.config.userAgent,\n Accept: 'audio/*,*/*;q=0.1',\n },\n timeout: this.config.timeout,\n maxRedirects: this.config.maxRedirects,\n validateStatus: status => status < 400,\n });\n\n res.json({\n url,\n status: response.status,\n headers: response.headers,\n contentType: response.headers['content-type'],\n contentLength: response.headers['content-length'],\n acceptRanges: response.headers['accept-ranges'],\n lastModified: response.headers['last-modified'],\n });\n } catch (error: unknown) {\n console.error('[AudioProxy] Info error:', error);\n\n if (error && typeof error === 'object' && 'response' in error) {\n const axiosError = error as AxiosError;\n if (axiosError.response) {\n res.status(axiosError.response.status).json({\n error: `Upstream error: ${axiosError.response.status} ${axiosError.response.statusText}`,\n url: url,\n });\n }\n } else {\n const errorMessage =\n error instanceof Error ? error.message : 'Unknown error';\n res.status(500).json({\n error: 'Failed to get stream info',\n message: errorMessage,\n url: url,\n });\n }\n }\n });\n\n // Proxy endpoint\n this.app.get('/proxy', async (req: Request, res: Response) => {\n const url = req.query.url as string;\n if (!url) {\n return res.status(400).json({ error: 'URL parameter required' });\n }\n\n try {\n // Set CORS headers immediately\n res.set({\n 'Access-Control-Allow-Origin': this.config.corsOrigins,\n 'Access-Control-Allow-Credentials': 'true',\n 'Access-Control-Expose-Headers':\n 'Content-Length, Content-Range, Accept-Ranges',\n 'Access-Control-Allow-Methods': 'GET, OPTIONS, HEAD',\n 'Access-Control-Allow-Headers':\n 'Content-Type, Range, Accept-Encoding',\n });\n\n // Prepare request headers\n const requestHeaders: Record<string, string> = {\n 'User-Agent': this.config.userAgent,\n Accept: req.headers.accept || 'audio/*,*/*;q=0.1',\n 'Accept-Language': req.headers['accept-language'] || 'en-US,en;q=0.9',\n 'Cache-Control': 'no-cache',\n Pragma: 'no-cache',\n };\n\n // Handle range requests for seeking support\n if (req.headers.range) {\n requestHeaders['Range'] = req.headers.range;\n }\n\n // Handle encoding\n if (req.headers['accept-encoding']) {\n requestHeaders['Accept-Encoding'] = req.headers['accept-encoding'];\n }\n\n // Use axios for better stream handling\n const response: AxiosResponse = await axios({\n method: 'GET',\n url: url,\n headers: requestHeaders,\n responseType: 'stream',\n timeout: this.config.timeout,\n maxRedirects: this.config.maxRedirects,\n validateStatus: status => status < 400, // Accept redirects and success codes\n });\n\n // Set response status\n res.status(response.status);\n\n // Copy relevant headers from the original response\n const headersToProxy = [\n 'content-type',\n 'content-length',\n 'content-range',\n 'accept-ranges',\n 'cache-control',\n 'expires',\n 'last-modified',\n 'etag',\n ];\n\n headersToProxy.forEach(header => {\n const value = response.headers[header];\n if (value) {\n res.set(header, value);\n }\n });\n\n // Handle errors during streaming\n const stream = response.data as Readable;\n\n stream.on('error', error => {\n console.error('[AudioProxy] Stream error:', error);\n if (!res.headersSent) {\n res.status(500).json({\n error: 'Stream error',\n message: error.message,\n });\n } else {\n res.end();\n }\n });\n\n res.on('close', () => {\n // Clean up stream if client disconnects\n if (stream && !stream.destroyed) {\n stream.destroy();\n }\n });\n\n res.on('error', error => {\n console.error('[AudioProxy] Response error:', error);\n if (stream && !stream.destroyed) {\n stream.destroy();\n }\n });\n\n // Pipe the stream to response\n stream.pipe(res);\n } catch (error: unknown) {\n console.error('[AudioProxy] Proxy error:', error);\n\n if (!res.headersSent) {\n if (error && typeof error === 'object' && 'response' in error) {\n const axiosError = error as AxiosError;\n if (axiosError.response) {\n // HTTP error from upstream\n res.status(axiosError.response.status).json({\n error: `Upstream error: ${axiosError.response.status} ${axiosError.response.statusText}`,\n url: url,\n });\n }\n } else if (error && typeof error === 'object' && 'code' in error) {\n const nodeError = error as { code: string; message?: string };\n if (nodeError.code === 'ENOTFOUND') {\n // DNS resolution failed\n res.status(404).json({\n error: 'Audio source not found',\n message: 'Unable to resolve hostname',\n url: url,\n });\n } else if (nodeError.code === 'ECONNREFUSED') {\n // Connection refused\n res.status(503).json({\n error: 'Audio source unavailable',\n message: 'Connection refused',\n url: url,\n });\n } else if (nodeError.code === 'ETIMEDOUT') {\n // Request timeout\n res.status(408).json({\n error: 'Request timeout',\n message: 'Audio source did not respond in time',\n url: url,\n });\n } else {\n // Generic error with code\n const errorMessage = nodeError.message || 'Unknown error';\n res.status(500).json({\n error: 'Proxy request failed',\n message: errorMessage,\n url: url,\n });\n }\n } else {\n // Generic error\n const errorMessage =\n error instanceof Error ? error.message : 'Unknown error';\n res.status(500).json({\n error: 'Proxy request failed',\n message: errorMessage,\n url: url,\n });\n }\n }\n }\n });\n }\n\n public async start(): Promise<void> {\n try {\n // Find an available port starting from the configured port\n this.actualPort = await findAvailablePort(\n this.config.port,\n this.config.host\n );\n\n return new Promise((resolve, reject) => {\n this.server = this.app.listen(this.actualPort, this.config.host, () => {\n if (this.actualPort !== this.config.port) {\n console.log(\n `⚠️ Port ${this.config.port} was occupied, using port ${this.actualPort} instead`\n );\n }\n console.log(\n `Desktop Audio Proxy running on http://${this.config.host}:${this.actualPort}`\n );\n console.log(\n `Use http://${this.config.host}:${this.actualPort}/proxy?url=YOUR_AUDIO_URL`\n );\n resolve();\n });\n\n this.server.on('error', (error: Error) => {\n reject(error);\n });\n });\n } catch (error: unknown) {\n const errorMessage =\n error instanceof Error ? error.message : 'Unknown error';\n throw new Error(`Failed to start proxy server: ${errorMessage}`);\n }\n }\n\n public async stop(): Promise<void> {\n return new Promise(resolve => {\n if (this.server) {\n this.server.close(() => {\n console.log('Desktop Audio Proxy stopped');\n resolve();\n });\n } else {\n resolve();\n }\n });\n }\n\n public getActualPort(): number {\n return this.actualPort || this.config.port;\n }\n\n public getProxyUrl(): string {\n return `http://${this.config.host}:${this.getActualPort()}`;\n }\n}\n\n// Convenience functions\nexport function createProxyServer(config?: ProxyConfig): AudioProxyServer {\n return new AudioProxyServer(config);\n}\n\nexport async function startProxyServer(\n config?: ProxyConfig\n): Promise<AudioProxyServer> {\n const server = createProxyServer(config);\n await server.start();\n return server;\n}\n"],"names":["createServer"],"mappings":"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