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.
- package/dist/structures/FilterManager.d.ts.map +1 -1
- package/dist/structures/FilterManager.js +3 -1
- package/dist/structures/FilterManager.js.map +1 -1
- package/dist/structures/Player.d.ts +1 -2
- package/dist/structures/Player.d.ts.map +1 -1
- package/dist/structures/Player.js +29 -22
- package/dist/structures/Player.js.map +1 -1
- package/dist/types/extension.d.ts +114 -0
- package/dist/types/extension.d.ts.map +1 -0
- package/dist/types/extension.js +3 -0
- package/dist/types/extension.js.map +1 -0
- package/dist/types/fillter.d.ts +44 -0
- package/dist/types/fillter.d.ts.map +1 -0
- package/dist/types/fillter.js +226 -0
- package/dist/types/fillter.js.map +1 -0
- package/dist/types/index.d.ts +14 -209
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/index.js +17 -223
- package/dist/types/index.js.map +1 -1
- package/dist/types/plugin.d.ts +58 -0
- package/dist/types/plugin.d.ts.map +1 -0
- package/dist/types/plugin.js +21 -0
- package/dist/types/plugin.js.map +1 -0
- package/package.json +7 -2
- package/src/structures/FilterManager.ts +267 -262
- package/src/structures/Player.ts +36 -27
- package/src/types/extension.ts +129 -0
- package/src/types/fillter.ts +264 -0
- package/src/types/index.ts +15 -443
- package/src/types/plugin.ts +57 -0
- package/dist/plugins/SoundCloudPlugin.d.ts +0 -22
- package/dist/plugins/SoundCloudPlugin.d.ts.map +0 -1
- package/dist/plugins/SoundCloudPlugin.js +0 -171
- package/dist/plugins/SoundCloudPlugin.js.map +0 -1
- package/dist/plugins/SpotifyPlugin.d.ts +0 -26
- package/dist/plugins/SpotifyPlugin.d.ts.map +0 -1
- package/dist/plugins/SpotifyPlugin.js +0 -183
- package/dist/plugins/SpotifyPlugin.js.map +0 -1
- package/dist/plugins/YouTubePlugin.d.ts +0 -25
- package/dist/plugins/YouTubePlugin.d.ts.map +0 -1
- package/dist/plugins/YouTubePlugin.js +0 -314
- 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(
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
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
|
+
}
|
package/src/structures/Player.ts
CHANGED
|
@@ -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
|
-
|
|
398
|
-
|
|
399
|
-
|
|
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
|
-
|
|
572
|
-
|
|
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
|
-
|
|
588
|
-
|
|
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
|
-
|
|
640
|
-
|
|
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
|
|
771
|
-
|
|
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
|
-
|
|
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()
|