@elizaos/plugin-music 2.0.0-beta.1 → 2.0.3-beta.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts DELETED
@@ -1,2263 +0,0 @@
1
- import { Action, UUID, IAgentRuntime, AgentRuntime, Service, Plugin } from '@elizaos/core';
2
- import { EventEmitter } from 'node:events';
3
- import { Readable, PassThrough } from 'node:stream';
4
- import { ServerResponse } from 'node:http';
5
-
6
- declare const musicAction: Action;
7
-
8
- /**
9
- * Analytics data for a guild/room
10
- */
11
- interface DJAnalytics {
12
- totalTracksPlayed: number;
13
- totalPlayTime: number;
14
- mostPlayedTracks: Array<{
15
- url: string;
16
- title: string;
17
- playCount: number;
18
- lastPlayed: number;
19
- }>;
20
- mostRequestedBy: Array<{
21
- entityId: UUID;
22
- name: string;
23
- requestCount: number;
24
- }>;
25
- popularTimes: Array<{
26
- hour: number;
27
- playCount: number;
28
- }>;
29
- popularDays: Array<{
30
- day: number;
31
- playCount: number;
32
- }>;
33
- milestones: Array<{
34
- type: string;
35
- value: number;
36
- timestamp: number;
37
- }>;
38
- sessionStats: {
39
- totalSessions: number;
40
- averageSessionDuration: number;
41
- longestSession: number;
42
- };
43
- }
44
- interface ListenerSnapshot {
45
- timestamp: number;
46
- listenerCount: number;
47
- humanListenerCount: number;
48
- botListenerCount: number;
49
- }
50
- /**
51
- * Get analytics for a guild/room
52
- */
53
- declare function getAnalytics(runtime: IAgentRuntime, roomId: UUID): Promise<DJAnalytics | null>;
54
- /**
55
- * Track a track being played
56
- */
57
- declare function trackTrackPlayed(runtime: IAgentRuntime, roomId: UUID, track: {
58
- url: string;
59
- title: string;
60
- }, duration: number, requestedBy?: {
61
- entityId: UUID;
62
- name: string;
63
- }): Promise<void>;
64
- /**
65
- * Track a listening session
66
- */
67
- declare function trackSession(runtime: IAgentRuntime, roomId: UUID, duration: number): Promise<void>;
68
- /**
69
- * Track a listener snapshot for analytics
70
- * Called by the listener tracking service in plugin-radio
71
- */
72
- declare function trackListenerSnapshot(runtime: IAgentRuntime, roomId: UUID, snapshot: ListenerSnapshot): Promise<void>;
73
-
74
- /**
75
- * Per-Guild DJ Settings
76
- * Comprehensive settings for DJ behavior specific to each guild/room
77
- */
78
- interface DJGuildSettings {
79
- autonomyLevel?: "MANUAL" | "BALANCED" | "AUTONOMOUS" | "RADIO";
80
- enabled: boolean;
81
- autoFillEnabled: boolean;
82
- autoFillThreshold?: number;
83
- timeBasedProgramming: boolean;
84
- repetitionControlEnabled: boolean;
85
- introsEnabled: boolean;
86
- commentaryEnabled: boolean;
87
- useLLMForCommentary: boolean;
88
- jokesSetting: boolean;
89
- autoTriviaEnabled: boolean;
90
- autoTriviaMinTracks?: number;
91
- autoTriviaMinMinutes?: number;
92
- autoJokesEnabled: boolean;
93
- autoJokesMinTracks?: number;
94
- autoJokesMinMinutes?: number;
95
- autoRecapEnabled: boolean;
96
- autoRecapMinTracks?: number;
97
- autoRecapMinMinutes?: number;
98
- stationName?: string;
99
- stationDescription?: string;
100
- listenerTrackingEnabled: boolean;
101
- audioQuality?: "high" | "medium" | "low";
102
- crossFadeEnabled: boolean;
103
- crossFadeDuration?: number;
104
- createdAt: number;
105
- updatedAt: number;
106
- createdBy?: UUID;
107
- lastModifiedBy?: UUID;
108
- }
109
- /**
110
- * Default guild settings
111
- */
112
- declare const DEFAULT_GUILD_SETTINGS: DJGuildSettings;
113
- /**
114
- * Get DJ guild settings for a room
115
- */
116
- declare function getDJGuildSettings(runtime: IAgentRuntime, roomId: UUID): Promise<DJGuildSettings>;
117
- /**
118
- * Set DJ guild settings for a room
119
- */
120
- declare function setDJGuildSettings(runtime: IAgentRuntime, roomId: UUID, settings: Partial<DJGuildSettings>, modifiedBy?: UUID): Promise<void>;
121
- /**
122
- * Reset DJ guild settings to defaults
123
- */
124
- declare function resetDJGuildSettings(runtime: IAgentRuntime, roomId: UUID, modifiedBy?: UUID): Promise<void>;
125
- /**
126
- * Enable/disable DJ for a guild
127
- */
128
- declare function toggleDJ(runtime: IAgentRuntime, roomId: UUID, enabled: boolean, modifiedBy?: UUID): Promise<void>;
129
- /**
130
- * Set autonomy level for a guild
131
- */
132
- declare function setAutonomyLevel(runtime: IAgentRuntime, roomId: UUID, level: "MANUAL" | "BALANCED" | "AUTONOMOUS" | "RADIO", modifiedBy?: UUID): Promise<void>;
133
- /**
134
- * Get all configured guilds
135
- */
136
- declare function getAllConfiguredGuilds(_runtime: IAgentRuntime): Promise<Array<{
137
- roomId: UUID;
138
- settings: DJGuildSettings;
139
- }>>;
140
-
141
- /**
142
- * DJ Intro Options
143
- * Advanced configuration for DJ track introductions and commentary
144
- */
145
- interface DJIntroOptions {
146
- useLLM: boolean;
147
- llmModel?: string;
148
- style: "concise" | "detailed" | "casual" | "professional" | "energetic" | "chill";
149
- personality?: string;
150
- includeJokes: boolean;
151
- jokeFrequency?: number;
152
- includeFunFacts: boolean;
153
- includeArtistInfo: boolean;
154
- includeDedications: boolean;
155
- dedicationStyle?: "brief" | "heartfelt" | "casual";
156
- introDuration: "short" | "medium" | "long";
157
- skipIntroChance?: number;
158
- minTracksBetweenIntros?: number;
159
- customTemplates?: string[];
160
- templateVariety: boolean;
161
- contextWindow?: number;
162
- musicInfoIntegration: boolean;
163
- listenerCountIntegration: boolean;
164
- }
165
- /**
166
- * Default DJ intro options
167
- */
168
- declare const DEFAULT_DJ_INTRO_OPTIONS: DJIntroOptions;
169
- /**
170
- * Get DJ intro options for a room
171
- */
172
- declare function getDJIntroOptions(runtime: IAgentRuntime, roomId: UUID): Promise<DJIntroOptions>;
173
- /**
174
- * Set DJ intro options for a room
175
- */
176
- declare function setDJIntroOptions(runtime: IAgentRuntime, roomId: UUID, options: Partial<DJIntroOptions>): Promise<void>;
177
- /**
178
- * Reset DJ intro options to defaults for a room
179
- */
180
- declare function resetDJIntroOptions(runtime: IAgentRuntime, roomId: UUID): Promise<void>;
181
- /**
182
- * Get intro prompt based on options
183
- */
184
- declare function buildIntroPrompt(options: DJIntroOptions, context: {
185
- characterName: string;
186
- trackTitle: string;
187
- artistName?: string;
188
- albumName?: string;
189
- dedicatedTo?: string;
190
- dedicationMessage?: string;
191
- listenerCount?: number;
192
- previousTracks?: string[];
193
- }): string;
194
-
195
- /**
196
- * DJ Tip record
197
- */
198
- interface DJTip {
199
- from: string;
200
- fromUserId: string;
201
- amount: number;
202
- currency: string;
203
- message?: string;
204
- timestamp: number;
205
- transactionId?: string;
206
- roomId?: UUID;
207
- }
208
- interface TopTipper {
209
- userId: string;
210
- username: string;
211
- totalAmount: number;
212
- currency: string;
213
- tipCount: number;
214
- }
215
- /**
216
- * DJ Tip Statistics
217
- */
218
- interface DJTipStats {
219
- totalTips: number;
220
- totalAmount: Record<string, number>;
221
- tips: DJTip[];
222
- topTippers: TopTipper[];
223
- }
224
- /**
225
- * Track a DJ tip
226
- */
227
- declare function trackDJTip(runtime: IAgentRuntime, roomId: UUID, tip: Omit<DJTip, "roomId">): Promise<void>;
228
- /**
229
- * Get DJ tip statistics
230
- */
231
- declare function getDJTipStats(runtime: IAgentRuntime): Promise<DJTipStats>;
232
- /**
233
- * Get recent tips
234
- */
235
- declare function getRecentTips(runtime: IAgentRuntime, limit?: number): Promise<DJTip[]>;
236
- /**
237
- * Get top tippers
238
- */
239
- declare function getTopTippers(runtime: IAgentRuntime, limit?: number): Promise<DJTipStats["topTippers"]>;
240
-
241
- /**
242
- * Represents a song in the global music library
243
- */
244
- interface LibrarySong {
245
- id: string;
246
- url: string;
247
- title: string;
248
- artist?: string;
249
- channel?: string;
250
- duration?: number;
251
- playCount: number;
252
- lastPlayed: number;
253
- firstAdded: number;
254
- requestedBy: Set<string>;
255
- }
256
- /**
257
- * Get a song from the library by URL
258
- */
259
- declare function getSong(runtime: IAgentRuntime, url: string): Promise<LibrarySong | null>;
260
- /**
261
- * Add or update a song in the library
262
- */
263
- declare function addSongToLibrary(runtime: IAgentRuntime, songData: {
264
- url: string;
265
- title: string;
266
- artist?: string;
267
- channel?: string;
268
- duration?: number;
269
- requestedBy?: string;
270
- }): Promise<LibrarySong>;
271
- /**
272
- * Get recent songs from the library (sorted by last played)
273
- */
274
- declare function getRecentSongs(runtime: IAgentRuntime, limit?: number): Promise<LibrarySong[]>;
275
- /**
276
- * Search the library for songs matching a query
277
- */
278
- declare function searchLibrary(runtime: IAgentRuntime, query: string, limit?: number): Promise<LibrarySong[]>;
279
- /**
280
- * Get the most recently played song
281
- */
282
- declare function getLastPlayedSong(runtime: IAgentRuntime): Promise<LibrarySong | null>;
283
- /**
284
- * Get all songs sorted by play count
285
- */
286
- declare function getMostPlayedSongs(runtime: IAgentRuntime, limit?: number): Promise<LibrarySong[]>;
287
- /**
288
- * Get library statistics
289
- */
290
- declare function getLibraryStats(runtime: IAgentRuntime): Promise<{
291
- totalSongs: number;
292
- totalPlays: number;
293
- mostPlayed?: LibrarySong;
294
- }>;
295
-
296
- /**
297
- * Represents a track in a playlist
298
- */
299
- interface PlaylistTrack {
300
- url: string;
301
- title: string;
302
- duration?: number;
303
- addedAt?: number;
304
- requestedBy?: string;
305
- dedicatedTo?: string;
306
- dedicationMessage?: string;
307
- }
308
- /**
309
- * Represents a saved playlist
310
- */
311
- interface Playlist {
312
- id: string;
313
- name: string;
314
- tracks: PlaylistTrack[];
315
- createdAt: number;
316
- updatedAt: number;
317
- isFavorite?: boolean;
318
- }
319
- /**
320
- * Save a playlist to user's entity components
321
- */
322
- declare function savePlaylist(runtime: IAgentRuntime, entityId: UUID, playlist: Omit<Playlist, "id" | "createdAt" | "updatedAt"> & {
323
- id?: string;
324
- createdAt?: number;
325
- }): Promise<Playlist>;
326
- /**
327
- * Load all playlists for a user
328
- */
329
- declare function loadPlaylists(runtime: IAgentRuntime, entityId: UUID): Promise<Playlist[]>;
330
- /**
331
- * Delete a playlist
332
- */
333
- declare function deletePlaylist(runtime: IAgentRuntime, entityId: UUID, playlistId: string): Promise<boolean>;
334
-
335
- /**
336
- * User music preferences
337
- */
338
- interface UserMusicPreferences {
339
- favoriteGenres?: string[];
340
- favoriteArtists?: string[];
341
- favoriteTracks?: Array<{
342
- url: string;
343
- title: string;
344
- playCount?: number;
345
- }>;
346
- dislikedTracks?: string[];
347
- skipHistory?: Array<{
348
- url: string;
349
- timestamp: number;
350
- }>;
351
- requestHistory?: Array<{
352
- url: string;
353
- title: string;
354
- timestamp: number;
355
- }>;
356
- listeningSessions?: Array<{
357
- startTime: number;
358
- endTime?: number;
359
- tracksPlayed: number;
360
- }>;
361
- }
362
- /**
363
- * Update user music preferences
364
- */
365
- declare function updateUserPreferences(runtime: IAgentRuntime, entityId: UUID, preferences: Partial<UserMusicPreferences>, roomId?: UUID, worldId?: UUID): Promise<UserMusicPreferences>;
366
- /**
367
- * Get user music preferences
368
- */
369
- declare function getUserPreferences(runtime: IAgentRuntime, entityId: UUID): Promise<UserMusicPreferences | null>;
370
- /**
371
- * Get preferences for all users in a room
372
- */
373
- declare function getRoomPreferences(runtime: IAgentRuntime, roomId: UUID): Promise<Map<UUID, UserMusicPreferences>>;
374
- /**
375
- * Track a track request
376
- */
377
- declare function trackTrackRequest(runtime: IAgentRuntime, entityId: UUID, track: {
378
- url: string;
379
- title: string;
380
- }, roomId?: UUID, worldId?: UUID): Promise<void>;
381
- /**
382
- * Track a skip
383
- */
384
- declare function trackSkip(runtime: IAgentRuntime, entityId: UUID, trackUrl: string, roomId?: UUID, worldId?: UUID): Promise<void>;
385
- /**
386
- * Track favorite track
387
- */
388
- declare function trackFavorite(runtime: IAgentRuntime, entityId: UUID, track: {
389
- url: string;
390
- title: string;
391
- }, roomId?: UUID, worldId?: UUID): Promise<void>;
392
-
393
- /**
394
- * Track play history for repetition control
395
- */
396
- interface PlayHistoryEntry {
397
- url: string;
398
- title: string;
399
- playedAt: number;
400
- }
401
- /**
402
- * Smart repetition control to avoid playing same songs too frequently
403
- */
404
- declare class RepetitionControl {
405
- private playHistory;
406
- private readonly MAX_HISTORY_SIZE;
407
- private readonly MIN_REPLAY_INTERVAL;
408
- /**
409
- * Record a track play
410
- */
411
- recordPlay(guildId: string, url: string, title: string): void;
412
- /**
413
- * Check if a track can be played (not played too recently)
414
- */
415
- canPlay(guildId: string, url: string, minInterval?: number): boolean;
416
- /**
417
- * Get recently played tracks for a guild
418
- */
419
- getRecentlyPlayed(guildId: string, count?: number): PlayHistoryEntry[];
420
- /**
421
- * Filter tracks to avoid repetition
422
- */
423
- filterRepetition(guildId: string, tracks: Array<{
424
- url: string;
425
- title: string;
426
- }>, minInterval?: number): Array<{
427
- url: string;
428
- title: string;
429
- }>;
430
- /**
431
- * Get play count for a track in recent history
432
- */
433
- getRecentPlayCount(guildId: string, url: string, timeWindow?: number): number;
434
- /**
435
- * Score tracks based on variety (lower score = played less recently)
436
- */
437
- scoreByVariety(guildId: string, tracks: Array<{
438
- url: string;
439
- title: string;
440
- playCount?: number;
441
- }>): Array<{
442
- url: string;
443
- title: string;
444
- playCount?: number;
445
- varietyScore: number;
446
- }>;
447
- /**
448
- * Clear history for a guild
449
- */
450
- clearHistory(guildId: string): void;
451
- /**
452
- * Get statistics for a guild
453
- */
454
- getStats(guildId: string): {
455
- totalPlays: number;
456
- uniqueTracks: number;
457
- averageRepeatInterval: number;
458
- };
459
- }
460
- declare const repetitionControl: RepetitionControl;
461
-
462
- /**
463
- * Detailed per-song memory and statistics
464
- * Tracks everything about a song across all rooms
465
- */
466
- interface SongMemory {
467
- url: string;
468
- title: string;
469
- artist?: string;
470
- album?: string;
471
- duration?: number;
472
- totalPlays: number;
473
- totalPlayTime: number;
474
- lastPlayed: number;
475
- firstPlayed: number;
476
- totalRequests: number;
477
- uniqueRequesters: number;
478
- topRequesters: Array<{
479
- entityId: UUID;
480
- name: string;
481
- count: number;
482
- }>;
483
- totalLikes: number;
484
- totalDislikes: number;
485
- skipCount: number;
486
- completionRate: number;
487
- playedInRooms: Array<{
488
- roomId: UUID;
489
- playCount: number;
490
- lastPlayed: number;
491
- }>;
492
- dedicationCount: number;
493
- dedications: Array<{
494
- from: string;
495
- to: string;
496
- message?: string;
497
- timestamp: number;
498
- }>;
499
- popularHours: number[];
500
- popularDays: number[];
501
- averageListenerCount: number;
502
- peakListenerCount: number;
503
- listenerEngagement: number;
504
- createdAt: number;
505
- updatedAt: number;
506
- tags?: string[];
507
- notes?: string;
508
- }
509
- /**
510
- * Get song memory
511
- */
512
- declare function getSongMemory(runtime: IAgentRuntime, url: string): Promise<SongMemory | null>;
513
- /**
514
- * Record a play
515
- */
516
- declare function recordSongPlay(runtime: IAgentRuntime, song: {
517
- url: string;
518
- title: string;
519
- artist?: string;
520
- album?: string;
521
- duration?: number;
522
- }, context: {
523
- roomId?: UUID;
524
- playDuration: number;
525
- listenerCount?: number;
526
- requestedBy?: {
527
- entityId: UUID;
528
- name: string;
529
- };
530
- wasSkipped?: boolean;
531
- }): Promise<void>;
532
- /**
533
- * Record a request
534
- */
535
- declare function recordSongRequest(runtime: IAgentRuntime, song: {
536
- url: string;
537
- title: string;
538
- artist?: string;
539
- album?: string;
540
- duration?: number;
541
- }, requester: {
542
- entityId: UUID;
543
- name: string;
544
- }): Promise<void>;
545
- /**
546
- * Record a dedication
547
- */
548
- declare function recordSongDedication(runtime: IAgentRuntime, url: string, dedication: {
549
- from: string;
550
- to: string;
551
- message?: string;
552
- }): Promise<void>;
553
- /**
554
- * Get top songs by play count
555
- */
556
- declare function getTopSongs(_runtime: IAgentRuntime, _limit?: number): Promise<SongMemory[]>;
557
- /**
558
- * Get most requested songs
559
- */
560
- declare function getMostRequestedSongs(_runtime: IAgentRuntime, _limit?: number): Promise<SongMemory[]>;
561
-
562
- /**
563
- * Audio Broadcast Contracts
564
- *
565
- * WHY THIS EXISTS:
566
- * These contracts define how audio flows from music-player to consumers (Discord, Web, etc.)
567
- * without creating dependencies between plugins. This allows:
568
- *
569
- * 1. Plugin Independence: music-player doesn't know about Discord, Discord doesn't know
570
- * about music-player internals. They communicate through contracts.
571
- *
572
- * 2. Multiple Consumers: Multiple outputs (Discord + 5 web listeners) can all receive
573
- * the same broadcast independently without affecting each other.
574
- *
575
- * 3. Resilience: If Discord disconnects, the broadcast continues. Web listeners are
576
- * unaffected. Discord can reconnect and resume from live position.
577
- *
578
- * 4. No Radio Dependency: music-player + discord work together without needing the
579
- * radio plugin. Radio can add orchestration features, but isn't required.
580
- */
581
- /**
582
- * Subscription to an audio broadcast stream
583
- *
584
- * WHY: Each consumer needs independent control over their subscription.
585
- * One consumer unsubscribing shouldn't affect others.
586
- */
587
- interface AudioSubscription {
588
- /** Unique identifier for this subscription */
589
- readonly consumerId: string;
590
- /** The audio stream (PCM or Opus frames) */
591
- readonly stream: Readable;
592
- /** Unsubscribe from the broadcast */
593
- unsubscribe(): void;
594
- }
595
- /**
596
- * Audio broadcast state
597
- */
598
- type BroadcastState = "live" | "silence" | "stopped";
599
- /**
600
- * Track metadata for broadcast events
601
- */
602
- interface BroadcastTrackMetadata {
603
- title: string;
604
- url: string;
605
- duration?: number;
606
- requestedBy?: string;
607
- }
608
- /**
609
- * Audio broadcast interface - represents a continuous audio stream
610
- * that multiple consumers can subscribe to independently.
611
- *
612
- * WHY NON-BLOCKING:
613
- * A slow web listener with a bad connection should NOT cause Discord audio to stutter.
614
- * Each consumer is isolated - if they can't keep up, we drop their frames, not the source.
615
- *
616
- * WHY RESILIENT:
617
- * Radio stations don't stop broadcasting when everyone changes the channel. Similarly,
618
- * the broadcast continues even with zero subscribers, so reconnecting is seamless.
619
- *
620
- * WHY LIVE:
621
- * When Discord reconnects after a hiccup, it should hear what's playing NOW, not
622
- * buffered audio from 10 seconds ago. This is radio-style, not recording-style.
623
- *
624
- * Implementations must ensure:
625
- * - Non-blocking: slow consumers don't affect the broadcast or other consumers
626
- * - Resilient: broadcast continues even if all consumers disconnect
627
- * - Live: new subscribers get current audio, not buffered past audio
628
- */
629
- interface IAudioBroadcast extends EventEmitter {
630
- /** Guild/server this broadcast is for */
631
- readonly guildId: string;
632
- /** Current broadcast state */
633
- readonly state: BroadcastState;
634
- /**
635
- * Subscribe to this broadcast stream
636
- * @param consumerId Unique identifier for the consumer
637
- * @returns Subscription object with stream and unsubscribe method
638
- */
639
- subscribe(consumerId: string): AudioSubscription;
640
- /**
641
- * Get current subscriber count
642
- */
643
- getSubscriberCount(): number;
644
- /**
645
- * Check if a consumer is currently subscribed
646
- */
647
- isSubscribed(consumerId: string): boolean;
648
- on(event: "stateChange", listener: (state: BroadcastState) => void): this;
649
- on(event: "metadata", listener: (metadata: BroadcastTrackMetadata) => void): this;
650
- on(event: "subscriberAdded", listener: (consumerId: string) => void): this;
651
- on(event: "subscriberRemoved", listener: (consumerId: string) => void): this;
652
- emit(event: "stateChange", state: BroadcastState): boolean;
653
- emit(event: "metadata", metadata: BroadcastTrackMetadata): boolean;
654
- emit(event: "subscriberAdded", consumerId: string): boolean;
655
- emit(event: "subscriberRemoved", consumerId: string): boolean;
656
- }
657
-
658
- /**
659
- * Broadcast - Implements IAudioBroadcast contract
660
- *
661
- * WHY THIS FACADE:
662
- * Consumers (Discord, Web) shouldn't care about internal complexity (StreamCore,
663
- * StreamMultiplexer, silence generation, backpressure policies). They just want:
664
- * - subscribe() → get audio stream
665
- * - unsubscribe() → stop receiving audio
666
- * - events → know what's playing
667
- *
668
- * This class is the ONLY public interface. Everything else (core components) is internal.
669
- *
670
- * ARCHITECTURE LAYERS:
671
- * ```
672
- * Consumer Layer: Discord, Web, etc. → call subscribe()
673
- * ↓
674
- * Facade Layer: Broadcast (this class) → implements IAudioBroadcast
675
- * ↓
676
- * Generation Layer: StreamCore → produces audio (tracks or silence)
677
- * ↓
678
- * Distribution Layer: StreamMultiplexer → fans out to N consumers
679
- * ```
680
- *
681
- * WHY WIRE THEM IN CONSTRUCTOR:
682
- * StreamCore → StreamMultiplexer wiring happens once. The output stream is long-lived
683
- * (never closes). Tracks come and go, but the pipeline stays connected.
684
- *
685
- * This is the public interface that consumers (Discord, Web, etc.) interact with.
686
- * Internally uses StreamCore for audio generation and StreamMultiplexer for fan-out.
687
- */
688
- declare class Broadcast extends EventEmitter implements IAudioBroadcast {
689
- readonly guildId: string;
690
- private streamCore;
691
- private multiplexer;
692
- private _state;
693
- constructor(guildId: string);
694
- /**
695
- * Get current broadcast state
696
- */
697
- get state(): BroadcastState;
698
- /**
699
- * Subscribe to this broadcast
700
- * @param consumerId Unique identifier for the consumer
701
- * @returns AudioSubscription with stream and unsubscribe method
702
- */
703
- subscribe(consumerId: string): AudioSubscription;
704
- /**
705
- * Get current subscriber count
706
- */
707
- getSubscriberCount(): number;
708
- /**
709
- * Check if a consumer is subscribed
710
- */
711
- isSubscribed(consumerId: string): boolean;
712
- /**
713
- * Push a track stream into the broadcast (internal API for MusicQueue)
714
- * @param stream Audio stream
715
- * @param metadata Track metadata
716
- */
717
- pushTrack(stream: Readable, metadata: BroadcastTrackMetadata): Promise<void>;
718
- /**
719
- * Start silence generation (internal API)
720
- */
721
- startSilence(): void;
722
- /**
723
- * Stop the broadcast (internal API)
724
- */
725
- stop(): void;
726
- /**
727
- * Destroy the broadcast and clean up resources
728
- */
729
- destroy(): void;
730
- /**
731
- * Get statistics about the broadcast
732
- */
733
- getStats(): {
734
- state: BroadcastState;
735
- subscriberCount: number;
736
- consumerStats: Map<string, {
737
- droppedFrames: number;
738
- totalFrames: number;
739
- }>;
740
- };
741
- /**
742
- * Map StreamCore state to BroadcastState
743
- */
744
- private mapCoreStateToBroadcastState;
745
- }
746
-
747
- interface AudioCacheKey {
748
- artist?: string;
749
- album?: string;
750
- song: string;
751
- quality: "low" | "medium" | "high" | "highest";
752
- url: string;
753
- }
754
- /**
755
- * Audio cache service that downloads, converts, and caches audio files
756
- *
757
- * ## Overview
758
- * This service manages a file-based cache of audio files downloaded from YouTube.
759
- * Audio is downloaded using yt-dlp and converted to OGG Opus format, which is:
760
- * - Native to Discord voice (no transcoding needed)
761
- * - Efficiently compressed (smaller cache size)
762
- * - High quality (VBR encoding)
763
- * - Web-compatible (supported by modern browsers)
764
- *
765
- * ## Caching Strategy
766
- * Files are cached based on artist/album/song/quality and stored in the configured
767
- * cache directory. Each cached file includes metadata (duration, bitrate, etc.)
768
- * obtained via ffprobe.
769
- *
770
- * ## Stream Handling (CRITICAL)
771
- * ⚠️ This service creates clean, unmodified file streams. DO NOT add event listeners
772
- * or call resume() on these streams - adding listeners (especially 'readable' and 'data')
773
- * puts streams into paused mode and prevents Discord.js from controlling stream flow.
774
- *
775
- * Let the consumer (Discord.js) handle all stream control, probing, and consumption.
776
- *
777
- * @example
778
- * ```typescript
779
- * const cache = new AudioCacheService('/path/to/cache');
780
- * const key = { song: 'Track Name', quality: 'high', url: 'https://...' };
781
- * const stream = await cache.getAudioStream(key, 'https://youtube.com/...');
782
- * // Pass stream directly to Discord.js - don't touch it!
783
- * voiceManager.playAudio(stream, { guildId, channel });
784
- * ```
785
- */
786
- declare class AudioCacheService {
787
- private cacheDir;
788
- private readonly CACHE_TTL;
789
- private cacheIndex;
790
- constructor(cacheDir?: string);
791
- /**
792
- * Generate cache key from track metadata
793
- */
794
- private generateCacheKey;
795
- /**
796
- * Simple hash function for URLs
797
- */
798
- private hashString;
799
- /**
800
- * Extract YouTube video ID from various URL formats
801
- * WHY: YouTube URLs can come in many forms but point to the same video:
802
- * - https://www.youtube.com/watch?v=ABC123
803
- * - https://youtube.com/watch?v=ABC123
804
- * - https://youtu.be/ABC123
805
- * - https://www.youtube.com/watch?v=ABC123&list=PLxyz&index=1
806
- *
807
- * We need to normalize these to get consistent cache keys
808
- */
809
- private extractYouTubeVideoId;
810
- /**
811
- * Normalize URL for consistent caching
812
- * WHY: Same video can have different URL formats - we want them to hit the same cache entry
813
- */
814
- private normalizeUrlForCache;
815
- /**
816
- * Get cache file path for a key
817
- */
818
- private getCacheFilePath;
819
- /**
820
- * Check if audio is cached (checks all supported formats)
821
- */
822
- isCached(key: AudioCacheKey): boolean;
823
- /**
824
- * Find cached audio by URL (searches all cached files)
825
- * This is useful when you only have the URL and don't know the exact cache key
826
- */
827
- findCachedAudioByUrl(url: string, quality?: "low" | "medium" | "high" | "highest"): Promise<Readable | null>;
828
- /**
829
- * Get cached audio file as a readable stream
830
- *
831
- * Returns a clean file stream with NO event listeners attached (except error handling).
832
- * The stream is in paused mode by default - the consumer must control flow.
833
- *
834
- * ⚠️ WARNING: Do not add event listeners or call resume() on the returned stream.
835
- * Adding listeners puts the stream in paused mode and prevents proper playback.
836
- * Let Discord.js handle all stream control.
837
- *
838
- * @param key - Cache key identifying the audio file
839
- * @returns Clean readable stream, or null if not cached/invalid
840
- */
841
- getCachedAudio(key: AudioCacheKey): Promise<Readable | null>;
842
- /**
843
- * Download and cache audio from YouTube URL
844
- *
845
- * Uses yt-dlp to download audio and convert to the configured format.
846
- * yt-dlp automatically invokes ffmpeg for format conversion.
847
- *
848
- * ## Process
849
- * 1. Check if already cached (skip if so)
850
- * 2. Download audio using yt-dlp with quality settings
851
- * 3. Convert to configured format (default: Opus, configurable via AUDIO_CACHE_FORMAT)
852
- * 4. Probe metadata with ffprobe (duration, bitrate, etc.)
853
- * 5. Update cache index
854
- *
855
- * ## Supported Formats
856
- * - opus: Discord-optimized, lossy but high quality (default)
857
- * - flac: Lossless format, prevents additional quality loss from lossy-to-lossy conversions (larger files)
858
- * Even though YouTube source is lossy, FLAC preserves the best quality available and prevents
859
- * further degradation from multiple lossy conversions (e.g., Opus→Opus or Opus→MP3)
860
- * - wav: Uncompressed lossless (very large files)
861
- * - mp3: Lossy, widely compatible
862
- * - m4a: Lossy, Apple format
863
- *
864
- * Set AUDIO_CACHE_FORMAT environment variable to choose format (e.g., export AUDIO_CACHE_FORMAT=flac)
865
- *
866
- * Note: Using FLAC prevents quality loss from lossy-to-lossy transcoding, preserving the best
867
- * quality available from the YouTube source even if the source itself is lossy.
868
- *
869
- * ## Quality Mapping
870
- * - low: worst available audio
871
- * - medium: up to 128kbps
872
- * - high: up to 192kbps
873
- * - highest: best available audio
874
- *
875
- * Note: FLAC is lossless, so quality settings don't apply (preserves source quality)
876
- *
877
- * @param key - Cache key for the audio
878
- * @param youtubeUrl - YouTube URL to download from
879
- * @returns Path to cached file
880
- * @throws Error if yt-dlp/ffmpeg not installed, download fails, or file invalid
881
- */
882
- downloadAndCache(key: AudioCacheKey, youtubeUrl: string): Promise<string>;
883
- /**
884
- * Get or download audio (returns file path)
885
- */
886
- getOrDownload(key: AudioCacheKey, youtubeUrl: string): Promise<string>;
887
- /**
888
- * Resolve the on-disk file path for a cached track by URL (any quality/format).
889
- * Returns null when the file has not been cached yet or is expired.
890
- */
891
- findCachedFilePathByUrl(url: string): string | null;
892
- /**
893
- * Get cached audio as stream, or download and return stream
894
- */
895
- getAudioStream(key: AudioCacheKey, youtubeUrl: string): Promise<Readable>;
896
- /**
897
- * Probe audio file using ffprobe to get metadata
898
- */
899
- private probeAudioFile;
900
- /**
901
- * Load cache index from filesystem
902
- */
903
- private loadCacheIndex;
904
- /**
905
- * Clean up expired cache entries
906
- */
907
- private cleanExpiredCache;
908
- /**
909
- * Get cache statistics
910
- */
911
- getCacheStats(): {
912
- totalFiles: number;
913
- totalSize: number;
914
- oldestEntry: number | null;
915
- newestEntry: number | null;
916
- };
917
- /**
918
- * Clear all cache
919
- */
920
- clearCache(): void;
921
- /**
922
- * Get audio metadata for a cached file (returns undefined if not cached)
923
- */
924
- getAudioMetadata(key: AudioCacheKey): Promise<{
925
- bitrate?: number;
926
- format?: string;
927
- sampleRate?: number;
928
- channels?: number;
929
- size?: number;
930
- duration?: number;
931
- } | undefined>;
932
- }
933
-
934
- interface VoicePlaybackHandle {
935
- finished?: Promise<void>;
936
- }
937
- /** Minimal voice pipeline from plugin-discord (avoids hard dependency here). */
938
- interface VoiceManagerLike {
939
- stopAudio(guildId: string, channel: number): Promise<void>;
940
- pauseAudio(guildId: string, channel: number): Promise<void>;
941
- resumeAudio(guildId: string, channel: number): Promise<void>;
942
- isPlaying(guildId: string, channel: number): Promise<boolean>;
943
- on(event: "audio:finished", handler: (data: {
944
- guildId: string;
945
- channel: number;
946
- }) => void): void;
947
- off(event: "audio:finished", handler: (data: {
948
- guildId: string;
949
- channel: number;
950
- }) => void): void;
951
- playAudio(stream: Readable, options: {
952
- guildId: string;
953
- channel: number;
954
- volume?: number;
955
- metadata?: Record<string, unknown>;
956
- }): Promise<VoicePlaybackHandle>;
957
- }
958
- /**
959
- * Represents a track in the music queue
960
- */
961
- interface QueuedTrack {
962
- id: string;
963
- url: string;
964
- title: string;
965
- duration?: number;
966
- requestedBy?: string;
967
- dedicatedTo?: string;
968
- dedicationMessage?: string;
969
- addedAt: number;
970
- stream?: Readable;
971
- audioMetadata?: {
972
- bitrate?: number;
973
- format?: string;
974
- sampleRate?: number;
975
- channels?: number;
976
- size?: number;
977
- };
978
- }
979
- /**
980
- * Options for cross-fading between tracks
981
- */
982
- interface CrossFadeOptions {
983
- enabled: boolean;
984
- duration: number;
985
- }
986
- /**
987
- * Music queue manager with cross-fading support
988
- */
989
- declare class MusicQueue extends EventEmitter {
990
- private queue;
991
- private currentTrack;
992
- private currentPlaybackHandle;
993
- private isPlaying;
994
- private isPaused;
995
- private guildId;
996
- private voiceManager;
997
- private crossFadeOptions;
998
- private nextTrackPreBuffered;
999
- private readonly MUSIC_CHANNEL;
1000
- private continuousStream;
1001
- private nextTrackStream;
1002
- private preBufferTimeout;
1003
- private trackPlayedCallback;
1004
- private audioCache;
1005
- private runtime;
1006
- private broadcast;
1007
- constructor(guildId: string, voiceManager: VoiceManagerLike | null, runtime?: IAgentRuntime, audioCache?: AudioCacheService);
1008
- /**
1009
- * Set the broadcast for this queue (new architecture)
1010
- * @param broadcast Broadcast instance to push audio to
1011
- */
1012
- setBroadcast(broadcast: Broadcast): void;
1013
- /**
1014
- * Register callback for when a track finishes playing
1015
- */
1016
- onTrackPlayed(callback: (track: QueuedTrack, duration: number) => Promise<void>): void;
1017
- /**
1018
- * Set cross-fade options
1019
- */
1020
- setCrossFadeOptions(options: Partial<CrossFadeOptions>): void;
1021
- /**
1022
- * Add a track to the queue
1023
- */
1024
- addTrack(track: Omit<QueuedTrack, "id" | "addedAt">): Promise<QueuedTrack>;
1025
- /**
1026
- * Remove a track from the queue
1027
- */
1028
- removeTrack(trackId: string): boolean;
1029
- /**
1030
- * Skip the current track.
1031
- * Works for both legacy voiceManager and broadcast architectures.
1032
- */
1033
- skip(): Promise<boolean>;
1034
- /**
1035
- * Pause playback.
1036
- * For Discord voice: delegates to voiceManager.pauseAudio.
1037
- * For broadcast (web): stops the broadcast StreamCore so HTTP clients
1038
- * stop receiving new audio frames (they stay connected but hear silence).
1039
- */
1040
- pause(): Promise<void>;
1041
- /**
1042
- * Resume playback.
1043
- * For Discord voice: delegates to voiceManager.resumeAudio.
1044
- * For broadcast (web): re-pushes the current track metadata so clients
1045
- * know playback resumed. The actual audio stream is still wired; calling
1046
- * broadcast.startSilence() unblocks the pipeline and the next pushTrack
1047
- * will pick it back up.
1048
- */
1049
- resume(): Promise<void>;
1050
- /**
1051
- * Stop playback and clear queue.
1052
- * Stops both legacy voiceManager and broadcast paths so all consumers
1053
- * (Discord voice + HTTP stream clients) stop receiving audio.
1054
- */
1055
- stop(): Promise<void>;
1056
- /**
1057
- * Clear the queue
1058
- */
1059
- clear(): void;
1060
- /**
1061
- * Shuffle the queue
1062
- */
1063
- shuffle(): void;
1064
- /**
1065
- * Get the queue
1066
- */
1067
- getQueue(): QueuedTrack[];
1068
- /**
1069
- * Get the current track
1070
- */
1071
- getCurrentTrack(): QueuedTrack | null;
1072
- /**
1073
- * Get whether playback is active
1074
- */
1075
- getIsPlaying(): boolean;
1076
- /**
1077
- * Get whether playback is paused
1078
- */
1079
- getIsPaused(): boolean;
1080
- /**
1081
- * Get the continuous stream
1082
- */
1083
- getContinuousStream(): PassThrough | null;
1084
- /**
1085
- * Get audio stream for a track (cached or download)
1086
- * Tries cache first (title-based, then URL-based), then downloads if not cached.
1087
- * @param track - Track to get stream for
1088
- * @param shouldCache - Whether to cache downloaded streams (default: true)
1089
- * @returns Audio stream or null if failed
1090
- */
1091
- private getAudioStreamForTrack;
1092
- /**
1093
- * Clean up pre-buffered stream
1094
- */
1095
- private cleanupPreBufferedStream;
1096
- /**
1097
- * Pre-buffer the next track for cross-fading
1098
- */
1099
- private preBufferNextTrack;
1100
- /**
1101
- * Play the next track in the queue
1102
- */
1103
- private playNext;
1104
- }
1105
-
1106
- /**
1107
- * Compatibility fallback for `/music-player/status`.
1108
- *
1109
- * The UI polls this route even when plugin-music-player is not enabled. When the
1110
- * plugin route is absent, returning a stable JSON payload is better than a noisy
1111
- * 404 loop in desktop logs.
1112
- */
1113
- declare function tryHandleMusicPlayerStatusFallback(options: {
1114
- pathname: string;
1115
- method: string;
1116
- runtime: AgentRuntime | null | undefined;
1117
- res: ServerResponse;
1118
- }): boolean;
1119
-
1120
- /** Minimal VoiceTarget shape — avoids hard dep on @elizaos/plugin-discord. */
1121
- interface VoiceTarget {
1122
- id: string;
1123
- type: string;
1124
- guildId?: string;
1125
- channelId?: string;
1126
- play?: (stream: Readable, opts?: Record<string, unknown>) => Promise<unknown>;
1127
- playAudio?: (stream: Readable, opts?: Record<string, unknown>) => Promise<unknown>;
1128
- feed?: (stream: Readable) => Promise<unknown>;
1129
- stop?: () => Promise<unknown>;
1130
- stopAudio?: () => Promise<unknown>;
1131
- [key: string]: unknown;
1132
- }
1133
- /**
1134
- * Audio routing mode
1135
- */
1136
- type AudioRoutingMode = "simulcast" | "independent";
1137
- /**
1138
- * Configuration for audio routing
1139
- */
1140
- interface AudioRouteConfig {
1141
- sourceId: string;
1142
- targetIds: string[];
1143
- mode?: AudioRoutingMode;
1144
- }
1145
- /**
1146
- * AudioRouter manages routing of audio streams to multiple voice targets
1147
- * Supports both simulcast (same stream to all) and independent (separate streams) modes
1148
- */
1149
- declare class AudioRouter {
1150
- private routes;
1151
- private defaultMode;
1152
- private targetRegistry;
1153
- constructor(defaultMode?: AudioRoutingMode);
1154
- /**
1155
- * Register voice targets for routing
1156
- */
1157
- registerTargets(targets: VoiceTarget[]): void;
1158
- /**
1159
- * Unregister voice targets
1160
- */
1161
- unregisterTarget(targetId: string): void;
1162
- /**
1163
- * Get registered target by ID
1164
- */
1165
- getTarget(targetId: string): VoiceTarget | undefined;
1166
- /**
1167
- * Route an audio stream to multiple targets
1168
- * @param sourceId Unique identifier for the audio source
1169
- * @param stream The audio stream to route
1170
- * @param targetIds Array of target IDs to route to
1171
- * @param mode Routing mode (defaults to configured default)
1172
- */
1173
- route(sourceId: string, stream: Readable, targetIds: string[], mode?: AudioRoutingMode, onCleanup?: () => void): Promise<void>;
1174
- /**
1175
- * Simulcast mode: Clone single stream to all targets
1176
- * Uses PassThrough streams to multiplex
1177
- */
1178
- private routeSimulcast;
1179
- /**
1180
- * Independent mode: Separate streams per target
1181
- * Note: This requires the source to provide multiple independent streams
1182
- * For now, this will use the same stream but track separately
1183
- */
1184
- private routeIndependent;
1185
- /**
1186
- * Stop routing for a source
1187
- */
1188
- unroute(sourceId: string): Promise<void>;
1189
- /**
1190
- * Stop all active routes
1191
- */
1192
- unrouteAll(): Promise<void>;
1193
- /**
1194
- * Get active routes
1195
- */
1196
- getActiveRoutes(): Array<{
1197
- sourceId: string;
1198
- targetIds: string[];
1199
- mode: AudioRoutingMode;
1200
- }>;
1201
- /**
1202
- * Set default routing mode
1203
- */
1204
- setDefaultMode(mode: AudioRoutingMode): void;
1205
- /**
1206
- * Get default routing mode
1207
- */
1208
- getDefaultMode(): AudioRoutingMode;
1209
- /**
1210
- * Get all registered routing target IDs
1211
- */
1212
- getRegisteredTargetIds(): string[];
1213
- /**
1214
- * Check if a source is currently routed
1215
- */
1216
- isRouted(sourceId: string): boolean;
1217
- /**
1218
- * Get route info for a source
1219
- */
1220
- getRoute(sourceId: string): {
1221
- sourceId: string;
1222
- targetIds: string[];
1223
- mode: AudioRoutingMode;
1224
- } | undefined;
1225
- /**
1226
- * Add target to existing route
1227
- */
1228
- addTargetToRoute(sourceId: string, targetId: string): Promise<void>;
1229
- /**
1230
- * Remove target from existing route
1231
- */
1232
- removeTargetFromRoute(sourceId: string, targetId: string): Promise<void>;
1233
- private startTargetPlayback;
1234
- private stopTargetPlayback;
1235
- }
1236
-
1237
- /**
1238
- * Configuration for a mix session
1239
- */
1240
- interface MixConfig {
1241
- name: string;
1242
- zones: Record<string, string[]>;
1243
- routing?: {
1244
- mode: AudioRoutingMode;
1245
- mappings: Record<string, string[]>;
1246
- };
1247
- autoCleanup?: boolean;
1248
- cleanupDelay?: number;
1249
- metadata?: Record<string, unknown>;
1250
- }
1251
- /**
1252
- * Active mix session state
1253
- */
1254
- interface MixSession {
1255
- id: string;
1256
- config: MixConfig;
1257
- startedAt: number;
1258
- cleanupTimeout?: NodeJS.Timeout;
1259
- }
1260
- /**
1261
- * MixSessionManager handles runtime mixing configurations
1262
- * Manages temporary bot assignments, routing, and auto-cleanup
1263
- */
1264
- declare class MixSessionManager {
1265
- private sessions;
1266
- private runtime;
1267
- constructor(runtime: IAgentRuntime);
1268
- /**
1269
- * Start a new mix session
1270
- * @param config Mix configuration
1271
- * @returns Created session
1272
- */
1273
- start(config: MixConfig): Promise<MixSession>;
1274
- /**
1275
- * End a mix session
1276
- * @param sessionId Session ID to end
1277
- */
1278
- end(sessionId: string): Promise<void>;
1279
- /**
1280
- * Update an existing session
1281
- * @param sessionId Session ID to update
1282
- * @param config Partial configuration to update
1283
- */
1284
- update(sessionId: string, config: Partial<MixConfig>): Promise<MixSession>;
1285
- /**
1286
- * Get a session by ID
1287
- */
1288
- get(sessionId: string): MixSession | undefined;
1289
- /**
1290
- * Get all active sessions
1291
- */
1292
- list(): MixSession[];
1293
- /**
1294
- * Find sessions by name
1295
- */
1296
- findByName(name: string): MixSession[];
1297
- /**
1298
- * End all sessions
1299
- */
1300
- endAll(): Promise<void>;
1301
- /**
1302
- * Check if a session exists
1303
- */
1304
- exists(sessionId: string): boolean;
1305
- /**
1306
- * Get active session count
1307
- */
1308
- count(): number;
1309
- /**
1310
- * Extend session cleanup timer
1311
- * @param sessionId Session ID
1312
- * @param additionalTime Additional time in milliseconds
1313
- */
1314
- extendSession(sessionId: string, additionalTime: number): void;
1315
- /**
1316
- * Cancel auto-cleanup for a session
1317
- */
1318
- cancelAutoCleanup(sessionId: string): void;
1319
- /**
1320
- * Save session to agent memory
1321
- * @private
1322
- */
1323
- private saveToMemory;
1324
- /**
1325
- * Remove session from agent memory
1326
- * @private
1327
- */
1328
- private removeFromMemory;
1329
- /**
1330
- * Load sessions from agent memory on startup
1331
- */
1332
- loadFromMemory(): Promise<void>;
1333
- /**
1334
- * Get session duration in milliseconds
1335
- */
1336
- getSessionDuration(sessionId: string): number;
1337
- /**
1338
- * Get session info summary
1339
- */
1340
- getSessionInfo(sessionId: string): {
1341
- id: string;
1342
- name: string;
1343
- duration: number;
1344
- zoneCount: number;
1345
- hasAutoCleanup: boolean;
1346
- } | undefined;
1347
- }
1348
-
1349
- /**
1350
- * A named group of voice targets
1351
- */
1352
- interface Zone {
1353
- name: string;
1354
- targetIds: string[];
1355
- createdAt: number;
1356
- metadata?: Record<string, unknown>;
1357
- }
1358
- /**
1359
- * ZoneManager manages logical groupings of voice targets
1360
- * Zones allow routing audio to named collections of targets
1361
- */
1362
- declare class ZoneManager {
1363
- private zones;
1364
- /**
1365
- * Create a new zone
1366
- * @param name Zone name
1367
- * @param targetIds Array of target IDs in this zone
1368
- * @param metadata Optional metadata for the zone
1369
- */
1370
- create(name: string, targetIds: string[], metadata?: Record<string, unknown>): Zone;
1371
- /**
1372
- * Delete a zone
1373
- */
1374
- delete(name: string): boolean;
1375
- /**
1376
- * Get a zone by name
1377
- */
1378
- get(name: string): Zone | undefined;
1379
- /**
1380
- * Get all zones
1381
- */
1382
- list(): Zone[];
1383
- /**
1384
- * Check if a zone exists
1385
- */
1386
- exists(name: string): boolean;
1387
- /**
1388
- * Add target to a zone
1389
- */
1390
- addTarget(zoneName: string, targetId: string): void;
1391
- /**
1392
- * Remove target from a zone
1393
- */
1394
- removeTarget(zoneName: string, targetId: string): void;
1395
- /**
1396
- * Get all target IDs in a zone
1397
- */
1398
- getTargets(zoneName: string): string[];
1399
- /**
1400
- * Update zone metadata
1401
- */
1402
- updateMetadata(zoneName: string, metadata: Record<string, unknown>): void;
1403
- /**
1404
- * Find all zones containing a specific target
1405
- */
1406
- findZonesWithTarget(targetId: string): Zone[];
1407
- /**
1408
- * Clear all zones
1409
- */
1410
- clear(): void;
1411
- /**
1412
- * Get zone count
1413
- */
1414
- count(): number;
1415
- /**
1416
- * Merge multiple zones into a new zone
1417
- */
1418
- merge(newZoneName: string, zoneNames: string[]): Zone;
1419
- /**
1420
- * Clone a zone with a new name
1421
- */
1422
- clone(sourceName: string, newName: string): Zone;
1423
- /**
1424
- * Get targets that are in multiple zones (intersection)
1425
- */
1426
- getIntersection(zoneNames: string[]): string[];
1427
- /**
1428
- * Get all unique targets across multiple zones (union)
1429
- */
1430
- getUnion(zoneNames: string[]): string[];
1431
- }
1432
-
1433
- interface RoutingTarget {
1434
- id: string;
1435
- type: string;
1436
- guildId?: string;
1437
- channelId?: string;
1438
- play?: (stream: NodeJS.ReadableStream, opts?: Record<string, unknown>) => Promise<unknown>;
1439
- playAudio?: (stream: NodeJS.ReadableStream, opts?: Record<string, unknown>) => Promise<unknown>;
1440
- feed?: (stream: NodeJS.ReadableStream) => Promise<unknown>;
1441
- stop?: () => Promise<unknown>;
1442
- stopAudio?: () => Promise<unknown>;
1443
- [key: string]: unknown;
1444
- }
1445
- /**
1446
- * Music service that manages music queues and playback
1447
- * Pure playback engine - analytics/preferences are optional via music-library plugin
1448
- */
1449
- declare class MusicService extends Service {
1450
- static serviceType: string;
1451
- capabilityDescription: string;
1452
- private queues;
1453
- private broadcasts;
1454
- private voiceManager;
1455
- private audioCache;
1456
- private readonly audioRouter;
1457
- private readonly zoneManager;
1458
- constructor(runtime?: IAgentRuntime);
1459
- static start(runtime: IAgentRuntime): Promise<MusicService>;
1460
- stop(): Promise<void>;
1461
- /**
1462
- * Initialize the service with voice manager
1463
- */
1464
- setVoiceManager(voiceManager: VoiceManagerLike): void;
1465
- getAudioRouter(): AudioRouter;
1466
- getZoneManager(): ZoneManager;
1467
- setRoutingMode(mode: AudioRoutingMode): void;
1468
- getRoutingMode(): AudioRoutingMode;
1469
- registerRoutingTargets(targets: RoutingTarget[]): void;
1470
- unregisterRoutingTarget(targetId: string): void;
1471
- listRoutingTargets(): string[];
1472
- startBroadcastRoute(sourceId: string, targetIds: string[], mode?: AudioRoutingMode): Promise<{
1473
- sourceId: string;
1474
- targetIds: string[];
1475
- mode: AudioRoutingMode;
1476
- }>;
1477
- stopBroadcastRoute(sourceId: string): Promise<void>;
1478
- getRoutingStatus(): {
1479
- mode: AudioRoutingMode;
1480
- activeRoutes: Array<{
1481
- sourceId: string;
1482
- targetIds: string[];
1483
- mode: AudioRoutingMode;
1484
- }>;
1485
- registeredTargets: string[];
1486
- zoneCount: number;
1487
- };
1488
- /**
1489
- * Get or create a queue for a guild
1490
- */
1491
- getQueue(guildId: string): MusicQueue;
1492
- /**
1493
- * Get all queues (for external monitoring)
1494
- */
1495
- getQueues(): Map<string, MusicQueue>;
1496
- /**
1497
- * Get audio cache service
1498
- */
1499
- getAudioCache(): AudioCacheService;
1500
- /**
1501
- * Add a track to the queue
1502
- */
1503
- addTrack(guildId: string, track: Omit<QueuedTrack, "id" | "addedAt">): Promise<QueuedTrack>;
1504
- /**
1505
- * Skip current track
1506
- */
1507
- skip(guildId: string, skipperEntityId?: UUID): Promise<boolean>;
1508
- /**
1509
- * Pause playback
1510
- */
1511
- pause(guildId: string): Promise<void>;
1512
- /**
1513
- * Resume playback
1514
- */
1515
- resume(guildId: string): Promise<void>;
1516
- /**
1517
- * Stop playback for a specific guild
1518
- */
1519
- stopPlayback(guildId: string): Promise<void>;
1520
- /**
1521
- * Clear queue
1522
- */
1523
- clear(guildId: string): void;
1524
- /**
1525
- * Get queue
1526
- */
1527
- getQueueList(guildId: string): QueuedTrack[];
1528
- /**
1529
- * Get current track
1530
- */
1531
- getCurrentTrack(guildId: string): QueuedTrack | null;
1532
- /**
1533
- * Get or create a broadcast for a guild
1534
- * Broadcasts provide a clean interface for multiple consumers (Discord, Web, etc.)
1535
- * to subscribe to the audio stream independently.
1536
- * @param guildId Guild/server ID
1537
- * @returns IAudioBroadcast instance
1538
- */
1539
- getBroadcast(guildId: string): IAudioBroadcast;
1540
- /**
1541
- * Remove a track from queue
1542
- */
1543
- removeTrack(guildId: string, trackId: string): boolean;
1544
- /**
1545
- * Shuffle queue
1546
- */
1547
- shuffle(guildId: string): void;
1548
- /**
1549
- * Get whether queue is currently playing
1550
- */
1551
- getIsPlaying(guildId: string): boolean;
1552
- /**
1553
- * Get whether playback is paused
1554
- */
1555
- getIsPaused(guildId: string): boolean;
1556
- /**
1557
- * Handle track played event (optional analytics integration)
1558
- */
1559
- private handleTrackPlayed;
1560
- /**
1561
- * Helper to get roomId from guildId via Discord service
1562
- */
1563
- private getRoomIdFromGuild;
1564
- /**
1565
- * Update Discord listening activity to show currently playing track
1566
- */
1567
- private updateDiscordListeningActivity;
1568
- /**
1569
- * Clear Discord activity when music stops
1570
- */
1571
- private clearDiscordActivity;
1572
- /**
1573
- * Auto-subscribe Discord to broadcast (enables new architecture without requiring radio plugin)
1574
- *
1575
- * WHY AUTO-WIRING:
1576
- * Design goal: music-player + discord should work together WITHOUT needing radio plugin.
1577
- * Radio can add advanced features, but shouldn't be required for basic playback.
1578
- *
1579
- * THE CHALLENGE:
1580
- * - MusicQueue pushes audio to broadcast
1581
- * - Discord needs to subscribe to broadcast
1582
- * - But music-player shouldn't import discord plugin (circular dependency)
1583
- *
1584
- * THE SOLUTION:
1585
- * When a track starts, MusicService (which has access to runtime) checks:
1586
- * 1. Is discord service available? (optional dependency)
1587
- * 2. Is Discord connected to this guild?
1588
- * 3. If yes, auto-subscribe Discord to the broadcast
1589
- * 4. If not yet connected, attach listener to subscribe when it connects
1590
- *
1591
- * This happens transparently - user just plays music, and it works.
1592
- *
1593
- * WHY HANDLE RECONNECTS HERE:
1594
- * Discord will emit status changes (connected/disconnected). MusicService listens
1595
- * and auto-resubscribes on reconnect. This keeps the reconnection logic in one place.
1596
- *
1597
- * GRACEFUL DEGRADATION:
1598
- * If discord service doesn't exist, or doesn't support IAudioSink, this silently
1599
- * does nothing. Old architecture keeps working.
1600
- */
1601
- private autoSubscribeDiscord;
1602
- }
1603
-
1604
- interface DetectedMusicEntity {
1605
- type: "artist" | "album" | "song";
1606
- name: string;
1607
- confidence: number;
1608
- context?: string;
1609
- }
1610
- /**
1611
- * Service for detecting music entity names (artists, albums, songs) from text
1612
- * Uses LLM for intelligent extraction with caching
1613
- */
1614
- declare class MusicEntityDetectionHelper {
1615
- capabilityDescription: string;
1616
- private cache;
1617
- private readonly CACHE_TTL;
1618
- private readonly runtime?;
1619
- constructor(runtime?: IAgentRuntime);
1620
- stop(): Promise<void>;
1621
- /**
1622
- * Detect music entities from text using LLM
1623
- */
1624
- detectEntities(text: string): Promise<DetectedMusicEntity[]>;
1625
- /**
1626
- * Clear cache
1627
- */
1628
- clearCache(): void;
1629
- /**
1630
- * Clear expired cache entries
1631
- */
1632
- clearExpiredCache(): void;
1633
- }
1634
-
1635
- /**
1636
- * Audio features data structure for music tracks
1637
- * Based on Spotify's audio features API
1638
- */
1639
- interface AudioFeatures {
1640
- trackId?: string;
1641
- danceability: number;
1642
- energy: number;
1643
- valence: number;
1644
- acousticness: number;
1645
- instrumentalness: number;
1646
- liveness: number;
1647
- speechiness: number;
1648
- key: number;
1649
- mode: number;
1650
- tempo: number;
1651
- timeSignature: number;
1652
- loudness: number;
1653
- duration: number;
1654
- source?: "spotify" | "computed" | "manual";
1655
- }
1656
- /**
1657
- * Simplified audio features for recommendations
1658
- */
1659
- interface AudioFeatureSeed {
1660
- targetDanceability?: number;
1661
- targetEnergy?: number;
1662
- targetValence?: number;
1663
- targetTempo?: number;
1664
- targetLoudness?: number;
1665
- targetAcousticness?: number;
1666
- targetInstrumentalness?: number;
1667
- targetPopularity?: number;
1668
- }
1669
- /**
1670
- * Track recommendation request
1671
- */
1672
- interface RecommendationRequest {
1673
- seedArtists?: string[];
1674
- seedTracks?: string[];
1675
- seedGenres?: string[];
1676
- audioFeatures?: AudioFeatureSeed;
1677
- limit?: number;
1678
- }
1679
- /**
1680
- * Track recommendation result
1681
- */
1682
- interface TrackRecommendation {
1683
- trackName: string;
1684
- artistName: string;
1685
- albumName?: string;
1686
- url?: string;
1687
- previewUrl?: string;
1688
- audioFeatures?: AudioFeatures;
1689
- popularity?: number;
1690
- }
1691
-
1692
- /**
1693
- * Music information types
1694
- */
1695
- interface TrackInfo {
1696
- title: string;
1697
- artist: string;
1698
- album?: string;
1699
- duration?: number;
1700
- genre?: string[];
1701
- year?: number;
1702
- url?: string;
1703
- thumbnail?: string;
1704
- description?: string;
1705
- tags?: string[];
1706
- lyrics?: string;
1707
- lyricsUrl?: string;
1708
- }
1709
- interface ArtistInfo {
1710
- name: string;
1711
- genres?: string[];
1712
- bio?: string;
1713
- image?: string;
1714
- imageThumb?: string;
1715
- imageLogo?: string;
1716
- imageFanart?: string;
1717
- imageBanner?: string;
1718
- similarArtists?: string[];
1719
- albums?: string[];
1720
- topTracks?: string[];
1721
- }
1722
- interface AlbumInfo {
1723
- title: string;
1724
- artist: string;
1725
- year?: number;
1726
- genre?: string[];
1727
- tracks?: string[];
1728
- coverArt?: string;
1729
- coverArtThumb?: string;
1730
- coverArtCD?: string;
1731
- description?: string;
1732
- }
1733
- interface MusicInfoResult {
1734
- track?: TrackInfo;
1735
- artist?: ArtistInfo;
1736
- album?: AlbumInfo;
1737
- source: "youtube" | "spotify" | "lastfm" | "musicbrainz" | "wikipedia" | "genius" | "theaudiodb";
1738
- }
1739
-
1740
- /**
1741
- * Service status tracking for music info services
1742
- */
1743
- declare enum ServiceStatus {
1744
- ACTIVE = "active",
1745
- DEGRADED = "degraded",// Available but experiencing issues
1746
- UNAVAILABLE = "unavailable",
1747
- NOT_CONFIGURED = "not_configured"
1748
- }
1749
- interface ServiceHealth {
1750
- status: ServiceStatus;
1751
- lastChecked: number;
1752
- lastError?: string;
1753
- responseTime?: number;
1754
- }
1755
- interface MusicInfoServiceStatus {
1756
- musicBrainz: ServiceHealth;
1757
- lastFm: ServiceHealth;
1758
- genius: ServiceHealth;
1759
- theAudioDb: ServiceHealth;
1760
- wikipedia: ServiceHealth;
1761
- }
1762
-
1763
- /**
1764
- * Service for Wikipedia API access
1765
- * Free, no authentication required
1766
- * Rate limit: 2 requests per second (shared across all agents per instance)
1767
- */
1768
- declare class WikipediaClient {
1769
- capabilityDescription: string;
1770
- private readonly baseUrl;
1771
- private readonly searchUrl;
1772
- private readonly rateLimiter;
1773
- stop(): Promise<void>;
1774
- /**
1775
- * Search for a Wikipedia page and get summary
1776
- */
1777
- private getPageSummary;
1778
- /**
1779
- * Search Wikipedia for pages matching a query
1780
- */
1781
- private searchPages;
1782
- /**
1783
- * Get track information from Wikipedia
1784
- */
1785
- getTrackInfo(trackName: string, artistName?: string): Promise<TrackInfo | null>;
1786
- /**
1787
- * Get artist information from Wikipedia
1788
- */
1789
- getArtistInfo(artistName: string): Promise<ArtistInfo | null>;
1790
- /**
1791
- * Get album information from Wikipedia
1792
- */
1793
- getAlbumInfo(albumTitle: string, artistName?: string): Promise<AlbumInfo | null>;
1794
- /**
1795
- * Clean Wikipedia extract text (remove HTML, truncate)
1796
- */
1797
- private cleanExtract;
1798
- /**
1799
- * Try to extract artist name from Wikipedia extract
1800
- */
1801
- private extractArtistFromExtract;
1802
- /**
1803
- * Extract related artists, influences, and similar acts from Wikipedia extract
1804
- * Looks for patterns like "influenced by", "similar to", "associated acts", etc.
1805
- * This data helps drive intelligent music selection and discovery
1806
- */
1807
- private extractRelatedArtists;
1808
- }
1809
-
1810
- /**
1811
- * Service for fetching music information from authoritative sources.
1812
- * Track metadata comes from YouTube for direct URLs and MusicBrainz for
1813
- * text queries; artist and album metadata comes from MusicBrainz only.
1814
- */
1815
- declare class MusicInfoHelper {
1816
- capabilityDescription: string;
1817
- private cache;
1818
- private readonly CACHE_TTL;
1819
- private musicBrainzClient;
1820
- private lastFmClient;
1821
- private geniusClient;
1822
- private theAudioDbClient;
1823
- private readonly wikipediaClient;
1824
- private serviceStatus;
1825
- constructor(runtime?: IAgentRuntime, wikipediaClient?: WikipediaClient | null);
1826
- stop(): Promise<void>;
1827
- /**
1828
- * Get service status for all integrated APIs
1829
- */
1830
- getServiceStatus(): MusicInfoServiceStatus;
1831
- /**
1832
- * Validate API keys for all configured services
1833
- * Updates service status based on validation results
1834
- */
1835
- private validateApiKeys;
1836
- /**
1837
- * Extract track information from a YouTube URL or MusicBrainz lookup.
1838
- */
1839
- getTrackInfo(urlOrTitle: string): Promise<MusicInfoResult | null>;
1840
- /**
1841
- * Parse title string to extract artist and track name
1842
- */
1843
- private parseTitle;
1844
- /**
1845
- * Get artist information from MusicBrainz.
1846
- */
1847
- getArtistInfo(artistName: string): Promise<ArtistInfo | null>;
1848
- /**
1849
- * Get album information from MusicBrainz.
1850
- */
1851
- getAlbumInfo(albumTitle: string, artistName?: string): Promise<AlbumInfo | null>;
1852
- /**
1853
- * Check if a string is a YouTube URL
1854
- */
1855
- private isYouTubeUrl;
1856
- /**
1857
- * Extract information from YouTube URL using play-dl
1858
- */
1859
- private getInfoFromYouTube;
1860
- /**
1861
- * Clear cache
1862
- */
1863
- clearCache(): void;
1864
- /**
1865
- * Clear expired cache entries
1866
- */
1867
- clearExpiredCache(): void;
1868
- /**
1869
- * Pre-warm cache for a track (non-blocking)
1870
- * This is called by plugin-dj to prepare caches before tracks are played
1871
- * @param urlOrTitle - YouTube URL or track title
1872
- */
1873
- prewarmTrackInfo(urlOrTitle: string): Promise<void>;
1874
- /**
1875
- * Pre-warm cache for multiple tracks (non-blocking)
1876
- * @param tracks - Array of YouTube URLs or track titles
1877
- */
1878
- prewarmTracks(tracks: string[]): Promise<void>;
1879
- }
1880
-
1881
- /**
1882
- * Client for Spotify Web API
1883
- * Provides access to audio features and track recommendations
1884
- */
1885
- declare class SpotifyClient {
1886
- private clientId;
1887
- private clientSecret;
1888
- private token;
1889
- private baseUrl;
1890
- private authUrl;
1891
- private lastRequestTime;
1892
- private minRequestInterval;
1893
- constructor(clientId?: string, clientSecret?: string);
1894
- /**
1895
- * Check if API credentials are configured
1896
- */
1897
- isConfigured(): boolean;
1898
- /**
1899
- * Get or refresh access token
1900
- */
1901
- private getAccessToken;
1902
- /**
1903
- * Rate limiting helper
1904
- */
1905
- private rateLimit;
1906
- /**
1907
- * Search for a track on Spotify
1908
- */
1909
- searchTrack(query: string): Promise<string | null>;
1910
- /**
1911
- * Get audio features for a track
1912
- */
1913
- getAudioFeatures(trackId: string): Promise<AudioFeatures | null>;
1914
- /**
1915
- * Get audio features for a track by search query
1916
- */
1917
- getAudioFeaturesByQuery(query: string): Promise<AudioFeatures | null>;
1918
- /**
1919
- * Get track recommendations
1920
- */
1921
- getRecommendations(request: RecommendationRequest): Promise<TrackRecommendation[]>;
1922
- /**
1923
- * Validate API credentials
1924
- */
1925
- validateCredentials(): Promise<boolean>;
1926
- }
1927
-
1928
- interface WikipediaExtractionContext {
1929
- purpose: "dj_intro" | "music_selection" | "general_info" | "related_artists";
1930
- currentArtist?: string;
1931
- currentTrack?: string;
1932
- currentAlbum?: string;
1933
- }
1934
- interface ExtractedMusicInfo {
1935
- artist?: Partial<ArtistInfo>;
1936
- track?: Partial<TrackInfo>;
1937
- album?: Partial<AlbumInfo>;
1938
- relatedArtists?: string[];
1939
- influences?: string[];
1940
- genres?: string[];
1941
- interestingFacts?: string[];
1942
- selectionSuggestions?: string[];
1943
- }
1944
- /**
1945
- * Service that uses LLMs to dynamically extract relevant information from Wikipedia
1946
- * Based on context (e.g., DJ intro, music selection), extracts different information
1947
- */
1948
- declare class WikipediaExtractionHelper {
1949
- capabilityDescription: string;
1950
- private cache;
1951
- private readonly CACHE_TTL;
1952
- private readonly runtime?;
1953
- private readonly wikipediaClient;
1954
- constructor(runtime?: IAgentRuntime, wikipediaClient?: WikipediaClient | null);
1955
- private getWikipediaService;
1956
- stop(): Promise<void>;
1957
- /**
1958
- * Extract music information from Wikipedia using LLM based on context
1959
- */
1960
- extractFromWikipedia(entityName: string, entityType: "artist" | "album" | "song", context: WikipediaExtractionContext): Promise<ExtractedMusicInfo | null>;
1961
- /**
1962
- * Build extraction prompt based on context
1963
- */
1964
- private buildExtractionPrompt;
1965
- /**
1966
- * Parse LLM extraction response
1967
- */
1968
- private parseExtractionResponse;
1969
- /**
1970
- * Extract list items from text using pattern
1971
- */
1972
- private extractList;
1973
- /**
1974
- * Clear cache
1975
- */
1976
- clearCache(): void;
1977
- /**
1978
- * Clear expired cache entries
1979
- */
1980
- clearExpiredCache(): void;
1981
- }
1982
-
1983
- interface YouTubeSearchResult {
1984
- url: string;
1985
- title: string;
1986
- duration?: number;
1987
- channel?: string;
1988
- views?: number;
1989
- }
1990
- /**
1991
- * Service for searching YouTube videos
1992
- * Centralizes YouTube search logic for reuse across multiple actions
1993
- */
1994
- declare class YouTubeSearchHelper {
1995
- capabilityDescription: string;
1996
- private cache;
1997
- private readonly CACHE_TTL;
1998
- stop(): Promise<void>;
1999
- /**
2000
- * Clear all cached searches
2001
- */
2002
- clearCache(): void;
2003
- /**
2004
- * Search YouTube for videos
2005
- * @param query - Search query string
2006
- * @param options - Search options
2007
- * @returns Array of search results
2008
- */
2009
- search(query: string, options?: {
2010
- limit?: number;
2011
- includeShorts?: boolean;
2012
- }): Promise<YouTubeSearchResult[]>;
2013
- /**
2014
- * Search and return the top result
2015
- * @param query - Search query
2016
- * @returns Top search result or null
2017
- */
2018
- searchOne(query: string): Promise<YouTubeSearchResult | null>;
2019
- /**
2020
- * Validate if a URL is a valid YouTube video
2021
- */
2022
- validateUrl(url: string): Promise<boolean>;
2023
- /**
2024
- * Get video info from URL
2025
- */
2026
- getVideoInfo(url: string): Promise<YouTubeSearchResult | null>;
2027
- }
2028
-
2029
- interface AggregatedRoomPreferences {
2030
- favoriteTracks: Array<{
2031
- url: string;
2032
- title: string;
2033
- requestedBy: UUID[];
2034
- playCount: number;
2035
- }>;
2036
- dislikedTracks: string[];
2037
- }
2038
- declare class MusicLibraryService extends Service {
2039
- static serviceType: string;
2040
- capabilityDescription: string;
2041
- readonly spotifyClient: SpotifyClient;
2042
- readonly repetitionControl: RepetitionControl;
2043
- private readonly youtubeSearch;
2044
- private readonly wikipedia;
2045
- private readonly musicInfo;
2046
- private readonly entityDetection;
2047
- private readonly wikipediaExtraction;
2048
- constructor(runtime?: IAgentRuntime);
2049
- static start(runtime: IAgentRuntime): Promise<MusicLibraryService>;
2050
- stop(): Promise<void>;
2051
- private ensureRuntime;
2052
- addSong(songData: Parameters<typeof addSongToLibrary>[1]): Promise<LibrarySong>;
2053
- getSong(url: string): Promise<LibrarySong | null>;
2054
- getRecentSongs(limit?: number): Promise<LibrarySong[]>;
2055
- getLastPlayedSong(): Promise<LibrarySong | null>;
2056
- getMostPlayedSongs(limit?: number): Promise<LibrarySong[]>;
2057
- searchLibrary(query: string, limit?: number): Promise<LibrarySong[]>;
2058
- getLibraryStats(): Promise<{
2059
- totalSongs: number;
2060
- totalPlays: number;
2061
- mostPlayed?: LibrarySong;
2062
- }>;
2063
- savePlaylist(entityId: UUID, playlist: Parameters<typeof savePlaylist>[2]): Promise<Playlist>;
2064
- loadPlaylists(entityId: UUID): Promise<Playlist[]>;
2065
- deletePlaylist(entityId: UUID, playlistId: string): Promise<boolean>;
2066
- getUserPreferences(entityId: UUID): Promise<UserMusicPreferences | null>;
2067
- getRoomPreferences(roomId: UUID): Promise<Map<UUID, UserMusicPreferences>>;
2068
- getAggregatedRoomPreferences(roomId: UUID): Promise<AggregatedRoomPreferences>;
2069
- trackTrackRequest(entityId: UUID, track: {
2070
- url: string;
2071
- title: string;
2072
- }, roomId?: UUID, worldId?: UUID): Promise<void>;
2073
- trackSkip(entityId: UUID, trackUrl: string, roomId?: UUID, worldId?: UUID): Promise<void>;
2074
- trackFavorite(entityId: UUID, track: {
2075
- url: string;
2076
- title: string;
2077
- }, roomId?: UUID, worldId?: UUID): Promise<void>;
2078
- trackTrackPlayed(roomId: UUID, track: {
2079
- url: string;
2080
- title: string;
2081
- }, duration: number, requestedBy?: {
2082
- entityId: UUID;
2083
- name: string;
2084
- }): Promise<void>;
2085
- getAnalytics(roomId: UUID): Promise<DJAnalytics | null>;
2086
- getSongMemory(url: string): Promise<SongMemory | null>;
2087
- recordSongPlay(song: Parameters<typeof recordSongPlay>[1], context: Parameters<typeof recordSongPlay>[2]): Promise<void>;
2088
- recordSongRequest(song: Parameters<typeof recordSongRequest>[1], requester: Parameters<typeof recordSongRequest>[2]): Promise<void>;
2089
- recordSongDedication(url: string, dedication: Parameters<typeof recordSongDedication>[2]): Promise<void>;
2090
- trackDJTip(roomId: UUID, tip: Omit<DJTip, "roomId">): Promise<void>;
2091
- getDJTipStats(): Promise<DJTipStats>;
2092
- search(query: string, options?: {
2093
- limit?: number;
2094
- includeShorts?: boolean;
2095
- }): Promise<YouTubeSearchResult[]>;
2096
- searchYouTube(query: string, options?: {
2097
- limit?: number;
2098
- includeShorts?: boolean;
2099
- }): Promise<YouTubeSearchResult[]>;
2100
- searchOneYouTube(query: string): Promise<YouTubeSearchResult | null>;
2101
- validateYouTubeUrl(url: string): Promise<boolean>;
2102
- getYouTubeVideoInfo(url: string): Promise<YouTubeSearchResult | null>;
2103
- getServiceStatus(): MusicInfoServiceStatus;
2104
- getTrackInfo(urlOrTitle: string): Promise<MusicInfoResult | null>;
2105
- getArtistInfo(artistName: string): Promise<ArtistInfo | null>;
2106
- getAlbumInfo(albumTitle: string, artistName?: string): Promise<AlbumInfo | null>;
2107
- prewarmTrackInfo(urlOrTitle: string): Promise<void>;
2108
- prewarmTracks(tracks: string[]): Promise<void>;
2109
- detectEntities(text: string): Promise<DetectedMusicEntity[]>;
2110
- getWikipediaTrackInfo(trackName: string, artistName?: string): Promise<TrackInfo | null>;
2111
- getWikipediaArtistInfo(artistName: string): Promise<ArtistInfo | null>;
2112
- getWikipediaAlbumInfo(albumTitle: string, artistName?: string): Promise<AlbumInfo | null>;
2113
- extractFromWikipedia(entityName: string, entityType: "artist" | "album" | "song", context: WikipediaExtractionContext): Promise<ExtractedMusicInfo | null>;
2114
- }
2115
-
2116
- interface StoredTrack {
2117
- url: string;
2118
- title: string;
2119
- artist?: string;
2120
- album?: string;
2121
- filePath: string;
2122
- format: string;
2123
- size: number;
2124
- duration?: number;
2125
- bitrate?: number;
2126
- storedAt: number;
2127
- }
2128
- /**
2129
- * Music Storage Service
2130
- * Stores high-quality original music files for archival and library purposes
2131
- *
2132
- * Unlike the Discord-optimized cache in music-player, this stores:
2133
- * - Original quality files (no transcoding)
2134
- * - Permanent storage (not temporary cache)
2135
- * - Organized by artist/album/track
2136
- * - Indexed for library browsing
2137
- */
2138
- declare class MusicStorageService {
2139
- private storageDir;
2140
- private highQuality;
2141
- private index;
2142
- private readonly INDEX_FILE;
2143
- constructor(storageDir?: string, highQuality?: boolean);
2144
- /**
2145
- * Check if a track is stored
2146
- */
2147
- isStored(url: string): boolean;
2148
- /**
2149
- * Get stored track info
2150
- */
2151
- getStoredTrack(url: string): StoredTrack | null;
2152
- /**
2153
- * Get all stored tracks
2154
- */
2155
- getAllTracks(): StoredTrack[];
2156
- /**
2157
- * Store a track from YouTube URL
2158
- */
2159
- storeTrack(url: string, metadata: {
2160
- title: string;
2161
- artist?: string;
2162
- album?: string;
2163
- }): Promise<StoredTrack>;
2164
- /**
2165
- * Get a readable stream for a stored track
2166
- */
2167
- getStream(url: string): Readable | null;
2168
- /**
2169
- * Delete a stored track
2170
- */
2171
- deleteTrack(url: string): boolean;
2172
- /**
2173
- * Get total storage size in bytes
2174
- */
2175
- getTotalSize(): number;
2176
- /**
2177
- * Get storage statistics
2178
- */
2179
- getStats(): {
2180
- totalTracks: number;
2181
- totalSize: number;
2182
- totalDuration: number;
2183
- byArtist: Map<string, number>;
2184
- };
2185
- /**
2186
- * Load index from disk
2187
- */
2188
- private loadIndex;
2189
- /**
2190
- * Save index to disk
2191
- */
2192
- private saveIndex;
2193
- }
2194
-
2195
- interface FetchProgress {
2196
- stage: "checking_library" | "trying_ytdlp" | "searching_torrents" | "downloading_torrents" | "indexing" | "ready" | "failed";
2197
- message: string;
2198
- details?: unknown;
2199
- }
2200
- interface FetchResult {
2201
- success: boolean;
2202
- source: "library" | "ytdlp" | "torrent";
2203
- url?: string;
2204
- files?: string[];
2205
- error?: string;
2206
- }
2207
- interface SmartFetchOptions {
2208
- query: string;
2209
- requestedBy?: UUID;
2210
- onProgress?: (progress: FetchProgress) => void;
2211
- preferredQuality?: "flac" | "mp3_320" | "any";
2212
- parallelDownloads?: number;
2213
- }
2214
- /**
2215
- * Smart music fetch service that tries multiple sources automatically
2216
- * 1. Check music library
2217
- * 2. Try yt-dlp (YouTube, SoundCloud, etc.)
2218
- * 3. Search and download torrents (2-3 in parallel)
2219
- * 4. Notify when ready
2220
- */
2221
- declare class SmartMusicFetchService extends Service {
2222
- static serviceType: string;
2223
- capabilityDescription: string;
2224
- static start(runtime: IAgentRuntime): Promise<SmartMusicFetchService>;
2225
- stop(): Promise<void>;
2226
- /**
2227
- * Smart fetch music from any available source
2228
- */
2229
- fetchMusic(options: SmartFetchOptions): Promise<FetchResult>;
2230
- /**
2231
- * Check if music exists in library
2232
- */
2233
- private checkMusicLibrary;
2234
- /**
2235
- * Try to find and get URL from YouTube/SoundCloud via search
2236
- */
2237
- private tryYtdlp;
2238
- /**
2239
- * Search for music torrents with quality scoring
2240
- */
2241
- private searchMusicTorrents;
2242
- /**
2243
- * Calculate quality score for a torrent
2244
- * Considers: quality match, seeders, and file format
2245
- */
2246
- private calculateQualityScore;
2247
- /**
2248
- * Check if torrent is likely music
2249
- */
2250
- private isMusicTorrent;
2251
- /**
2252
- * Download multiple torrents in parallel, return first to complete
2253
- */
2254
- private downloadTorrentsParallel;
2255
- /**
2256
- * Wait for torrent to complete
2257
- */
2258
- private waitForTorrentCompletion;
2259
- }
2260
-
2261
- declare const musicPlugin: Plugin;
2262
-
2263
- export { type AlbumInfo, type ArtistInfo, type AudioFeatureSeed, type AudioFeatures, type AudioRouteConfig, AudioRouter, type AudioRoutingMode, type AudioSubscription, Broadcast, type BroadcastState, type BroadcastTrackMetadata, type CrossFadeOptions, DEFAULT_DJ_INTRO_OPTIONS, DEFAULT_GUILD_SETTINGS, type DJAnalytics, type DJGuildSettings, type DJIntroOptions, type DJTip, type DJTipStats, type DetectedMusicEntity, type ExtractedMusicInfo, type FetchProgress, type FetchResult, type IAudioBroadcast, type LibrarySong, type MixConfig, type MixSession, MixSessionManager, MusicEntityDetectionHelper, MusicEntityDetectionHelper as MusicEntityDetectionService, MusicInfoHelper, type MusicInfoResult, MusicInfoHelper as MusicInfoService, type MusicInfoServiceStatus, MusicLibraryService, MusicService, MusicStorageService, type Playlist, type PlaylistTrack, type QueuedTrack, type RecommendationRequest, type ServiceHealth, ServiceStatus, type SmartFetchOptions, SmartMusicFetchService, type SongMemory, SpotifyClient, type StoredTrack, type TrackInfo, type TrackRecommendation, type UserMusicPreferences, WikipediaClient, type WikipediaExtractionContext, WikipediaExtractionHelper, WikipediaExtractionHelper as WikipediaExtractionService, WikipediaClient as WikipediaService, YouTubeSearchHelper, type YouTubeSearchResult, YouTubeSearchHelper as YouTubeSearchService, type Zone, ZoneManager, addSongToLibrary, buildIntroPrompt, musicPlugin as default, deletePlaylist, getAllConfiguredGuilds, getAnalytics, getDJGuildSettings, getDJIntroOptions, getDJTipStats, getLastPlayedSong, getLibraryStats, getMostPlayedSongs, getMostRequestedSongs, getRecentSongs, getRecentTips, getRoomPreferences, getSong, getSongMemory, getTopSongs, getTopTippers, getUserPreferences, loadPlaylists, musicAction, recordSongDedication, recordSongPlay, recordSongRequest, repetitionControl, resetDJGuildSettings, resetDJIntroOptions, savePlaylist, searchLibrary, setAutonomyLevel, setDJGuildSettings, setDJIntroOptions, toggleDJ, trackDJTip, trackFavorite, trackListenerSnapshot, trackSession, trackSkip, trackTrackPlayed, trackTrackRequest, tryHandleMusicPlayerStatusFallback, updateUserPreferences };