From 1d901c522243308e179bfecebdebf72240388230 Mon Sep 17 00:00:00 2001 From: eric <3024947@stud.hs-mannheim.de> Date: Fri, 8 Aug 2025 00:08:09 +0200 Subject: [PATCH] =?UTF-8?q?ausw=C3=A4hlen=20von=20ger=C3=A4ten=20f=C3=BCr?= =?UTF-8?q?=20den=20player?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- dependency-reduced-pom.xml | 6 -- pom.xml | 10 +++ .../Roullette/controller/GameController.java | 33 ++++++++- .../Roullette/service/SpotifyAuthService.java | 73 +++++++++++++++++-- .../websocket/GameWebSocketHandler.java | 4 +- src/main/resources/public/game.html | 3 +- src/main/resources/public/js/device-select.js | 45 ++++++++++++ src/main/resources/public/js/game.js | 12 ++- .../resources/public/js/spotify-player.js | 0 9 files changed, 166 insertions(+), 20 deletions(-) create mode 100644 src/main/resources/public/js/device-select.js create mode 100644 src/main/resources/public/js/spotify-player.js diff --git a/dependency-reduced-pom.xml b/dependency-reduced-pom.xml index bd4ff2e..5fe83a3 100644 --- a/dependency-reduced-pom.xml +++ b/dependency-reduced-pom.xml @@ -78,12 +78,6 @@ javalin-testtools 6.7.0 test - - - okhttp - com.squareup.okhttp3 - - diff --git a/pom.xml b/pom.xml index da4e0f5..12ff0a0 100644 --- a/pom.xml +++ b/pom.xml @@ -67,6 +67,16 @@ ${javalin.version} test + + com.squareup.okhttp3 + okhttp + 4.11.0 + + + org.jetbrains.kotlin + kotlin-stdlib + 1.9.22 + diff --git a/src/main/java/eric/Roullette/controller/GameController.java b/src/main/java/eric/Roullette/controller/GameController.java index cadd609..077e2bb 100644 --- a/src/main/java/eric/Roullette/controller/GameController.java +++ b/src/main/java/eric/Roullette/controller/GameController.java @@ -1,22 +1,36 @@ package eric.Roullette.controller; import com.fasterxml.jackson.core.type.TypeReference; + import com.fasterxml.jackson.databind.node.JsonNodeFactory; + import com.fasterxml.jackson.databind.node.ObjectNode; import io.javalin.Javalin; import io.javalin.http.Context; import eric.Roullette.service.GameService; import eric.Roullette.service.SpotifyAuthService; import eric.Roullette.websocket.GameWebSocketHandler; - + import okhttp3.OkHttpClient; + import okhttp3.Request; + import okhttp3.RequestBody; + import okhttp3.Response; + import okhttp3.MediaType; + import com.fasterxml.jackson.databind.node.JsonNodeFactory; + import com.fasterxml.jackson.databind.node.ObjectNode; + import org.slf4j.Logger; + import org.slf4j.LoggerFactory; + import java.awt.*; + import java.io.IOException; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.UUID; import java.util.concurrent.ThreadLocalRandom; + import java.util.concurrent.TimeUnit; - public class GameController { +public class GameController { private final GameService gameService; private final SpotifyAuthService authService; private final GameWebSocketHandler webSocketHandler; + private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(GameController.class); public GameController(Javalin app, GameService gs, SpotifyAuthService sas, GameWebSocketHandler wsHandler) { this.gameService = gs; @@ -27,6 +41,7 @@ package eric.Roullette.controller; app.get("/api/game/{gameId}/players", this::getPlayers); app.post("/api/game/{gameId}/start-round", this::startRound); app.post("/api/game/{gameId}/guess", this::guess); + app.get("/api/spotify/devices", this::getDevices); } private void createGame(Context ctx) { @@ -94,4 +109,18 @@ package eric.Roullette.controller; "scores", game.scores() )); } + // Backend: Devices abrufen + private void getDevices(Context ctx) { + String username = ctx.queryParam("username"); + if (username == null) { + ctx.status(400).result("username fehlt"); + return; + } + List> devices = authService.getDevices(username); // Implementiere diese Methode im SpotifyAuthService + ctx.json(Map.of("devices", devices)); + } + + + + } \ No newline at end of file diff --git a/src/main/java/eric/Roullette/service/SpotifyAuthService.java b/src/main/java/eric/Roullette/service/SpotifyAuthService.java index a5c199f..9349393 100644 --- a/src/main/java/eric/Roullette/service/SpotifyAuthService.java +++ b/src/main/java/eric/Roullette/service/SpotifyAuthService.java @@ -1,17 +1,20 @@ package eric.Roullette.service; + import com.fasterxml.jackson.core.type.TypeReference; + import com.fasterxml.jackson.databind.ObjectMapper; import com.neovisionaries.i18n.CountryCode; + import okhttp3.OkHttpClient; + import okhttp3.Request; + import okhttp3.Response; import org.apache.hc.core5.http.ParseException; import se.michaelthelin.spotify.SpotifyApi; import se.michaelthelin.spotify.SpotifyHttpManager; import se.michaelthelin.spotify.exceptions.SpotifyWebApiException; import se.michaelthelin.spotify.model_objects.credentials.AuthorizationCodeCredentials; - import se.michaelthelin.spotify.model_objects.specification.Paging; - import se.michaelthelin.spotify.model_objects.specification.PagingCursorbased; - import se.michaelthelin.spotify.model_objects.specification.PlayHistory; + import se.michaelthelin.spotify.model_objects.specification.*; import se.michaelthelin.spotify.requests.data.player.GetCurrentUsersRecentlyPlayedTracksRequest; import se.michaelthelin.spotify.requests.data.library.GetUsersSavedTracksRequest; - import se.michaelthelin.spotify.model_objects.specification.SavedTrack; + import java.io.IOException; import java.net.URI; import java.util.*; @@ -41,7 +44,7 @@ public class SpotifyAuthService { .build(); return tempApi.authorizationCodeUri() - .scope("user-read-recently-played user-library-read") + .scope("user-read-recently-played user-library-read user-read-playback-state user-modify-playback-state streaming") .state(user) // Der Benutzername wird im State mitgegeben .build() .execute(); @@ -140,5 +143,63 @@ public class SpotifyAuthService { return Collections.emptyList(); } } + public List getTrackInfos(List allTracks) { + //für jede URI den titel holen + List trackInfos = new ArrayList<>(); + for (String uri : allTracks) { + SpotifyApi userApi = userApis.values().stream().findFirst().orElse(null); + if (userApi == null) { + System.err.println("Kein SpotifyApi-Client gefunden."); + return Collections.emptyList(); + } + try { + String trackId = uri.startsWith("spotify:track:") ? uri.substring("spotify:track:".length()) : uri; + var track = userApi.getTrack(trackId) + .build() + .execute(); + if (track != null) { + String info = track.getName() + " - " + Arrays.stream(track.getArtists()) + .map(ArtistSimplified::getName) + .reduce((a, b) -> a + ", " + b) + .orElse("Unbekannt"); + trackInfos.add(info); + + } else { + System.err.println("Track nicht gefunden: " + uri); + } + } catch (IOException | SpotifyWebApiException | ParseException e) { + System.err.println("Fehler beim Abrufen des Tracks: " + uri); + e.printStackTrace(); + } + } return trackInfos; + } + + + public String getAccessTokenForUser(String username) { + SpotifyApi userApi = userApis.get(username); + if (userApi == null) { + System.err.println("Kein SpotifyApi-Client für Benutzer gefunden: " + username); + return null; + } + return userApi.getAccessToken(); + } + public List> getDevices(String username) { + String accessToken = getAccessTokenForUser(username); + OkHttpClient client = new OkHttpClient(); + Request req = new Request.Builder() + .url("https://api.spotify.com/v1/me/player/devices") + .addHeader("Authorization", "Bearer " + accessToken) + .build(); + try (Response resp = client.newCall(req).execute()) { + if (!resp.isSuccessful()) return List.of(); + String body = resp.body().string(); + // Parsen, z.B. mit Jackson + var node = new ObjectMapper().readTree(body); + var devices = node.get("devices"); + return new ObjectMapper().convertValue(devices, new TypeReference>>(){}); + } catch (Exception e) { + return List.of(); + } + } +} - } \ No newline at end of file diff --git a/src/main/java/eric/Roullette/websocket/GameWebSocketHandler.java b/src/main/java/eric/Roullette/websocket/GameWebSocketHandler.java index ad3f0a6..b98b534 100644 --- a/src/main/java/eric/Roullette/websocket/GameWebSocketHandler.java +++ b/src/main/java/eric/Roullette/websocket/GameWebSocketHandler.java @@ -102,11 +102,13 @@ public class GameWebSocketHandler { List opts = game.players(); String songUri = game.currentSong(); List allTracks = game.allTracks(); + List trackInfos = authService.getTrackInfos(allTracks); String msg = JsonUtil.toJson(Map.of( "type", "round-start", "ownerOptions", opts, "songUri", songUri, - "allTracks", allTracks + "allTracks", allTracks, + "trackInfos", trackInfos )); broadcastToAll(gameId, msg); } diff --git a/src/main/resources/public/game.html b/src/main/resources/public/game.html index 13dd250..0577766 100644 --- a/src/main/resources/public/game.html +++ b/src/main/resources/public/game.html @@ -36,7 +36,8 @@

Scoreboard

    - +
    + diff --git a/src/main/resources/public/js/device-select.js b/src/main/resources/public/js/device-select.js new file mode 100644 index 0000000..64ac2d8 --- /dev/null +++ b/src/main/resources/public/js/device-select.js @@ -0,0 +1,45 @@ +// public/js/device-select.js + +import { getParam, fetchJson } from "./utils.js"; + +const username = getParam("username"); +const area = document.getElementById("deviceSelectArea") || document.body; + +const label = document.createElement("label"); +label.textContent = "Wiedergabegerät wählen: "; +label.htmlFor = "deviceSelect"; + +const select = document.createElement("select"); +select.id = "deviceSelect"; +select.style.margin = "0.5rem"; + +area.appendChild(label); +area.appendChild(select); + +async function loadDevices() { + select.innerHTML = ""; + const { devices } = await fetchJson(`/api/spotify/devices?username=${encodeURIComponent(username)}`); + if (!devices.length) { + const opt = document.createElement("option"); + opt.textContent = "Keine Geräte gefunden"; + opt.disabled = true; + select.appendChild(opt); + select.disabled = true; + return; + } + devices.forEach(d => { + const opt = document.createElement("option"); + opt.value = d.id; + opt.textContent = `${d.name} (${d.type})${d.is_active ? " [aktiv]" : ""}`; + select.appendChild(opt); + }); + select.disabled = false; +} +loadDevices(); + +select.addEventListener("change", () => { + const deviceId = select.value; + // Hier kannst du deviceId speichern oder an dein Backend schicken + console.log("Ausgewähltes Device:", deviceId); + // window.selectedDeviceId = deviceId; // z.B. global speichern +}); \ No newline at end of file diff --git a/src/main/resources/public/js/game.js b/src/main/resources/public/js/game.js index 540d2a8..a0af177 100644 --- a/src/main/resources/public/js/game.js +++ b/src/main/resources/public/js/game.js @@ -97,7 +97,7 @@ const optionsDiv = document.getElementById("options"); const resultP = document.getElementById("result"); const scoreboard = document.getElementById("scoreboard"); -function handleRoundStart({ ownerOptions, songUri, allTracks }) { +function handleRoundStart({ ownerOptions, songUri, allTracks, trackInfos }) { // UI zurücksetzen resultP.textContent = ""; optionsDiv.innerHTML = ""; @@ -111,6 +111,10 @@ function handleRoundStart({ ownerOptions, songUri, allTracks }) { width="100%" height="80" frameborder="0" allow="encrypted-media"> `; + // Song automatisch abspielen (sofern deviceId bereit und Username vorhanden) + if (window.playOnSpotify && typeof window.playOnSpotify === "function") { + window.playOnSpotify(songUri, username); + } // Tipp-Buttons erzeugen ownerOptions.forEach(user => { @@ -133,10 +137,10 @@ function handleRoundStart({ ownerOptions, songUri, allTracks }) { roundArea.hidden = false; const songList = document.getElementById("songList"); songList.innerHTML = ""; - if (Array.isArray(allTracks)) { - allTracks.forEach(uri => { + if (Array.isArray(trackInfos)) { + trackInfos.forEach(trackInfo => { const li = document.createElement("li"); - li.textContent = uri; + li.textContent = trackInfo songList.appendChild(li); }); } diff --git a/src/main/resources/public/js/spotify-player.js b/src/main/resources/public/js/spotify-player.js new file mode 100644 index 0000000..e69de29