mirror of
https://github.com/LucasVbr/Queezer.git
synced 2026-05-13 17:11:55 +00:00
feat: Fetch tracks, update interface and music
This commit is contained in:
@@ -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()
|
||||
audioManager.play(tracks[currentTrackIndex].preview)
|
||||
|
||||
// countDownManager.start()
|
||||
Log.i("GameManager", "Next track: ${tracks[currentTrackIndex].preview}")
|
||||
mediaPlayer = MediaPlayer().apply {
|
||||
setDataSource(tracks[currentTrackIndex].preview)
|
||||
prepare()
|
||||
start()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
Reference in New Issue
Block a user