ziplayer 0.2.1 → 0.2.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.
Files changed (42) hide show
  1. package/dist/structures/FilterManager.d.ts.map +1 -1
  2. package/dist/structures/FilterManager.js +3 -1
  3. package/dist/structures/FilterManager.js.map +1 -1
  4. package/dist/structures/Player.d.ts +1 -2
  5. package/dist/structures/Player.d.ts.map +1 -1
  6. package/dist/structures/Player.js +29 -22
  7. package/dist/structures/Player.js.map +1 -1
  8. package/dist/types/extension.d.ts +114 -0
  9. package/dist/types/extension.d.ts.map +1 -0
  10. package/dist/types/extension.js +3 -0
  11. package/dist/types/extension.js.map +1 -0
  12. package/dist/types/fillter.d.ts +44 -0
  13. package/dist/types/fillter.d.ts.map +1 -0
  14. package/dist/types/fillter.js +226 -0
  15. package/dist/types/fillter.js.map +1 -0
  16. package/dist/types/index.d.ts +14 -209
  17. package/dist/types/index.d.ts.map +1 -1
  18. package/dist/types/index.js +17 -223
  19. package/dist/types/index.js.map +1 -1
  20. package/dist/types/plugin.d.ts +58 -0
  21. package/dist/types/plugin.d.ts.map +1 -0
  22. package/dist/types/plugin.js +21 -0
  23. package/dist/types/plugin.js.map +1 -0
  24. package/package.json +7 -2
  25. package/src/structures/FilterManager.ts +267 -262
  26. package/src/structures/Player.ts +36 -27
  27. package/src/types/extension.ts +129 -0
  28. package/src/types/fillter.ts +264 -0
  29. package/src/types/index.ts +15 -443
  30. package/src/types/plugin.ts +57 -0
  31. package/dist/plugins/SoundCloudPlugin.d.ts +0 -22
  32. package/dist/plugins/SoundCloudPlugin.d.ts.map +0 -1
  33. package/dist/plugins/SoundCloudPlugin.js +0 -171
  34. package/dist/plugins/SoundCloudPlugin.js.map +0 -1
  35. package/dist/plugins/SpotifyPlugin.d.ts +0 -26
  36. package/dist/plugins/SpotifyPlugin.d.ts.map +0 -1
  37. package/dist/plugins/SpotifyPlugin.js +0 -183
  38. package/dist/plugins/SpotifyPlugin.js.map +0 -1
  39. package/dist/plugins/YouTubePlugin.d.ts +0 -25
  40. package/dist/plugins/YouTubePlugin.d.ts.map +0 -1
  41. package/dist/plugins/YouTubePlugin.js +0 -314
  42. package/dist/plugins/YouTubePlugin.js.map +0 -1
@@ -1,262 +1,267 @@
1
- import type { AudioFilter } from "../types";
2
- import { PREDEFINED_FILTERS } from "../types";
3
- import type { Player } from "./Player";
4
- import type { PlayerManager } from "./PlayerManager";
5
- import prism, { FFmpeg } from "prism-media";
6
- import type { Readable } from "stream";
7
-
8
- type DebugFn = (message?: any, ...optionalParams: any[]) => void;
9
-
10
- export class FilterManager {
11
- private activeFilters: AudioFilter[] = [];
12
- private debug: DebugFn;
13
- private player: Player;
14
- private ffmpeg: FFmpeg | null = null;
15
- public StreamType: "webm/opus" | "ogg/opus" | "mp3" | "arbitrary" = "mp3";
16
-
17
- constructor(player: Player, manager: PlayerManager) {
18
- this.player = player as Player;
19
-
20
- this.debug = (message?: any, ...optionalParams: any[]) => {
21
- if (manager.debugEnabled) {
22
- manager.emit("debug", `[FilterManager] ${message}`, ...optionalParams);
23
- }
24
- };
25
- }
26
-
27
- /**
28
- * Destroy the filter manager
29
- *
30
- * @returns {void}
31
- * @example
32
- * player.filter.destroy();
33
- */
34
- destroy(): void {
35
- this.activeFilters = [];
36
- if (this.ffmpeg) {
37
- try {
38
- this.ffmpeg.destroy();
39
- } catch {}
40
- this.ffmpeg = null;
41
- }
42
- }
43
-
44
- /**
45
- * Get the combined FFmpeg filter string for all active filters
46
- *
47
- * @returns {string} Combined FFmpeg filter string
48
- * @example
49
- * const filterString = player.getFilterString();
50
- * console.log(`Filter string: ${filterString}`);
51
- */
52
- public getFilterString(): string {
53
- if (this.activeFilters.length === 0) return "";
54
- return this.activeFilters.map((f) => f.ffmpegFilter).join(",");
55
- }
56
-
57
- /**
58
- * Get all currently applied filters
59
- *
60
- * @returns {AudioFilter[]} Array of active filters
61
- * @example
62
- * const filters = player.getActiveFilters();
63
- * console.log(`Active filters: ${filters.map(f => f.name).join(', ')}`);
64
- */
65
- public getActiveFilters(): AudioFilter[] {
66
- return [...this.activeFilters];
67
- }
68
-
69
- /**
70
- * Check if a specific filter is currently applied
71
- *
72
- * @param {string} filterName - Name of the filter to check
73
- * @returns {boolean} True if filter is applied
74
- * @example
75
- * const hasBassBoost = player.hasFilter("bassboost");
76
- * console.log(`Has bass boost: ${hasBassBoost}`);
77
- */
78
- public hasFilter(filterName: string): boolean {
79
- return this.activeFilters.some((f) => f.name === filterName);
80
- }
81
-
82
- /**
83
- * Get available predefined filters
84
- *
85
- * @returns {AudioFilter[]} Array of all predefined filters
86
- * @example
87
- * const availableFilters = player.getAvailableFilters();
88
- * console.log(`Available filters: ${availableFilters.length}`);
89
- */
90
- public getAvailableFilters(): AudioFilter[] {
91
- return Object.values(PREDEFINED_FILTERS);
92
- }
93
-
94
- /**
95
- * Get filters by category
96
- *
97
- * @param {string} category - Category to filter by
98
- * @returns {AudioFilter[]} Array of filters in the category
99
- * @example
100
- * const eqFilters = player.getFiltersByCategory("eq");
101
- * console.log(`EQ filters: ${eqFilters.map(f => f.name).join(', ')}`);
102
- */
103
- public getFiltersByCategory(category: string): AudioFilter[] {
104
- return Object.values(PREDEFINED_FILTERS).filter((f) => f.category === category);
105
- }
106
-
107
- /**
108
- * Apply an audio filter to the player
109
- *
110
- * @param {string | AudioFilter} filter - Filter name or AudioFilter object
111
- * @returns {Promise<boolean>} True if filter was applied successfully
112
- * @example
113
- * // Apply predefined filter to current track
114
- * await player.applyFilter("bassboost");
115
- *
116
- * // Apply custom filter to current track
117
- * await player.applyFilter({
118
- * name: "custom",
119
- * ffmpegFilter: "volume=1.5,treble=g=5",
120
- * description: "Tăng âm lượng và âm cao"
121
- * });
122
- *
123
- * // Apply filter without affecting current track
124
- * await player.applyFilter("bassboost", false);
125
- */
126
- public async applyFilter(filter?: string | AudioFilter): Promise<boolean> {
127
- if (!filter) return false;
128
-
129
- let audioFilter: AudioFilter | undefined;
130
- if (typeof filter === "string") {
131
- const predefined = PREDEFINED_FILTERS[filter];
132
- if (!predefined) {
133
- this.debug(`[FilterManager] Predefined filter not found: ${filter}`);
134
- return false;
135
- }
136
- audioFilter = predefined;
137
- } else {
138
- audioFilter = filter;
139
- }
140
-
141
- if (this.activeFilters.some((f) => f.name === audioFilter.name)) {
142
- this.debug(`[FilterManager] Filter already applied: ${audioFilter.name}`);
143
- return false;
144
- }
145
-
146
- this.activeFilters.push(audioFilter);
147
- this.debug(`[FilterManager] Applied filter: ${audioFilter.name} - ${audioFilter.description}`);
148
- return await this.player.refeshPlayerResource();
149
- }
150
-
151
- /**
152
- * Apply multiple filters at once
153
- *
154
- * @param {(string | AudioFilter)[]} filters - Array of filter names or AudioFilter objects
155
- * @returns {Promise<boolean>} True if all filters were applied successfully
156
- * @example
157
- * // Apply multiple filters to current track
158
- * await player.applyFilters(["bassboost", "trebleboost"]);
159
- *
160
- * // Apply filters without affecting current track
161
- * await player.applyFilters(["bassboost", "trebleboost"], false);
162
- */
163
- public async applyFilters(filters: (string | AudioFilter)[]): Promise<boolean> {
164
- let allApplied = true;
165
- for (const f of filters) {
166
- const ok = await this.applyFilter(f);
167
- if (!ok) allApplied = false;
168
- }
169
- return allApplied;
170
- }
171
- /**
172
- * Remove an audio filter from the player
173
- *
174
- * @param {string} filterName - Name of the filter to remove
175
- * @returns {boolean} True if filter was removed successfully
176
- * @example
177
- * player.removeFilter("bassboost");
178
- */
179
- public async removeFilter(filterName: string): Promise<boolean> {
180
- const index = this.activeFilters.findIndex((f) => f.name === filterName);
181
- if (index === -1) {
182
- this.debug(`[FilterManager] Filter not found: ${filterName}`);
183
- return false;
184
- }
185
- const removed = this.activeFilters.splice(index, 1)[0];
186
- this.debug(`[FilterManager] Removed filter: ${removed.name}`);
187
- return await this.player.refeshPlayerResource();
188
- }
189
-
190
- /**
191
- * Clear all audio filters from the player
192
- *
193
- * @returns {boolean} True if filters were cleared successfully
194
- * @example
195
- * player.clearFilters();
196
- */
197
- public async clearAll(): Promise<boolean> {
198
- const count = this.activeFilters.length;
199
- this.activeFilters = [];
200
- this.debug(`[FilterManager] Cleared ${count} filters`);
201
- return await this.player.refeshPlayerResource();
202
- }
203
-
204
- /**
205
- * Apply filters and seek to a stream
206
- *
207
- * @param {Readable} stream - The stream to apply filters and seek to
208
- * @param {number} position - The position to seek to in milliseconds (default: 0)
209
- * @returns {Promise<Readable>} The stream with filters and seek applied
210
- */
211
- public async applyFiltersAndSeek(stream: Readable, position: number = -1): Promise<Readable> {
212
- const filterString = this.getFilterString();
213
- this.debug(`[FilterManager] Applying filters and seek to stream: ${filterString || "none"}, seek: ${position}ms`);
214
- try {
215
- const args = ["-analyzeduration", "0", "-loglevel", "0"];
216
-
217
- if (position > 0) {
218
- const seekSeconds = Math.floor(position / 1000);
219
- args.push("-ss", seekSeconds.toString());
220
- }
221
-
222
- // Add filter if any are active
223
- if (filterString) {
224
- args.push("-af", filterString);
225
- }
226
- args.push("-f", this.StreamType === "webm/opus" ? "webm/opus" : this.StreamType === "ogg/opus" ? "ogg/opus" : "mp3");
227
- args.push("-ar", "48000", "-ac", "2");
228
-
229
- try {
230
- if (this.ffmpeg) {
231
- this.ffmpeg.destroy();
232
- }
233
- this.ffmpeg = null;
234
- } catch {}
235
-
236
- this.ffmpeg = stream.pipe(new prism.FFmpeg({ args }));
237
-
238
- this.ffmpeg.on("close", () => {
239
- this.debug(`[FilterManager] FFmpeg filter+seek processing completed`);
240
- if (this.ffmpeg) {
241
- this.ffmpeg.destroy();
242
- this.ffmpeg = null;
243
- }
244
- });
245
-
246
- this.ffmpeg.on("error", (err: Error) => {
247
- this.debug(`[FilterManager] FFmpeg filter+seek error:`, err);
248
- if (this.ffmpeg) {
249
- this.ffmpeg.destroy();
250
- }
251
- if (this.ffmpeg) {
252
- this.ffmpeg = null;
253
- }
254
- });
255
- return this.ffmpeg;
256
- } catch (error) {
257
- this.debug(`[FilterManager] Error creating FFmpeg instance:`, error);
258
- // Fallback to original stream if FFmpeg fails
259
- throw error;
260
- }
261
- }
262
- }
1
+ import type { AudioFilter } from "../types";
2
+ import { PREDEFINED_FILTERS } from "../types";
3
+ import type { Player } from "./Player";
4
+ import type { PlayerManager } from "./PlayerManager";
5
+ import prism, { FFmpeg } from "prism-media";
6
+ import type { Readable } from "stream";
7
+
8
+ type DebugFn = (message?: any, ...optionalParams: any[]) => void;
9
+
10
+ export class FilterManager {
11
+ private activeFilters: AudioFilter[] = [];
12
+ private debug: DebugFn;
13
+ private player: Player;
14
+ private ffmpeg: FFmpeg | null = null;
15
+ public StreamType: "webm/opus" | "ogg/opus" | "mp3" | "arbitrary" = "mp3";
16
+
17
+ constructor(player: Player, manager: PlayerManager) {
18
+ this.player = player as Player;
19
+
20
+ this.debug = (message?: any, ...optionalParams: any[]) => {
21
+ if (manager.debugEnabled) {
22
+ manager.emit("debug", `[FilterManager] ${message}`, ...optionalParams);
23
+ }
24
+ };
25
+ }
26
+
27
+ /**
28
+ * Destroy the filter manager
29
+ *
30
+ * @returns {void}
31
+ * @example
32
+ * player.filter.destroy();
33
+ */
34
+ destroy(): void {
35
+ this.activeFilters = [];
36
+ if (this.ffmpeg) {
37
+ try {
38
+ this.ffmpeg.destroy();
39
+ } catch {}
40
+ this.ffmpeg = null;
41
+ }
42
+ }
43
+
44
+ /**
45
+ * Get the combined FFmpeg filter string for all active filters
46
+ *
47
+ * @returns {string} Combined FFmpeg filter string
48
+ * @example
49
+ * const filterString = player.getFilterString();
50
+ * console.log(`Filter string: ${filterString}`);
51
+ */
52
+ public getFilterString(): string {
53
+ if (this.activeFilters.length === 0) return "";
54
+ return this.activeFilters.map((f) => f.ffmpegFilter).join(",");
55
+ }
56
+
57
+ /**
58
+ * Get all currently applied filters
59
+ *
60
+ * @returns {AudioFilter[]} Array of active filters
61
+ * @example
62
+ * const filters = player.getActiveFilters();
63
+ * console.log(`Active filters: ${filters.map(f => f.name).join(', ')}`);
64
+ */
65
+ public getActiveFilters(): AudioFilter[] {
66
+ return [...this.activeFilters];
67
+ }
68
+
69
+ /**
70
+ * Check if a specific filter is currently applied
71
+ *
72
+ * @param {string} filterName - Name of the filter to check
73
+ * @returns {boolean} True if filter is applied
74
+ * @example
75
+ * const hasBassBoost = player.hasFilter("bassboost");
76
+ * console.log(`Has bass boost: ${hasBassBoost}`);
77
+ */
78
+ public hasFilter(filterName: string): boolean {
79
+ return this.activeFilters.some((f) => f.name === filterName);
80
+ }
81
+
82
+ /**
83
+ * Get available predefined filters
84
+ *
85
+ * @returns {AudioFilter[]} Array of all predefined filters
86
+ * @example
87
+ * const availableFilters = player.getAvailableFilters();
88
+ * console.log(`Available filters: ${availableFilters.length}`);
89
+ */
90
+ public getAvailableFilters(): AudioFilter[] {
91
+ return Object.values(PREDEFINED_FILTERS);
92
+ }
93
+
94
+ /**
95
+ * Get filters by category
96
+ *
97
+ * @param {string} category - Category to filter by
98
+ * @returns {AudioFilter[]} Array of filters in the category
99
+ * @example
100
+ * const eqFilters = player.getFiltersByCategory("eq");
101
+ * console.log(`EQ filters: ${eqFilters.map(f => f.name).join(', ')}`);
102
+ */
103
+ public getFiltersByCategory(category: string): AudioFilter[] {
104
+ return Object.values(PREDEFINED_FILTERS).filter((f) => f.category === category);
105
+ }
106
+
107
+ /**
108
+ * Apply an audio filter to the player
109
+ *
110
+ * @param {string | AudioFilter} filter - Filter name or AudioFilter object
111
+ * @returns {Promise<boolean>} True if filter was applied successfully
112
+ * @example
113
+ * // Apply predefined filter to current track
114
+ * await player.applyFilter("bassboost");
115
+ *
116
+ * // Apply custom filter to current track
117
+ * await player.applyFilter({
118
+ * name: "custom",
119
+ * ffmpegFilter: "volume=1.5,treble=g=5",
120
+ * description: "Tăng âm lượng và âm cao"
121
+ * });
122
+ *
123
+ * // Apply filter without affecting current track
124
+ * await player.applyFilter("bassboost", false);
125
+ */
126
+ public async applyFilter(filter?: string | AudioFilter): Promise<boolean> {
127
+ if (!filter) return false;
128
+
129
+ let audioFilter: AudioFilter | undefined;
130
+ if (typeof filter === "string") {
131
+ const predefined = PREDEFINED_FILTERS[filter];
132
+ if (!predefined) {
133
+ this.debug(`[FilterManager] Predefined filter not found: ${filter}`);
134
+ return false;
135
+ }
136
+ audioFilter = predefined;
137
+ } else {
138
+ audioFilter = filter;
139
+ }
140
+
141
+ if (this.activeFilters.some((f) => f.name === audioFilter.name)) {
142
+ this.debug(`[FilterManager] Filter already applied: ${audioFilter.name}`);
143
+ return false;
144
+ }
145
+
146
+ this.activeFilters.push(audioFilter);
147
+ this.debug(`[FilterManager] Applied filter: ${audioFilter.name} - ${audioFilter.description}`);
148
+ return await this.player.refeshPlayerResource();
149
+ }
150
+
151
+ /**
152
+ * Apply multiple filters at once
153
+ *
154
+ * @param {(string | AudioFilter)[]} filters - Array of filter names or AudioFilter objects
155
+ * @returns {Promise<boolean>} True if all filters were applied successfully
156
+ * @example
157
+ * // Apply multiple filters to current track
158
+ * await player.applyFilters(["bassboost", "trebleboost"]);
159
+ *
160
+ * // Apply filters without affecting current track
161
+ * await player.applyFilters(["bassboost", "trebleboost"], false);
162
+ */
163
+ public async applyFilters(filters: (string | AudioFilter)[]): Promise<boolean> {
164
+ let allApplied = true;
165
+ for (const f of filters) {
166
+ const ok = await this.applyFilter(f);
167
+ if (!ok) allApplied = false;
168
+ }
169
+ return allApplied;
170
+ }
171
+ /**
172
+ * Remove an audio filter from the player
173
+ *
174
+ * @param {string} filterName - Name of the filter to remove
175
+ * @returns {boolean} True if filter was removed successfully
176
+ * @example
177
+ * player.removeFilter("bassboost");
178
+ */
179
+ public async removeFilter(filterName: string): Promise<boolean> {
180
+ const index = this.activeFilters.findIndex((f) => f.name === filterName);
181
+ if (index === -1) {
182
+ this.debug(`[FilterManager] Filter not found: ${filterName}`);
183
+ return false;
184
+ }
185
+ const removed = this.activeFilters.splice(index, 1)[0];
186
+ this.debug(`[FilterManager] Removed filter: ${removed.name}`);
187
+ return await this.player.refeshPlayerResource();
188
+ }
189
+
190
+ /**
191
+ * Clear all audio filters from the player
192
+ *
193
+ * @returns {boolean} True if filters were cleared successfully
194
+ * @example
195
+ * player.clearFilters();
196
+ */
197
+ public async clearAll(): Promise<boolean> {
198
+ const count = this.activeFilters.length;
199
+ this.activeFilters = [];
200
+ this.debug(`[FilterManager] Cleared ${count} filters`);
201
+ return await this.player.refeshPlayerResource();
202
+ }
203
+
204
+ /**
205
+ * Apply filters and seek to a stream
206
+ *
207
+ * @param {Readable} stream - The stream to apply filters and seek to
208
+ * @param {number} position - The position to seek to in milliseconds (default: 0)
209
+ * @returns {Promise<Readable>} The stream with filters and seek applied
210
+ */
211
+ public async applyFiltersAndSeek(stream: Readable, position: number = -1): Promise<Readable> {
212
+ const filterString = this.getFilterString();
213
+ this.debug(`[FilterManager] Applying filters and seek to stream: ${filterString || "none"}, seek: ${position}ms`);
214
+ try {
215
+ const args = ["-analyzeduration", "0", "-loglevel", "0"];
216
+
217
+ if (position > 0) {
218
+ const seekSeconds = Math.floor(position / 1000);
219
+ args.push("-ss", seekSeconds.toString());
220
+ }
221
+
222
+ // Add filter if any are active
223
+ if (filterString) {
224
+ args.push("-af", filterString);
225
+ }
226
+ args.push(
227
+ "-f",
228
+ this.StreamType === "webm/opus" ? "webm/opus"
229
+ : this.StreamType === "ogg/opus" ? "ogg/opus"
230
+ : "mp3",
231
+ );
232
+ args.push("-ar", "48000", "-ac", "2");
233
+
234
+ try {
235
+ if (this.ffmpeg) {
236
+ this.ffmpeg.destroy();
237
+ }
238
+ this.ffmpeg = null;
239
+ } catch {}
240
+
241
+ this.ffmpeg = stream.pipe(new prism.FFmpeg({ args }));
242
+
243
+ this.ffmpeg.on("close", () => {
244
+ this.debug(`[FilterManager] FFmpeg filter+seek processing completed`);
245
+ if (this.ffmpeg) {
246
+ this.ffmpeg.destroy();
247
+ this.ffmpeg = null;
248
+ }
249
+ });
250
+
251
+ this.ffmpeg.on("error", (err: Error) => {
252
+ this.debug(`[FilterManager] FFmpeg filter+seek error:`, err);
253
+ if (this.ffmpeg) {
254
+ this.ffmpeg.destroy();
255
+ }
256
+ if (this.ffmpeg) {
257
+ this.ffmpeg = null;
258
+ }
259
+ });
260
+ return this.ffmpeg;
261
+ } catch (error) {
262
+ this.debug(`[FilterManager] Error creating FFmpeg instance:`, error);
263
+ // Fallback to original stream if FFmpeg fails
264
+ throw error;
265
+ }
266
+ }
267
+ }
@@ -13,7 +13,6 @@ import {
13
13
  StreamType,
14
14
  } from "@discordjs/voice";
15
15
 
16
- import { VoiceChannel } from "discord.js";
17
16
  import { Readable } from "stream";
18
17
  import type { BaseExtension } from "../extensions";
19
18
  import type {
@@ -26,6 +25,7 @@ import type {
26
25
  LoopMode,
27
26
  StreamInfo,
28
27
  SaveOptions,
28
+ VoiceChannel,
29
29
  ExtensionPlayRequest,
30
30
  ExtensionPlayResponse,
31
31
  ExtensionAfterPlayPayload,
@@ -393,13 +393,10 @@ export class Player extends EventEmitter {
393
393
  */
394
394
  async play(query: string | Track | SearchResult | null, requestedBy?: string): Promise<boolean> {
395
395
  const debugInfo =
396
- query === null
397
- ? "null"
398
- : typeof query === "string"
399
- ? query
400
- : "tracks" in query
401
- ? `${query.tracks.length} tracks`
402
- : query.title || "unknown";
396
+ query === null ? "null"
397
+ : typeof query === "string" ? query
398
+ : "tracks" in query ? `${query.tracks.length} tracks`
399
+ : query.title || "unknown";
403
400
  this.debug(`[Player] Play called with query: ${debugInfo}`);
404
401
  this.clearLeaveTimeout();
405
402
  let tracksToAdd: Track[] = [];
@@ -567,11 +564,9 @@ export class Player extends EventEmitter {
567
564
  const resource = createAudioResource(stream, {
568
565
  metadata: track,
569
566
  inputType:
570
- streamInfo.type === "webm/opus"
571
- ? StreamType.WebmOpus
572
- : streamInfo.type === "ogg/opus"
573
- ? StreamType.OggOpus
574
- : StreamType.Arbitrary,
567
+ streamInfo.type === "webm/opus" ? StreamType.WebmOpus
568
+ : streamInfo.type === "ogg/opus" ? StreamType.OggOpus
569
+ : StreamType.Arbitrary,
575
570
  inlineVolume: true,
576
571
  });
577
572
 
@@ -583,11 +578,9 @@ export class Player extends EventEmitter {
583
578
  const resource = createAudioResource(streamInfo.stream, {
584
579
  metadata: track,
585
580
  inputType:
586
- streamInfo.type === "webm/opus"
587
- ? StreamType.WebmOpus
588
- : streamInfo.type === "ogg/opus"
589
- ? StreamType.OggOpus
590
- : StreamType.Arbitrary,
581
+ streamInfo.type === "webm/opus" ? StreamType.WebmOpus
582
+ : streamInfo.type === "ogg/opus" ? StreamType.OggOpus
583
+ : StreamType.Arbitrary,
591
584
  inlineVolume: true,
592
585
  });
593
586
  return resource;
@@ -635,11 +628,9 @@ export class Player extends EventEmitter {
635
628
  const fallbackResource = createAudioResource(streamInfo.stream, {
636
629
  metadata: track,
637
630
  inputType:
638
- streamInfo.type === "webm/opus"
639
- ? StreamType.WebmOpus
640
- : streamInfo.type === "ogg/opus"
641
- ? StreamType.OggOpus
642
- : StreamType.Arbitrary,
631
+ streamInfo.type === "webm/opus" ? StreamType.WebmOpus
632
+ : streamInfo.type === "ogg/opus" ? StreamType.OggOpus
633
+ : StreamType.Arbitrary,
643
634
  inlineVolume: true,
644
635
  });
645
636
 
@@ -767,8 +758,15 @@ export class Player extends EventEmitter {
767
758
  // Derive timeoutMs from resource/track duration when available, with a sensible cap
768
759
  const md: any = (resource as any)?.metadata ?? {};
769
760
  const declared =
770
- typeof md.duration === "number" ? md.duration : typeof track?.duration === "number" ? track.duration : undefined;
771
- const declaredMs = declared ? (declared > 1000 ? declared : declared * 1000) : undefined;
761
+ typeof md.duration === "number" ? md.duration
762
+ : typeof track?.duration === "number" ? track.duration
763
+ : undefined;
764
+ const declaredMs =
765
+ declared ?
766
+ declared > 1000 ?
767
+ declared
768
+ : declared * 1000
769
+ : undefined;
772
770
  const cap = this.options?.tts?.Max_Time_TTS ?? 60_000;
773
771
  const idleTimeout = declaredMs ? Math.min(cap, Math.max(1_000, declaredMs + 1_500)) : cap;
774
772
  await entersState(ttsPlayer, AudioPlayerStatus.Idle, idleTimeout).catch(() => null);
@@ -1052,9 +1050,20 @@ export class Player extends EventEmitter {
1052
1050
 
1053
1051
  // Apply filters if any are active
1054
1052
  let finalStream = streamInfo.stream;
1055
- if (this.filter.getActiveFilters().length > 0) {
1053
+
1054
+ if (saveOptions.filter || saveOptions.seek) {
1055
+ try {
1056
+ this.filter.clearAll();
1057
+ this.filter.applyFilters(saveOptions.filter || []);
1058
+ } catch (err) {
1059
+ this.debug(`[Player] Error applying save filters:`, err);
1060
+ }
1061
+
1056
1062
  this.debug(`[Player] Applying filters to save stream: ${this.filter.getFilterString() || "none"}`);
1057
- finalStream = await this.filter.applyFiltersAndSeek(streamInfo.stream);
1063
+ finalStream = await this.filter.applyFiltersAndSeek(streamInfo.stream, saveOptions.seek || 0).catch((err) => {
1064
+ this.debug(`[Player] Error applying filters to save stream:`, err);
1065
+ return streamInfo!.stream; // Fallback to original stream
1066
+ });
1058
1067
  }
1059
1068
 
1060
1069
  // Return the stream directly - caller can pipe it to fs.createWriteStream()