feat: Fetch tracks, update interface and music

This commit is contained in:
Lucàs
2025-01-05 17:57:06 +01:00
parent 1edd76f618
commit 4c4e06b64b
9 changed files with 185 additions and 135 deletions
@@ -1,5 +0,0 @@
package fr.univpau.queezer.data
data class Album(
val cover: String
)
@@ -1,5 +0,0 @@
package fr.univpau.queezer.data
data class Artist(
val name: String
)
@@ -1,8 +1,19 @@
package fr.univpau.queezer.data
data class Track(
val title: String,
val artist: Artist,
val preview: String,
val album: Album
val preview: String, // Music preview URL
val album: String, // Album picture URL
val title: Input, // Title of the track
val artist: Input, // Artist of the track
)
data class Input(
val value: String,
val answer: Answer = Answer.UNKNOWN
)
enum class Answer {
CORRECT,
INCORRECT,
UNKNOWN
}
@@ -1,31 +0,0 @@
package fr.univpau.queezer.manager
import android.media.MediaPlayer
class AudioManager {
private var mediaPlayer: MediaPlayer = MediaPlayer()
fun play(url: String) {
mediaPlayer.apply {
setDataSource(url)
prepare()
start()
}
}
fun stop() {
mediaPlayer.release()
}
fun pause() {
mediaPlayer.pause()
}
fun resume() {
mediaPlayer.start()
}
fun isPlaying(): Boolean {
return mediaPlayer.isPlaying
}
}
@@ -4,7 +4,7 @@ import android.os.CountDownTimer
class CountdownManager (val duration: Long, val onFinish: () -> Unit) {
var timeLeft = duration;
var timeLeft = duration / 1000;
var interval = 1000L;
var timer: CountDownTimer? = null
@@ -1,71 +1,59 @@
package fr.univpau.queezer.manager
import com.google.gson.Gson
import com.google.gson.JsonObject
import com.google.gson.reflect.TypeToken
import android.media.MediaPlayer
import android.util.Log
import fr.univpau.queezer.data.Settings
import fr.univpau.queezer.data.Track
import java.net.HttpURLConnection
import java.net.URL
class GameManager(var settings: Settings) {
class GameManager(var settings: Settings, val tracks: List<Track>) {
val audioManager = AudioManager()
private var mediaPlayer: MediaPlayer = MediaPlayer()
val countDownManager = CountdownManager(30000L, onFinish = ::nextTrack)
var tracks: List<Track> = mutableListOf()
var currentTrackIndex: Int = 0;
var score = 0
suspend fun loadTracks() {
val url = URL(settings.playlistUrl)
val connection = url.openConnection() as HttpURLConnection
connection.requestMethod = "GET"
if (connection.responseCode != HttpURLConnection.HTTP_OK) {
throw Exception("Failed to load tracks")
fun getCurrentTrack(): Track? {
if (tracks.isEmpty()) return null;
return tracks[currentTrackIndex]
}
val response = connection.inputStream.bufferedReader().use { it.readText() }
val json = Gson().fromJson(response, JsonObject::class.java)
val tracksJson = json["tracks"].asJsonObject["data"].toString()
// Assurez-vous d'utiliser un TypeToken explicite pour une liste de Track
tracks = Gson().fromJson(tracksJson, object : TypeToken<List<Track>>() {}.type)
fun stop() {
mediaPlayer.release()
// countDownManager.stop()
}
fun nextTrack() {
// Stop the current track
audioManager.stop()
mediaPlayer.release()
// Play the next track
if (currentTrackIndex >= tracks.size - 1) {
return
return; // TODO vérifier avant meme de lancer la partie si le nombre de titres est suffisant
}
if (currentTrackIndex >= settings.numberOfTitles!! - 1) {
return;
}
currentTrackIndex++
audioManager.play(getCurrentTrack().preview)
// Restart the countdown
countDownManager.restart()
mediaPlayer = MediaPlayer().apply {
setDataSource(tracks[currentTrackIndex].preview)
prepare()
start()
}
fun getCurrentTrack(): Track {
if (tracks.isEmpty()) {
throw IllegalStateException("La liste des pistes est vide")
}
return tracks[currentTrackIndex]
// countDownManager.restart()
}
fun start() {
if (tracks.isEmpty()) {
throw IllegalStateException("Aucune piste n'a été trouvée dans la playlist")
// countDownManager.start()
Log.i("GameManager", "Next track: ${tracks[currentTrackIndex].preview}")
mediaPlayer = MediaPlayer().apply {
setDataSource(tracks[currentTrackIndex].preview)
prepare()
start()
}
countDownManager.start()
audioManager.play(tracks[currentTrackIndex].preview)
}
}
@@ -0,0 +1,31 @@
package fr.univpau.queezer.manager
import fr.univpau.queezer.data.Input
import fr.univpau.queezer.data.Track
import fr.univpau.queezer.service.createDeezerApiService
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
suspend fun fetchAndFormatPlaylist(apiUrl: String): List<Track> {
val deezerApiService = createDeezerApiService()
return withContext(Dispatchers.IO) {
try {
// Récupérer la réponse de la playlist
val playlist = deezerApiService.getPlaylist(apiUrl)
// Transformer les données en liste de `Track`
playlist.tracks.data.map { track ->
Track(
preview = track.preview,
album = track.album.cover,
title = Input(value = track.title),
artist = Input(value = track.artist.name)
)
}
} catch (e: Exception) {
e.printStackTrace()
emptyList()
}
}
}
@@ -1,10 +1,9 @@
package fr.univpau.queezer.screen
import androidx.compose.foundation.Image
import android.util.Log
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.aspectRatio
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
@@ -12,14 +11,18 @@ import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.material3.Button
import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.material3.TextField
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.blur
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.text.input.KeyboardType
@@ -30,18 +33,25 @@ import fr.univpau.queezer.R
import fr.univpau.queezer.data.Settings
import fr.univpau.queezer.manager.loadSettings
import coil.compose.AsyncImage
import fr.univpau.queezer.manager.GameManager
import fr.univpau.queezer.manager.fetchAndFormatPlaylist
@Composable
fun GameScreen(navController: NavHostController) {
val context = LocalContext.current
val settings: Settings = loadSettings(context)
// val gameManager: GameManager = remember { GameManager(settings) }
var gameManager by remember { mutableStateOf(GameManager(settings, emptyList())) }
var currentTrack by remember { mutableStateOf(gameManager.getCurrentTrack()) }
// LaunchedEffect(gameManager) {
// gameManager.loadTracks()
// gameManager.start()
// }
LaunchedEffect(settings.playlistUrl) {
val tracks = fetchAndFormatPlaylist(settings.playlistUrl).shuffled()
gameManager = GameManager(settings, tracks)
currentTrack = gameManager.getCurrentTrack()
gameManager.start()
Log.i("GameScreen", gameManager.getCurrentTrack().toString())
}
// État de l'utilisateur et des éléments du jeu
val userInput = remember { mutableStateOf("") }
@@ -54,25 +64,27 @@ fun GameScreen(navController: NavHostController) {
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.spacedBy(16.dp)
) {
if (currentTrack == null) {
CircularProgressIndicator(
modifier = Modifier.width(64.dp),
color = MaterialTheme.colorScheme.secondary,
trackColor = MaterialTheme.colorScheme.surfaceVariant,
)
} else {
Text("Score : ${gameManager.score}", fontSize = 24.sp)
Text("Temps restant : ${gameManager.countDownManager.timeLeft}sec", fontSize = 20.sp)
Text("Score : 0", fontSize = 24.sp)
Text("Temps restant : 30sec", fontSize = 20.sp)
// Affiche une image a partir d'une url
AsyncImage(
model = "https://api.deezer.com/album/382921287/image",
model = currentTrack!!.album,
contentDescription = "Image from URL",
modifier = Modifier
.width(200.dp)
.height(200.dp)
.blur(30.dp)
,
.height(200.dp),
contentScale = ContentScale.Crop
)
Text("Titre : Légende Vivante", fontSize = 20.sp)
Text("Artiste : Lorenzo", fontSize = 20.sp)
Text("Titre : ${currentTrack!!.title.value}", fontSize = 20.sp)
Text("Artiste : ${currentTrack!!.artist.value}", fontSize = 20.sp)
// Champ de texte pour entrer la proposition
TextField(
@@ -94,11 +106,17 @@ fun GameScreen(navController: NavHostController) {
Button(
onClick = {
userInput.value = "" // Réinitialiser le champ de texte
gameManager.nextTrack()
currentTrack = gameManager.getCurrentTrack()
},
) { Text(context.resources.getString(R.string.skip)) }
Button(onClick = { navController.popBackStack() })
Button(onClick = {
gameManager.stop()
navController.popBackStack()
})
{ Text(context.resources.getString(R.string.give_up)) }
}
}
}
}
@@ -0,0 +1,43 @@
package fr.univpau.queezer.service
import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory
import retrofit2.http.GET
import retrofit2.http.Url
data class PlaylistResponse(
val tracks: TrackList
)
data class TrackList(
val data: List<ApiTrack>
)
data class ApiTrack(
val preview: String, // URL d'aperçu de la musique
val album: ApiAlbum, // Informations sur l'album
val title: String, // Titre de la piste
val artist: Artist // Informations sur l'artiste
)
data class ApiAlbum(
val cover: String // URL de la couverture de l'album
)
data class Artist(
val name: String // Nom de l'artiste
)
interface DeezerApiService {
@GET
suspend fun getPlaylist(@Url url: String): PlaylistResponse
}
fun createDeezerApiService(): DeezerApiService {
return Retrofit.Builder()
.baseUrl("https://api.deezer.com/") // Base URL par défaut
.addConverterFactory(GsonConverterFactory.create())
.build()
.create(DeezerApiService::class.java)
}