streamify-audio 2.1.7 → 2.1.9

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/index.js CHANGED
@@ -249,14 +249,4 @@ Streamify.Manager = Manager;
249
249
  Streamify.Player = Manager ? require('./src/discord/Player') : null;
250
250
  Streamify.Queue = Manager ? require('./src/discord/Queue') : null;
251
251
 
252
- const persistence = require('./src/persistence');
253
- Streamify.PersistenceAdapter = persistence.PersistenceAdapter;
254
- Streamify.MemoryAdapter = persistence.MemoryAdapter;
255
- Streamify.MongoAdapter = persistence.MongoAdapter;
256
-
257
- const presets = require('./src/filters/presets');
258
- Streamify.EffectPresets = presets;
259
- Streamify.getEffectPresetNames = presets.getPresetNames;
260
- Streamify.getEffectPresetsInfo = presets.getAllPresetsInfo;
261
-
262
252
  module.exports = Streamify;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "streamify-audio",
3
- "version": "2.1.7",
3
+ "version": "2.1.9",
4
4
  "description": "Dual-mode audio library: HTTP streaming proxy + Discord player (Lavalink alternative). Supports YouTube, Spotify, SoundCloud with audio filters.",
5
5
  "main": "index.js",
6
6
  "types": "index.d.ts",
@@ -29,8 +29,7 @@
29
29
  "author": "Lucas",
30
30
  "license": "MIT",
31
31
  "dependencies": {
32
- "express": "^4.18.2",
33
- "streamify-audio": "^2.1.4"
32
+ "express": "^4.18.2"
34
33
  },
35
34
  "optionalDependencies": {
36
35
  "@discordjs/opus": "^0.9.0",
@@ -5,7 +5,6 @@ const spotify = require('../providers/spotify');
5
5
  const soundcloud = require('../providers/soundcloud');
6
6
  const log = require('../utils/logger');
7
7
  const { loadConfig } = require('../config');
8
- const { MemoryAdapter, MongoAdapter, serializeTrack, deserializeTrack } = require('../persistence');
9
8
 
10
9
  function checkDependencies() {
11
10
  const missing = [];
@@ -74,182 +73,11 @@ class Manager extends EventEmitter {
74
73
  maxTracks: options.autoplay?.maxTracks ?? 5
75
74
  };
76
75
 
77
- this.fallback = {
78
- enabled: options.fallback?.enabled ?? false,
79
- order: options.fallback?.order || ['youtube', 'soundcloud']
80
- };
81
-
82
- this.persistence = null;
83
- this._persistenceInterval = null;
84
- this._persistenceIntervalMs = options.persistence?.interval || 30000;
85
-
86
76
  this._setupVoiceStateListener();
87
77
 
88
78
  log.info('MANAGER', 'Streamify Manager initialized');
89
79
  }
90
80
 
91
- async enablePersistence(options = {}) {
92
- if (options.adapter) {
93
- this.persistence = options.adapter;
94
- } else if (options.type === 'mongodb') {
95
- this.persistence = new MongoAdapter({
96
- client: options.client,
97
- uri: options.uri,
98
- db: options.db,
99
- collection: options.collection
100
- });
101
- } else {
102
- this.persistence = new MemoryAdapter();
103
- }
104
-
105
- if (options.autoSave !== false) {
106
- this._persistenceInterval = setInterval(() => {
107
- this._saveAllSessions();
108
- }, this._persistenceIntervalMs);
109
- }
110
-
111
- log.info('MANAGER', `Persistence enabled (type: ${options.type || 'memory'})`);
112
- return this;
113
- }
114
-
115
- async _saveAllSessions() {
116
- if (!this.persistence) return;
117
-
118
- for (const [guildId, player] of this.players) {
119
- if (player.connected) {
120
- await this.saveSession(guildId);
121
- }
122
- }
123
- }
124
-
125
- async saveSession(guildId) {
126
- if (!this.persistence) return false;
127
-
128
- const player = this.players.get(guildId);
129
- if (!player || !player.connected) return false;
130
-
131
- const current = player.queue.current;
132
- if (!current && player.queue.tracks.length === 0) {
133
- await this.persistence.delete(guildId);
134
- return true;
135
- }
136
-
137
- const sessionData = {
138
- guildId,
139
- voiceChannelId: player.voiceChannelId,
140
- textChannelId: player.textChannelId,
141
- currentTrack: serializeTrack(current),
142
- positionMs: player.position || 0,
143
- queue: player.queue.tracks.map(serializeTrack),
144
- volume: player.volume,
145
- loop: player.queue.repeatMode || 'off',
146
- filters: player.filters || {}
147
- };
148
-
149
- await this.persistence.save(guildId, sessionData);
150
- log.info('PERSISTENCE', `Session saved: ${guildId} (track: ${sessionData.currentTrack?.title || 'none'}, queue: ${sessionData.queue?.length || 0})`);
151
- return true;
152
- }
153
-
154
- async restoreSession(guildId, options = {}) {
155
- if (!this.persistence) return null;
156
-
157
- const session = await this.persistence.load(guildId);
158
- if (!session) return null;
159
-
160
- const guild = this.client.guilds.cache.get(guildId);
161
- if (!guild) {
162
- log.debug('MANAGER', `Guild ${guildId} not found, skipping restore`);
163
- return null;
164
- }
165
-
166
- const voiceChannel = this.client.channels.cache.get(session.voiceChannelId);
167
- if (!voiceChannel) {
168
- log.debug('MANAGER', `Voice channel ${session.voiceChannelId} not found, skipping restore`);
169
- await this.persistence.delete(guildId);
170
- return null;
171
- }
172
-
173
- log.info('MANAGER', `Restoring session for guild ${guildId}`);
174
-
175
- const player = await this.create(guildId, session.voiceChannelId, session.textChannelId);
176
-
177
- if (session.volume !== undefined) player.setVolume(session.volume);
178
- if (session.loop && session.loop !== 'off') player.setLoop(session.loop);
179
-
180
- if (session.currentTrack) {
181
- const track = deserializeTrack(session.currentTrack);
182
- log.info('MANAGER', `[RESTORE] Playing track: ${track.title}`);
183
- try {
184
- await player.play(track);
185
- } catch (playErr) {
186
- log.error('MANAGER', `[RESTORE] Play error: ${playErr.message}`);
187
- }
188
- log.info('MANAGER', `[RESTORE] Track play() returned`);
189
-
190
- if (session.positionMs > 0 && !track.isLive && options.seekToPosition !== false) {
191
- try {
192
- await player.seek(session.positionMs);
193
- } catch (e) {
194
- log.debug('MANAGER', `Could not seek to position: ${e.message}`);
195
- }
196
- }
197
- }
198
-
199
- if (session.queue && session.queue.length > 0) {
200
- const tracks = session.queue.map(deserializeTrack);
201
- player.queue.addMany(tracks);
202
- }
203
-
204
- log.info('MANAGER', `[RESTORE] Checking voice channel members`);
205
- let memberCount = 0;
206
- try {
207
- const members = voiceChannel.members;
208
- if (members) {
209
- memberCount = members.filter(m => !m.user.bot).size;
210
- }
211
- } catch (e) {
212
- log.info('MANAGER', `[RESTORE] Error getting members: ${e.message}`);
213
- }
214
-
215
- if (memberCount === 0 && player.autoPause.enabled) {
216
- player.pause();
217
- player._autoPaused = true;
218
- log.info('MANAGER', `Session restored but auto-paused (empty channel)`);
219
- }
220
-
221
- log.info('MANAGER', `[RESTORE] Emitting sessionRestored event`);
222
- this.emit('sessionRestored', { guildId, voiceChannelId: player.voiceChannelId });
223
- log.info('MANAGER', `[RESTORE] Returning true`);
224
- return true;
225
- }
226
-
227
- async restoreAllSessions(options = {}) {
228
- if (!this.persistence) return [];
229
-
230
- const sessions = await this.persistence.loadAll();
231
- const restored = [];
232
-
233
- for (const session of sessions) {
234
- try {
235
- const success = await this.restoreSession(session.guildId, options);
236
- if (success) {
237
- restored.push({ guildId: session.guildId });
238
- }
239
- } catch (error) {
240
- log.error('MANAGER', `Failed to restore session ${session.guildId}: ${error.message}`);
241
- }
242
- }
243
-
244
- log.info('MANAGER', `Restored ${restored.length}/${sessions.length} sessions`);
245
- return restored;
246
- }
247
-
248
- async deleteSession(guildId) {
249
- if (!this.persistence) return false;
250
- return this.persistence.delete(guildId);
251
- }
252
-
253
81
  _setupVoiceStateListener() {
254
82
  this.client.on('voiceStateUpdate', (oldState, newState) => {
255
83
  const player = this.players.get(oldState.guild.id) || this.players.get(newState.guild.id);
@@ -646,84 +474,7 @@ class Manager extends EventEmitter {
646
474
  }
647
475
  }
648
476
 
649
- async searchWithFallback(query, options = {}) {
650
- if (!this.fallback.enabled) {
651
- return this.search(query, options);
652
- }
653
-
654
- const sources = options.sources || this.fallback.order;
655
- let lastError = null;
656
-
657
- for (const source of sources) {
658
- try {
659
- const result = await this.search(query, { ...options, source });
660
- if (result.loadType !== 'error' && result.tracks.length > 0) {
661
- return result;
662
- }
663
- } catch (error) {
664
- lastError = error;
665
- log.debug('MANAGER', `Fallback: ${source} failed, trying next...`);
666
- }
667
- }
668
-
669
- return {
670
- loadType: 'error',
671
- tracks: [],
672
- error: lastError?.message || 'All sources failed'
673
- };
674
- }
675
-
676
- async resolveWithFallback(query) {
677
- if (!this.fallback.enabled) {
678
- return this.resolve(query);
679
- }
680
-
681
- const result = await this.resolve(query);
682
- if (result.loadType !== 'error' && result.tracks.length > 0) {
683
- return result;
684
- }
685
-
686
- const detected = this._detectSource(query);
687
- if (detected === 'spotify') {
688
- const match = this._extractId(query, detected);
689
- if (match) {
690
- try {
691
- const track = await this.getInfo(match.id, 'spotify');
692
- const searchQuery = `${track.author} - ${track.title}`;
693
-
694
- for (const source of this.fallback.order) {
695
- if (source === 'spotify') continue;
696
- try {
697
- const fallbackResult = await this.search(searchQuery, { source, limit: 1 });
698
- if (fallbackResult.tracks.length > 0) {
699
- const fallbackTrack = fallbackResult.tracks[0];
700
- fallbackTrack._originalSpotifyTrack = track;
701
- log.info('MANAGER', `Spotify fallback: resolved via ${source}`);
702
- return {
703
- loadType: 'track',
704
- tracks: [fallbackTrack],
705
- fallbackUsed: source
706
- };
707
- }
708
- } catch (e) {
709
- continue;
710
- }
711
- }
712
- } catch (e) {
713
- log.debug('MANAGER', `Spotify info fetch failed: ${e.message}`);
714
- }
715
- }
716
- }
717
-
718
- return result;
719
- }
720
-
721
477
  destroyAll() {
722
- if (this._persistenceInterval) {
723
- clearInterval(this._persistenceInterval);
724
- this._persistenceInterval = null;
725
- }
726
-
727
478
  for (const [guildId, player] of this.players) {
728
479
  player.destroy();
729
480
  }
@@ -2,7 +2,6 @@ const { EventEmitter } = require('events');
2
2
  const Queue = require('./Queue');
3
3
  const { createStream } = require('./Stream');
4
4
  const log = require('../utils/logger');
5
- const { buildPresetFilters, getPresetNames, getAllPresetsInfo } = require('../filters/presets');
6
5
 
7
6
  let voiceModule;
8
7
  try {
@@ -588,70 +587,6 @@ class Player extends EventEmitter {
588
587
  return this.setFilter('preset', presetName);
589
588
  }
590
589
 
591
- async setEffectPreset(presetName, intensity = 0.5) {
592
- const filters = buildPresetFilters(presetName, intensity);
593
-
594
- for (const [key, value] of Object.entries(filters)) {
595
- if (key === '_intensityFactor') continue;
596
- this._filters[key] = value;
597
- }
598
-
599
- this._activePresets = this._activePresets || new Map();
600
- this._activePresets.set(presetName, intensity);
601
-
602
- if (this._playing && this.queue.current) {
603
- return this.setFilter('_trigger', null);
604
- }
605
- return true;
606
- }
607
-
608
- async setEffectPresets(presets) {
609
- this._activePresets = new Map();
610
-
611
- for (const { name, intensity = 0.5 } of presets) {
612
- const filters = buildPresetFilters(name, intensity);
613
- for (const [key, value] of Object.entries(filters)) {
614
- if (key === '_intensityFactor') continue;
615
- this._filters[key] = value;
616
- }
617
- this._activePresets.set(name, intensity);
618
- }
619
-
620
- if (this._playing && this.queue.current) {
621
- return this.setFilter('_trigger', null);
622
- }
623
- return true;
624
- }
625
-
626
- async clearEffectPresets() {
627
- const presetsToRemove = ['bass', 'treble', 'speed', 'pitch', 'rotation', 'karaoke',
628
- 'lowpass', 'highpass', 'bandpass', 'vibrato', 'tremolo', 'chorus',
629
- 'compressor', 'normalizer', 'nightcore', 'vaporwave', 'bassboost', '8d'];
630
-
631
- for (const key of presetsToRemove) {
632
- delete this._filters[key];
633
- }
634
-
635
- this._activePresets = new Map();
636
-
637
- if (this._playing && this.queue.current) {
638
- return this.setFilter('_trigger', null);
639
- }
640
- return true;
641
- }
642
-
643
- getActiveEffectPresets() {
644
- return this._activePresets ? Array.from(this._activePresets.entries()).map(([name, intensity]) => ({ name, intensity })) : [];
645
- }
646
-
647
- getAvailableEffectPresets() {
648
- return getAllPresetsInfo();
649
- }
650
-
651
- static getEffectPresetNames() {
652
- return getPresetNames();
653
- }
654
-
655
590
  async clearEQ() {
656
591
  delete this._filters.equalizer;
657
592
  delete this._filters.preset;
@@ -1,227 +0,0 @@
1
- const EFFECT_PRESETS = {
2
- bassboost: {
3
- description: 'Boost bass frequencies',
4
- build: (intensity = 0.5) => {
5
- const gain = 5 + (intensity * 15);
6
- return { bass: gain };
7
- }
8
- },
9
-
10
- nightcore: {
11
- description: 'Speed up with higher pitch (anime style)',
12
- build: (intensity = 0.5) => {
13
- const speedBoost = 0.15 + (intensity * 0.2);
14
- return {
15
- speed: 1 + speedBoost,
16
- pitch: 1 + speedBoost
17
- };
18
- }
19
- },
20
-
21
- vaporwave: {
22
- description: 'Slow down with lower pitch (aesthetic)',
23
- build: (intensity = 0.5) => {
24
- const slowdown = 0.1 + (intensity * 0.2);
25
- return {
26
- speed: 1 - slowdown,
27
- pitch: 1 - slowdown
28
- };
29
- }
30
- },
31
-
32
- '8d': {
33
- description: 'Rotating 8D audio effect',
34
- build: (intensity = 0.5) => {
35
- const speed = 0.05 + (intensity * 0.2);
36
- return { rotation: { speed } };
37
- }
38
- },
39
-
40
- karaoke: {
41
- description: 'Reduce vocals (center channel removal)',
42
- build: () => ({ karaoke: true })
43
- },
44
-
45
- trebleboost: {
46
- description: 'Boost high frequencies',
47
- build: (intensity = 0.5) => {
48
- const gain = 5 + (intensity * 15);
49
- return { treble: gain };
50
- }
51
- },
52
-
53
- deep: {
54
- description: 'Deep bass with reduced highs',
55
- build: (intensity = 0.5) => {
56
- const bassGain = 8 + (intensity * 12);
57
- const trebleCut = -3 - (intensity * 7);
58
- return { bass: bassGain, treble: trebleCut };
59
- }
60
- },
61
-
62
- pop: {
63
- description: 'Enhanced clarity for pop music',
64
- build: (intensity = 0.5) => {
65
- const factor = 0.5 + (intensity * 0.5);
66
- return {
67
- preset: 'pop',
68
- _intensityFactor: factor
69
- };
70
- }
71
- },
72
-
73
- rock: {
74
- description: 'Punchy mids and lows for rock',
75
- build: (intensity = 0.5) => {
76
- const factor = 0.5 + (intensity * 0.5);
77
- return {
78
- preset: 'rock',
79
- _intensityFactor: factor
80
- };
81
- }
82
- },
83
-
84
- electronic: {
85
- description: 'Enhanced bass and highs for EDM',
86
- build: (intensity = 0.5) => {
87
- const factor = 0.5 + (intensity * 0.5);
88
- return {
89
- preset: 'electronic',
90
- _intensityFactor: factor
91
- };
92
- }
93
- },
94
-
95
- lofi: {
96
- description: 'Lo-fi aesthetic with warmth',
97
- build: (intensity = 0.5) => ({
98
- lowpass: 8000 - (intensity * 4000),
99
- bass: 3 + (intensity * 5)
100
- })
101
- },
102
-
103
- radio: {
104
- description: 'AM radio effect',
105
- build: (intensity = 0.5) => ({
106
- lowpass: 5000 - (intensity * 2000),
107
- highpass: 300 + (intensity * 200)
108
- })
109
- },
110
-
111
- telephone: {
112
- description: 'Telephone/walkie-talkie effect',
113
- build: () => ({
114
- bandpass: { frequency: 1500, width: 1000 }
115
- })
116
- },
117
-
118
- soft: {
119
- description: 'Soft, gentle sound',
120
- build: (intensity = 0.5) => ({
121
- lowpass: 12000 - (intensity * 4000),
122
- compressor: true
123
- })
124
- },
125
-
126
- loud: {
127
- description: 'Louder, more compressed',
128
- build: (intensity = 0.5) => ({
129
- preset: 'loudness',
130
- compressor: true,
131
- normalizer: true
132
- })
133
- },
134
-
135
- chipmunk: {
136
- description: 'High-pitched chipmunk voice',
137
- build: (intensity = 0.5) => ({
138
- pitch: 1.3 + (intensity * 0.5),
139
- speed: 1.2 + (intensity * 0.3)
140
- })
141
- },
142
-
143
- darth: {
144
- description: 'Deep Darth Vader-like voice',
145
- build: (intensity = 0.5) => ({
146
- pitch: 0.7 - (intensity * 0.2),
147
- speed: 0.9 - (intensity * 0.1)
148
- })
149
- },
150
-
151
- echo: {
152
- description: 'Echo/reverb effect',
153
- build: () => ({
154
- chorus: true
155
- })
156
- },
157
-
158
- vibrato: {
159
- description: 'Vibrating pitch effect',
160
- build: (intensity = 0.5) => ({
161
- vibrato: {
162
- frequency: 4 + (intensity * 6),
163
- depth: 0.3 + (intensity * 0.4)
164
- }
165
- })
166
- },
167
-
168
- tremolo: {
169
- description: 'Trembling volume effect',
170
- build: (intensity = 0.5) => ({
171
- tremolo: {
172
- frequency: 4 + (intensity * 8),
173
- depth: 0.4 + (intensity * 0.4)
174
- }
175
- })
176
- }
177
- };
178
-
179
- function getPresetNames() {
180
- return Object.keys(EFFECT_PRESETS);
181
- }
182
-
183
- function getPresetInfo(name) {
184
- const preset = EFFECT_PRESETS[name];
185
- if (!preset) return null;
186
- return {
187
- name,
188
- description: preset.description
189
- };
190
- }
191
-
192
- function getAllPresetsInfo() {
193
- return Object.entries(EFFECT_PRESETS).map(([name, preset]) => ({
194
- name,
195
- description: preset.description
196
- }));
197
- }
198
-
199
- function buildPresetFilters(name, intensity = 0.5) {
200
- const preset = EFFECT_PRESETS[name];
201
- if (!preset) {
202
- throw new Error(`Unknown preset: ${name}. Available: ${getPresetNames().join(', ')}`);
203
- }
204
-
205
- const clampedIntensity = Math.max(0, Math.min(1, intensity));
206
- return preset.build(clampedIntensity);
207
- }
208
-
209
- function combinePresets(presetNames, intensity = 0.5) {
210
- const combined = {};
211
-
212
- for (const name of presetNames) {
213
- const filters = buildPresetFilters(name, intensity);
214
- Object.assign(combined, filters);
215
- }
216
-
217
- return combined;
218
- }
219
-
220
- module.exports = {
221
- EFFECT_PRESETS,
222
- getPresetNames,
223
- getPresetInfo,
224
- getAllPresetsInfo,
225
- buildPresetFilters,
226
- combinePresets
227
- };
@@ -1,166 +0,0 @@
1
- const log = require('../utils/logger');
2
-
3
- class PersistenceAdapter {
4
- async save(guildId, sessionData) {
5
- throw new Error('save() must be implemented by adapter');
6
- }
7
-
8
- async load(guildId) {
9
- throw new Error('load() must be implemented by adapter');
10
- }
11
-
12
- async delete(guildId) {
13
- throw new Error('delete() must be implemented by adapter');
14
- }
15
-
16
- async loadAll() {
17
- throw new Error('loadAll() must be implemented by adapter');
18
- }
19
- }
20
-
21
- class MemoryAdapter extends PersistenceAdapter {
22
- constructor() {
23
- super();
24
- this.sessions = new Map();
25
- }
26
-
27
- async save(guildId, sessionData) {
28
- this.sessions.set(guildId, {
29
- ...sessionData,
30
- updatedAt: Date.now()
31
- });
32
- return true;
33
- }
34
-
35
- async load(guildId) {
36
- return this.sessions.get(guildId) || null;
37
- }
38
-
39
- async delete(guildId) {
40
- return this.sessions.delete(guildId);
41
- }
42
-
43
- async loadAll() {
44
- return Array.from(this.sessions.entries()).map(([guildId, data]) => ({
45
- guildId,
46
- ...data
47
- }));
48
- }
49
- }
50
-
51
- class MongoAdapter extends PersistenceAdapter {
52
- constructor(options = {}) {
53
- super();
54
- this.collection = null;
55
- this.collectionName = options.collection || 'streamify_sessions';
56
- this.mongoClient = options.client || null;
57
- this.mongoUri = options.uri || null;
58
- this.db = options.db || null;
59
- }
60
-
61
- async connect() {
62
- if (this.collection) return;
63
-
64
- if (this.mongoClient) {
65
- const dbName = this.db || 'streamify';
66
- this.collection = this.mongoClient.db(dbName).collection(this.collectionName);
67
- } else if (this.mongoUri) {
68
- const { MongoClient } = require('mongodb');
69
- const client = new MongoClient(this.mongoUri);
70
- await client.connect();
71
- const dbName = this.db || 'streamify';
72
- this.collection = client.db(dbName).collection(this.collectionName);
73
- } else {
74
- throw new Error('MongoAdapter requires either client or uri option');
75
- }
76
-
77
- log.info('PERSISTENCE', `Connected to MongoDB collection: ${this.collectionName}`);
78
- }
79
-
80
- async save(guildId, sessionData) {
81
- await this.connect();
82
- await this.collection.updateOne(
83
- { _id: guildId },
84
- { $set: { ...sessionData, updatedAt: new Date() } },
85
- { upsert: true }
86
- );
87
- return true;
88
- }
89
-
90
- async load(guildId) {
91
- await this.connect();
92
- const doc = await this.collection.findOne({ _id: guildId });
93
- return doc || null;
94
- }
95
-
96
- async delete(guildId) {
97
- await this.connect();
98
- const result = await this.collection.deleteOne({ _id: guildId });
99
- return result.deletedCount > 0;
100
- }
101
-
102
- async loadAll() {
103
- await this.connect();
104
- const docs = await this.collection.find({}).toArray();
105
- return docs.map(doc => ({
106
- guildId: doc._id,
107
- ...doc
108
- }));
109
- }
110
- }
111
-
112
- function serializeTrack(track) {
113
- if (!track) return null;
114
-
115
- let requestedBy = null;
116
- if (track.requestedBy) {
117
- if (typeof track.requestedBy === 'string') {
118
- requestedBy = { username: track.requestedBy };
119
- } else if (typeof track.requestedBy === 'object') {
120
- requestedBy = {
121
- id: track.requestedBy.id || null,
122
- username: track.requestedBy.username || track.requestedBy.tag || String(track.requestedBy),
123
- avatarUrl: track.requestedBy.avatarUrl || null
124
- };
125
- }
126
- }
127
-
128
- return {
129
- id: track.id,
130
- title: track.title,
131
- author: track.author,
132
- duration: track.duration,
133
- thumbnail: track.thumbnail,
134
- uri: track.uri,
135
- source: track.source,
136
- isLive: track.isLive || false,
137
- _resolvedId: track._resolvedId,
138
- requestedBy,
139
- artistUrl: track.artistUrl || null
140
- };
141
- }
142
-
143
- function deserializeTrack(data) {
144
- if (!data) return null;
145
- return {
146
- id: data.id,
147
- title: data.title,
148
- author: data.author,
149
- duration: data.duration,
150
- thumbnail: data.thumbnail,
151
- uri: data.uri,
152
- source: data.source,
153
- isLive: data.isLive || false,
154
- _resolvedId: data._resolvedId,
155
- requestedBy: data.requestedBy,
156
- artistUrl: data.artistUrl
157
- };
158
- }
159
-
160
- module.exports = {
161
- PersistenceAdapter,
162
- MemoryAdapter,
163
- MongoAdapter,
164
- serializeTrack,
165
- deserializeTrack
166
- };