vantiv.io 1.0.0 → 1.0.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/package.json +1 -1
- package/src/classes/Actions/Inventory.js +1 -1
- package/src/classes/Actions/Music.js +1 -1
- package/src/classes/Actions/Utils.js +4 -4
- package/src/classes/Actions/lib/AudioStreaming.js +217 -71
- package/src/classes/Handlers/EventHandlers.js +2 -2
- package/src/classes/Handlers/WebSocketHandlers.js +1 -1
- package/src/classes/Managers/Helpers/MetricsManager.js +1 -1
- package/src/classes/Managers/Networking/ConnectionManager.js +2 -42
- package/src/classes/Managers/Networking/EventsManager.js +1 -1
- package/src/classes/Managers/Networking/KeepAliveManager.js +2 -53
- package/src/classes/Managers/Networking/Request.js +1 -1
- package/src/constants/WebSocketConstants.js +1 -2
- package/src/core/Highrise.js +8 -8
- package/src/core/HighriseWebsocket.js +17 -17
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "vantiv.io",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.2",
|
|
4
4
|
"description": "Enterprise WebSocket infrastructure for Highrise featuring spatial intelligence systems, memory-optimized architecture, and production-grade reliability for scalable application development",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"highrise",
|
|
@@ -33,7 +33,7 @@ class MusicClass extends EventEmitter {
|
|
|
33
33
|
initializeStreamer() {
|
|
34
34
|
try {
|
|
35
35
|
this.streamer = new IcecastStreamer(this.config.icecast);
|
|
36
|
-
this.queue = new IcecastQueue(this.streamer, this.config.queue);
|
|
36
|
+
this.queue = new IcecastQueue(this.streamer, { ...this.config.queue, ...this.config.icecast});
|
|
37
37
|
|
|
38
38
|
this.streamer.on('playbackStart', (track) => {
|
|
39
39
|
this.emit('playbackStart', track);
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
const PermissionManager = require("
|
|
2
|
-
const CooldownManager = require("
|
|
3
|
-
const DanceFloor = require("
|
|
4
|
-
const { Logger } = require("
|
|
1
|
+
const PermissionManager = require("vantiv.io/src/classes/Managers/PermissionManager");
|
|
2
|
+
const CooldownManager = require("vantiv.io/src/classes/Managers/Networking/CooldownManager");
|
|
3
|
+
const DanceFloor = require("vantiv.io/src/classes/Managers/DanceFloorManagers");
|
|
4
|
+
const { Logger } = require("vantiv.io/src/classes/Managers/Helpers/LoggerManager")
|
|
5
5
|
|
|
6
6
|
class Utils {
|
|
7
7
|
constructor(bot, options) {
|
|
@@ -10,22 +10,25 @@ class IcecastStreamer extends EventEmitter {
|
|
|
10
10
|
port: config.port || 8000,
|
|
11
11
|
mount: config.mount || '/stream',
|
|
12
12
|
sourcePassword: config.sourcePassword || 'hackme',
|
|
13
|
+
fallbackUrl: config.fallbackUrl || '',
|
|
14
|
+
fallbackEnabled: config.fallbackEnabled || false,
|
|
13
15
|
audioFormat: config.audioFormat || 'mp3',
|
|
14
16
|
audioBitrate: config.audioBitrate || '192k',
|
|
15
17
|
audioSampleRate: config.audioSampleRate || 48000,
|
|
16
18
|
audioChannels: config.audioChannels || 2,
|
|
17
19
|
contentType: config.contentType || 'audio/mpeg'
|
|
18
20
|
};
|
|
19
|
-
|
|
21
|
+
|
|
20
22
|
this.ffmpegProcess = null;
|
|
21
|
-
this.
|
|
23
|
+
this.fallbackProcess = null;
|
|
24
|
+
this.isStreaming = false; // Only for actual music
|
|
25
|
+
this.isFallbackActive = false; // Separate flag for fallback
|
|
22
26
|
this.currentTrack = null;
|
|
23
|
-
|
|
27
|
+
|
|
24
28
|
this.icecastUrl = `icecast://source:${this.config.sourcePassword}@${this.config.server}:${this.config.port}${this.config.mount}`;
|
|
25
29
|
this.publicStreamUrl = `http://${this.config.server}:${this.config.port}${this.config.mount}`;
|
|
26
|
-
|
|
27
30
|
}
|
|
28
|
-
|
|
31
|
+
|
|
29
32
|
getFFmpegArgs(url) {
|
|
30
33
|
return [
|
|
31
34
|
'-re',
|
|
@@ -39,17 +42,34 @@ class IcecastStreamer extends EventEmitter {
|
|
|
39
42
|
this.icecastUrl
|
|
40
43
|
];
|
|
41
44
|
}
|
|
42
|
-
|
|
45
|
+
|
|
46
|
+
getFallbackFFmpegArgs() {
|
|
47
|
+
return [
|
|
48
|
+
'-re',
|
|
49
|
+
'-i', this.config.fallbackUrl,
|
|
50
|
+
'-f', this.config.audioFormat,
|
|
51
|
+
'-content_type', this.config.contentType,
|
|
52
|
+
'-vn',
|
|
53
|
+
'-ar', this.config.audioSampleRate.toString(),
|
|
54
|
+
'-ac', this.config.audioChannels.toString(),
|
|
55
|
+
'-b:a', this.config.audioBitrate,
|
|
56
|
+
this.icecastUrl
|
|
57
|
+
];
|
|
58
|
+
}
|
|
59
|
+
|
|
43
60
|
async streamToIcecast(url, metadata = {}) {
|
|
61
|
+
if (this.isFallbackActive) {
|
|
62
|
+
this.stopFallback();
|
|
63
|
+
await this.delay(200);
|
|
64
|
+
}
|
|
65
|
+
|
|
44
66
|
if (this.ffmpegProcess) {
|
|
45
67
|
this.stop();
|
|
46
68
|
await this.delay(200);
|
|
47
69
|
}
|
|
48
|
-
|
|
70
|
+
|
|
49
71
|
return new Promise((resolve, reject) => {
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
this.currentTrack = {
|
|
72
|
+
this.currentTrack = new Track({
|
|
53
73
|
url,
|
|
54
74
|
title: metadata.title || 'Unknown Track',
|
|
55
75
|
duration: metadata.duration || 0,
|
|
@@ -58,16 +78,17 @@ class IcecastStreamer extends EventEmitter {
|
|
|
58
78
|
thumbnail: metadata.thumbnail,
|
|
59
79
|
source: metadata.source || 'stream',
|
|
60
80
|
startedAt: Date.now()
|
|
61
|
-
};
|
|
62
|
-
|
|
81
|
+
});
|
|
82
|
+
|
|
63
83
|
this.isStreaming = true;
|
|
64
|
-
|
|
84
|
+
this.isFallbackActive = false;
|
|
85
|
+
|
|
65
86
|
const args = this.getFFmpegArgs(url);
|
|
66
87
|
this.ffmpegProcess = spawn('ffmpeg', args);
|
|
67
|
-
|
|
88
|
+
|
|
68
89
|
this.ffmpegProcess.stderr.on('data', (data) => {
|
|
69
90
|
const message = data.toString();
|
|
70
|
-
|
|
91
|
+
|
|
71
92
|
const durationMatch = message.match(/Duration: (\d+):(\d+):(\d+\.\d+)/);
|
|
72
93
|
if (durationMatch) {
|
|
73
94
|
const hours = parseInt(durationMatch[1]);
|
|
@@ -75,46 +96,44 @@ class IcecastStreamer extends EventEmitter {
|
|
|
75
96
|
const seconds = parseFloat(durationMatch[3]);
|
|
76
97
|
this.currentTrack.duration = hours * 3600 + minutes * 60 + seconds;
|
|
77
98
|
}
|
|
78
|
-
|
|
99
|
+
|
|
79
100
|
const timeMatch = message.match(/time=(\d+):(\d+):(\d+\.\d+)/);
|
|
80
101
|
if (timeMatch) {
|
|
81
102
|
const hours = parseInt(timeMatch[1]);
|
|
82
103
|
const minutes = parseInt(timeMatch[2]);
|
|
83
104
|
const seconds = parseFloat(timeMatch[3]);
|
|
84
105
|
this.currentTrack.position = hours * 3600 + minutes * 60 + seconds;
|
|
85
|
-
|
|
106
|
+
|
|
86
107
|
this.emit('progress', {
|
|
87
108
|
position: this.currentTrack.position,
|
|
88
109
|
duration: this.currentTrack.duration,
|
|
89
|
-
progress: this.currentTrack.duration > 0 ?
|
|
110
|
+
progress: this.currentTrack.duration > 0 ?
|
|
90
111
|
(this.currentTrack.position / this.currentTrack.duration) * 100 : 0
|
|
91
112
|
});
|
|
92
113
|
}
|
|
93
|
-
|
|
114
|
+
|
|
94
115
|
if (message.includes('Stream mapping') || message.includes('Output')) {
|
|
95
|
-
console.log('Icecast stream started successfully');
|
|
96
116
|
this.emit('playbackStart', this.currentTrack);
|
|
97
117
|
resolve();
|
|
98
118
|
}
|
|
99
|
-
|
|
119
|
+
|
|
100
120
|
if (message.includes('Error') || message.includes('Failed')) {
|
|
101
121
|
console.error('FFmpeg error:', message.trim());
|
|
102
122
|
this.emit('error', new Error(message.trim()));
|
|
103
123
|
}
|
|
104
124
|
});
|
|
105
|
-
|
|
125
|
+
|
|
106
126
|
this.ffmpegProcess.on('close', (code) => {
|
|
107
|
-
console.log(`Icecast stream ended with code ${code}`);
|
|
108
127
|
const endedTrack = this.currentTrack;
|
|
109
|
-
|
|
128
|
+
|
|
110
129
|
this.isStreaming = false;
|
|
111
130
|
this.currentTrack = null;
|
|
112
131
|
this.ffmpegProcess = null;
|
|
113
|
-
|
|
132
|
+
|
|
114
133
|
this.emit('playbackEnd', endedTrack);
|
|
115
134
|
this.emit('streamEnd', { code, track: endedTrack });
|
|
116
135
|
});
|
|
117
|
-
|
|
136
|
+
|
|
118
137
|
this.ffmpegProcess.on('error', (err) => {
|
|
119
138
|
console.error('FFmpeg process error:', err);
|
|
120
139
|
this.isStreaming = false;
|
|
@@ -124,26 +143,96 @@ class IcecastStreamer extends EventEmitter {
|
|
|
124
143
|
});
|
|
125
144
|
});
|
|
126
145
|
}
|
|
127
|
-
|
|
146
|
+
|
|
147
|
+
startFallback() {
|
|
148
|
+
if (!this.config.fallbackUrl || this.isFallbackActive || this.isStreaming) {
|
|
149
|
+
return false;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
if (this.ffmpegProcess) {
|
|
153
|
+
this.ffmpegProcess.kill('SIGKILL');
|
|
154
|
+
this.ffmpegProcess = null;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
const args = this.getFallbackFFmpegArgs();
|
|
158
|
+
this.fallbackProcess = spawn('ffmpeg', args);
|
|
159
|
+
|
|
160
|
+
this.isFallbackActive = true;
|
|
161
|
+
this.isStreaming = false;
|
|
162
|
+
|
|
163
|
+
this.currentTrack = new Track({
|
|
164
|
+
url: this.config.fallbackUrl,
|
|
165
|
+
title: 'Fallback Music',
|
|
166
|
+
duration: 0,
|
|
167
|
+
requester: 'System',
|
|
168
|
+
requesterId: 'system',
|
|
169
|
+
source: 'fallback',
|
|
170
|
+
startedAt: Date.now(),
|
|
171
|
+
isFallback: true
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
this.fallbackProcess.stderr.on('data', (data) => {
|
|
175
|
+
const message = data.toString();
|
|
176
|
+
|
|
177
|
+
if (message.includes('Stream mapping') || message.includes('Output')) {
|
|
178
|
+
this.emit('fallbackStart', this.currentTrack);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
if (message.includes('Error') || message.includes('Failed')) {
|
|
182
|
+
console.error('Fallback FFmpeg error:', message.trim());
|
|
183
|
+
}
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
this.fallbackProcess.on('close', (code) => {
|
|
187
|
+
this.isFallbackActive = false;
|
|
188
|
+
this.currentTrack = null;
|
|
189
|
+
this.fallbackProcess = null;
|
|
190
|
+
|
|
191
|
+
this.emit('fallbackEnd', { code });
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
this.fallbackProcess.on('error', (err) => {
|
|
195
|
+
console.error('Fallback FFmpeg process error:', err);
|
|
196
|
+
this.isFallbackActive = false;
|
|
197
|
+
this.currentTrack = null;
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
return true;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
stopFallback() {
|
|
204
|
+
if (this.fallbackProcess) {
|
|
205
|
+
this.fallbackProcess.kill('SIGKILL');
|
|
206
|
+
this.fallbackProcess = null;
|
|
207
|
+
this.isFallbackActive = false;
|
|
208
|
+
this.currentTrack = null;
|
|
209
|
+
this.emit('fallbackStopped');
|
|
210
|
+
return true;
|
|
211
|
+
}
|
|
212
|
+
return false;
|
|
213
|
+
}
|
|
214
|
+
|
|
128
215
|
stop() {
|
|
129
216
|
if (this.ffmpegProcess) {
|
|
130
|
-
console.log('Stopping Icecast stream...');
|
|
131
217
|
this.ffmpegProcess.kill('SIGKILL');
|
|
132
218
|
this.ffmpegProcess = null;
|
|
133
219
|
this.isStreaming = false;
|
|
134
220
|
this.emit('streamStopped');
|
|
135
221
|
}
|
|
222
|
+
|
|
223
|
+
this.stopFallback();
|
|
136
224
|
}
|
|
137
|
-
|
|
225
|
+
|
|
138
226
|
getStatus() {
|
|
139
227
|
return {
|
|
140
228
|
isStreaming: this.isStreaming,
|
|
229
|
+
isFallbackActive: this.isFallbackActive,
|
|
141
230
|
currentTrack: this.currentTrack,
|
|
142
231
|
config: this.config,
|
|
143
232
|
publicStreamUrl: this.publicStreamUrl
|
|
144
233
|
};
|
|
145
234
|
}
|
|
146
|
-
|
|
235
|
+
|
|
147
236
|
getNowPlaying() {
|
|
148
237
|
if (!this.currentTrack) return null;
|
|
149
238
|
|
|
@@ -151,15 +240,15 @@ class IcecastStreamer extends EventEmitter {
|
|
|
151
240
|
track: this.currentTrack,
|
|
152
241
|
position: this.currentTrack.position || 0,
|
|
153
242
|
duration: this.currentTrack.duration || 0,
|
|
154
|
-
progress: this.currentTrack.duration > 0 ?
|
|
243
|
+
progress: this.currentTrack.duration > 0 ?
|
|
155
244
|
((this.currentTrack.position || 0) / this.currentTrack.duration) * 100 : 0,
|
|
156
|
-
remaining: this.currentTrack.duration > 0 ?
|
|
245
|
+
remaining: this.currentTrack.duration > 0 ?
|
|
157
246
|
this.currentTrack.duration - (this.currentTrack.position || 0) : 0,
|
|
158
|
-
elapsed: this.currentTrack.startedAt ?
|
|
247
|
+
elapsed: this.currentTrack.startedAt ?
|
|
159
248
|
Math.floor((Date.now() - this.currentTrack.startedAt) / 1000) : 0
|
|
160
249
|
};
|
|
161
250
|
}
|
|
162
|
-
|
|
251
|
+
|
|
163
252
|
updateConfig(newConfig) {
|
|
164
253
|
this.config = { ...this.config, ...newConfig };
|
|
165
254
|
this.icecastUrl = `icecast://source:${this.config.sourcePassword}@${this.config.server}:${this.config.port}${this.config.mount}`;
|
|
@@ -167,7 +256,7 @@ class IcecastStreamer extends EventEmitter {
|
|
|
167
256
|
console.log('Icecast config updated');
|
|
168
257
|
return this.config;
|
|
169
258
|
}
|
|
170
|
-
|
|
259
|
+
|
|
171
260
|
async delay(ms) {
|
|
172
261
|
return new Promise(resolve => setTimeout(resolve, ms));
|
|
173
262
|
}
|
|
@@ -184,6 +273,7 @@ class Track {
|
|
|
184
273
|
this.addedAt = Date.now();
|
|
185
274
|
this.thumbnail = data.thumbnail;
|
|
186
275
|
this.source = data.source;
|
|
276
|
+
this.isFallback = data.isFallback || false;
|
|
187
277
|
}
|
|
188
278
|
|
|
189
279
|
getFormattedDuration() {
|
|
@@ -211,34 +301,65 @@ class IcecastQueue {
|
|
|
211
301
|
this.maxHistory = config.maxHistory || 50;
|
|
212
302
|
this.skipRequested = false;
|
|
213
303
|
|
|
304
|
+
this.fallbackEnabled = config.fallbackEnabled || false;
|
|
305
|
+
this.fallbackUrl = config.fallbackUrl || '';
|
|
306
|
+
|
|
214
307
|
this.setupEventListeners();
|
|
308
|
+
|
|
309
|
+
this.checkAndStartFallback();
|
|
215
310
|
}
|
|
216
|
-
|
|
311
|
+
|
|
217
312
|
setupEventListeners() {
|
|
218
313
|
this.streamer.on('playbackEnd', (track) => {
|
|
219
|
-
if (track) {
|
|
314
|
+
if (track && !track.isFallback) {
|
|
220
315
|
this.addToHistory(track, true);
|
|
221
316
|
}
|
|
222
|
-
|
|
317
|
+
|
|
223
318
|
if (this.skipRequested) {
|
|
224
319
|
this.skipRequested = false;
|
|
225
320
|
return;
|
|
226
321
|
}
|
|
227
|
-
|
|
228
|
-
if (this.loopMode === 'track' && track) {
|
|
322
|
+
|
|
323
|
+
if (this.loopMode === 'track' && track && !track.isFallback) {
|
|
229
324
|
this.queue.unshift(new Track({ ...track }));
|
|
230
|
-
} else if (this.loopMode === 'queue' && track) {
|
|
325
|
+
} else if (this.loopMode === 'queue' && track && !track.isFallback) {
|
|
231
326
|
this.queue.push(new Track({ ...track }));
|
|
232
327
|
}
|
|
233
|
-
|
|
328
|
+
|
|
234
329
|
setTimeout(() => this.playNext(), 300);
|
|
235
330
|
});
|
|
236
|
-
|
|
331
|
+
|
|
237
332
|
this.streamer.on('playbackStart', (track) => {
|
|
238
333
|
this.currentTrack = track;
|
|
239
334
|
});
|
|
335
|
+
|
|
336
|
+
this.streamer.on('fallbackStart', (track) => {
|
|
337
|
+
this.currentTrack = track;
|
|
338
|
+
});
|
|
339
|
+
|
|
340
|
+
this.streamer.on('fallbackEnd', () => {
|
|
341
|
+
this.currentTrack = null;
|
|
342
|
+
this.checkAndStartFallback();
|
|
343
|
+
});
|
|
240
344
|
}
|
|
241
345
|
|
|
346
|
+
checkAndStartFallback() {
|
|
347
|
+
if (this.queue.length === 0 &&
|
|
348
|
+
!this.streamer.isStreaming &&
|
|
349
|
+
!this.streamer.isFallbackActive &&
|
|
350
|
+
this.fallbackEnabled &&
|
|
351
|
+
this.fallbackUrl) {
|
|
352
|
+
|
|
353
|
+
setTimeout(() => {
|
|
354
|
+
if (this.queue.length === 0 &&
|
|
355
|
+
!this.streamer.isStreaming &&
|
|
356
|
+
!this.streamer.isFallbackActive) {
|
|
357
|
+
this.streamer.startFallback();
|
|
358
|
+
}
|
|
359
|
+
}, 1000);
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
|
|
242
363
|
async add(track) {
|
|
243
364
|
const isNewTrack = !this.queue.some(q => q.id === track.id);
|
|
244
365
|
if (!isNewTrack) {
|
|
@@ -255,34 +376,42 @@ class IcecastQueue {
|
|
|
255
376
|
|
|
256
377
|
return { position, added: true, isNowPlaying: false };
|
|
257
378
|
}
|
|
258
|
-
|
|
379
|
+
|
|
259
380
|
async addImmediate(track) {
|
|
260
|
-
if (this.currentTrack) {
|
|
381
|
+
if (this.currentTrack && !this.currentTrack.isFallback) {
|
|
261
382
|
this.queue.unshift(new Track({ ...this.currentTrack }));
|
|
262
383
|
}
|
|
263
|
-
|
|
384
|
+
|
|
264
385
|
this.queue.unshift(track);
|
|
265
386
|
await this.skip();
|
|
266
387
|
return { position: 1, added: true, isNowPlaying: true };
|
|
267
388
|
}
|
|
268
|
-
|
|
389
|
+
|
|
269
390
|
async playNext() {
|
|
270
|
-
if (this.isProcessing
|
|
391
|
+
if (this.isProcessing) {
|
|
271
392
|
return false;
|
|
272
393
|
}
|
|
273
|
-
|
|
394
|
+
|
|
274
395
|
this.isProcessing = true;
|
|
275
|
-
|
|
396
|
+
|
|
276
397
|
try {
|
|
398
|
+
if (this.streamer.isFallbackActive) {
|
|
399
|
+
this.streamer.stopFallback();
|
|
400
|
+
await this.delay(200);
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
if (this.queue.length === 0) {
|
|
404
|
+
this.checkAndStartFallback();
|
|
405
|
+
return false;
|
|
406
|
+
}
|
|
407
|
+
|
|
277
408
|
const nextTrack = this.queue.shift();
|
|
278
|
-
|
|
409
|
+
|
|
279
410
|
if (!nextTrack?.url) {
|
|
280
411
|
setTimeout(() => this.playNext(), 1000);
|
|
281
412
|
return false;
|
|
282
413
|
}
|
|
283
|
-
|
|
284
|
-
console.log(`🎵 Playing on Icecast: ${nextTrack.title}`);
|
|
285
|
-
|
|
414
|
+
|
|
286
415
|
await this.streamer.streamToIcecast(nextTrack.url, {
|
|
287
416
|
title: nextTrack.title,
|
|
288
417
|
duration: nextTrack.duration,
|
|
@@ -291,32 +420,31 @@ class IcecastQueue {
|
|
|
291
420
|
thumbnail: nextTrack.thumbnail,
|
|
292
421
|
source: nextTrack.source
|
|
293
422
|
});
|
|
294
|
-
|
|
423
|
+
|
|
295
424
|
return true;
|
|
296
425
|
} catch (error) {
|
|
297
426
|
console.error('Error playing next track:', error);
|
|
298
|
-
// Retry with next track
|
|
299
427
|
setTimeout(() => this.playNext(), 1000);
|
|
300
428
|
return false;
|
|
301
429
|
} finally {
|
|
302
430
|
this.isProcessing = false;
|
|
303
431
|
}
|
|
304
432
|
}
|
|
305
|
-
|
|
433
|
+
|
|
306
434
|
async skip() {
|
|
307
|
-
if (!this.streamer.isStreaming) {
|
|
435
|
+
if (!this.streamer.isStreaming && this.streamer.isFallbackActive) {
|
|
308
436
|
return false;
|
|
309
437
|
}
|
|
310
|
-
|
|
438
|
+
|
|
311
439
|
this.skipRequested = true;
|
|
312
440
|
this.streamer.stop();
|
|
313
|
-
|
|
441
|
+
|
|
314
442
|
await this.delay(100);
|
|
315
443
|
await this.playNext();
|
|
316
|
-
|
|
444
|
+
|
|
317
445
|
return true;
|
|
318
446
|
}
|
|
319
|
-
|
|
447
|
+
|
|
320
448
|
remove(position) {
|
|
321
449
|
if (position < 1 || position > this.queue.length) {
|
|
322
450
|
return null;
|
|
@@ -324,14 +452,19 @@ class IcecastQueue {
|
|
|
324
452
|
|
|
325
453
|
return this.queue.splice(position - 1, 1)[0];
|
|
326
454
|
}
|
|
327
|
-
|
|
455
|
+
|
|
328
456
|
clear() {
|
|
329
457
|
const cleared = [...this.queue];
|
|
330
458
|
this.queue = [];
|
|
459
|
+
|
|
460
|
+
this.checkAndStartFallback();
|
|
461
|
+
|
|
331
462
|
return cleared;
|
|
332
463
|
}
|
|
333
|
-
|
|
464
|
+
|
|
334
465
|
addToHistory(track, success = true) {
|
|
466
|
+
if (track.isFallback) return;
|
|
467
|
+
|
|
335
468
|
const historyEntry = {
|
|
336
469
|
...track,
|
|
337
470
|
playedAt: Date.now(),
|
|
@@ -345,14 +478,16 @@ class IcecastQueue {
|
|
|
345
478
|
this.history.pop();
|
|
346
479
|
}
|
|
347
480
|
}
|
|
348
|
-
|
|
481
|
+
|
|
349
482
|
getQueue() {
|
|
350
483
|
return [...this.queue];
|
|
351
484
|
}
|
|
352
|
-
|
|
485
|
+
|
|
353
486
|
getStatus() {
|
|
354
487
|
return {
|
|
355
|
-
isPlaying: this.streamer.isStreaming,
|
|
488
|
+
isPlaying: this.streamer.isStreaming || this.streamer.isFallbackActive,
|
|
489
|
+
isMusicPlaying: this.streamer.isStreaming,
|
|
490
|
+
isFallbackActive: this.streamer.isFallbackActive,
|
|
356
491
|
currentTrack: this.currentTrack,
|
|
357
492
|
queueLength: this.queue.length,
|
|
358
493
|
loopMode: this.loopMode,
|
|
@@ -360,10 +495,21 @@ class IcecastQueue {
|
|
|
360
495
|
upcoming: this.queue.slice(0, 10)
|
|
361
496
|
};
|
|
362
497
|
}
|
|
363
|
-
|
|
498
|
+
|
|
364
499
|
async delay(ms) {
|
|
365
500
|
return new Promise(resolve => setTimeout(resolve, ms));
|
|
366
501
|
}
|
|
502
|
+
|
|
503
|
+
startFallback() {
|
|
504
|
+
if (this.fallbackEnabled && this.fallbackUrl) {
|
|
505
|
+
return this.streamer.startFallback();
|
|
506
|
+
}
|
|
507
|
+
return false;
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
stopFallback() {
|
|
511
|
+
return this.streamer.stopFallback();
|
|
512
|
+
}
|
|
367
513
|
}
|
|
368
514
|
|
|
369
515
|
class YouTubeExtractor {
|
|
@@ -421,15 +567,15 @@ class IcecastServer {
|
|
|
421
567
|
this.logger = config.logger
|
|
422
568
|
this.setupExpress();
|
|
423
569
|
}
|
|
424
|
-
|
|
570
|
+
|
|
425
571
|
setupExpress() {
|
|
426
572
|
this.app.use(express.json());
|
|
427
|
-
|
|
573
|
+
|
|
428
574
|
this.app.get('/stream', (req, res) => {
|
|
429
575
|
res.redirect(this.config.publicStreamUrl);
|
|
430
576
|
});
|
|
431
577
|
}
|
|
432
|
-
|
|
578
|
+
|
|
433
579
|
start() {
|
|
434
580
|
this.app.listen(this.port, () => {
|
|
435
581
|
this.logger.success(`IcecastServer.start`, `running: http://localhost:${this.port}`);
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
const ModelPool = require("
|
|
2
|
-
const { User, Position, Message, AnchorPosition, SessionMetadata, Voice, Direct, HiddenChannel, Tip, RoomModerate } = require('
|
|
1
|
+
const ModelPool = require("vantiv.io/src/utils/ModelPool");
|
|
2
|
+
const { User, Position, Message, AnchorPosition, SessionMetadata, Voice, Direct, HiddenChannel, Tip, RoomModerate } = require('vantiv.io/src/utils/Models')
|
|
3
3
|
|
|
4
4
|
const modelPool = new ModelPool()
|
|
5
5
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
const WebSocketConstants = require('
|
|
1
|
+
const WebSocketConstants = require('vantiv.io/src/constants/WebSocketConstants');
|
|
2
2
|
const EventEmitter = require('events');
|
|
3
3
|
const WebSocket = require('ws');
|
|
4
4
|
|
|
@@ -109,37 +109,10 @@ class ConnectionManager extends EventEmitter {
|
|
|
109
109
|
this.reconnecting = false;
|
|
110
110
|
|
|
111
111
|
if (this.autoReconnect && !this.isManualDisconnect && !wasReconnecting) {
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
if (shouldReconnect) {
|
|
115
|
-
this._scheduleReconnect();
|
|
116
|
-
} else {
|
|
117
|
-
this.logger.warn('ConnectionManager', 'Not reconnecting due to close code or reason', {
|
|
118
|
-
code,
|
|
119
|
-
reason: reasonStr
|
|
120
|
-
});
|
|
121
|
-
}
|
|
112
|
+
this._scheduleReconnect();
|
|
122
113
|
}
|
|
123
114
|
}
|
|
124
115
|
|
|
125
|
-
_shouldReconnect(code, reason) {
|
|
126
|
-
if (code === 1000 && reason.includes('Manual disconnect')) {
|
|
127
|
-
return false;
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
if (code === 4001 || code === 4003 || code === 4004) {
|
|
131
|
-
this.logger.error('ConnectionManager', 'Authentication failed, not reconnecting', { code, reason });
|
|
132
|
-
return false;
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
if (code === 4009 || code === 4029) {
|
|
136
|
-
this.logger.error('ConnectionManager', 'Rate limited or banned, not reconnecting', { code, reason });
|
|
137
|
-
return false;
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
return true;
|
|
141
|
-
}
|
|
142
|
-
|
|
143
116
|
_handleError(error) {
|
|
144
117
|
this.emit('error', error);
|
|
145
118
|
this.logger.error('ConnectionManager', 'WebSocket error', {
|
|
@@ -162,19 +135,6 @@ class ConnectionManager extends EventEmitter {
|
|
|
162
135
|
return;
|
|
163
136
|
}
|
|
164
137
|
|
|
165
|
-
const maxAttempts = WebSocketConstants.MAX_RECONNECT_ATTEMPTS || 10;
|
|
166
|
-
if (this.reconnectAttempts >= maxAttempts) {
|
|
167
|
-
this.logger.error('ConnectionManager', 'Max reconnection attempts reached', {
|
|
168
|
-
attempts: this.reconnectAttempts,
|
|
169
|
-
maxAttempts
|
|
170
|
-
});
|
|
171
|
-
this.emit('reconnectFailed', {
|
|
172
|
-
attempts: this.reconnectAttempts,
|
|
173
|
-
maxAttempts
|
|
174
|
-
});
|
|
175
|
-
return;
|
|
176
|
-
}
|
|
177
|
-
|
|
178
138
|
this._clearReconnectTimeout();
|
|
179
139
|
|
|
180
140
|
const delay = Math.min(
|
|
@@ -9,7 +9,7 @@ const {
|
|
|
9
9
|
RoomModerateHandler,
|
|
10
10
|
HiddenChannelHandler,
|
|
11
11
|
SessionMetadataHandler,
|
|
12
|
-
} = require('
|
|
12
|
+
} = require('vantiv.io/src/classes/Handlers/EventHandlers');
|
|
13
13
|
|
|
14
14
|
class EventsManager {
|
|
15
15
|
constructor(server) {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
const WebSocketConstants = require('
|
|
1
|
+
const WebSocketConstants = require('vantiv.io/src/constants/WebSocketConstants');
|
|
2
2
|
const crypto = require('crypto');
|
|
3
3
|
|
|
4
4
|
class KeepAliveManager {
|
|
@@ -7,18 +7,11 @@ class KeepAliveManager {
|
|
|
7
7
|
this.logger = logger;
|
|
8
8
|
|
|
9
9
|
this.interval = null;
|
|
10
|
-
this.lastKeepaliveSent = 0;
|
|
11
|
-
this.lastPongReceived = 0;
|
|
12
|
-
this.consecutiveNoPong = 0;
|
|
13
|
-
this.maxConsecutiveNoPong = 3;
|
|
14
|
-
this.firstKeepaliveSent = false;
|
|
15
10
|
}
|
|
16
11
|
|
|
17
12
|
start() {
|
|
18
13
|
this.stop();
|
|
19
14
|
|
|
20
|
-
this.firstKeepaliveSent = false;
|
|
21
|
-
|
|
22
15
|
this._sendKeepAlive();
|
|
23
16
|
|
|
24
17
|
this.interval = setInterval(() => {
|
|
@@ -33,13 +26,11 @@ class KeepAliveManager {
|
|
|
33
26
|
clearInterval(this.interval);
|
|
34
27
|
this.interval = null;
|
|
35
28
|
}
|
|
36
|
-
this.consecutiveNoPong = 0;
|
|
37
|
-
this.firstKeepaliveSent = false;
|
|
38
29
|
}
|
|
39
30
|
|
|
40
31
|
_sendKeepAlive() {
|
|
41
32
|
if (!this.connectionManager.isConnected()) {
|
|
42
|
-
this.logger.debug('KeepAliveManager', 'Skipping keepalive');
|
|
33
|
+
this.logger.debug('KeepAliveManager', 'Skipping keepalive - not connected');
|
|
43
34
|
return;
|
|
44
35
|
}
|
|
45
36
|
|
|
@@ -50,13 +41,6 @@ class KeepAliveManager {
|
|
|
50
41
|
};
|
|
51
42
|
|
|
52
43
|
this.connectionManager.send(keepalive);
|
|
53
|
-
this.lastKeepaliveSent = Date.now();
|
|
54
|
-
|
|
55
|
-
if (!this.firstKeepaliveSent) {
|
|
56
|
-
this.firstKeepaliveSent = true;
|
|
57
|
-
this.lastPongReceived = Date.now();
|
|
58
|
-
return;
|
|
59
|
-
}
|
|
60
44
|
|
|
61
45
|
} catch (error) {
|
|
62
46
|
this.logger.error('KeepAliveManager', 'Failed to send keepalive', {
|
|
@@ -65,41 +49,6 @@ class KeepAliveManager {
|
|
|
65
49
|
}
|
|
66
50
|
}
|
|
67
51
|
|
|
68
|
-
_forceReconnect() {
|
|
69
|
-
if (this.connectionManager.isConnected()) {
|
|
70
|
-
this.connectionManager.disconnect(
|
|
71
|
-
WebSocketConstants.ERROR_CODES.ABNORMAL_CLOSURE,
|
|
72
|
-
'Keepalive timeout'
|
|
73
|
-
);
|
|
74
|
-
}
|
|
75
|
-
this.consecutiveNoPong = 0;
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
handlePong() {
|
|
79
|
-
this.lastPongReceived = Date.now();
|
|
80
|
-
this.consecutiveNoPong = 0;
|
|
81
|
-
|
|
82
|
-
const latency = this.lastPongReceived - this.lastKeepaliveSent;
|
|
83
|
-
if (latency > 10000) {
|
|
84
|
-
this.logger.warn('KeepAliveManager', `High latency: ${latency}ms`);
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
getStats() {
|
|
89
|
-
const timeSinceLastPong = Date.now() - this.lastPongReceived;
|
|
90
|
-
const timeSinceLastKeepalive = Date.now() - this.lastKeepaliveSent;
|
|
91
|
-
|
|
92
|
-
return {
|
|
93
|
-
isActive: this.interval !== null,
|
|
94
|
-
lastKeepaliveSent: this.lastKeepaliveSent,
|
|
95
|
-
lastPongReceived: this.lastPongReceived,
|
|
96
|
-
timeSinceLastPong: timeSinceLastPong,
|
|
97
|
-
timeSinceLastKeepalive: timeSinceLastKeepalive,
|
|
98
|
-
consecutiveNoPong: this.consecutiveNoPong,
|
|
99
|
-
healthStatus: this.firstKeepaliveSent ? (timeSinceLastPong < 45000 ? 'healthy' : 'degraded') : 'initializing'
|
|
100
|
-
};
|
|
101
|
-
}
|
|
102
|
-
|
|
103
52
|
forceKeepAlive() {
|
|
104
53
|
this._sendKeepAlive();
|
|
105
54
|
this.logger.info('KeepAliveManager', 'Manual keepalive sent');
|
|
@@ -2,11 +2,10 @@ const WebSocketConstants = {
|
|
|
2
2
|
// Connection
|
|
3
3
|
KEEPALIVE_INTERVAL: 15000,
|
|
4
4
|
MAX_RECONNECT_DELAY: 300000, // 5 minutes
|
|
5
|
-
RECONNECT_BACKOFF_FACTOR: 1.
|
|
5
|
+
RECONNECT_BACKOFF_FACTOR: 1.1,
|
|
6
6
|
DEFAULT_RECONNECT_DELAY: 5000,
|
|
7
7
|
DEFAULT_AUTO_RECONNECT: true,
|
|
8
8
|
MAX_RECONNECT_ATTEMPTS: 10,
|
|
9
|
-
RECONNECT_BACKOFF_FACTOR: 1.5,
|
|
10
9
|
|
|
11
10
|
// Logger Options
|
|
12
11
|
DEFAULT_LOGGER_OPTIONS: {
|
package/src/core/Highrise.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
const HighriseWebsocket = require('
|
|
2
|
-
const { ConfigValidator } = require('
|
|
3
|
-
const { checkVersion } = require('
|
|
4
|
-
const WebSocketConstants = require('
|
|
5
|
-
const Utils = require('
|
|
1
|
+
const HighriseWebsocket = require('vantiv.io/src/core/HighriseWebsocket');
|
|
2
|
+
const { ConfigValidator } = require('vantiv.io/src/validators/ConfigValidator');
|
|
3
|
+
const { checkVersion } = require('vantiv.io/src/utils/versionCheck');
|
|
4
|
+
const WebSocketConstants = require('vantiv.io/src/constants/WebSocketConstants')
|
|
5
|
+
const Utils = require('vantiv.io/src/classes/Actions/Utils');
|
|
6
6
|
|
|
7
7
|
class Highrise extends HighriseWebsocket {
|
|
8
8
|
constructor(events, userOptions = {}) {
|
|
@@ -35,9 +35,9 @@ class Highrise extends HighriseWebsocket {
|
|
|
35
35
|
? userOptions.customRoles
|
|
36
36
|
: [],
|
|
37
37
|
|
|
38
|
-
music:
|
|
39
|
-
|
|
40
|
-
}
|
|
38
|
+
music: userOptions.music !== undefined
|
|
39
|
+
? userOptions.music
|
|
40
|
+
: {}
|
|
41
41
|
};
|
|
42
42
|
|
|
43
43
|
const eventsValidation = ConfigValidator.validateEvents(events);
|
|
@@ -1,22 +1,22 @@
|
|
|
1
1
|
const EventEmitter = require('events');
|
|
2
2
|
|
|
3
|
-
const ConnectionManager = require('
|
|
4
|
-
const KeepAliveManager = require('
|
|
5
|
-
const MessageHandler = require('
|
|
6
|
-
const EventsManager = require('
|
|
7
|
-
const { FireAndForgetSender, RequestResponseSender } = require('
|
|
8
|
-
|
|
9
|
-
const MetricsManager = require('
|
|
10
|
-
const CleanupManager = require('
|
|
11
|
-
|
|
12
|
-
const ChannelManager = require('
|
|
13
|
-
const ConnectionValidator = require('
|
|
14
|
-
const WebSocketHandlers = require('
|
|
15
|
-
|
|
16
|
-
const { DirectClass } = require('
|
|
17
|
-
const { MovementCache } = require('
|
|
18
|
-
const AwaitClass = require('
|
|
19
|
-
const { Logger } = require('
|
|
3
|
+
const ConnectionManager = require('vantiv.io/src/classes/Managers/Networking/ConnectionManager');
|
|
4
|
+
const KeepAliveManager = require('vantiv.io/src/classes/Managers/Networking/KeepAliveManager');
|
|
5
|
+
const MessageHandler = require('vantiv.io/src/classes/Managers/Networking/MessageHandler');
|
|
6
|
+
const EventsManager = require('vantiv.io/src/classes/Managers/Networking/EventsManager');
|
|
7
|
+
const { FireAndForgetSender, RequestResponseSender } = require('vantiv.io/src/classes/Managers/Networking/Request');
|
|
8
|
+
|
|
9
|
+
const MetricsManager = require('vantiv.io/src/classes/Managers/Helpers/MetricsManager');
|
|
10
|
+
const CleanupManager = require('vantiv.io/src/classes/Managers/Helpers/CleanupManager');
|
|
11
|
+
|
|
12
|
+
const ChannelManager = require('vantiv.io/src/classes/Managers/ChannelManager');
|
|
13
|
+
const ConnectionValidator = require('vantiv.io/src/validators/ConnectionValidator');
|
|
14
|
+
const WebSocketHandlers = require('vantiv.io/src/classes/Handlers/WebSocketHandlers');
|
|
15
|
+
|
|
16
|
+
const { DirectClass } = require('vantiv.io/src/classes/Actions/Direct');
|
|
17
|
+
const { MovementCache } = require('vantiv.io/src/classes/Caches/MovementCache');
|
|
18
|
+
const AwaitClass = require('vantiv.io/src/classes/Actions/Awaiter');
|
|
19
|
+
const { Logger } = require('vantiv.io/src/classes/Managers/Helpers/LoggerManager');
|
|
20
20
|
|
|
21
21
|
class HighriseWebsocket extends EventEmitter {
|
|
22
22
|
constructor(events, options = {}) {
|