WIP starbook demo
This commit is contained in:
@@ -74,16 +74,17 @@ class Disc {
|
||||
private _inertiaVelocity: number = 0
|
||||
private _isInertiaActive: boolean = false
|
||||
private _basePlaybackSpeed: number = 1 // Vitesse de lecture normale
|
||||
private _inertiaFriction: number = 0.93 // Coefficient de frottement pour l'inertie (plus proche de 1 = plus long)
|
||||
private _inertiaFriction: number = 1 // Coefficient de frottement pour l'inertie (plus proche de 1 = plus long)
|
||||
private _lastDragVelocity: number = 0 // Dernière vitesse de drag
|
||||
private _lastDragTime: number = 0 // Dernier temps de drag
|
||||
private _inertiaAmplification: number = 25 // Facteur d'amplification de l'inertie
|
||||
private _inertiaAmplification: number = 45 // Facteur d'amplification de l'inertie
|
||||
private _previousDuration: number = 0 // Pour suivre les changements de durée
|
||||
|
||||
public isReversed: boolean = false
|
||||
|
||||
public callbacks = {
|
||||
onDragStart: (): void => {},
|
||||
onDragProgress: (): void => {},
|
||||
onDragEnded: (secondsPlayed: number): void => {},
|
||||
onStop: (): void => {},
|
||||
onLoop: (params: DiscProgress): void => {}
|
||||
@@ -147,12 +148,9 @@ class Disc {
|
||||
}
|
||||
|
||||
powerOn() {
|
||||
if (!this.rafId) {
|
||||
this.start()
|
||||
}
|
||||
this._isPoweredOn = true
|
||||
this._basePlaybackSpeed = 1
|
||||
this._playbackSpeed = 1
|
||||
this._basePlaybackSpeed = this.isReversed ? -1 : 1
|
||||
this.start()
|
||||
}
|
||||
|
||||
powerOff() {
|
||||
@@ -250,15 +248,18 @@ class Disc {
|
||||
|
||||
const anglePointerToCenter = angleBetween(this._center, pointerPosition)
|
||||
const angle_DraggingFromToCenter = angleBetween(this._center, this._draggingFrom)
|
||||
const angleDragged = angleDifference(angle_DraggingFromToCenter, anglePointerToCenter)
|
||||
let angleDragged = angleDifference(angle_DraggingFromToCenter, anglePointerToCenter)
|
||||
|
||||
// Calcul de la vitesse de déplacement angulaire (radians par milliseconde)
|
||||
// On inverse le signe pour que le sens de l'inertie soit naturel
|
||||
// Le signe est inversé pour que le glissement vers la droite fasse tourner vers la droite
|
||||
if (deltaTime > 0) {
|
||||
this._lastDragVelocity = -angleDragged / deltaTime
|
||||
}
|
||||
|
||||
this._lastDragTime = currentTime
|
||||
|
||||
// Appliquer la rotation au disque
|
||||
// Le signe est inversé pour que le glissement vers la droite fasse tourner vers la droite
|
||||
this.setAngle(this._currentAngle - angleDragged)
|
||||
this._draggingFrom = { ...pointerPosition }
|
||||
}
|
||||
@@ -269,12 +270,15 @@ class Disc {
|
||||
|
||||
// Activer l'inertie avec la vitesse de drag actuelle
|
||||
this._isInertiaActive = true
|
||||
// Augmenter la sensibilité du drag avec le facteur d'amplification
|
||||
this._inertiaVelocity = this._lastDragVelocity * this._inertiaAmplification
|
||||
|
||||
// Ajuster la direction de l'inertie en fonction du mode reverse
|
||||
const direction = this.isReversed ? -1 : 1
|
||||
this._inertiaVelocity = this._lastDragVelocity * this._inertiaAmplification * direction
|
||||
|
||||
this.isDragging = false
|
||||
|
||||
// Toujours conserver la vitesse de base actuelle (1 si allumé, 0 si éteint)
|
||||
this._basePlaybackSpeed = this._isPoweredOn ? 1 : 0
|
||||
this._basePlaybackSpeed = this._isPoweredOn ? (this.isReversed ? -1 : 1) : 0
|
||||
|
||||
// Si le lecteur est éteint, s'assurer que la vitesse de base est bien à 0
|
||||
if (!this._isPoweredOn) {
|
||||
@@ -286,25 +290,41 @@ class Disc {
|
||||
|
||||
autoRotate(currentTimestamp: number) {
|
||||
const timestampElapsed = currentTimestamp - this.previousTimestamp
|
||||
const direction = this.isReversed ? -1 : 1
|
||||
|
||||
// Vérifier si on est au début du morceau en mode reverse
|
||||
if (this.isReversed && this.secondsPlayed <= 0) {
|
||||
this._currentAngle = 0
|
||||
this._inertiaVelocity = 0
|
||||
this._isInertiaActive = false
|
||||
this._playbackSpeed = 0
|
||||
this._basePlaybackSpeed = 0 // Arrêter complètement la lecture
|
||||
this.el.style.transform = 'rotate(0rad)'
|
||||
this.callbacks.onStop()
|
||||
|
||||
// Éteindre le lecteur pour éviter toute reprise automatique
|
||||
this._isPoweredOn = false
|
||||
this.stop()
|
||||
return
|
||||
}
|
||||
|
||||
if (this._isInertiaActive) {
|
||||
// Appliquer l'inertie
|
||||
const inertiaRotation = this._inertiaVelocity * timestampElapsed
|
||||
this.setAngle(this._currentAngle + inertiaRotation)
|
||||
// Appliquer l'inertie en tenant compte de la direction
|
||||
const inertiaRotation = this._inertiaVelocity * timestampElapsed * direction
|
||||
this.setAngle(this._currentAngle + inertiaRotation, true)
|
||||
|
||||
// Si le lecteur est allumé, faire une transition fluide vers la vitesse de lecture
|
||||
if (this._isPoweredOn) {
|
||||
const targetVelocity =
|
||||
RADIANS_PER_MILLISECOND * Math.abs(this._basePlaybackSpeed) * direction
|
||||
|
||||
// Si on est proche de la vitesse de lecture normale, on désactive l'inertie
|
||||
if (
|
||||
Math.abs(this._inertiaVelocity - RADIANS_PER_MILLISECOND * this._basePlaybackSpeed) <
|
||||
0.0001
|
||||
) {
|
||||
if (Math.abs(this._inertiaVelocity - targetVelocity) < 0.0001) {
|
||||
this._isInertiaActive = false
|
||||
this._inertiaVelocity = RADIANS_PER_MILLISECOND * this._basePlaybackSpeed
|
||||
this._inertiaVelocity = targetVelocity
|
||||
} else {
|
||||
// Réduire progressivement la vitesse d'inertie vers la vitesse de lecture
|
||||
this._inertiaVelocity +=
|
||||
(RADIANS_PER_MILLISECOND * this._basePlaybackSpeed - this._inertiaVelocity) * 0.1
|
||||
this._inertiaVelocity += (targetVelocity - this._inertiaVelocity) * 0.1
|
||||
}
|
||||
} else {
|
||||
// Si le lecteur est éteint, appliquer un frottement normal
|
||||
@@ -314,18 +334,61 @@ class Disc {
|
||||
if (Math.abs(this._inertiaVelocity) < 0.0001) {
|
||||
this._isInertiaActive = false
|
||||
this._inertiaVelocity = 0
|
||||
this._playbackSpeed = 0 // Mettre à jour la vitesse de lecture à 0 uniquement à la fin
|
||||
this._playbackSpeed = 0
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Rotation normale à la vitesse de lecture de base
|
||||
const baseRotation = RADIANS_PER_MILLISECOND * this._basePlaybackSpeed * timestampElapsed
|
||||
this.setAngle(this._currentAngle + baseRotation)
|
||||
// Rotation normale à la vitesse de lecture de base, dans la direction actuelle
|
||||
const baseRotation =
|
||||
RADIANS_PER_MILLISECOND * Math.abs(this._basePlaybackSpeed) * timestampElapsed * direction
|
||||
this.setAngle(this._currentAngle + baseRotation, true)
|
||||
}
|
||||
}
|
||||
|
||||
setAngle(angle: number) {
|
||||
this._currentAngle = clamp(angle, 0, this._maxAngle)
|
||||
setAngle(angle: number, checkBounds = false) {
|
||||
// Vérifier les limites si demandé
|
||||
if (checkBounds) {
|
||||
// Arrêt au début (angle < 0)
|
||||
if (angle < 0) {
|
||||
this._currentAngle = 0
|
||||
this._inertiaVelocity = 0
|
||||
this._isInertiaActive = false
|
||||
this._playbackSpeed = 0
|
||||
this._basePlaybackSpeed = 0
|
||||
this.el.style.transform = 'rotate(0rad)'
|
||||
this.callbacks.onStop()
|
||||
this._isPoweredOn = false
|
||||
this.stop()
|
||||
return 0
|
||||
}
|
||||
// Arrêt à la fin (angle >= _maxAngle)
|
||||
else if (angle >= this._maxAngle) {
|
||||
this._currentAngle = this._maxAngle
|
||||
this._inertiaVelocity = 0
|
||||
this._isInertiaActive = false
|
||||
this._playbackSpeed = 0
|
||||
this._basePlaybackSpeed = 0
|
||||
this.el.style.transform = `rotate(${this._maxAngle}rad)`
|
||||
this.callbacks.onStop()
|
||||
this._isPoweredOn = false
|
||||
this.stop()
|
||||
return this._maxAngle
|
||||
}
|
||||
}
|
||||
|
||||
// Si on dépasse les limites, on reste aux bornes
|
||||
if (angle < 0) {
|
||||
this._currentAngle = 0
|
||||
} else if (angle > this._maxAngle) {
|
||||
this._currentAngle = this._maxAngle
|
||||
} else {
|
||||
this._currentAngle = angle
|
||||
}
|
||||
|
||||
// Appliquer la rotation à l'élément
|
||||
if (this.el) {
|
||||
this.el.style.transform = `rotate(${this._currentAngle}rad)`
|
||||
}
|
||||
|
||||
return this._currentAngle
|
||||
}
|
||||
@@ -337,15 +400,43 @@ class Disc {
|
||||
}
|
||||
|
||||
stop() {
|
||||
if (this.rafId) {
|
||||
cancelAnimationFrame(this.rafId)
|
||||
this.rafId = null
|
||||
}
|
||||
this.callbacks.onStop()
|
||||
this._isInertiaActive = false
|
||||
this._inertiaVelocity = 0
|
||||
cancelAnimationFrame(this.rafId!)
|
||||
this.rafId = null
|
||||
}
|
||||
|
||||
rewind() {
|
||||
this.setAngle(0)
|
||||
/**
|
||||
* Vérifie si le disque est à l'arrêt
|
||||
*/
|
||||
isStopped(): boolean {
|
||||
return this.rafId === null && !this._isInertiaActive
|
||||
}
|
||||
|
||||
/**
|
||||
* Inverse la direction de rotation du disque
|
||||
* @returns true si l'inversion a réussi, false sinon
|
||||
*/
|
||||
reverse(): boolean {
|
||||
if (!this.el) return false
|
||||
|
||||
// Inverser la direction
|
||||
this.isReversed = !this.isReversed
|
||||
|
||||
// Inverser la vitesse de base si nécessaire
|
||||
if (this._isPoweredOn) {
|
||||
this._basePlaybackSpeed = this.isReversed ? -1 : 1
|
||||
}
|
||||
|
||||
// Mettre à jour la direction de l'animation
|
||||
this.el.style.animationDirection = this.isReversed ? 'reverse' : 'normal'
|
||||
|
||||
// Inverser la vitesse d'inertie si elle est active
|
||||
if (this._isInertiaActive) {
|
||||
this._inertiaVelocity = -this._inertiaVelocity
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
loop() {
|
||||
@@ -357,11 +448,18 @@ class Disc {
|
||||
this.autoRotate(currentTimestamp)
|
||||
}
|
||||
|
||||
// Calculer la vitesse de lecture
|
||||
const rotated = this._currentAngle - this._previousAngle
|
||||
const rotationNormal = RADIANS_PER_MILLISECOND * timestampDifferenceMS
|
||||
this.playbackSpeed = rotated / rotationNormal || 0
|
||||
this.isReversed = this._currentAngle < this._previousAngle
|
||||
// Calculer la vitesse de lecture uniquement pendant le glissement ou l'inertie
|
||||
if (this.isDragging || this._isInertiaActive) {
|
||||
const rotated = this._currentAngle - this._previousAngle
|
||||
const rotationNormal = RADIANS_PER_MILLISECOND * timestampDifferenceMS
|
||||
this.playbackSpeed = rotated / rotationNormal || 0
|
||||
} else if (this._isPoweredOn) {
|
||||
// En mode lecture normale, utiliser la vitesse de base
|
||||
this._playbackSpeed = this._basePlaybackSpeed
|
||||
} else {
|
||||
// Si le lecteur est éteint, vitesse à 0
|
||||
this._playbackSpeed = 0
|
||||
}
|
||||
|
||||
// Mettre à jour la rotation visuelle
|
||||
this.el.style.transform = `rotate(${this._currentAngle}rad)`
|
||||
@@ -373,7 +471,7 @@ class Disc {
|
||||
// Ne pas appeler onLoop si rien n'a changé
|
||||
if (this._previousAngle !== this._currentAngle || this._previousDuration !== this._duration) {
|
||||
this.callbacks.onLoop({
|
||||
playbackSpeed: this.playbackSpeed,
|
||||
playbackSpeed: this._playbackSpeed, // Utiliser _playbackSpeed directement
|
||||
isReversed: this.isReversed,
|
||||
secondsPlayed,
|
||||
progress
|
||||
|
||||
Reference in New Issue
Block a user