import * as plugins from '../plugins.js'; import { Device } from '../abstract/device.abstract.js'; import type { IDeviceInfo, IRetryOptions } from '../interfaces/index.js'; /** * Speaker protocol types */ export type TSpeakerProtocol = 'sonos' | 'airplay' | 'chromecast' | 'dlna'; /** * Playback state */ export type TPlaybackState = 'playing' | 'paused' | 'stopped' | 'transitioning' | 'unknown'; /** * Track information */ export interface ITrackInfo { title: string; artist?: string; album?: string; duration: number; // seconds position: number; // seconds albumArtUri?: string; uri?: string; } /** * Speaker playback status */ export interface IPlaybackStatus { state: TPlaybackState; volume: number; // 0-100 muted: boolean; track?: ITrackInfo; } /** * Speaker device info */ export interface ISpeakerInfo extends IDeviceInfo { type: 'speaker'; protocol: TSpeakerProtocol; roomName?: string; modelName?: string; supportsGrouping?: boolean; groupId?: string; isGroupCoordinator?: boolean; } /** * Abstract Speaker base class * Common interface for all speaker types (Sonos, AirPlay, Chromecast) */ export abstract class Speaker extends Device { protected _protocol: TSpeakerProtocol; protected _roomName?: string; protected _modelName?: string; protected _volume: number = 0; protected _muted: boolean = false; protected _playbackState: TPlaybackState = 'unknown'; constructor( info: IDeviceInfo, protocol: TSpeakerProtocol, options?: { roomName?: string; modelName?: string; }, retryOptions?: IRetryOptions ) { super(info, retryOptions); this._protocol = protocol; this._roomName = options?.roomName; this._modelName = options?.modelName; } // Getters public get protocol(): TSpeakerProtocol { return this._protocol; } public get roomName(): string | undefined { return this._roomName; } public get speakerModelName(): string | undefined { return this._modelName; } public get volume(): number { return this._volume; } public get muted(): boolean { return this._muted; } public get playbackState(): TPlaybackState { return this._playbackState; } // ============================================================================ // Abstract Methods - Must be implemented by subclasses // ============================================================================ /** * Play media from URI */ public abstract play(uri?: string): Promise; /** * Pause playback */ public abstract pause(): Promise; /** * Stop playback */ public abstract stop(): Promise; /** * Next track */ public abstract next(): Promise; /** * Previous track */ public abstract previous(): Promise; /** * Seek to position */ public abstract seek(seconds: number): Promise; /** * Get volume level (0-100) */ public abstract getVolume(): Promise; /** * Set volume level (0-100) */ public abstract setVolume(level: number): Promise; /** * Get mute state */ public abstract getMute(): Promise; /** * Set mute state */ public abstract setMute(muted: boolean): Promise; /** * Get current track info */ public abstract getCurrentTrack(): Promise; /** * Get playback status */ public abstract getPlaybackStatus(): Promise; // ============================================================================ // Common Methods // ============================================================================ /** * Toggle mute */ public async toggleMute(): Promise { const currentMute = await this.getMute(); await this.setMute(!currentMute); return !currentMute; } /** * Volume up */ public async volumeUp(step: number = 5): Promise { const current = await this.getVolume(); const newVolume = Math.min(100, current + step); await this.setVolume(newVolume); return newVolume; } /** * Volume down */ public async volumeDown(step: number = 5): Promise { const current = await this.getVolume(); const newVolume = Math.max(0, current - step); await this.setVolume(newVolume); return newVolume; } /** * Get speaker info */ public getSpeakerInfo(): ISpeakerInfo { return { id: this.id, name: this.name, type: 'speaker', address: this.address, port: this.port, status: this.status, protocol: this._protocol, roomName: this._roomName, modelName: this._modelName, }; } }