nocopyrightsounds-widget 1.0.0
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/README.md +20 -0
- package/package.json +17 -0
- package/src/index.js +136 -0
package/README.md
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# NoCopyrightSounds Widget 🎵
|
|
2
|
+
|
|
3
|
+
Un module persistant et personnalisable pour intégrer de la musique NCS sur n'importe quel site web.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
\`\`\`bash
|
|
8
|
+
npm install nocopyrightsounds-widget
|
|
9
|
+
\`\`\`
|
|
10
|
+
|
|
11
|
+
## Utilisation
|
|
12
|
+
|
|
13
|
+
\`\`\`javascript
|
|
14
|
+
import NCSWidget from 'nocopyrightsounds-widget';
|
|
15
|
+
|
|
16
|
+
const widget = new NCSWidget({
|
|
17
|
+
position: 'bottom-right', // 'bottom-left', 'top-right', 'top-left'
|
|
18
|
+
apiUrl: 'https://ncs-api.kaninchenspeed.workers.dev' // L'URL de votre API
|
|
19
|
+
});
|
|
20
|
+
\`\`\`
|
package/package.json
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "nocopyrightsounds-widget",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Un widget musical persistant pour site web utilisant l'API NoCopyrightSounds",
|
|
5
|
+
"main": "src/index.js",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"scripts": {
|
|
8
|
+
"test": "echo \"Error: no test specified\" && exit 1"
|
|
9
|
+
},
|
|
10
|
+
"repository": {
|
|
11
|
+
"type": "git",
|
|
12
|
+
"url": "https://github.com/floriangobin/nocopyrightsounds-widget.git"
|
|
13
|
+
},
|
|
14
|
+
"keywords": ["ncs", "music", "widget", "audio", "player", "nocopyrightsounds"],
|
|
15
|
+
"author": "GOBIN Florian",
|
|
16
|
+
"license": "MIT"
|
|
17
|
+
}
|
package/src/index.js
ADDED
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
// src/index.js
|
|
2
|
+
|
|
3
|
+
class NCSWidget {
|
|
4
|
+
constructor(options = {}) {
|
|
5
|
+
this.position = options.position || 'bottom-right';
|
|
6
|
+
this.apiUrl = options.apiUrl || 'https://ncs-api.kaninchenspeed.workers.dev';
|
|
7
|
+
this.audio = new Audio();
|
|
8
|
+
this.isPlaying = false;
|
|
9
|
+
|
|
10
|
+
// Côté serveur (SSR comme Next.js), localStorage n'existe pas. On le sécurise.
|
|
11
|
+
this.savedTime = typeof window !== 'undefined' && localStorage.getItem('ncs_currentTime')
|
|
12
|
+
? localStorage.getItem('ncs_currentTime')
|
|
13
|
+
: 0;
|
|
14
|
+
|
|
15
|
+
this.initDOM();
|
|
16
|
+
this.attachEvents();
|
|
17
|
+
this.loadTrack('electronic');
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
initDOM() {
|
|
21
|
+
if (typeof document === 'undefined') return; // Sécurité pour le Server-Side Rendering
|
|
22
|
+
|
|
23
|
+
this.container = document.createElement('div');
|
|
24
|
+
this.container.id = 'ncs-persistent-widget';
|
|
25
|
+
|
|
26
|
+
const style = document.createElement('style');
|
|
27
|
+
style.textContent = `
|
|
28
|
+
#ncs-persistent-widget { position: fixed; ${this.getPositionStyles()} z-index: 9999; font-family: sans-serif; transition: all 0.3s ease; }
|
|
29
|
+
.ncs-minimized { width: 50px; height: 50px; border-radius: 50%; background: #1DB954; cursor: pointer; display: flex; justify-content: center; align-items: center; box-shadow: 0 4px 10px rgba(0,0,0,0.2); }
|
|
30
|
+
.ncs-expanded { width: 250px; background: #222; color: white; border-radius: 12px; padding: 15px; box-shadow: 0 4px 15px rgba(0,0,0,0.3); display: none; }
|
|
31
|
+
.ncs-expanded.active { display: block; }
|
|
32
|
+
.ncs-minimized.hidden { display: none; }
|
|
33
|
+
.ncs-controls { display: flex; justify-content: space-between; margin-top: 10px; }
|
|
34
|
+
button { background: #1DB954; border: none; color: white; padding: 5px 10px; border-radius: 5px; cursor: pointer; }
|
|
35
|
+
select { width: 100%; padding: 5px; margin-bottom: 10px; background: #333; color: white; border: 1px solid #444; }
|
|
36
|
+
`;
|
|
37
|
+
|
|
38
|
+
this.container.innerHTML = `
|
|
39
|
+
<div class="ncs-minimized">🎵</div>
|
|
40
|
+
<div class="ncs-expanded">
|
|
41
|
+
<div style="display:flex; justify-content:space-between; align-items:center; margin-bottom:10px;">
|
|
42
|
+
<strong>NCS Player</strong>
|
|
43
|
+
<button class="ncs-close-btn" style="background:transparent; padding:0;">✖</button>
|
|
44
|
+
</div>
|
|
45
|
+
<select id="ncs-genre">
|
|
46
|
+
<option value="electronic">Électronique</option>
|
|
47
|
+
<option value="chill">Chill / Lo-Fi</option>
|
|
48
|
+
<option value="synthwave">Synthwave</option>
|
|
49
|
+
</select>
|
|
50
|
+
<div id="ncs-track-name" style="font-size:12px; margin-bottom:10px;">Chargement...</div>
|
|
51
|
+
<div class="ncs-controls">
|
|
52
|
+
<button id="ncs-play-pause">Play</button>
|
|
53
|
+
<button id="ncs-next">Suivant</button>
|
|
54
|
+
</div>
|
|
55
|
+
</div>
|
|
56
|
+
`;
|
|
57
|
+
|
|
58
|
+
document.head.appendChild(style);
|
|
59
|
+
document.body.appendChild(this.container);
|
|
60
|
+
|
|
61
|
+
this.minimized = this.container.querySelector('.ncs-minimized');
|
|
62
|
+
this.expanded = this.container.querySelector('.ncs-expanded');
|
|
63
|
+
this.playBtn = this.container.querySelector('#ncs-play-pause');
|
|
64
|
+
this.genreSelect = this.container.querySelector('#ncs-genre');
|
|
65
|
+
this.trackName = this.container.querySelector('#ncs-track-name');
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
getPositionStyles() {
|
|
69
|
+
const positions = {
|
|
70
|
+
'bottom-right': 'bottom: 20px; right: 20px;',
|
|
71
|
+
'bottom-left': 'bottom: 20px; left: 20px;',
|
|
72
|
+
'top-right': 'top: 20px; right: 20px;',
|
|
73
|
+
'top-left': 'top: 20px; left: 20px;'
|
|
74
|
+
};
|
|
75
|
+
return positions[this.position] || positions['bottom-right'];
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
attachEvents() {
|
|
79
|
+
if (typeof document === 'undefined') return;
|
|
80
|
+
|
|
81
|
+
this.minimized.addEventListener('click', () => this.toggleState());
|
|
82
|
+
this.container.querySelector('.ncs-close-btn').addEventListener('click', () => this.toggleState());
|
|
83
|
+
this.playBtn.addEventListener('click', () => this.togglePlay());
|
|
84
|
+
this.genreSelect.addEventListener('change', (e) => this.loadTrack(e.target.value));
|
|
85
|
+
|
|
86
|
+
setInterval(() => {
|
|
87
|
+
if (this.isPlaying && typeof window !== 'undefined') {
|
|
88
|
+
localStorage.setItem('ncs_currentTime', this.audio.currentTime);
|
|
89
|
+
}
|
|
90
|
+
}, 1000);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
toggleState() {
|
|
94
|
+
this.minimized.classList.toggle('hidden');
|
|
95
|
+
this.expanded.classList.toggle('active');
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
togglePlay() {
|
|
99
|
+
if (this.isPlaying) {
|
|
100
|
+
this.audio.pause();
|
|
101
|
+
this.playBtn.innerText = 'Play';
|
|
102
|
+
} else {
|
|
103
|
+
this.audio.play();
|
|
104
|
+
this.playBtn.innerText = 'Pause';
|
|
105
|
+
}
|
|
106
|
+
this.isPlaying = !this.isPlaying;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
async loadTrack(genre) {
|
|
110
|
+
this.trackName.innerText = "Recherche...";
|
|
111
|
+
try {
|
|
112
|
+
const response = await fetch(`${this.apiUrl}/search?genre=${genre}&limit=1`);
|
|
113
|
+
const data = await response.json();
|
|
114
|
+
|
|
115
|
+
if (data && data.length > 0) {
|
|
116
|
+
const track = data[0];
|
|
117
|
+
this.audio.src = track.audioUrl;
|
|
118
|
+
this.trackName.innerText = track.title;
|
|
119
|
+
|
|
120
|
+
if (this.savedTime > 0) {
|
|
121
|
+
this.audio.currentTime = this.savedTime;
|
|
122
|
+
this.savedTime = 0;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
if (this.isPlaying) this.audio.play();
|
|
126
|
+
} else {
|
|
127
|
+
this.trackName.innerText = "Aucune piste.";
|
|
128
|
+
}
|
|
129
|
+
} catch (error) {
|
|
130
|
+
this.trackName.innerText = "Erreur API";
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// Export par défaut pour l'utilisation en module
|
|
136
|
+
export default NCSWidget;
|