nocopyrightsounds-widget 1.0.4 → 1.0.6

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 (2) hide show
  1. package/package.json +1 -1
  2. package/src/index.js +137 -46
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nocopyrightsounds-widget",
3
- "version": "1.0.4",
3
+ "version": "1.0.6",
4
4
  "description": "Un widget musical persistant pour site web utilisant l'API NoCopyrightSounds",
5
5
  "main": "src/index.js",
6
6
  "type": "module",
package/src/index.js CHANGED
@@ -6,7 +6,6 @@ class NCSWidget {
6
6
  this.audio = new Audio();
7
7
  this.isPlaying = false;
8
8
 
9
- // Historique et File d'attente (Préchargement)
10
9
  this.trackHistory = [];
11
10
  this.currentHistoryIndex = -1;
12
11
  this.nextTracksQueue = [];
@@ -14,23 +13,27 @@ class NCSWidget {
14
13
 
15
14
  this.isBrowser = typeof window !== 'undefined';
16
15
  if (this.isBrowser) {
17
- this.audio.volume = localStorage.getItem('ncs_volume') || 0.5;
16
+ // Le volume est à 1 (100%) par défaut s'il n'y a rien en cache
17
+ const savedVol = localStorage.getItem('ncs_volume');
18
+ this.audio.volume = savedVol !== null ? parseFloat(savedVol) : 1.0;
19
+ this.lastVolume = this.audio.volume > 0 ? this.audio.volume : 1.0; // Pour le mute/unmute
20
+
18
21
  this.savedTime = localStorage.getItem('ncs_currentTime') || 0;
19
22
  this.savedTrack = localStorage.getItem('ncs_currentTrack') || null;
20
23
  this.savedCover = localStorage.getItem('ncs_currentCover') || null;
21
24
  this.savedTitle = localStorage.getItem('ncs_currentTitle') || null;
25
+ this.savedArtists = localStorage.getItem('ncs_currentArtists') || null;
22
26
  this.isWidgetOpen = localStorage.getItem('ncs_isOpen') === 'true';
23
27
  }
24
28
 
25
29
  this.initDOM();
26
30
  this.attachEvents();
27
31
 
28
- // Initialisation de la musique
29
32
  if (this.savedTrack && this.savedTime > 0) {
30
33
  this.restoreTrack();
31
- this.fillQueue(this.genreSelect.value); // Lancer le préchargement en fond
34
+ this.fillQueue(this.genreSelect.value);
32
35
  } else {
33
- this.changeGenre(this.genreSelect.value);
36
+ this.changeGenre('all');
34
37
  }
35
38
  }
36
39
 
@@ -43,23 +46,18 @@ class NCSWidget {
43
46
  const style = document.createElement('style');
44
47
  style.textContent = `
45
48
  #ncs-persistent-widget { position: fixed; ${this.getPositionStyles()} z-index: 99999; font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); }
46
-
47
- /* État Réduit */
48
49
  .ncs-minimized { width: 55px; height: 55px; border-radius: 50%; background: linear-gradient(135deg, #1DB954, #1ed760); cursor: pointer; display: flex; justify-content: center; align-items: center; box-shadow: 0 6px 15px rgba(29, 185, 84, 0.4); font-size: 24px; transition: transform 0.2s; }
49
50
  .ncs-minimized:hover { transform: scale(1.1); }
50
51
  .ncs-minimized.hidden { display: none; }
51
52
 
52
- /* État Agrandit */
53
53
  .ncs-expanded { width: 320px; background: #181818; color: white; border-radius: 16px; padding: 20px; box-shadow: 0 10px 30px rgba(0,0,0,0.5); display: none; border: 1px solid #282828; }
54
54
  .ncs-expanded.active { display: block; animation: ncsFadeIn 0.3s ease; }
55
55
 
56
- /* Header & Infos */
57
56
  .ncs-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 15px; }
58
57
  .ncs-header strong { font-size: 14px; font-weight: 600; color: #b3b3b3; letter-spacing: 1px; text-transform: uppercase; display: flex; align-items: center; gap: 8px; }
59
58
  .ncs-close-btn { background: transparent; border: none; color: #b3b3b3; font-size: 18px; cursor: pointer; padding: 0; transition: color 0.2s; }
60
59
  .ncs-close-btn:hover { color: white; }
61
60
 
62
- /* Animation Visualizer */
63
61
  .ncs-visualizer { display: flex; gap: 2px; height: 12px; align-items: flex-end; opacity: 0; transition: opacity 0.3s; }
64
62
  .ncs-visualizer.playing { opacity: 1; }
65
63
  .ncs-bar { width: 3px; background: #1DB954; border-radius: 2px; animation: bounce 0.5s infinite alternate; }
@@ -69,11 +67,11 @@ class NCSWidget {
69
67
 
70
68
  .ncs-track-info { display: flex; align-items: center; margin-bottom: 15px; }
71
69
  .ncs-cover { width: 65px; height: 65px; border-radius: 8px; background: #282828; margin-right: 15px; object-fit: cover; box-shadow: 0 4px 10px rgba(0,0,0,0.3); }
72
- .ncs-details { flex: 1; overflow: hidden; }
73
- #ncs-track-name { font-size: 14px; font-weight: bold; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; margin-bottom: 5px; }
70
+ .ncs-details { flex: 1; overflow: hidden; display: flex; flex-direction: column; justify-content: center; }
71
+ #ncs-track-name { font-size: 14px; font-weight: bold; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; margin-bottom: 2px; }
72
+ #ncs-artists { font-size: 11px; color: #a0a0a0; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; margin-bottom: 8px; }
74
73
  #ncs-genre { width: 100%; padding: 4px 8px; background: #282828; color: #b3b3b3; border: 1px solid #333; border-radius: 4px; font-size: 12px; cursor: pointer; outline: none; }
75
74
 
76
- /* Progress & Controls */
77
75
  .ncs-progress-container { margin-bottom: 15px; display:flex; align-items:center; gap: 10px; font-size: 11px; color: #b3b3b3; }
78
76
  .ncs-slider { -webkit-appearance: none; width: 100%; height: 4px; background: #535353; border-radius: 2px; outline: none; cursor: pointer; }
79
77
  .ncs-slider::-webkit-slider-thumb { -webkit-appearance: none; width: 12px; height: 12px; border-radius: 50%; background: #1DB954; cursor: pointer; transition: transform 0.1s; }
@@ -89,6 +87,8 @@ class NCSWidget {
89
87
 
90
88
  .ncs-bottom-bar { display: flex; justify-content: space-between; align-items: center; }
91
89
  .ncs-volume-container { display: flex; align-items: center; gap: 8px; color: #b3b3b3; flex: 1; margin-right: 15px; }
90
+ #ncs-mute-btn { cursor: pointer; transition: transform 0.1s; user-select: none; }
91
+ #ncs-mute-btn:hover { transform: scale(1.1); }
92
92
  .ncs-download-btn { color: #b3b3b3; text-decoration: none; font-size: 18px; transition: color 0.2s; }
93
93
  .ncs-download-btn:hover { color: #1DB954; }
94
94
 
@@ -99,8 +99,7 @@ class NCSWidget {
99
99
  <div class="ncs-minimized ${this.isWidgetOpen ? 'hidden' : ''}">🎧</div>
100
100
  <div class="ncs-expanded ${this.isWidgetOpen ? 'active' : ''}">
101
101
  <div class="ncs-header">
102
- <strong>
103
- NCS Player
102
+ <strong>NCS Player
104
103
  <div class="ncs-visualizer" id="ncs-vis">
105
104
  <div class="ncs-bar"></div><div class="ncs-bar"></div><div class="ncs-bar"></div>
106
105
  </div>
@@ -112,11 +111,75 @@ class NCSWidget {
112
111
  <img id="ncs-cover" class="ncs-cover" src="data:image/gif;base64,R0lGODlhAQABAIAAAMLCwgAAACH5BAAAAAAALAAAAAABAAEAAAICRAEAOw==" alt="Cover" />
113
112
  <div class="ncs-details">
114
113
  <div id="ncs-track-name">Chargement...</div>
114
+ <div id="ncs-artists">Artiste(s)</div>
115
115
  <select id="ncs-genre">
116
- <option value="electronic">⚡ Électronique</option>
117
- <option value="house">🏠 House</option>
118
- <option value="chill">☕ Chill</option>
119
- <option value="synthwave">🌆 Synthwave</option>
116
+ <option value="all">🌍 Tous les genres</option>
117
+ <option value="31">Alternative Dance</option>
118
+ <option value="32">Alternative Hip-Hop</option>
119
+ <option value="33">Alternative Pop</option>
120
+ <option value="23">Ambient</option>
121
+ <option value="34">Anti-Pop</option>
122
+ <option value="1">Bass</option>
123
+ <option value="18">Bass House</option>
124
+ <option value="78">Bass Music</option>
125
+ <option value="26">Brazilian Phonk</option>
126
+ <option value="27">Breakbeat</option>
127
+ <option value="2">Chill</option>
128
+ <option value="24">Chill Bass</option>
129
+ <option value="35">Chill Pop</option>
130
+ <option value="85">Colour Bass</option>
131
+ <option value="65">Complextro</option>
132
+ <option value="36">Dance-Pop</option>
133
+ <option value="66">Deep House</option>
134
+ <option value="45">Disco</option>
135
+ <option value="46">Disco House</option>
136
+ <option value="3">Drum & Bass</option>
137
+ <option value="4">Drumstep</option>
138
+ <option value="5">Dubstep</option>
139
+ <option value="6">EDM</option>
140
+ <option value="47">Electro</option>
141
+ <option value="48">Electro House</option>
142
+ <option value="7">Electronic</option>
143
+ <option value="39">Electronic Pop</option>
144
+ <option value="83">Electronic Rock</option>
145
+ <option value="17">Future Bass</option>
146
+ <option value="68">Future Bounce</option>
147
+ <option value="50">Future Funk</option>
148
+ <option value="8">Future House</option>
149
+ <option value="69">Future Rave</option>
150
+ <option value="57">Future Trap</option>
151
+ <option value="40">Futurepop</option>
152
+ <option value="51">Garage</option>
153
+ <option value="15">Glitch Hop</option>
154
+ <option value="82">Hardcore</option>
155
+ <option value="9">Hardstyle</option>
156
+ <option value="10">House</option>
157
+ <option value="41">Hyperpop</option>
158
+ <option value="11">Indie Dance</option>
159
+ <option value="91">J-Pop</option>
160
+ <option value="84">Jersey Club</option>
161
+ <option value="28">Jump-Up</option>
162
+ <option value="29">Liquid DnB</option>
163
+ <option value="60">Lofi Hip-Hop</option>
164
+ <option value="12">Melodic Dubstep</option>
165
+ <option value="54">Melodic House</option>
166
+ <option value="22">Midtempo Bass</option>
167
+ <option value="30">Neurofunk</option>
168
+ <option value="87">Nu-Jazz</option>
169
+ <option value="16">Phonk</option>
170
+ <option value="86">Pluggnb</option>
171
+ <option value="19">Pop</option>
172
+ <option value="55">Progressive House</option>
173
+ <option value="88">RnB</option>
174
+ <option value="89">Speed Garage</option>
175
+ <option value="73">Tech House</option>
176
+ <option value="80">Techno</option>
177
+ <option value="81">Trance</option>
178
+ <option value="14">Trap</option>
179
+ <option value="74">Tribal House</option>
180
+ <option value="21">UKG</option>
181
+ <option value="92">Wave</option>
182
+ <option value="90">Witch House</option>
120
183
  </select>
121
184
  </div>
122
185
  </div>
@@ -135,7 +198,7 @@ class NCSWidget {
135
198
 
136
199
  <div class="ncs-bottom-bar">
137
200
  <div class="ncs-volume-container">
138
- <span>🔉</span>
201
+ <span id="ncs-mute-btn">${this.audio.volume === 0 ? '🔇' : '🔊'}</span>
139
202
  <input type="range" id="ncs-volume" class="ncs-slider" min="0" max="1" step="0.05" value="${this.audio.volume}">
140
203
  </div>
141
204
  <a id="ncs-download" class="ncs-download-btn" href="#" target="_blank" title="Télécharger ce titre">⬇️</a>
@@ -152,9 +215,11 @@ class NCSWidget {
152
215
  this.nextBtn = this.container.querySelector('#ncs-next');
153
216
  this.prevBtn = this.container.querySelector('#ncs-prev');
154
217
  this.downloadBtn = this.container.querySelector('#ncs-download');
218
+ this.muteBtn = this.container.querySelector('#ncs-mute-btn');
155
219
  this.visualizer = this.container.querySelector('#ncs-vis');
156
220
  this.genreSelect = this.container.querySelector('#ncs-genre');
157
221
  this.trackName = this.container.querySelector('#ncs-track-name');
222
+ this.artistsName = this.container.querySelector('#ncs-artists');
158
223
  this.coverImg = this.container.querySelector('#ncs-cover');
159
224
  this.progressBar = this.container.querySelector('#ncs-progress');
160
225
  this.volumeBar = this.container.querySelector('#ncs-volume');
@@ -201,10 +266,31 @@ class NCSWidget {
201
266
  this.audio.addEventListener('ended', () => this.handleNext());
202
267
 
203
268
  this.progressBar.addEventListener('input', (e) => { this.audio.currentTime = e.target.value; });
269
+
270
+ // --- NOUVEAU : GESTION DU MUTE ET DU VOLUME ---
204
271
  this.volumeBar.addEventListener('input', (e) => {
205
- this.audio.volume = e.target.value;
206
- localStorage.setItem('ncs_volume', e.target.value);
272
+ const vol = parseFloat(e.target.value);
273
+ this.audio.volume = vol;
274
+ if (vol > 0) this.lastVolume = vol; // On mémorise si c'est > 0
275
+ this.updateMuteIcon(vol);
276
+ localStorage.setItem('ncs_volume', vol);
277
+ });
278
+
279
+ this.muteBtn.addEventListener('click', () => {
280
+ if (this.audio.volume > 0) {
281
+ // On Mute
282
+ this.lastVolume = this.audio.volume;
283
+ this.audio.volume = 0;
284
+ this.volumeBar.value = 0;
285
+ } else {
286
+ // On Unmute
287
+ this.audio.volume = this.lastVolume || 1.0;
288
+ this.volumeBar.value = this.audio.volume;
289
+ }
290
+ this.updateMuteIcon(this.audio.volume);
291
+ localStorage.setItem('ncs_volume', this.audio.volume);
207
292
  });
293
+ // ----------------------------------------------
208
294
 
209
295
  setInterval(() => {
210
296
  if (this.isPlaying && this.audio.currentTime > 0) {
@@ -213,6 +299,16 @@ class NCSWidget {
213
299
  }, 1000);
214
300
  }
215
301
 
302
+ updateMuteIcon(vol) {
303
+ if (vol === 0) {
304
+ this.muteBtn.innerText = '🔇';
305
+ } else if (vol < 0.5) {
306
+ this.muteBtn.innerText = '🔉';
307
+ } else {
308
+ this.muteBtn.innerText = '🔊';
309
+ }
310
+ }
311
+
216
312
  toggleState(isOpen) {
217
313
  if (isOpen) {
218
314
  this.minimized.classList.add('hidden');
@@ -248,10 +344,6 @@ class NCSWidget {
248
344
  return `${mins}:${secs < 10 ? '0' : ''}${secs}`;
249
345
  }
250
346
 
251
- // ----------------------------------------------------
252
- // GESTION DES REQUÊTES ET DE LA FILE D'ATTENTE (NOUVEAU)
253
- // ----------------------------------------------------
254
-
255
347
  async fetchSingleTrack(genre) {
256
348
  try {
257
349
  const response = await fetch(`${this.apiUrl}/search?genre=${genre}`);
@@ -264,50 +356,47 @@ class NCSWidget {
264
356
  }
265
357
 
266
358
  async fillQueue(genre) {
267
- if (this.isPreloading) return; // Éviter de lancer plusieurs recherches en même temps
359
+ if (this.isPreloading) return;
268
360
  this.isPreloading = true;
269
361
 
270
- // Tant qu'on n'a pas 2 musiques d'avance, on cherche silencieusement
271
362
  while (this.nextTracksQueue.length < 2) {
272
363
  const track = await this.fetchSingleTrack(genre);
273
364
  if (track) {
274
- // Vérifier qu'on n'ajoute pas un doublon dans la file
275
365
  const isDuplicate = this.nextTracksQueue.find(t => t.audioUrl === track.audioUrl);
276
366
  if (!isDuplicate) this.nextTracksQueue.push(track);
277
367
  } else {
278
- break; // Stop si l'API ne répond plus
368
+ break;
279
369
  }
280
370
  }
281
-
282
371
  this.isPreloading = false;
283
372
  }
284
373
 
285
374
  async changeGenre(genre) {
286
- this.nextTracksQueue = []; // On vide la file d'attente car le genre a changé
375
+ this.nextTracksQueue = [];
287
376
  this.trackName.innerText = "Recherche...";
377
+ this.artistsName.innerText = "...";
288
378
 
289
379
  const track = await this.fetchSingleTrack(genre);
290
380
  if (track) {
291
381
  this.setTrack(track, true);
292
- this.fillQueue(genre); // On lance le préchargement en fond !
382
+ this.fillQueue(genre);
293
383
  } else {
294
- this.trackName.innerText = "Aucune piste trouvée.";
384
+ this.trackName.innerText = "Aucune piste.";
385
+ this.artistsName.innerText = "";
295
386
  }
296
387
  }
297
388
 
298
- // ----------------------------------------------------
299
- // GESTION DU LECTEUR ET HISTORIQUE
300
- // ----------------------------------------------------
301
-
302
389
  restoreTrack() {
303
390
  this.audio.src = this.savedTrack;
304
391
  this.trackName.innerText = this.savedTitle;
392
+ this.artistsName.innerText = this.savedArtists || "NCS Release";
305
393
  if (this.savedCover) this.coverImg.src = this.savedCover;
306
394
  this.downloadBtn.href = this.savedTrack;
307
395
 
308
396
  this.trackHistory = [{
309
397
  audioUrl: this.savedTrack,
310
398
  title: this.savedTitle,
399
+ artists: this.savedArtists,
311
400
  coverUrl: this.savedCover
312
401
  }];
313
402
  this.currentHistoryIndex = 0;
@@ -316,11 +405,16 @@ class NCSWidget {
316
405
  setTrack(track, addToHistory = false) {
317
406
  this.audio.src = track.audioUrl;
318
407
  this.trackName.innerText = track.title;
408
+
409
+ // Mettre à jour l'affichage des artistes
410
+ const artistes = track.artists || "NCS Release";
411
+ this.artistsName.innerText = artistes;
412
+ this.artistsName.title = artistes; // Bulle info au survol si le texte est long
413
+
319
414
  if (track.coverUrl) this.coverImg.src = track.coverUrl;
320
415
  this.downloadBtn.href = track.audioUrl;
321
416
 
322
417
  if (addToHistory) {
323
- // Effacer le "futur" si on était revenu en arrière avant d'avancer
324
418
  this.trackHistory = this.trackHistory.slice(0, this.currentHistoryIndex + 1);
325
419
  this.trackHistory.push(track);
326
420
  this.currentHistoryIndex = this.trackHistory.length - 1;
@@ -330,6 +424,7 @@ class NCSWidget {
330
424
  if (this.isBrowser) {
331
425
  localStorage.setItem('ncs_currentTrack', track.audioUrl);
332
426
  localStorage.setItem('ncs_currentTitle', track.title);
427
+ localStorage.setItem('ncs_currentArtists', artistes);
333
428
  localStorage.setItem('ncs_currentCover', track.coverUrl);
334
429
  localStorage.setItem('ncs_currentTime', 0);
335
430
  this.savedTime = 0;
@@ -345,22 +440,19 @@ class NCSWidget {
345
440
 
346
441
  async handleNext() {
347
442
  const genre = this.genreSelect.value;
348
-
349
- // Cas 1 : On a reculé dans l'historique et on veut revenir à la musique suivante connue
350
443
  if (this.currentHistoryIndex < this.trackHistory.length - 1) {
351
444
  this.currentHistoryIndex++;
352
445
  this.setTrack(this.trackHistory[this.currentHistoryIndex], false);
353
446
  this.prevBtn.disabled = this.currentHistoryIndex <= 0;
354
447
  }
355
- // Cas 2 : L'historique est au bout, on pioche dans la file d'attente (instantané !)
356
448
  else if (this.nextTracksQueue.length > 0) {
357
- const nextTrack = this.nextTracksQueue.shift(); // Prend la 1ère de la file
449
+ const nextTrack = this.nextTracksQueue.shift();
358
450
  this.setTrack(nextTrack, true);
359
- this.fillQueue(genre); // On refait le plein discrètement
451
+ this.fillQueue(genre);
360
452
  }
361
- // Cas 3 : L'utilisateur clique trop vite et la file d'attente est vide (Secours)
362
453
  else {
363
454
  this.trackName.innerText = "Recherche...";
455
+ this.artistsName.innerText = "...";
364
456
  const track = await this.fetchSingleTrack(genre);
365
457
  if (track) {
366
458
  this.setTrack(track, true);
@@ -374,7 +466,6 @@ class NCSWidget {
374
466
  this.currentHistoryIndex--;
375
467
  this.setTrack(this.trackHistory[this.currentHistoryIndex], false);
376
468
  this.prevBtn.disabled = this.currentHistoryIndex <= 0;
377
-
378
469
  this.savedTime = 0;
379
470
  localStorage.setItem('ncs_currentTime', 0);
380
471
  }