Compare commits

11 Commits

Author SHA1 Message Date
64ba7f9006 Merge pull request 'Fix CORS' (#3) from prochains-changements into main
All checks were successful
Build and Push Docker Images / Build Backend Image (push) Successful in 12m50s
Build and Push Docker Images / Build Frontend Image (push) Successful in 7m10s
Reviewed-on: #3
2025-12-23 14:34:05 +01:00
cc2f1d0051 Fix CORS 2025-12-23 14:33:25 +01:00
169a759b57 Fix Build backend Gitea
All checks were successful
Build and Push Docker Images / Build Backend Image (push) Successful in 27m9s
Build and Push Docker Images / Build Frontend Image (push) Successful in 56s
2025-12-23 13:27:50 +01:00
88db8cc9c8 Fix build backend depuis Gitea 2025-12-23 13:27:33 +01:00
3e225b158f Fix build et actions
Some checks failed
Build and Push Docker Images / Build Backend Image (push) Failing after 37s
Build and Push Docker Images / Build Frontend Image (push) Has been cancelled
2025-12-23 13:23:07 +01:00
8ec8b1aa42 Merge branch 'main' of https://git.benoitsz.com/benoit/Audio-Classifier
Some checks failed
Build and Push Docker Images / Build Backend Image (push) Failing after 38s
Build and Push Docker Images / Build Frontend Image (push) Failing after 2m6s
2025-12-23 13:10:37 +01:00
e3d85f4775 Merge branch 'Backend'
Merge Backend
2025-12-23 13:08:43 +01:00
2a0d022e37 Fix Actions avec qwen
Some checks failed
Build and Push Docker Images / Build Backend Image (push) Failing after 37s
Build and Push Docker Images / Build Frontend Image (push) Failing after 46s
2025-12-23 12:10:51 +01:00
5fb56a636f Fix Gitea Actions
Some checks failed
Build and Push Docker Images / Build Backend Image (push) Failing after 38s
Build and Push Docker Images / Build Frontend Image (push) Failing after 46s
2025-12-23 12:03:55 +01:00
721f7b51f7 Ajouter .gitea/workflows/docker.yml
Some checks failed
Build and Push Docker Images / Build Backend Image (push) Failing after 38s
Build and Push Docker Images / Build Frontend Image (push) Failing after 3m15s
2025-12-23 11:24:24 +01:00
54086236c6 Merge pull request 'Backend' (#1) from Backend into main
Reviewed-on: #1
2025-12-23 10:58:10 +01:00
14 changed files with 401 additions and 41 deletions

View File

@@ -10,7 +10,8 @@
"Bash(curl:*)",
"Bash(docker logs:*)",
"Bash(docker exec:*)",
"Bash(ls:*)"
"Bash(ls:*)",
"Bash(docker build:*)"
]
}
}

View File

@@ -5,7 +5,9 @@ POSTGRES_PASSWORD=audio_password
POSTGRES_DB=audio_classifier
# Backend API
CORS_ORIGINS=http://localhost:3000,http://127.0.0.1:3000
# Use "*" to allow all origins (recommended for development/local deployment)
# Or specify comma-separated URLs for production: http://yourdomain.com,https://yourdomain.com
CORS_ORIGINS=*
API_HOST=0.0.0.0
API_PORT=8000
@@ -16,4 +18,5 @@ ESSENTIA_MODELS_PATH=/app/models
AUDIO_LIBRARY_PATH=/path/to/your/audio/library
# Frontend
NEXT_PUBLIC_API_URL=http://localhost:8000
# API URL accessed by the browser (use port 8001 since backend is mapped to 8001)
NEXT_PUBLIC_API_URL=http://localhost:8001

176
.gitea/workflows/docker.yml Normal file
View File

@@ -0,0 +1,176 @@
name: Build and Push Docker Images
on:
push:
branches:
- main
tags:
- 'v*.*.*'
env:
REGISTRY: git.benoitsz.com
IMAGE_BACKEND: audio-classifier-backend
IMAGE_FRONTEND: audio-classifier-frontend
jobs:
build-backend:
name: Build Backend Image
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Download Essentia models
run: |
mkdir -p backend/models
cd backend/models
# Download models from Essentia
echo "Downloading Essentia models..."
# Embedding model (18 MB)
curl -L -o discogs-effnet-bs64-1.pb \
https://essentia.upf.edu/models/feature-extractors/discogs-effnet/discogs-effnet-bs64-1.pb
# Genre classifier (2 MB)
curl -L -o genre_discogs400-discogs-effnet-1.pb \
https://essentia.upf.edu/models/classification-heads/genre_discogs400/genre_discogs400-discogs-effnet-1.pb
# Genre metadata
curl -L -o genre_discogs400-discogs-effnet-1.json \
https://essentia.upf.edu/models/classification-heads/genre_discogs400/genre_discogs400-discogs-effnet-1.json
# Mood classifier (2.7 MB)
curl -L -o mtg_jamendo_moodtheme-discogs-effnet-1.pb \
https://essentia.upf.edu/models/classification-heads/mtg_jamendo_moodtheme/mtg_jamendo_moodtheme-discogs-effnet-1.pb
# Instrument classifier (2.6 MB)
curl -L -o mtg_jamendo_instrument-discogs-effnet-1.pb \
https://essentia.upf.edu/models/classification-heads/mtg_jamendo_instrument/mtg_jamendo_instrument-discogs-effnet-1.pb
# Genre classifier alternative (2.7 MB)
curl -L -o mtg_jamendo_genre-discogs-effnet-1.pb \
https://essentia.upf.edu/models/classification-heads/mtg_jamendo_genre/mtg_jamendo_genre-discogs-effnet-1.pb
ls -lh
echo "Models downloaded successfully!"
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Login to Gitea Container Registry
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ gitea.actor }}
password: ${{ secrets.REGISTRY_TOKEN }}
- name: Determine version
id: version
run: |
if [[ "${{ gitea.ref }}" == refs/tags/v* ]]; then
echo "VERSION=${GITEA_REF#refs/tags/}" >> $GITHUB_OUTPUT
else
echo "VERSION=dev-$(git rev-parse --short HEAD)" >> $GITHUB_OUTPUT
fi
- name: Extract metadata
id: meta
uses: docker/metadata-action@v5
with:
images: ${{ env.REGISTRY }}/${{ gitea.repository_owner }}/${{ env.IMAGE_BACKEND }}
tags: |
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
type=raw,value=latest,enable=${{ startsWith(gitea.ref, 'refs/tags/v') }}
type=raw,value=dev,enable=${{ gitea.ref == 'refs/heads/main' }}
type=sha,prefix=dev-,format=short,enable=${{ gitea.ref == 'refs/heads/main' }}
- name: Build and push backend
uses: docker/build-push-action@v5
with:
context: .
file: ./backend/Dockerfile
push: true
build-args: |
VERSION=${{ steps.version.outputs.VERSION }}
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=registry,ref=${{ env.REGISTRY }}/${{ gitea.repository_owner }}/${{ env.IMAGE_BACKEND }}:buildcache
cache-to: type=registry,ref=${{ env.REGISTRY }}/${{ gitea.repository_owner }}/${{ env.IMAGE_BACKEND }}:buildcache,mode=max
build-frontend:
name: Build Frontend Image
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Download Essentia models (for context)
run: |
mkdir -p backend/models
cd backend/models
# Download models (needed because frontend build context is root)
curl -L -o discogs-effnet-bs64-1.pb \
https://essentia.upf.edu/models/feature-extractors/discogs-effnet/discogs-effnet-bs64-1.pb
curl -L -o genre_discogs400-discogs-effnet-1.pb \
https://essentia.upf.edu/models/classification-heads/genre_discogs400/genre_discogs400-discogs-effnet-1.pb
curl -L -o genre_discogs400-discogs-effnet-1.json \
https://essentia.upf.edu/models/classification-heads/genre_discogs400/genre_discogs400-discogs-effnet-1.json
curl -L -o mtg_jamendo_moodtheme-discogs-effnet-1.pb \
https://essentia.upf.edu/models/classification-heads/mtg_jamendo_moodtheme/mtg_jamendo_moodtheme-discogs-effnet-1.pb
curl -L -o mtg_jamendo_instrument-discogs-effnet-1.pb \
https://essentia.upf.edu/models/classification-heads/mtg_jamendo_instrument/mtg_jamendo_instrument-discogs-effnet-1.pb
curl -L -o mtg_jamendo_genre-discogs-effnet-1.pb \
https://essentia.upf.edu/models/classification-heads/mtg_jamendo_genre/mtg_jamendo_genre-discogs-effnet-1.pb
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Login to Gitea Container Registry
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ gitea.actor }}
password: ${{ secrets.REGISTRY_TOKEN }}
- name: Determine version
id: version
run: |
if [[ "${{ gitea.ref }}" == refs/tags/v* ]]; then
echo "VERSION=${GITEA_REF#refs/tags/}" >> $GITHUB_OUTPUT
else
echo "VERSION=dev-$(git rev-parse --short HEAD)" >> $GITHUB_OUTPUT
fi
- name: Extract metadata
id: meta
uses: docker/metadata-action@v5
with:
images: ${{ env.REGISTRY }}/${{ gitea.repository_owner }}/${{ env.IMAGE_FRONTEND }}
tags: |
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
type=raw,value=latest,enable=${{ startsWith(gitea.ref, 'refs/tags/v') }}
type=raw,value=dev,enable=${{ gitea.ref == 'refs/heads/main' }}
type=sha,prefix=dev-,format=short,enable=${{ gitea.ref == 'refs/heads/main' }}
- name: Build and push frontend
uses: docker/build-push-action@v5
with:
context: .
file: ./frontend/Dockerfile
push: true
build-args: |
VERSION=${{ steps.version.outputs.VERSION }}
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=registry,ref=${{ env.REGISTRY }}/${{ gitea.repository_owner }}/${{ env.IMAGE_FRONTEND }}:buildcache
cache-to: type=registry,ref=${{ env.REGISTRY }}/${{ gitea.repository_owner }}/${{ env.IMAGE_FRONTEND }}:buildcache,mode=max

View File

@@ -14,7 +14,7 @@ Le système est **100% autonome** - aucune action manuelle requise ! Les modèle
1. **Cloner le projet** :
```bash
git clone <votre-repo>
git clone https://git.benoitsz.com/benoit/Audio-Classifier.git
cd Audio-Classifier
```
@@ -36,6 +36,8 @@ docker-compose up -d
C'est tout ! 🎉
**Note** : Les images Docker sont automatiquement téléchargées depuis git.benoitsz.com. Aucun build nécessaire !
### Premier Scan
1. Ouvrir http://localhost:3000

View File

@@ -41,8 +41,8 @@ Outil de classification audio automatique capable d'indexer et analyser des bibl
```bash
# 1. Cloner le projet
git clone <repo>
cd audio-classifier
git clone https://git.benoitsz.com/benoit/Audio-Classifier.git
cd Audio-Classifier
# 2. Configurer le chemin audio (optionnel)
echo "AUDIO_LIBRARY_PATH=/chemin/vers/votre/musique" > .env
@@ -53,6 +53,8 @@ docker-compose up -d
**C'est tout !** 🎉
Les images Docker sont automatiquement téléchargées depuis le registry Gitea.
- Frontend : http://localhost:3000
- API : http://localhost:8001
- API Docs : http://localhost:8001/docs
@@ -66,13 +68,26 @@ docker-compose up -d
### ✨ Particularités
- **Aucun téléchargement manuel** : Les modèles Essentia (28 MB) sont inclus dans l'image Docker
- **Images pré-construites** : Téléchargées automatiquement depuis git.benoitsz.com
- **Modèles inclus** : Les modèles Essentia (28 MB) sont intégrés dans l'image
- **Aucune configuration** : Tout fonctionne out-of-the-box
- **Transcodage automatique** : MP3 128kbps créés pour streaming rapide
- **Waveforms pré-calculées** : Chargement instantané
📖 **Documentation complète** : Voir [DEPLOYMENT.md](DEPLOYMENT.md)
### 🛠 Build local (développement)
Si vous voulez builder les images localement, les modèles Essentia doivent être présents dans `backend/models/` (28 MB).
```bash
# Build avec docker-compose
docker-compose -f docker-compose.build.yml build
docker-compose -f docker-compose.build.yml up -d
```
**Note** : Les modèles Essentia (`.pb`, 28 MB) ne sont pas versionnés dans Git. Le workflow CI/CD les télécharge automatiquement depuis essentia.upf.edu pendant le build.
## 📖 Utilisation
### Scanner un dossier

View File

@@ -32,7 +32,7 @@ WORKDIR /app
RUN pip install --no-cache-dir --upgrade pip setuptools wheel
# Copy requirements
COPY requirements.txt .
COPY backend/requirements.txt .
# Install Python dependencies in stages for better caching
# Using versions compatible with Python 3.9
@@ -45,11 +45,11 @@ RUN pip install --no-cache-dir essentia-tensorflow
RUN pip install --no-cache-dir -r requirements.txt
# Copy application code
COPY src/ ./src/
COPY alembic.ini .
COPY backend/src/ ./src/
COPY backend/alembic.ini .
# Copy Essentia models into image
COPY models/ ./models/
# Copy Essentia models into image (28 MB total)
COPY backend/models/ ./models/
RUN ls -lh /app/models
# Expose port

52
backend/models/README.md Normal file
View File

@@ -0,0 +1,52 @@
# Essentia Models
Ce dossier contient les modèles pré-entraînés Essentia-TensorFlow pour la classification audio (28 MB total).
## Modèles requis
Les fichiers suivants sont nécessaires pour le fonctionnement de l'application :
1. **discogs-effnet-bs64-1.pb** (18 MB) - Embedding model
2. **genre_discogs400-discogs-effnet-1.pb** (2 MB) - Genre classifier
3. **genre_discogs400-discogs-effnet-1.json** (15 KB) - Genre metadata
4. **mtg_jamendo_moodtheme-discogs-effnet-1.pb** (2.7 MB) - Mood classifier
5. **mtg_jamendo_instrument-discogs-effnet-1.pb** (2.6 MB) - Instrument classifier
6. **mtg_jamendo_genre-discogs-effnet-1.pb** (2.7 MB) - Alternative genre classifier
## Téléchargement automatique
**Pour les utilisateurs** : Les modèles sont déjà inclus dans les images Docker depuis le registry `git.benoitsz.com`. Aucune action nécessaire.
**Pour le CI/CD** : Les modèles sont téléchargés automatiquement depuis essentia.upf.edu pendant le build (voir `.gitea/workflows/docker.yml`).
**Pour le développement local** : Si vous avez besoin de builder localement, vous devez avoir les modèles dans ce dossier. Ils ne sont pas versionnés dans Git car ils pèsent 28 MB.
### Téléchargement manuel (si nécessaire)
```bash
cd backend/models
# Embedding model (18 MB)
curl -L -O https://essentia.upf.edu/models/feature-extractors/discogs-effnet/discogs-effnet-bs64-1.pb
# Genre classifier (2 MB)
curl -L -O https://essentia.upf.edu/models/classification-heads/genre_discogs400/genre_discogs400-discogs-effnet-1.pb
curl -L -O https://essentia.upf.edu/models/classification-heads/genre_discogs400/genre_discogs400-discogs-effnet-1.json
# Mood classifier (2.7 MB)
curl -L -O https://essentia.upf.edu/models/classification-heads/mtg_jamendo_moodtheme/mtg_jamendo_moodtheme-discogs-effnet-1.pb
# Instrument classifier (2.6 MB)
curl -L -O https://essentia.upf.edu/models/classification-heads/mtg_jamendo_instrument/mtg_jamendo_instrument-discogs-effnet-1.pb
# Alternative genre classifier (2.7 MB)
curl -L -O https://essentia.upf.edu/models/classification-heads/mtg_jamendo_genre/mtg_jamendo_genre-discogs-effnet-1.pb
```
## Source
Tous les modèles proviennent du projet Essentia : https://essentia.upf.edu/models/
## Licence
Ces modèles sont fournis par le Music Technology Group de l'Universitat Pompeu Fabra sous licence permissive pour usage académique et commercial.

View File

@@ -10,7 +10,8 @@ class Settings(BaseSettings):
DATABASE_URL: str = "postgresql://audio_user:audio_password@localhost:5432/audio_classifier"
# API Configuration
CORS_ORIGINS: str = "http://localhost:3000,http://127.0.0.1:3000"
# Comma-separated list of allowed origins, or use "*" to allow all
CORS_ORIGINS: str = "*"
API_HOST: str = "0.0.0.0"
API_PORT: int = 8000
@@ -33,7 +34,13 @@ class Settings(BaseSettings):
@property
def cors_origins_list(self) -> List[str]:
"""Parse CORS origins string to list."""
"""Parse CORS origins string to list.
If CORS_ORIGINS is "*", allow all origins.
Otherwise, parse comma-separated list.
"""
if self.CORS_ORIGINS.strip() == "*":
return ["*"]
return [origin.strip() for origin in self.CORS_ORIGINS.split(",")]

64
docker-compose.build.yml Normal file
View File

@@ -0,0 +1,64 @@
# Docker Compose pour build local (développement)
# Usage: docker-compose -f docker-compose.build.yml build
services:
postgres:
image: pgvector/pgvector:pg16
container_name: audio_classifier_db
environment:
POSTGRES_USER: ${POSTGRES_USER:-audio_user}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-audio_password}
POSTGRES_DB: ${POSTGRES_DB:-audio_classifier}
ports:
- "5433:5432"
volumes:
- postgres_data:/var/lib/postgresql/data
- ./backend/init-db.sql:/docker-entrypoint-initdb.d/init-db.sql
healthcheck:
test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER:-audio_user}"]
interval: 10s
timeout: 5s
retries: 5
restart: unless-stopped
backend:
build:
context: .
dockerfile: backend/Dockerfile
container_name: audio_classifier_api
depends_on:
postgres:
condition: service_healthy
environment:
DATABASE_URL: postgresql://${POSTGRES_USER:-audio_user}:${POSTGRES_PASSWORD:-audio_password}@postgres:5432/${POSTGRES_DB:-audio_classifier}
CORS_ORIGINS: ${CORS_ORIGINS:-*}
ANALYSIS_USE_CLAP: ${ANALYSIS_USE_CLAP:-false}
ANALYSIS_NUM_WORKERS: ${ANALYSIS_NUM_WORKERS:-4}
ESSENTIA_MODELS_PATH: /app/models
ports:
- "8001:8000"
volumes:
# Mount your audio library (read-write for transcoding and waveforms)
- ${AUDIO_LIBRARY_PATH:-./audio_samples}:/audio
restart: unless-stopped
frontend:
build:
context: .
dockerfile: frontend/Dockerfile
args:
NEXT_PUBLIC_API_URL: http://localhost:8001
container_name: audio_classifier_ui
environment:
# Use localhost:8001 because the browser (client-side) needs to access the API
# The backend is mapped to port 8001 on the host machine
NEXT_PUBLIC_API_URL: http://localhost:8001
ports:
- "3000:3000"
depends_on:
- backend
restart: unless-stopped
volumes:
postgres_data:
driver: local

View File

@@ -19,14 +19,14 @@ services:
restart: unless-stopped
backend:
build: ./backend
image: git.benoitsz.com/benoit/audio-classifier-backend:latest
container_name: audio_classifier_api
depends_on:
postgres:
condition: service_healthy
environment:
DATABASE_URL: postgresql://${POSTGRES_USER:-audio_user}:${POSTGRES_PASSWORD:-audio_password}@postgres:5432/${POSTGRES_DB:-audio_classifier}
CORS_ORIGINS: ${CORS_ORIGINS:-http://localhost:3000}
CORS_ORIGINS: ${CORS_ORIGINS:-*}
ANALYSIS_USE_CLAP: ${ANALYSIS_USE_CLAP:-false}
ANALYSIS_NUM_WORKERS: ${ANALYSIS_NUM_WORKERS:-4}
ESSENTIA_MODELS_PATH: /app/models
@@ -38,10 +38,7 @@ services:
restart: unless-stopped
frontend:
build:
context: ./frontend
args:
NEXT_PUBLIC_API_URL: http://localhost:8001
image: git.benoitsz.com/benoit/audio-classifier-frontend:latest
container_name: audio_classifier_ui
environment:
# Use localhost:8001 because the browser (client-side) needs to access the API

View File

@@ -4,13 +4,13 @@ FROM node:20-alpine
WORKDIR /app
# Copy package files
COPY package*.json ./
COPY frontend/package*.json ./
# Install dependencies
RUN npm ci
# Copy application code
COPY . .
COPY frontend/ .
# Build argument for API URL
ARG NEXT_PUBLIC_API_URL=http://localhost:8001

19
frontend/Dockerfile.dev Normal file
View File

@@ -0,0 +1,19 @@
FROM node:20-alpine
# Set working directory
WORKDIR /app
# Copy package files
COPY package*.json ./
# Debug: List files and Node.js version
RUN ls -la && node --version && npm --version
# Install dependencies with more verbose output
RUN npm install --verbose
# Expose port
EXPOSE 3000
# Start the development server
CMD ["npm", "run", "dev"]

View File

@@ -52,6 +52,7 @@ export default function Home() {
const [filters, setFilters] = useState<FilterParams>({})
const [page, setPage] = useState(0)
const [currentTrack, setCurrentTrack] = useState<Track | null>(null)
const [isPlaying, setIsPlaying] = useState(false)
const [searchQuery, setSearchQuery] = useState("")
const [isScanning, setIsScanning] = useState(false)
const [scanStatus, setScanStatus] = useState<string>("")
@@ -233,10 +234,19 @@ export default function Home() {
<div className="flex items-center gap-4">
{/* Play button */}
<button
onClick={() => setCurrentTrack(track)}
onClick={() => {
if (currentTrack?.id === track.id) {
// Toggle play/pause for current track
setIsPlaying(!isPlaying)
} else {
// Switch to new track and start playing
setCurrentTrack(track)
setIsPlaying(true)
}
}}
className="flex-shrink-0 w-12 h-12 flex items-center justify-center bg-orange-500 hover:bg-orange-600 rounded-full transition-colors shadow-sm"
>
{currentTrack?.id === track.id ? (
{currentTrack?.id === track.id && isPlaying ? (
<svg className="w-5 h-5 text-white" fill="currentColor" viewBox="0 0 24 24">
<path d="M6 4h4v16H6V4zm8 0h4v16h-4V4z"/>
</svg>
@@ -347,7 +357,11 @@ export default function Home() {
{/* Fixed Audio Player at bottom */}
<div className="fixed bottom-0 left-0 right-0 z-50">
<AudioPlayer track={currentTrack} />
<AudioPlayer
track={currentTrack}
isPlaying={isPlaying}
onPlayingChange={setIsPlaying}
/>
</div>
</div>
)

View File

@@ -5,10 +5,11 @@ import type { Track } from "@/lib/types"
interface AudioPlayerProps {
track: Track | null
isPlaying: boolean
onPlayingChange: (playing: boolean) => void
}
export default function AudioPlayer({ track }: AudioPlayerProps) {
const [isPlaying, setIsPlaying] = useState(false)
export default function AudioPlayer({ track, isPlaying, onPlayingChange }: AudioPlayerProps) {
const [currentTime, setCurrentTime] = useState(0)
const [duration, setDuration] = useState(0)
const [volume, setVolume] = useState(1)
@@ -22,7 +23,7 @@ export default function AudioPlayer({ track }: AudioPlayerProps) {
// Load audio and waveform when track changes
useEffect(() => {
if (!track) {
setIsPlaying(false)
onPlayingChange(false)
setCurrentTime(0)
setWaveformPeaks([])
return
@@ -33,13 +34,13 @@ export default function AudioPlayer({ track }: AudioPlayerProps) {
if (audioRef.current) {
audioRef.current.load()
// Autoplay when track loads
audioRef.current.play().then(() => {
setIsPlaying(true)
}).catch((error: unknown) => {
console.error("Autoplay failed:", error)
setIsPlaying(false)
})
// Autoplay when track loads if isPlaying is true
if (isPlaying) {
audioRef.current.play().catch((error: unknown) => {
console.error("Autoplay failed:", error)
onPlayingChange(false)
})
}
}
}, [track?.id])
@@ -54,7 +55,7 @@ export default function AudioPlayer({ track }: AudioPlayerProps) {
setDuration(audio.duration)
}
}
const handleEnded = () => setIsPlaying(false)
const handleEnded = () => onPlayingChange(false)
audio.addEventListener("timeupdate", updateTime)
audio.addEventListener("loadedmetadata", updateDuration)
@@ -91,15 +92,24 @@ export default function AudioPlayer({ track }: AudioPlayerProps) {
}
}
const togglePlay = () => {
if (!audioRef.current || !track) return
// Sync playing state with audio element
useEffect(() => {
const audio = audioRef.current
if (!audio) return
if (isPlaying) {
audioRef.current.pause()
audio.play().catch((error: unknown) => {
console.error("Play failed:", error)
onPlayingChange(false)
})
} else {
audioRef.current.play()
audio.pause()
}
setIsPlaying(!isPlaying)
}, [isPlaying, onPlayingChange])
const togglePlay = () => {
if (!audioRef.current || !track) return
onPlayingChange(!isPlaying)
}
const handleVolumeChange = (e: React.ChangeEvent<HTMLInputElement>) => {