canvasframework 0.3.22 → 0.3.24
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/components/AudioPlayer.js +611 -0
- package/components/SliverAppBar.js +139 -0
- package/core/CanvasFramework.js +99 -0
- package/core/UIBuilder.js +4 -0
- package/index.js +4 -1
- package/package.json +1 -1
- package/utils/DevTools.js +1247 -0
- package/utils/DevToolsConsole.js +201 -0
- package/utils/InspectionOverlay.js +308 -0
|
@@ -0,0 +1,611 @@
|
|
|
1
|
+
import Component from '../core/Component.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Lecteur audio avec gestion directe des événements
|
|
5
|
+
* @class
|
|
6
|
+
* @extends Component
|
|
7
|
+
*/
|
|
8
|
+
class AudioPlayer extends Component {
|
|
9
|
+
constructor(framework, options = {}) {
|
|
10
|
+
super(framework, options);
|
|
11
|
+
this.src = options.src || '';
|
|
12
|
+
this.playing = false;
|
|
13
|
+
this.platform = framework.platform;
|
|
14
|
+
this.showControls = true;
|
|
15
|
+
this.controlsTimeout = null;
|
|
16
|
+
this.currentTime = 0;
|
|
17
|
+
this.duration = 0;
|
|
18
|
+
this.progress = 0;
|
|
19
|
+
this.volume = 1;
|
|
20
|
+
this.showVolume = false;
|
|
21
|
+
this.loaded = false;
|
|
22
|
+
this.isLoading = false;
|
|
23
|
+
this.userInteracted = false;
|
|
24
|
+
|
|
25
|
+
// Élément audio HTML caché
|
|
26
|
+
this.audioElement = null;
|
|
27
|
+
|
|
28
|
+
// Gestion directe des événements
|
|
29
|
+
this.canvas = framework.canvas;
|
|
30
|
+
this.isMouseDownOnPlayer = false;
|
|
31
|
+
this.clickStartTime = 0;
|
|
32
|
+
this.clickThreshold = 200; // ms
|
|
33
|
+
|
|
34
|
+
this.controlsHeight = 50;
|
|
35
|
+
|
|
36
|
+
// Callbacks
|
|
37
|
+
this.onPlay = options.onPlay;
|
|
38
|
+
this.onPause = options.onPause;
|
|
39
|
+
this.onEnded = options.onEnded;
|
|
40
|
+
|
|
41
|
+
// NE PAS utiliser les handlers du framework
|
|
42
|
+
this.onPress = null;
|
|
43
|
+
this.onMove = null;
|
|
44
|
+
this.onRelease = null;
|
|
45
|
+
|
|
46
|
+
// Attacher les événements directement au canvas
|
|
47
|
+
this.setupDirectEventListeners();
|
|
48
|
+
|
|
49
|
+
// Initialiser l'audio
|
|
50
|
+
this.initAudio();
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
setupDirectEventListeners() {
|
|
54
|
+
// Stocker les listeners originaux pour pouvoir les retirer plus tard
|
|
55
|
+
this.originalListeners = {
|
|
56
|
+
mousedown: this.canvas.onmousedown,
|
|
57
|
+
mouseup: this.canvas.onmouseup,
|
|
58
|
+
mousemove: this.canvas.onmousemove,
|
|
59
|
+
touchstart: this.canvas.ontouchstart,
|
|
60
|
+
touchend: this.canvas.ontouchend,
|
|
61
|
+
touchmove: this.canvas.ontouchmove
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
// Attacher nos propres handlers
|
|
65
|
+
this.canvas.addEventListener('mousedown', this.handleCanvasMouseDown.bind(this));
|
|
66
|
+
this.canvas.addEventListener('mouseup', this.handleCanvasMouseUp.bind(this));
|
|
67
|
+
this.canvas.addEventListener('mousemove', this.handleCanvasMouseMove.bind(this));
|
|
68
|
+
|
|
69
|
+
this.canvas.addEventListener('touchstart', this.handleCanvasTouchStart.bind(this));
|
|
70
|
+
this.canvas.addEventListener('touchend', this.handleCanvasTouchEnd.bind(this));
|
|
71
|
+
this.canvas.addEventListener('touchmove', this.handleCanvasTouchMove.bind(this));
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
removeDirectEventListeners() {
|
|
75
|
+
// Retirer nos listeners
|
|
76
|
+
this.canvas.removeEventListener('mousedown', this.handleCanvasMouseDown);
|
|
77
|
+
this.canvas.removeEventListener('mouseup', this.handleCanvasMouseUp);
|
|
78
|
+
this.canvas.removeEventListener('mousemove', this.handleCanvasMouseMove);
|
|
79
|
+
|
|
80
|
+
this.canvas.removeEventListener('touchstart', this.handleCanvasTouchStart);
|
|
81
|
+
this.canvas.removeEventListener('touchend', this.handleCanvasTouchEnd);
|
|
82
|
+
this.canvas.removeEventListener('touchmove', this.handleCanvasTouchMove);
|
|
83
|
+
|
|
84
|
+
// Restaurer les listeners originaux
|
|
85
|
+
Object.keys(this.originalListeners).forEach(event => {
|
|
86
|
+
if (this.originalListeners[event]) {
|
|
87
|
+
this.canvas[`on${event}`] = this.originalListeners[event];
|
|
88
|
+
}
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
handleCanvasMouseDown(e) {
|
|
93
|
+
const rect = this.canvas.getBoundingClientRect();
|
|
94
|
+
const x = e.clientX - rect.left;
|
|
95
|
+
const y = e.clientY - rect.top;
|
|
96
|
+
|
|
97
|
+
if (this.isPointInside(x, y)) {
|
|
98
|
+
e.stopPropagation();
|
|
99
|
+
this.isMouseDownOnPlayer = true;
|
|
100
|
+
this.clickStartTime = Date.now();
|
|
101
|
+
this.handleInteraction(x, y, 'start');
|
|
102
|
+
return true; // Empêche la propagation au framework
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
handleCanvasMouseUp(e) {
|
|
107
|
+
if (!this.isMouseDownOnPlayer) return;
|
|
108
|
+
|
|
109
|
+
const rect = this.canvas.getBoundingClientRect();
|
|
110
|
+
const x = e.clientX - rect.left;
|
|
111
|
+
const y = e.clientY - rect.top;
|
|
112
|
+
|
|
113
|
+
const clickDuration = Date.now() - this.clickStartTime;
|
|
114
|
+
|
|
115
|
+
if (clickDuration < this.clickThreshold && this.isPointInside(x, y)) {
|
|
116
|
+
e.stopPropagation();
|
|
117
|
+
this.handleInteraction(x, y, 'end');
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
this.isMouseDownOnPlayer = false;
|
|
121
|
+
return true; // Empêche la propagation au framework
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
handleCanvasMouseMove(e) {
|
|
125
|
+
if (!this.isMouseDownOnPlayer) return;
|
|
126
|
+
|
|
127
|
+
const rect = this.canvas.getBoundingClientRect();
|
|
128
|
+
const x = e.clientX - rect.left;
|
|
129
|
+
const y = e.clientY - rect.top;
|
|
130
|
+
|
|
131
|
+
if (this.isPointInside(x, y)) {
|
|
132
|
+
e.stopPropagation();
|
|
133
|
+
this.handleInteraction(x, y, 'move');
|
|
134
|
+
return true;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
handleCanvasTouchStart(e) {
|
|
139
|
+
const touch = e.touches[0];
|
|
140
|
+
const rect = this.canvas.getBoundingClientRect();
|
|
141
|
+
const x = touch.clientX - rect.left;
|
|
142
|
+
const y = touch.clientY - rect.top;
|
|
143
|
+
|
|
144
|
+
if (this.isPointInside(x, y)) {
|
|
145
|
+
e.stopPropagation();
|
|
146
|
+
e.preventDefault();
|
|
147
|
+
this.isMouseDownOnPlayer = true;
|
|
148
|
+
this.clickStartTime = Date.now();
|
|
149
|
+
this.handleInteraction(x, y, 'start');
|
|
150
|
+
return true;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
handleCanvasTouchEnd(e) {
|
|
155
|
+
if (!this.isMouseDownOnPlayer) return;
|
|
156
|
+
|
|
157
|
+
const touch = e.changedTouches[0];
|
|
158
|
+
const rect = this.canvas.getBoundingClientRect();
|
|
159
|
+
const x = touch.clientX - rect.left;
|
|
160
|
+
const y = touch.clientY - rect.top;
|
|
161
|
+
|
|
162
|
+
const clickDuration = Date.now() - this.clickStartTime;
|
|
163
|
+
|
|
164
|
+
if (clickDuration < this.clickThreshold && this.isPointInside(x, y)) {
|
|
165
|
+
e.stopPropagation();
|
|
166
|
+
e.preventDefault();
|
|
167
|
+
this.handleInteraction(x, y, 'end');
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
this.isMouseDownOnPlayer = false;
|
|
171
|
+
return true;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
handleCanvasTouchMove(e) {
|
|
175
|
+
if (!this.isMouseDownOnPlayer) return;
|
|
176
|
+
|
|
177
|
+
const touch = e.touches[0];
|
|
178
|
+
const rect = this.canvas.getBoundingClientRect();
|
|
179
|
+
const x = touch.clientX - rect.left;
|
|
180
|
+
const y = touch.clientY - rect.top;
|
|
181
|
+
|
|
182
|
+
if (this.isPointInside(x, y)) {
|
|
183
|
+
e.stopPropagation();
|
|
184
|
+
e.preventDefault();
|
|
185
|
+
this.handleInteraction(x, y, 'move');
|
|
186
|
+
return true;
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
handleInteraction(x, y, type) {
|
|
191
|
+
// Ajuster les coordonnées pour le scrolling
|
|
192
|
+
const adjustedY = y - this.framework.scrollOffset;
|
|
193
|
+
|
|
194
|
+
// Afficher les contrôles
|
|
195
|
+
this.showControls = true;
|
|
196
|
+
this.showControlsTemporarily();
|
|
197
|
+
|
|
198
|
+
// Marquer l'interaction utilisateur
|
|
199
|
+
if (!this.userInteracted) {
|
|
200
|
+
this.userInteracted = true;
|
|
201
|
+
this.showInteractionMessage = false;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// Calculer la position relative
|
|
205
|
+
const localX = x - this.x;
|
|
206
|
+
const localY = adjustedY - this.y;
|
|
207
|
+
|
|
208
|
+
// Bouton play/pause central
|
|
209
|
+
const centerX = this.width / 2;
|
|
210
|
+
const centerY = this.height / 2;
|
|
211
|
+
|
|
212
|
+
const buttonSize = 40;
|
|
213
|
+
const isInButton = localX >= centerX - buttonSize && localX <= centerX + buttonSize &&
|
|
214
|
+
localY >= centerY - buttonSize && localY <= centerY + buttonSize;
|
|
215
|
+
|
|
216
|
+
// Barre de progression
|
|
217
|
+
const progressBarY = this.height - 30;
|
|
218
|
+
const isInProgressBar = this.showControls && this.loaded &&
|
|
219
|
+
localY >= progressBarY && localY <= progressBarY + 15;
|
|
220
|
+
|
|
221
|
+
// Traiter selon le type d'événement
|
|
222
|
+
switch (type) {
|
|
223
|
+
case 'start':
|
|
224
|
+
// Marquer le début du clic
|
|
225
|
+
break;
|
|
226
|
+
|
|
227
|
+
case 'move':
|
|
228
|
+
if (isInProgressBar) {
|
|
229
|
+
const newProgress = Math.max(0, Math.min(100, (localX / this.width) * 100));
|
|
230
|
+
this.seekTo((this.duration * newProgress) / 100);
|
|
231
|
+
}
|
|
232
|
+
break;
|
|
233
|
+
|
|
234
|
+
case 'end':
|
|
235
|
+
// Bouton play/pause
|
|
236
|
+
if (isInButton) {
|
|
237
|
+
console.log('AudioPlayer: Bouton cliqué (gestion directe)');
|
|
238
|
+
if (!this.loaded && !this.isLoading) {
|
|
239
|
+
this.initAudio();
|
|
240
|
+
} else if (this.loaded) {
|
|
241
|
+
this.togglePlay();
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
break;
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
initAudio() {
|
|
249
|
+
if (!this.src) {
|
|
250
|
+
console.warn('AudioPlayer: Pas de source audio fournie');
|
|
251
|
+
return;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
console.log('AudioPlayer: Initialisation de l\'audio:', this.src);
|
|
255
|
+
|
|
256
|
+
this.isLoading = true;
|
|
257
|
+
|
|
258
|
+
try {
|
|
259
|
+
// Créer l'élément audio
|
|
260
|
+
this.audioElement = document.createElement('audio');
|
|
261
|
+
this.audioElement.src = this.src;
|
|
262
|
+
this.audioElement.preload = 'auto';
|
|
263
|
+
this.audioElement.crossOrigin = 'anonymous';
|
|
264
|
+
this.audioElement.style.display = 'none';
|
|
265
|
+
this.audioElement.style.position = 'absolute';
|
|
266
|
+
this.audioElement.style.left = '-9999px';
|
|
267
|
+
|
|
268
|
+
this.audioElement.autoplay = false;
|
|
269
|
+
this.audioElement.controls = false;
|
|
270
|
+
|
|
271
|
+
document.body.appendChild(this.audioElement);
|
|
272
|
+
|
|
273
|
+
// Événements de l'audio
|
|
274
|
+
this.audioElement.addEventListener('loadedmetadata', () => {
|
|
275
|
+
console.log('AudioPlayer: Métadonnées chargées');
|
|
276
|
+
this.duration = this.audioElement.duration;
|
|
277
|
+
this.loaded = true;
|
|
278
|
+
this.isLoading = false;
|
|
279
|
+
});
|
|
280
|
+
|
|
281
|
+
this.audioElement.addEventListener('timeupdate', () => {
|
|
282
|
+
this.currentTime = this.audioElement.currentTime;
|
|
283
|
+
this.progress = (this.currentTime / this.duration) * 100;
|
|
284
|
+
});
|
|
285
|
+
|
|
286
|
+
this.audioElement.addEventListener('ended', () => {
|
|
287
|
+
console.log('AudioPlayer: Audio terminé');
|
|
288
|
+
this.playing = false;
|
|
289
|
+
if (this.onEnded) this.onEnded();
|
|
290
|
+
});
|
|
291
|
+
|
|
292
|
+
this.audioElement.addEventListener('play', () => {
|
|
293
|
+
console.log('AudioPlayer: Lecture démarrée');
|
|
294
|
+
this.playing = true;
|
|
295
|
+
if (this.onPlay) this.onPlay();
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
this.audioElement.addEventListener('pause', () => {
|
|
299
|
+
console.log('AudioPlayer: Lecture en pause');
|
|
300
|
+
this.playing = false;
|
|
301
|
+
if (this.onPause) this.onPause();
|
|
302
|
+
});
|
|
303
|
+
|
|
304
|
+
this.audioElement.addEventListener('error', (e) => {
|
|
305
|
+
console.error('AudioPlayer: Erreur audio:', e);
|
|
306
|
+
this.isLoading = false;
|
|
307
|
+
this.loaded = false;
|
|
308
|
+
});
|
|
309
|
+
|
|
310
|
+
this.audioElement.addEventListener('canplaythrough', () => {
|
|
311
|
+
console.log('AudioPlayer: Prêt à jouer');
|
|
312
|
+
this.loaded = true;
|
|
313
|
+
this.isLoading = false;
|
|
314
|
+
});
|
|
315
|
+
|
|
316
|
+
// Démarrer le chargement
|
|
317
|
+
this.audioElement.load();
|
|
318
|
+
|
|
319
|
+
} catch (error) {
|
|
320
|
+
console.error('AudioPlayer: Erreur d\'initialisation:', error);
|
|
321
|
+
this.isLoading = false;
|
|
322
|
+
this.loaded = false;
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
play() {
|
|
327
|
+
if (!this.loaded || !this.audioElement) {
|
|
328
|
+
console.log('AudioPlayer: Impossible de jouer - pas chargé');
|
|
329
|
+
return;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
// Vérifier l'interaction utilisateur
|
|
333
|
+
if (!this.userInteracted) {
|
|
334
|
+
console.log('AudioPlayer: Attente interaction utilisateur...');
|
|
335
|
+
this.showInteractionMessage = true;
|
|
336
|
+
setTimeout(() => {
|
|
337
|
+
this.showInteractionMessage = false;
|
|
338
|
+
}, 2000);
|
|
339
|
+
return;
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
console.log('AudioPlayer: Lancement de la lecture');
|
|
343
|
+
|
|
344
|
+
const playPromise = this.audioElement.play();
|
|
345
|
+
|
|
346
|
+
if (playPromise !== undefined) {
|
|
347
|
+
playPromise
|
|
348
|
+
.then(() => {
|
|
349
|
+
console.log('AudioPlayer: Lecture réussie');
|
|
350
|
+
this.playing = true;
|
|
351
|
+
this.showControlsTemporarily();
|
|
352
|
+
})
|
|
353
|
+
.catch(error => {
|
|
354
|
+
console.error('AudioPlayer: Erreur de lecture:', error);
|
|
355
|
+
this.playing = false;
|
|
356
|
+
|
|
357
|
+
if (error.name === 'NotAllowedError') {
|
|
358
|
+
console.log('AudioPlayer: Interaction nécessaire');
|
|
359
|
+
this.userInteracted = false;
|
|
360
|
+
}
|
|
361
|
+
});
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
pause() {
|
|
366
|
+
if (!this.playing || !this.audioElement) return;
|
|
367
|
+
|
|
368
|
+
console.log('AudioPlayer: Mise en pause');
|
|
369
|
+
this.audioElement.pause();
|
|
370
|
+
this.playing = false;
|
|
371
|
+
this.showControlsTemporarily();
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
stop() {
|
|
375
|
+
if (this.audioElement) {
|
|
376
|
+
this.audioElement.pause();
|
|
377
|
+
this.audioElement.currentTime = 0;
|
|
378
|
+
}
|
|
379
|
+
this.playing = false;
|
|
380
|
+
this.currentTime = 0;
|
|
381
|
+
this.progress = 0;
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
togglePlay() {
|
|
385
|
+
console.log('AudioPlayer: togglePlay - état actuel:', this.playing);
|
|
386
|
+
|
|
387
|
+
if (!this.loaded) {
|
|
388
|
+
console.log('AudioPlayer: Pas encore chargé');
|
|
389
|
+
if (!this.isLoading) {
|
|
390
|
+
this.initAudio();
|
|
391
|
+
}
|
|
392
|
+
return;
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
if (!this.userInteracted) {
|
|
396
|
+
console.log('AudioPlayer: Interaction utilisateur déclenchée');
|
|
397
|
+
this.userInteracted = true;
|
|
398
|
+
this.showInteractionMessage = false;
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
if (this.playing) {
|
|
402
|
+
this.pause();
|
|
403
|
+
} else {
|
|
404
|
+
this.play();
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
seekTo(time) {
|
|
409
|
+
if (!this.loaded || !this.audioElement) return;
|
|
410
|
+
|
|
411
|
+
const wasPlaying = this.playing;
|
|
412
|
+
|
|
413
|
+
if (wasPlaying) {
|
|
414
|
+
this.audioElement.pause();
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
const newTime = Math.max(0, Math.min(time, this.duration));
|
|
418
|
+
this.audioElement.currentTime = newTime;
|
|
419
|
+
this.currentTime = newTime;
|
|
420
|
+
this.progress = (this.currentTime / this.duration) * 100;
|
|
421
|
+
|
|
422
|
+
if (wasPlaying) {
|
|
423
|
+
this.audioElement.play().catch(e => {
|
|
424
|
+
console.error('AudioPlayer: Erreur reprise après seek:', e);
|
|
425
|
+
});
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
setVolume(value) {
|
|
430
|
+
this.volume = Math.max(0, Math.min(1, value));
|
|
431
|
+
if (this.audioElement) {
|
|
432
|
+
this.audioElement.volume = this.volume;
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
showControlsTemporarily() {
|
|
437
|
+
this.showControls = true;
|
|
438
|
+
clearTimeout(this.controlsTimeout);
|
|
439
|
+
this.controlsTimeout = setTimeout(() => {
|
|
440
|
+
if (this.playing) this.showControls = false;
|
|
441
|
+
}, 3000);
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
update(deltaTime) {
|
|
445
|
+
// Mise à jour automatique via timeupdate
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
draw(ctx) {
|
|
449
|
+
ctx.save();
|
|
450
|
+
|
|
451
|
+
// Fond du lecteur
|
|
452
|
+
ctx.fillStyle = '#111111';
|
|
453
|
+
ctx.fillRect(this.x, this.y, this.width, this.height);
|
|
454
|
+
|
|
455
|
+
// Message d'interaction
|
|
456
|
+
if (this.showInteractionMessage) {
|
|
457
|
+
ctx.fillStyle = 'rgba(255, 255, 255, 0.9)';
|
|
458
|
+
ctx.font = 'bold 14px Arial';
|
|
459
|
+
ctx.textAlign = 'center';
|
|
460
|
+
ctx.textBaseline = 'middle';
|
|
461
|
+
ctx.fillText('Cliquez pour activer l\'audio', this.x + this.width / 2, this.y + this.height / 2);
|
|
462
|
+
ctx.restore();
|
|
463
|
+
return;
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
// Indicateur de chargement
|
|
467
|
+
if (this.isLoading) {
|
|
468
|
+
ctx.fillStyle = '#FFFFFF';
|
|
469
|
+
ctx.font = '14px Arial';
|
|
470
|
+
ctx.textAlign = 'center';
|
|
471
|
+
ctx.textBaseline = 'middle';
|
|
472
|
+
ctx.fillText('Chargement...', this.x + this.width / 2, this.y + this.height / 2);
|
|
473
|
+
ctx.restore();
|
|
474
|
+
return;
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
// Indicateur si non chargé
|
|
478
|
+
if (!this.loaded && !this.isLoading) {
|
|
479
|
+
ctx.fillStyle = '#FFFFFF';
|
|
480
|
+
ctx.font = '14px Arial';
|
|
481
|
+
ctx.textAlign = 'center';
|
|
482
|
+
ctx.textBaseline = 'middle';
|
|
483
|
+
ctx.fillText('Audio non disponible', this.x + this.width / 2, this.y + this.height / 2);
|
|
484
|
+
ctx.restore();
|
|
485
|
+
return;
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
// Bouton play/pause
|
|
489
|
+
const centerX = this.x + this.width / 2;
|
|
490
|
+
const centerY = this.y + this.height / 2;
|
|
491
|
+
|
|
492
|
+
if (!this.playing || this.showControls) {
|
|
493
|
+
// Cercle de fond
|
|
494
|
+
ctx.fillStyle = 'rgba(0, 0, 0, 0.6)';
|
|
495
|
+
ctx.beginPath();
|
|
496
|
+
ctx.arc(centerX, centerY, 40, 0, Math.PI * 2);
|
|
497
|
+
ctx.fill();
|
|
498
|
+
|
|
499
|
+
// Icône
|
|
500
|
+
ctx.fillStyle = '#FFFFFF';
|
|
501
|
+
if (this.playing) {
|
|
502
|
+
// Icône pause
|
|
503
|
+
const barWidth = 8;
|
|
504
|
+
const barHeight = 30;
|
|
505
|
+
const gap = 5;
|
|
506
|
+
|
|
507
|
+
ctx.fillRect(centerX - barWidth - gap/2, centerY - barHeight/2, barWidth, barHeight);
|
|
508
|
+
ctx.fillRect(centerX + gap/2, centerY - barHeight/2, barWidth, barHeight);
|
|
509
|
+
} else {
|
|
510
|
+
// Icône play
|
|
511
|
+
const triangleSize = 25;
|
|
512
|
+
ctx.beginPath();
|
|
513
|
+
ctx.moveTo(centerX - triangleSize/2, centerY - triangleSize);
|
|
514
|
+
ctx.lineTo(centerX - triangleSize/2, centerY + triangleSize);
|
|
515
|
+
ctx.lineTo(centerX + triangleSize, centerY);
|
|
516
|
+
ctx.closePath();
|
|
517
|
+
ctx.fill();
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
// Contrôles en bas
|
|
522
|
+
if (this.showControls && this.loaded) {
|
|
523
|
+
ctx.fillStyle = 'rgba(0, 0, 0, 0.8)';
|
|
524
|
+
ctx.fillRect(this.x, this.y + this.height - this.controlsHeight, this.width, this.controlsHeight);
|
|
525
|
+
|
|
526
|
+
const progressX = this.x + 15;
|
|
527
|
+
const progressY = this.y + this.height - 25;
|
|
528
|
+
const progressWidth = this.width - 30;
|
|
529
|
+
|
|
530
|
+
// Barre de progression
|
|
531
|
+
ctx.fillStyle = '#555555';
|
|
532
|
+
ctx.fillRect(progressX, progressY, progressWidth, 6);
|
|
533
|
+
ctx.fillStyle = '#333333';
|
|
534
|
+
ctx.fillRect(progressX, progressY, progressWidth, 2);
|
|
535
|
+
|
|
536
|
+
// Progression actuelle
|
|
537
|
+
ctx.fillStyle = '#FF4444';
|
|
538
|
+
const currentProgressWidth = (progressWidth * this.progress) / 100;
|
|
539
|
+
ctx.fillRect(progressX, progressY, currentProgressWidth, 6);
|
|
540
|
+
ctx.fillStyle = '#FF0000';
|
|
541
|
+
ctx.fillRect(progressX, progressY, currentProgressWidth, 2);
|
|
542
|
+
|
|
543
|
+
// Curseur
|
|
544
|
+
if (currentProgressWidth > 0) {
|
|
545
|
+
const thumbX = progressX + currentProgressWidth;
|
|
546
|
+
ctx.fillStyle = '#FFFFFF';
|
|
547
|
+
ctx.beginPath();
|
|
548
|
+
ctx.arc(thumbX, progressY + 3, 8, 0, Math.PI * 2);
|
|
549
|
+
ctx.fill();
|
|
550
|
+
|
|
551
|
+
ctx.strokeStyle = '#000000';
|
|
552
|
+
ctx.lineWidth = 2;
|
|
553
|
+
ctx.stroke();
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
// Temps
|
|
557
|
+
const currentTimeStr = this.formatTime(this.currentTime);
|
|
558
|
+
const totalTimeStr = this.formatTime(this.duration);
|
|
559
|
+
|
|
560
|
+
ctx.fillStyle = '#FFFFFF';
|
|
561
|
+
ctx.font = 'bold 12px Arial';
|
|
562
|
+
ctx.textAlign = 'left';
|
|
563
|
+
ctx.fillText(currentTimeStr, this.x + 15, this.y + this.height - 35);
|
|
564
|
+
|
|
565
|
+
ctx.textAlign = 'right';
|
|
566
|
+
ctx.fillText(totalTimeStr, this.x + this.width - 15, this.y + this.height - 35);
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
ctx.restore();
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
formatTime(seconds) {
|
|
573
|
+
if (isNaN(seconds) || seconds === Infinity) {
|
|
574
|
+
return "0:00";
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
const mins = Math.floor(seconds / 60);
|
|
578
|
+
const secs = Math.floor(seconds % 60);
|
|
579
|
+
return `${mins}:${secs < 10 ? '0' : ''}${secs}`;
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
isPointInside(x, y) {
|
|
583
|
+
const adjustedY = y - this.framework.scrollOffset;
|
|
584
|
+
return x >= this.x && x <= this.x + this.width &&
|
|
585
|
+
adjustedY >= this.y && adjustedY <= this.y + this.height;
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
remove() {
|
|
589
|
+
// Arrêter la lecture
|
|
590
|
+
this.stop();
|
|
591
|
+
|
|
592
|
+
// Retirer les event listeners
|
|
593
|
+
this.removeDirectEventListeners();
|
|
594
|
+
|
|
595
|
+
// Supprimer l'élément audio
|
|
596
|
+
if (this.audioElement && this.audioElement.parentNode) {
|
|
597
|
+
this.audioElement.pause();
|
|
598
|
+
this.audioElement.src = '';
|
|
599
|
+
this.audioElement.load();
|
|
600
|
+
this.audioElement.parentNode.removeChild(this.audioElement);
|
|
601
|
+
this.audioElement = null;
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
// Nettoyer le timeout
|
|
605
|
+
clearTimeout(this.controlsTimeout);
|
|
606
|
+
|
|
607
|
+
console.log('AudioPlayer: Nettoyé et supprimé');
|
|
608
|
+
}
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
export default AudioPlayer;
|