ziplayer 0.2.1 → 0.2.3-dev-1

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 (43) hide show
  1. package/dist/structures/FilterManager.d.ts +1 -0
  2. package/dist/structures/FilterManager.d.ts.map +1 -1
  3. package/dist/structures/FilterManager.js +49 -10
  4. package/dist/structures/FilterManager.js.map +1 -1
  5. package/dist/structures/Player.d.ts +15 -2
  6. package/dist/structures/Player.d.ts.map +1 -1
  7. package/dist/structures/Player.js +123 -37
  8. package/dist/structures/Player.js.map +1 -1
  9. package/dist/types/extension.d.ts +114 -0
  10. package/dist/types/extension.d.ts.map +1 -0
  11. package/dist/types/extension.js +3 -0
  12. package/dist/types/extension.js.map +1 -0
  13. package/dist/types/fillter.d.ts +44 -0
  14. package/dist/types/fillter.d.ts.map +1 -0
  15. package/dist/types/fillter.js +226 -0
  16. package/dist/types/fillter.js.map +1 -0
  17. package/dist/types/index.d.ts +14 -209
  18. package/dist/types/index.d.ts.map +1 -1
  19. package/dist/types/index.js +17 -223
  20. package/dist/types/index.js.map +1 -1
  21. package/dist/types/plugin.d.ts +58 -0
  22. package/dist/types/plugin.d.ts.map +1 -0
  23. package/dist/types/plugin.js +21 -0
  24. package/dist/types/plugin.js.map +1 -0
  25. package/package.json +7 -2
  26. package/src/structures/FilterManager.ts +303 -262
  27. package/src/structures/Player.ts +135 -41
  28. package/src/types/extension.ts +129 -0
  29. package/src/types/fillter.ts +264 -0
  30. package/src/types/index.ts +15 -443
  31. package/src/types/plugin.ts +57 -0
  32. package/dist/plugins/SoundCloudPlugin.d.ts +0 -22
  33. package/dist/plugins/SoundCloudPlugin.d.ts.map +0 -1
  34. package/dist/plugins/SoundCloudPlugin.js +0 -171
  35. package/dist/plugins/SoundCloudPlugin.js.map +0 -1
  36. package/dist/plugins/SpotifyPlugin.d.ts +0 -26
  37. package/dist/plugins/SpotifyPlugin.d.ts.map +0 -1
  38. package/dist/plugins/SpotifyPlugin.js +0 -183
  39. package/dist/plugins/SpotifyPlugin.js.map +0 -1
  40. package/dist/plugins/YouTubePlugin.d.ts +0 -25
  41. package/dist/plugins/YouTubePlugin.d.ts.map +0 -1
  42. package/dist/plugins/YouTubePlugin.js +0 -314
  43. package/dist/plugins/YouTubePlugin.js.map +0 -1
@@ -1,262 +1,303 @@
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
+ private currentInputStream: Readable | null = null;
16
+ public StreamType: "webm/opus" | "ogg/opus" | "mp3" | "arbitrary" = "mp3";
17
+
18
+ constructor(player: Player, manager: PlayerManager) {
19
+ this.player = player as Player;
20
+
21
+ this.debug = (message?: any, ...optionalParams: any[]) => {
22
+ if (manager.debugEnabled) {
23
+ manager.emit("debug", `[FilterManager] ${message}`, ...optionalParams);
24
+ }
25
+ };
26
+ }
27
+
28
+ /**
29
+ * Destroy the filter manager
30
+ *
31
+ * @returns {void}
32
+ * @example
33
+ * player.filter.destroy();
34
+ */
35
+ destroy(): void {
36
+ this.activeFilters = [];
37
+
38
+ // Destroy FFmpeg process
39
+ if (this.ffmpeg) {
40
+ try {
41
+ this.ffmpeg.destroy();
42
+ } catch {}
43
+ this.ffmpeg = null;
44
+ }
45
+
46
+ // Destroy input stream
47
+ if (this.currentInputStream && typeof (this.currentInputStream as any).destroy === "function") {
48
+ try {
49
+ (this.currentInputStream as any).destroy();
50
+ } catch {}
51
+ }
52
+ this.currentInputStream = null;
53
+ }
54
+
55
+ /**
56
+ * Get the combined FFmpeg filter string for all active filters
57
+ *
58
+ * @returns {string} Combined FFmpeg filter string
59
+ * @example
60
+ * const filterString = player.getFilterString();
61
+ * console.log(`Filter string: ${filterString}`);
62
+ */
63
+ public getFilterString(): string {
64
+ if (this.activeFilters.length === 0) return "";
65
+ return this.activeFilters.map((f) => f.ffmpegFilter).join(",");
66
+ }
67
+
68
+ /**
69
+ * Get all currently applied filters
70
+ *
71
+ * @returns {AudioFilter[]} Array of active filters
72
+ * @example
73
+ * const filters = player.getActiveFilters();
74
+ * console.log(`Active filters: ${filters.map(f => f.name).join(', ')}`);
75
+ */
76
+ public getActiveFilters(): AudioFilter[] {
77
+ return [...this.activeFilters];
78
+ }
79
+
80
+ /**
81
+ * Check if a specific filter is currently applied
82
+ *
83
+ * @param {string} filterName - Name of the filter to check
84
+ * @returns {boolean} True if filter is applied
85
+ * @example
86
+ * const hasBassBoost = player.hasFilter("bassboost");
87
+ * console.log(`Has bass boost: ${hasBassBoost}`);
88
+ */
89
+ public hasFilter(filterName: string): boolean {
90
+ return this.activeFilters.some((f) => f.name === filterName);
91
+ }
92
+
93
+ /**
94
+ * Get available predefined filters
95
+ *
96
+ * @returns {AudioFilter[]} Array of all predefined filters
97
+ * @example
98
+ * const availableFilters = player.getAvailableFilters();
99
+ * console.log(`Available filters: ${availableFilters.length}`);
100
+ */
101
+ public getAvailableFilters(): AudioFilter[] {
102
+ return Object.values(PREDEFINED_FILTERS);
103
+ }
104
+
105
+ /**
106
+ * Get filters by category
107
+ *
108
+ * @param {string} category - Category to filter by
109
+ * @returns {AudioFilter[]} Array of filters in the category
110
+ * @example
111
+ * const eqFilters = player.getFiltersByCategory("eq");
112
+ * console.log(`EQ filters: ${eqFilters.map(f => f.name).join(', ')}`);
113
+ */
114
+ public getFiltersByCategory(category: string): AudioFilter[] {
115
+ return Object.values(PREDEFINED_FILTERS).filter((f) => f.category === category);
116
+ }
117
+
118
+ /**
119
+ * Apply an audio filter to the player
120
+ *
121
+ * @param {string | AudioFilter} filter - Filter name or AudioFilter object
122
+ * @returns {Promise<boolean>} True if filter was applied successfully
123
+ * @example
124
+ * // Apply predefined filter to current track
125
+ * await player.applyFilter("bassboost");
126
+ *
127
+ * // Apply custom filter to current track
128
+ * await player.applyFilter({
129
+ * name: "custom",
130
+ * ffmpegFilter: "volume=1.5,treble=g=5",
131
+ * description: "Tăng âm lượng và âm cao"
132
+ * });
133
+ *
134
+ * // Apply filter without affecting current track
135
+ * await player.applyFilter("bassboost", false);
136
+ */
137
+ public async applyFilter(filter?: string | AudioFilter): Promise<boolean> {
138
+ if (!filter) return false;
139
+
140
+ let audioFilter: AudioFilter | undefined;
141
+ if (typeof filter === "string") {
142
+ const predefined = PREDEFINED_FILTERS[filter];
143
+ if (!predefined) {
144
+ this.debug(`[FilterManager] Predefined filter not found: ${filter}`);
145
+ return false;
146
+ }
147
+ audioFilter = predefined;
148
+ } else {
149
+ audioFilter = filter;
150
+ }
151
+
152
+ if (this.activeFilters.some((f) => f.name === audioFilter.name)) {
153
+ this.debug(`[FilterManager] Filter already applied: ${audioFilter.name}`);
154
+ return false;
155
+ }
156
+
157
+ this.activeFilters.push(audioFilter);
158
+ this.debug(`[FilterManager] Applied filter: ${audioFilter.name} - ${audioFilter.description}`);
159
+ return await this.player.refeshPlayerResource();
160
+ }
161
+
162
+ /**
163
+ * Apply multiple filters at once
164
+ *
165
+ * @param {(string | AudioFilter)[]} filters - Array of filter names or AudioFilter objects
166
+ * @returns {Promise<boolean>} True if all filters were applied successfully
167
+ * @example
168
+ * // Apply multiple filters to current track
169
+ * await player.applyFilters(["bassboost", "trebleboost"]);
170
+ *
171
+ * // Apply filters without affecting current track
172
+ * await player.applyFilters(["bassboost", "trebleboost"], false);
173
+ */
174
+ public async applyFilters(filters: (string | AudioFilter)[]): Promise<boolean> {
175
+ let allApplied = true;
176
+ for (const f of filters) {
177
+ const ok = await this.applyFilter(f);
178
+ if (!ok) allApplied = false;
179
+ }
180
+ return allApplied;
181
+ }
182
+ /**
183
+ * Remove an audio filter from the player
184
+ *
185
+ * @param {string} filterName - Name of the filter to remove
186
+ * @returns {boolean} True if filter was removed successfully
187
+ * @example
188
+ * player.removeFilter("bassboost");
189
+ */
190
+ public async removeFilter(filterName: string): Promise<boolean> {
191
+ const index = this.activeFilters.findIndex((f) => f.name === filterName);
192
+ if (index === -1) {
193
+ this.debug(`[FilterManager] Filter not found: ${filterName}`);
194
+ return false;
195
+ }
196
+ const removed = this.activeFilters.splice(index, 1)[0];
197
+ this.debug(`[FilterManager] Removed filter: ${removed.name}`);
198
+ return await this.player.refeshPlayerResource();
199
+ }
200
+
201
+ /**
202
+ * Clear all audio filters from the player
203
+ *
204
+ * @returns {boolean} True if filters were cleared successfully
205
+ * @example
206
+ * player.clearFilters();
207
+ */
208
+ public async clearAll(): Promise<boolean> {
209
+ const count = this.activeFilters.length;
210
+ this.activeFilters = [];
211
+ this.debug(`[FilterManager] Cleared ${count} filters`);
212
+ return await this.player.refeshPlayerResource();
213
+ }
214
+
215
+ /**
216
+ * Apply filters and seek to a stream
217
+ *
218
+ * @param {Readable} stream - The stream to apply filters and seek to
219
+ * @param {number} position - The position to seek to in milliseconds (default: 0)
220
+ * @returns {Promise<Readable>} The stream with filters and seek applied
221
+ */
222
+ public async applyFiltersAndSeek(stream: Readable, position: number = -1): Promise<Readable> {
223
+ const filterString = this.getFilterString();
224
+ this.debug(`[FilterManager] Applying filters and seek to stream: ${filterString || "none"}, seek: ${position}ms`);
225
+ try {
226
+ const args = ["-analyzeduration", "0", "-loglevel", "0"];
227
+
228
+ if (position > 0) {
229
+ const seekSeconds = Math.floor(position / 1000);
230
+ args.push("-ss", seekSeconds.toString());
231
+ }
232
+
233
+ // Add filter if any are active
234
+ if (filterString) {
235
+ args.push("-af", filterString);
236
+ }
237
+ args.push(
238
+ "-f",
239
+ this.StreamType === "webm/opus" ? "webm/opus"
240
+ : this.StreamType === "ogg/opus" ? "ogg/opus"
241
+ : "mp3",
242
+ );
243
+ args.push("-ar", "48000", "-ac", "2");
244
+
245
+ try {
246
+ if (this.ffmpeg) {
247
+ this.ffmpeg.destroy();
248
+ this.ffmpeg = null;
249
+ }
250
+ // Destroy previous input stream
251
+ if (this.currentInputStream && typeof (this.currentInputStream as any).destroy === "function") {
252
+ try {
253
+ (this.currentInputStream as any).destroy();
254
+ } catch {}
255
+ }
256
+ this.currentInputStream = null;
257
+ } catch {}
258
+
259
+ // Store reference to input stream
260
+ this.currentInputStream = stream;
261
+
262
+ this.ffmpeg = stream.pipe(new prism.FFmpeg({ args }));
263
+
264
+ this.ffmpeg.on("close", () => {
265
+ this.debug(`[FilterManager] FFmpeg filter+seek processing completed`);
266
+ try {
267
+ if (this.ffmpeg) {
268
+ this.ffmpeg.destroy();
269
+ this.ffmpeg = null;
270
+ }
271
+ } catch {}
272
+ });
273
+
274
+ this.ffmpeg.on("error", (err: Error) => {
275
+ this.debug(`[FilterManager] FFmpeg filter+seek error:`, err);
276
+ try {
277
+ if (this.ffmpeg) {
278
+ this.ffmpeg.destroy();
279
+ this.ffmpeg = null;
280
+ }
281
+ // Also destroy input stream on error
282
+ if (this.currentInputStream && typeof (this.currentInputStream as any).destroy === "function") {
283
+ (this.currentInputStream as any).destroy();
284
+ }
285
+ } catch {}
286
+ this.currentInputStream = null;
287
+ });
288
+
289
+ return this.ffmpeg;
290
+ } catch (error) {
291
+ this.debug(`[FilterManager] Error creating FFmpeg instance:`, error);
292
+ // Destroy input stream if FFmpeg fails
293
+ if (this.currentInputStream && typeof (this.currentInputStream as any).destroy === "function") {
294
+ try {
295
+ (this.currentInputStream as any).destroy();
296
+ } catch {}
297
+ }
298
+ this.currentInputStream = null;
299
+ // Fallback to original stream if FFmpeg fails
300
+ throw error;
301
+ }
302
+ }
303
+ }