nocopyrightsounds-widget 1.0.0 → 1.0.1
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/index.js +160 -43
package/package.json
CHANGED
package/src/index.js
CHANGED
|
@@ -1,56 +1,111 @@
|
|
|
1
|
-
// src/index.js
|
|
2
|
-
|
|
3
1
|
class NCSWidget {
|
|
4
2
|
constructor(options = {}) {
|
|
5
3
|
this.position = options.position || 'bottom-right';
|
|
6
|
-
|
|
4
|
+
|
|
5
|
+
// 🌟 L'URL de VOTRE serveur par défaut (les autres devs n'auront rien à faire)
|
|
6
|
+
this.apiUrl = options.apiUrl || 'https://ncs-backend-api.onrender.com';
|
|
7
|
+
|
|
7
8
|
this.audio = new Audio();
|
|
8
9
|
this.isPlaying = false;
|
|
9
10
|
|
|
10
|
-
//
|
|
11
|
-
this.
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
11
|
+
// Persistance des données (Volume, Temps, et Musique en cours)
|
|
12
|
+
this.isBrowser = typeof window !== 'undefined';
|
|
13
|
+
if (this.isBrowser) {
|
|
14
|
+
this.audio.volume = localStorage.getItem('ncs_volume') || 0.5;
|
|
15
|
+
this.savedTime = localStorage.getItem('ncs_currentTime') || 0;
|
|
16
|
+
this.savedTrack = localStorage.getItem('ncs_currentTrack') || null;
|
|
17
|
+
this.savedCover = localStorage.getItem('ncs_currentCover') || null;
|
|
18
|
+
this.savedTitle = localStorage.getItem('ncs_currentTitle') || null;
|
|
19
|
+
}
|
|
20
|
+
|
|
15
21
|
this.initDOM();
|
|
16
22
|
this.attachEvents();
|
|
17
|
-
|
|
23
|
+
|
|
24
|
+
// Reprendre la lecture ou charger une nouvelle piste
|
|
25
|
+
if (this.savedTrack && this.savedTime > 0) {
|
|
26
|
+
this.restoreTrack();
|
|
27
|
+
} else {
|
|
28
|
+
this.loadTrack('electronic');
|
|
29
|
+
}
|
|
18
30
|
}
|
|
19
31
|
|
|
20
32
|
initDOM() {
|
|
21
|
-
if (
|
|
33
|
+
if (!this.isBrowser) return;
|
|
22
34
|
|
|
23
35
|
this.container = document.createElement('div');
|
|
24
36
|
this.container.id = 'ncs-persistent-widget';
|
|
25
37
|
|
|
26
38
|
const style = document.createElement('style');
|
|
27
39
|
style.textContent = `
|
|
28
|
-
#ncs-persistent-widget { position: fixed; ${this.getPositionStyles()} z-index:
|
|
29
|
-
.ncs-minimized { width:
|
|
30
|
-
.ncs-
|
|
31
|
-
.ncs-expanded
|
|
40
|
+
#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); }
|
|
41
|
+
.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; }
|
|
42
|
+
.ncs-minimized:hover { transform: scale(1.1); }
|
|
43
|
+
.ncs-expanded { width: 300px; 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; }
|
|
44
|
+
.ncs-expanded.active { display: block; animation: ncsFadeIn 0.3s ease; }
|
|
32
45
|
.ncs-minimized.hidden { display: none; }
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
46
|
+
|
|
47
|
+
.ncs-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 15px; }
|
|
48
|
+
.ncs-header strong { font-size: 14px; font-weight: 600; color: #b3b3b3; letter-spacing: 1px; text-transform: uppercase; }
|
|
49
|
+
.ncs-close-btn { background: transparent; border: none; color: #b3b3b3; font-size: 18px; cursor: pointer; padding: 0; transition: color 0.2s; }
|
|
50
|
+
.ncs-close-btn:hover { color: white; }
|
|
51
|
+
|
|
52
|
+
.ncs-track-info { display: flex; align-items: center; margin-bottom: 15px; }
|
|
53
|
+
.ncs-cover { width: 60px; height: 60px; border-radius: 8px; background: #282828; margin-right: 15px; object-fit: cover; box-shadow: 0 4px 10px rgba(0,0,0,0.3); }
|
|
54
|
+
.ncs-details { flex: 1; overflow: hidden; }
|
|
55
|
+
#ncs-track-name { font-size: 15px; font-weight: bold; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; margin-bottom: 5px; }
|
|
56
|
+
#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; }
|
|
57
|
+
|
|
58
|
+
.ncs-progress-container { margin-bottom: 15px; display:flex; align-items:center; gap: 10px; font-size: 11px; color: #b3b3b3; }
|
|
59
|
+
.ncs-slider { -webkit-appearance: none; width: 100%; height: 4px; background: #535353; border-radius: 2px; outline: none; cursor: pointer; }
|
|
60
|
+
.ncs-slider::-webkit-slider-thumb { -webkit-appearance: none; width: 12px; height: 12px; border-radius: 50%; background: #1DB954; cursor: pointer; transition: transform 0.1s; }
|
|
61
|
+
.ncs-slider::-webkit-slider-thumb:hover { transform: scale(1.2); }
|
|
62
|
+
|
|
63
|
+
.ncs-controls { display: flex; justify-content: center; align-items: center; gap: 20px; margin-bottom: 15px; }
|
|
64
|
+
.ncs-btn-circle { width: 45px; height: 45px; border-radius: 50%; background: white; color: black; border: none; font-size: 18px; cursor: pointer; display:flex; justify-content:center; align-items:center; transition: transform 0.2s; }
|
|
65
|
+
.ncs-btn-circle:hover { transform: scale(1.05); }
|
|
66
|
+
.ncs-btn-icon { background: transparent; border: none; color: #b3b3b3; font-size: 20px; cursor: pointer; transition: color 0.2s; }
|
|
67
|
+
.ncs-btn-icon:hover { color: white; }
|
|
68
|
+
|
|
69
|
+
.ncs-volume-container { display: flex; align-items: center; gap: 10px; color: #b3b3b3; }
|
|
70
|
+
|
|
71
|
+
@keyframes ncsFadeIn { from { opacity: 0; transform: translateY(10px); } to { opacity: 1; transform: translateY(0); } }
|
|
36
72
|
`;
|
|
37
73
|
|
|
38
74
|
this.container.innerHTML = `
|
|
39
|
-
<div class="ncs-minimized"
|
|
75
|
+
<div class="ncs-minimized">🎧</div>
|
|
40
76
|
<div class="ncs-expanded">
|
|
41
|
-
<div
|
|
77
|
+
<div class="ncs-header">
|
|
42
78
|
<strong>NCS Player</strong>
|
|
43
|
-
<button class="ncs-close-btn"
|
|
79
|
+
<button class="ncs-close-btn">✖</button>
|
|
80
|
+
</div>
|
|
81
|
+
|
|
82
|
+
<div class="ncs-track-info">
|
|
83
|
+
<img id="ncs-cover" class="ncs-cover" src="data:image/gif;base64,R0lGODlhAQABAIAAAMLCwgAAACH5BAAAAAAALAAAAAABAAEAAAICRAEAOw==" alt="Cover" />
|
|
84
|
+
<div class="ncs-details">
|
|
85
|
+
<div id="ncs-track-name">Chargement...</div>
|
|
86
|
+
<select id="ncs-genre">
|
|
87
|
+
<option value="electronic">⚡ Électronique</option>
|
|
88
|
+
<option value="house">🏠 House</option>
|
|
89
|
+
<option value="chill">☕ Chill</option>
|
|
90
|
+
<option value="synthwave">🌆 Synthwave</option>
|
|
91
|
+
</select>
|
|
92
|
+
</div>
|
|
44
93
|
</div>
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
<
|
|
48
|
-
<
|
|
49
|
-
|
|
50
|
-
|
|
94
|
+
|
|
95
|
+
<div class="ncs-progress-container">
|
|
96
|
+
<span id="ncs-time-current">0:00</span>
|
|
97
|
+
<input type="range" id="ncs-progress" class="ncs-slider" min="0" max="100" value="0">
|
|
98
|
+
<span id="ncs-time-total">0:00</span>
|
|
99
|
+
</div>
|
|
100
|
+
|
|
51
101
|
<div class="ncs-controls">
|
|
52
|
-
<button id="ncs-play-pause"
|
|
53
|
-
<button id="ncs-next"
|
|
102
|
+
<button id="ncs-play-pause" class="ncs-btn-circle">▶</button>
|
|
103
|
+
<button id="ncs-next" class="ncs-btn-icon">⏭</button>
|
|
104
|
+
</div>
|
|
105
|
+
|
|
106
|
+
<div class="ncs-volume-container">
|
|
107
|
+
<span>🔉</span>
|
|
108
|
+
<input type="range" id="ncs-volume" class="ncs-slider" min="0" max="1" step="0.05" value="${this.audio.volume}">
|
|
54
109
|
</div>
|
|
55
110
|
</div>
|
|
56
111
|
`;
|
|
@@ -58,33 +113,61 @@ class NCSWidget {
|
|
|
58
113
|
document.head.appendChild(style);
|
|
59
114
|
document.body.appendChild(this.container);
|
|
60
115
|
|
|
116
|
+
// Références
|
|
61
117
|
this.minimized = this.container.querySelector('.ncs-minimized');
|
|
62
118
|
this.expanded = this.container.querySelector('.ncs-expanded');
|
|
63
119
|
this.playBtn = this.container.querySelector('#ncs-play-pause');
|
|
120
|
+
this.nextBtn = this.container.querySelector('#ncs-next');
|
|
64
121
|
this.genreSelect = this.container.querySelector('#ncs-genre');
|
|
65
122
|
this.trackName = this.container.querySelector('#ncs-track-name');
|
|
123
|
+
this.coverImg = this.container.querySelector('#ncs-cover');
|
|
124
|
+
this.progressBar = this.container.querySelector('#ncs-progress');
|
|
125
|
+
this.volumeBar = this.container.querySelector('#ncs-volume');
|
|
126
|
+
this.timeCurrent = this.container.querySelector('#ncs-time-current');
|
|
127
|
+
this.timeTotal = this.container.querySelector('#ncs-time-total');
|
|
66
128
|
}
|
|
67
129
|
|
|
68
130
|
getPositionStyles() {
|
|
69
131
|
const positions = {
|
|
70
|
-
'bottom-right': 'bottom:
|
|
71
|
-
'bottom-left': 'bottom:
|
|
72
|
-
'top-right': 'top:
|
|
73
|
-
'top-left': 'top:
|
|
132
|
+
'bottom-right': 'bottom: 25px; right: 25px;',
|
|
133
|
+
'bottom-left': 'bottom: 25px; left: 25px;',
|
|
134
|
+
'top-right': 'top: 25px; right: 25px;',
|
|
135
|
+
'top-left': 'top: 25px; left: 25px;'
|
|
74
136
|
};
|
|
75
137
|
return positions[this.position] || positions['bottom-right'];
|
|
76
138
|
}
|
|
77
139
|
|
|
78
140
|
attachEvents() {
|
|
79
|
-
if (
|
|
141
|
+
if (!this.isBrowser) return;
|
|
80
142
|
|
|
143
|
+
// UI Events
|
|
81
144
|
this.minimized.addEventListener('click', () => this.toggleState());
|
|
82
145
|
this.container.querySelector('.ncs-close-btn').addEventListener('click', () => this.toggleState());
|
|
83
146
|
this.playBtn.addEventListener('click', () => this.togglePlay());
|
|
147
|
+
this.nextBtn.addEventListener('click', () => this.loadTrack(this.genreSelect.value));
|
|
84
148
|
this.genreSelect.addEventListener('change', (e) => this.loadTrack(e.target.value));
|
|
85
149
|
|
|
150
|
+
// Audio Events
|
|
151
|
+
this.audio.addEventListener('timeupdate', () => this.updateProgress());
|
|
152
|
+
this.audio.addEventListener('loadedmetadata', () => {
|
|
153
|
+
this.progressBar.max = this.audio.duration;
|
|
154
|
+
this.timeTotal.innerText = this.formatTime(this.audio.duration);
|
|
155
|
+
});
|
|
156
|
+
this.audio.addEventListener('ended', () => this.loadTrack(this.genreSelect.value)); // Auto-play suivant
|
|
157
|
+
|
|
158
|
+
// Inputs interactifs
|
|
159
|
+
this.progressBar.addEventListener('input', (e) => {
|
|
160
|
+
this.audio.currentTime = e.target.value;
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
this.volumeBar.addEventListener('input', (e) => {
|
|
164
|
+
this.audio.volume = e.target.value;
|
|
165
|
+
localStorage.setItem('ncs_volume', e.target.value);
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
// Sauvegarde de la progression chaque seconde
|
|
86
169
|
setInterval(() => {
|
|
87
|
-
if (this.isPlaying &&
|
|
170
|
+
if (this.isPlaying && this.audio.currentTime > 0) {
|
|
88
171
|
localStorage.setItem('ncs_currentTime', this.audio.currentTime);
|
|
89
172
|
}
|
|
90
173
|
}, 1000);
|
|
@@ -98,31 +181,66 @@ class NCSWidget {
|
|
|
98
181
|
togglePlay() {
|
|
99
182
|
if (this.isPlaying) {
|
|
100
183
|
this.audio.pause();
|
|
101
|
-
this.playBtn.
|
|
184
|
+
this.playBtn.innerHTML = '▶';
|
|
102
185
|
} else {
|
|
186
|
+
// Reprendre là où on en était si c'est le 1er clic après un changement de page
|
|
187
|
+
if (this.savedTime > 0 && this.audio.currentTime === 0) {
|
|
188
|
+
this.audio.currentTime = this.savedTime;
|
|
189
|
+
}
|
|
103
190
|
this.audio.play();
|
|
104
|
-
this.playBtn.
|
|
191
|
+
this.playBtn.innerHTML = '⏸';
|
|
105
192
|
}
|
|
106
193
|
this.isPlaying = !this.isPlaying;
|
|
107
194
|
}
|
|
108
195
|
|
|
196
|
+
updateProgress() {
|
|
197
|
+
this.progressBar.value = this.audio.currentTime;
|
|
198
|
+
this.timeCurrent.innerText = this.formatTime(this.audio.currentTime);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
formatTime(seconds) {
|
|
202
|
+
if (isNaN(seconds)) return "0:00";
|
|
203
|
+
const mins = Math.floor(seconds / 60);
|
|
204
|
+
const secs = Math.floor(seconds % 60);
|
|
205
|
+
return `${mins}:${secs < 10 ? '0' : ''}${secs}`;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
restoreTrack() {
|
|
209
|
+
this.audio.src = this.savedTrack;
|
|
210
|
+
this.trackName.innerText = this.savedTitle;
|
|
211
|
+
if (this.savedCover) this.coverImg.src = this.savedCover;
|
|
212
|
+
// On ne met pas play() automatiquement à cause des règles des navigateurs (Autoplay policy)
|
|
213
|
+
}
|
|
214
|
+
|
|
109
215
|
async loadTrack(genre) {
|
|
110
216
|
this.trackName.innerText = "Recherche...";
|
|
111
217
|
try {
|
|
112
|
-
const response = await fetch(`${this.apiUrl}/search?genre=${genre}
|
|
218
|
+
const response = await fetch(`${this.apiUrl}/search?genre=${genre}`);
|
|
113
219
|
const data = await response.json();
|
|
114
220
|
|
|
115
221
|
if (data && data.length > 0) {
|
|
116
222
|
const track = data[0];
|
|
117
|
-
this.audio.src = track.audioUrl;
|
|
223
|
+
this.audio.src = track.audioUrl;
|
|
118
224
|
this.trackName.innerText = track.title;
|
|
225
|
+
if (track.coverUrl) this.coverImg.src = track.coverUrl;
|
|
119
226
|
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
227
|
+
// Sauvegarder la nouvelle piste courante
|
|
228
|
+
if (this.isBrowser) {
|
|
229
|
+
localStorage.setItem('ncs_currentTrack', track.audioUrl);
|
|
230
|
+
localStorage.setItem('ncs_currentTitle', track.title);
|
|
231
|
+
localStorage.setItem('ncs_currentCover', track.coverUrl);
|
|
232
|
+
localStorage.setItem('ncs_currentTime', 0); // Reset timer
|
|
233
|
+
this.savedTime = 0;
|
|
123
234
|
}
|
|
124
235
|
|
|
125
|
-
|
|
236
|
+
// Si on était déjà en train d'écouter, on enchaîne
|
|
237
|
+
if (this.isPlaying) {
|
|
238
|
+
this.audio.play();
|
|
239
|
+
} else if (this.audio.currentTime === 0 && this.playBtn.innerHTML === '⏸') {
|
|
240
|
+
// Cas du clic sur "Suivant" alors que c'était en pause
|
|
241
|
+
this.audio.play();
|
|
242
|
+
this.isPlaying = true;
|
|
243
|
+
}
|
|
126
244
|
} else {
|
|
127
245
|
this.trackName.innerText = "Aucune piste.";
|
|
128
246
|
}
|
|
@@ -132,5 +250,4 @@ class NCSWidget {
|
|
|
132
250
|
}
|
|
133
251
|
}
|
|
134
252
|
|
|
135
|
-
// Export par défaut pour l'utilisation en module
|
|
136
253
|
export default NCSWidget;
|