nocopyrightsounds-widget 1.0.3 → 1.0.5

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 (3) hide show
  1. package/README.md +1 -1
  2. package/package.json +1 -1
  3. package/src/index.js +69 -41
package/README.md CHANGED
@@ -15,6 +15,6 @@ import NCSWidget from 'nocopyrightsounds-widget';
15
15
 
16
16
  const widget = new NCSWidget({
17
17
  position: 'bottom-right', // 'bottom-left', 'top-right', 'top-left'
18
- apiUrl: 'https://ncs-api.kaninchenspeed.workers.dev' // L'URL de votre API
18
+ apiUrl: 'https://ncs-backend-api.onrender.com' // L'URL de votre API
19
19
  });
20
20
  \`\`\`
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nocopyrightsounds-widget",
3
- "version": "1.0.3",
3
+ "version": "1.0.5",
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,21 +13,25 @@ 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
36
  this.changeGenre(this.genreSelect.value);
34
37
  }
@@ -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,6 +111,7 @@ 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
116
  <option value="electronic">⚡ Électronique</option>
117
117
  <option value="house">🏠 House</option>
@@ -135,7 +135,7 @@ class NCSWidget {
135
135
 
136
136
  <div class="ncs-bottom-bar">
137
137
  <div class="ncs-volume-container">
138
- <span>🔉</span>
138
+ <span id="ncs-mute-btn">${this.audio.volume === 0 ? '🔇' : '🔊'}</span>
139
139
  <input type="range" id="ncs-volume" class="ncs-slider" min="0" max="1" step="0.05" value="${this.audio.volume}">
140
140
  </div>
141
141
  <a id="ncs-download" class="ncs-download-btn" href="#" target="_blank" title="Télécharger ce titre">⬇️</a>
@@ -152,9 +152,11 @@ class NCSWidget {
152
152
  this.nextBtn = this.container.querySelector('#ncs-next');
153
153
  this.prevBtn = this.container.querySelector('#ncs-prev');
154
154
  this.downloadBtn = this.container.querySelector('#ncs-download');
155
+ this.muteBtn = this.container.querySelector('#ncs-mute-btn');
155
156
  this.visualizer = this.container.querySelector('#ncs-vis');
156
157
  this.genreSelect = this.container.querySelector('#ncs-genre');
157
158
  this.trackName = this.container.querySelector('#ncs-track-name');
159
+ this.artistsName = this.container.querySelector('#ncs-artists');
158
160
  this.coverImg = this.container.querySelector('#ncs-cover');
159
161
  this.progressBar = this.container.querySelector('#ncs-progress');
160
162
  this.volumeBar = this.container.querySelector('#ncs-volume');
@@ -201,10 +203,31 @@ class NCSWidget {
201
203
  this.audio.addEventListener('ended', () => this.handleNext());
202
204
 
203
205
  this.progressBar.addEventListener('input', (e) => { this.audio.currentTime = e.target.value; });
206
+
207
+ // --- NOUVEAU : GESTION DU MUTE ET DU VOLUME ---
204
208
  this.volumeBar.addEventListener('input', (e) => {
205
- this.audio.volume = e.target.value;
206
- localStorage.setItem('ncs_volume', e.target.value);
209
+ const vol = parseFloat(e.target.value);
210
+ this.audio.volume = vol;
211
+ if (vol > 0) this.lastVolume = vol; // On mémorise si c'est > 0
212
+ this.updateMuteIcon(vol);
213
+ localStorage.setItem('ncs_volume', vol);
214
+ });
215
+
216
+ this.muteBtn.addEventListener('click', () => {
217
+ if (this.audio.volume > 0) {
218
+ // On Mute
219
+ this.lastVolume = this.audio.volume;
220
+ this.audio.volume = 0;
221
+ this.volumeBar.value = 0;
222
+ } else {
223
+ // On Unmute
224
+ this.audio.volume = this.lastVolume || 1.0;
225
+ this.volumeBar.value = this.audio.volume;
226
+ }
227
+ this.updateMuteIcon(this.audio.volume);
228
+ localStorage.setItem('ncs_volume', this.audio.volume);
207
229
  });
230
+ // ----------------------------------------------
208
231
 
209
232
  setInterval(() => {
210
233
  if (this.isPlaying && this.audio.currentTime > 0) {
@@ -213,6 +236,16 @@ class NCSWidget {
213
236
  }, 1000);
214
237
  }
215
238
 
239
+ updateMuteIcon(vol) {
240
+ if (vol === 0) {
241
+ this.muteBtn.innerText = '🔇';
242
+ } else if (vol < 0.5) {
243
+ this.muteBtn.innerText = '🔉';
244
+ } else {
245
+ this.muteBtn.innerText = '🔊';
246
+ }
247
+ }
248
+
216
249
  toggleState(isOpen) {
217
250
  if (isOpen) {
218
251
  this.minimized.classList.add('hidden');
@@ -248,10 +281,6 @@ class NCSWidget {
248
281
  return `${mins}:${secs < 10 ? '0' : ''}${secs}`;
249
282
  }
250
283
 
251
- // ----------------------------------------------------
252
- // GESTION DES REQUÊTES ET DE LA FILE D'ATTENTE (NOUVEAU)
253
- // ----------------------------------------------------
254
-
255
284
  async fetchSingleTrack(genre) {
256
285
  try {
257
286
  const response = await fetch(`${this.apiUrl}/search?genre=${genre}`);
@@ -264,50 +293,47 @@ class NCSWidget {
264
293
  }
265
294
 
266
295
  async fillQueue(genre) {
267
- if (this.isPreloading) return; // Éviter de lancer plusieurs recherches en même temps
296
+ if (this.isPreloading) return;
268
297
  this.isPreloading = true;
269
298
 
270
- // Tant qu'on n'a pas 2 musiques d'avance, on cherche silencieusement
271
299
  while (this.nextTracksQueue.length < 2) {
272
300
  const track = await this.fetchSingleTrack(genre);
273
301
  if (track) {
274
- // Vérifier qu'on n'ajoute pas un doublon dans la file
275
302
  const isDuplicate = this.nextTracksQueue.find(t => t.audioUrl === track.audioUrl);
276
303
  if (!isDuplicate) this.nextTracksQueue.push(track);
277
304
  } else {
278
- break; // Stop si l'API ne répond plus
305
+ break;
279
306
  }
280
307
  }
281
-
282
308
  this.isPreloading = false;
283
309
  }
284
310
 
285
311
  async changeGenre(genre) {
286
- this.nextTracksQueue = []; // On vide la file d'attente car le genre a changé
312
+ this.nextTracksQueue = [];
287
313
  this.trackName.innerText = "Recherche...";
314
+ this.artistsName.innerText = "...";
288
315
 
289
316
  const track = await this.fetchSingleTrack(genre);
290
317
  if (track) {
291
318
  this.setTrack(track, true);
292
- this.fillQueue(genre); // On lance le préchargement en fond !
319
+ this.fillQueue(genre);
293
320
  } else {
294
- this.trackName.innerText = "Aucune piste trouvée.";
321
+ this.trackName.innerText = "Aucune piste.";
322
+ this.artistsName.innerText = "";
295
323
  }
296
324
  }
297
325
 
298
- // ----------------------------------------------------
299
- // GESTION DU LECTEUR ET HISTORIQUE
300
- // ----------------------------------------------------
301
-
302
326
  restoreTrack() {
303
327
  this.audio.src = this.savedTrack;
304
328
  this.trackName.innerText = this.savedTitle;
329
+ this.artistsName.innerText = this.savedArtists || "NCS Release";
305
330
  if (this.savedCover) this.coverImg.src = this.savedCover;
306
331
  this.downloadBtn.href = this.savedTrack;
307
332
 
308
333
  this.trackHistory = [{
309
334
  audioUrl: this.savedTrack,
310
335
  title: this.savedTitle,
336
+ artists: this.savedArtists,
311
337
  coverUrl: this.savedCover
312
338
  }];
313
339
  this.currentHistoryIndex = 0;
@@ -316,11 +342,16 @@ class NCSWidget {
316
342
  setTrack(track, addToHistory = false) {
317
343
  this.audio.src = track.audioUrl;
318
344
  this.trackName.innerText = track.title;
345
+
346
+ // Mettre à jour l'affichage des artistes
347
+ const artistes = track.artists || "NCS Release";
348
+ this.artistsName.innerText = artistes;
349
+ this.artistsName.title = artistes; // Bulle info au survol si le texte est long
350
+
319
351
  if (track.coverUrl) this.coverImg.src = track.coverUrl;
320
352
  this.downloadBtn.href = track.audioUrl;
321
353
 
322
354
  if (addToHistory) {
323
- // Effacer le "futur" si on était revenu en arrière avant d'avancer
324
355
  this.trackHistory = this.trackHistory.slice(0, this.currentHistoryIndex + 1);
325
356
  this.trackHistory.push(track);
326
357
  this.currentHistoryIndex = this.trackHistory.length - 1;
@@ -330,6 +361,7 @@ class NCSWidget {
330
361
  if (this.isBrowser) {
331
362
  localStorage.setItem('ncs_currentTrack', track.audioUrl);
332
363
  localStorage.setItem('ncs_currentTitle', track.title);
364
+ localStorage.setItem('ncs_currentArtists', artistes);
333
365
  localStorage.setItem('ncs_currentCover', track.coverUrl);
334
366
  localStorage.setItem('ncs_currentTime', 0);
335
367
  this.savedTime = 0;
@@ -345,22 +377,19 @@ class NCSWidget {
345
377
 
346
378
  async handleNext() {
347
379
  const genre = this.genreSelect.value;
348
-
349
- // Cas 1 : On a reculé dans l'historique et on veut revenir à la musique suivante connue
350
380
  if (this.currentHistoryIndex < this.trackHistory.length - 1) {
351
381
  this.currentHistoryIndex++;
352
382
  this.setTrack(this.trackHistory[this.currentHistoryIndex], false);
353
383
  this.prevBtn.disabled = this.currentHistoryIndex <= 0;
354
384
  }
355
- // Cas 2 : L'historique est au bout, on pioche dans la file d'attente (instantané !)
356
385
  else if (this.nextTracksQueue.length > 0) {
357
- const nextTrack = this.nextTracksQueue.shift(); // Prend la 1ère de la file
386
+ const nextTrack = this.nextTracksQueue.shift();
358
387
  this.setTrack(nextTrack, true);
359
- this.fillQueue(genre); // On refait le plein discrètement
388
+ this.fillQueue(genre);
360
389
  }
361
- // Cas 3 : L'utilisateur clique trop vite et la file d'attente est vide (Secours)
362
390
  else {
363
391
  this.trackName.innerText = "Recherche...";
392
+ this.artistsName.innerText = "...";
364
393
  const track = await this.fetchSingleTrack(genre);
365
394
  if (track) {
366
395
  this.setTrack(track, true);
@@ -374,7 +403,6 @@ class NCSWidget {
374
403
  this.currentHistoryIndex--;
375
404
  this.setTrack(this.trackHistory[this.currentHistoryIndex], false);
376
405
  this.prevBtn.disabled = this.currentHistoryIndex <= 0;
377
-
378
406
  this.savedTime = 0;
379
407
  localStorage.setItem('ncs_currentTime', 0);
380
408
  }