trackinfos optimiert laden ohne zu viele api aufrufe
parent
6028da6210
commit
3e0822df3b
|
|
@ -36,17 +36,17 @@ public class App {
|
||||||
config.staticFiles.add("/public", Location.CLASSPATH);
|
config.staticFiles.add("/public", Location.CLASSPATH);
|
||||||
}).start(cfg.port);
|
}).start(cfg.port);
|
||||||
|
|
||||||
app.before(ctx -> {
|
// app.before(ctx -> {
|
||||||
System.out.println("→ " + ctx.method() + " " + ctx.fullUrl());
|
// System.out.println("→ " + ctx.method() + " " + ctx.fullUrl());
|
||||||
});
|
// });
|
||||||
app.after(ctx -> {
|
// app.after(ctx -> {
|
||||||
String limit = ctx.header("x-rate-limit-limit");
|
// String limit = ctx.header("x-rate-limit-limit");
|
||||||
String remaining = ctx.header("x-rate-limit-remaining");
|
// String remaining = ctx.header("x-rate-limit-remaining");
|
||||||
String reset = ctx.header("x-rate-limit-reset");
|
// String reset = ctx.header("x-rate-limit-reset");
|
||||||
String retryAfter = ctx.header("Retry-After");
|
// String retryAfter = ctx.header("Retry-After");
|
||||||
System.out.printf("← %d | limit=%s remaining=%s reset=%s retry-after=%s%n",
|
// System.out.printf("← %d | limit=%s remaining=%s reset=%s retry-after=%s%n",
|
||||||
ctx.status().getCode(), limit, remaining, reset, retryAfter);
|
// ctx.status().getCode(), limit, remaining, reset, retryAfter);
|
||||||
});
|
// });
|
||||||
|
|
||||||
app.exception(Exception.class, (e, ctx) -> {
|
app.exception(Exception.class, (e, ctx) -> {
|
||||||
log.error("Unhandled error", e);
|
log.error("Unhandled error", e);
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
package eric.Roullette.controller;
|
package eric.Roullette.controller;
|
||||||
|
|
||||||
import com.fasterxml.jackson.core.type.TypeReference;
|
// import com.fasterxml.jackson.core.type.TypeReference;
|
||||||
import com.fasterxml.jackson.databind.node.JsonNodeFactory;
|
import com.fasterxml.jackson.databind.node.JsonNodeFactory;
|
||||||
import com.fasterxml.jackson.databind.node.ObjectNode;
|
import com.fasterxml.jackson.databind.node.ObjectNode;
|
||||||
import io.javalin.Javalin;
|
import io.javalin.Javalin;
|
||||||
|
|
@ -13,18 +13,18 @@ package eric.Roullette.controller;
|
||||||
import okhttp3.RequestBody;
|
import okhttp3.RequestBody;
|
||||||
import okhttp3.Response;
|
import okhttp3.Response;
|
||||||
import okhttp3.MediaType;
|
import okhttp3.MediaType;
|
||||||
import com.fasterxml.jackson.databind.node.JsonNodeFactory;
|
// import com.fasterxml.jackson.databind.node.JsonNodeFactory;
|
||||||
import com.fasterxml.jackson.databind.node.ObjectNode;
|
// import com.fasterxml.jackson.databind.node.ObjectNode;
|
||||||
import org.slf4j.Logger;
|
// import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
// import org.slf4j.LoggerFactory;
|
||||||
import java.awt.*;
|
import java.awt.*;
|
||||||
import java.io.IOException;
|
// import java.io.IOException;
|
||||||
import java.util.List;
|
// import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Objects;
|
// import java.util.Objects;
|
||||||
import java.util.UUID;
|
// import java.util.UUID;
|
||||||
import java.util.concurrent.ThreadLocalRandom;
|
import java.util.concurrent.ThreadLocalRandom;
|
||||||
import java.util.concurrent.TimeUnit;
|
// import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
public class GameController {
|
public class GameController {
|
||||||
private final GameService gameService;
|
private final GameService gameService;
|
||||||
|
|
@ -65,9 +65,9 @@ public class GameController {
|
||||||
private void setToken(String accessToken) {
|
private void setToken(String accessToken) {
|
||||||
this.accessToken = accessToken;
|
this.accessToken = accessToken;
|
||||||
}
|
}
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
private void createGame(Context ctx) throws InterruptedException {
|
private void createGame(Context ctx) throws InterruptedException {
|
||||||
Map<String, Object> body = ctx.bodyAsClass(Map.class);
|
Map<String, Object> body = (Map<String, Object>) ctx.bodyAsClass(Map.class);
|
||||||
String user = (String) body.get("username");
|
String user = (String) body.get("username");
|
||||||
if (user == null || user.isBlank()) {
|
if (user == null || user.isBlank()) {
|
||||||
ctx.status(400).result("username fehlt");
|
ctx.status(400).result("username fehlt");
|
||||||
|
|
@ -152,9 +152,10 @@ public class GameController {
|
||||||
.build();
|
.build();
|
||||||
try (Response trackResp = client.newCall(getTrack).execute()) {
|
try (Response trackResp = client.newCall(getTrack).execute()) {
|
||||||
if (!trackResp.isSuccessful()) {
|
if (!trackResp.isSuccessful()) {
|
||||||
ctx.status(trackResp.code()).result("Fehler beim Laden der Track-Details: " + trackResp.body().string());
|
//ctx.status(trackResp.code()).result("Fehler beim Laden der Track-Details: " + trackResp.body().string());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
assert trackResp.body() != null;
|
||||||
var node = new com.fasterxml.jackson.databind.ObjectMapper().readTree(trackResp.body().string());
|
var node = new com.fasterxml.jackson.databind.ObjectMapper().readTree(trackResp.body().string());
|
||||||
long durationMs = node.get("duration_ms").asLong();
|
long durationMs = node.get("duration_ms").asLong();
|
||||||
long startOffset = durationMs / 2;
|
long startOffset = durationMs / 2;
|
||||||
|
|
@ -171,16 +172,17 @@ public class GameController {
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
try (Response playResp = client.newCall(playReq).execute()) {
|
try (Response playResp = client.newCall(playReq).execute()) {
|
||||||
ctx.status(playResp.code());
|
// ctx.status(playResp.code());
|
||||||
ctx.header("Retry-After", playResp.header("Retry-After") != null ? playResp.header("Retry-After") : "");
|
// ctx.header("Retry-After", playResp.header("Retry-After") != null ? playResp.header("Retry-After") : "");
|
||||||
ctx.header("x-rate-limit-limit", playResp.header("x-rate-limit-limit"));
|
// ctx.header("x-rate-limit-limit", playResp.header("x-rate-limit-limit"));
|
||||||
ctx.header("x-rate-limit-remaining", playResp.header("x-rate-limit-remaining"));
|
// ctx.header("x-rate-limit-remaining", playResp.header("x-rate-limit-remaining"));
|
||||||
ctx.header("x-rate-limit-reset", playResp.header("x-rate-limit-reset"));
|
// ctx.header("x-rate-limit-reset", playResp.header("x-rate-limit-reset"));
|
||||||
ctx.result(playResp.body().string());
|
// ctx.result(playResp.body().string());
|
||||||
if (playResp.isSuccessful()) {
|
if (playResp.isSuccessful()) {
|
||||||
ctx.status(204).result("Track erfolgreich abgespielt");
|
ctx.status(204).result("Track erfolgreich abgespielt");
|
||||||
} else {
|
} else {
|
||||||
ctx.status(playResp.code()).result("Fehler beim Abspielen: " + playResp.body().string());
|
//ctx.status(playResp.code()).result("Fehler beim Abspielen: " + playResp.body().string());
|
||||||
|
System.out.println("Fehler beim Abspielen des Tracks");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -18,11 +18,30 @@ package eric.Roullette.service;
|
||||||
this.authService = authService;
|
this.authService = authService;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Map<String, List<String>> getTrackInfos(Map<String, List<String>> allPlayerTracks) {
|
||||||
|
// für jeden String Spieler in allPlayerTracks die Liste der Tracks an authservice übergeben
|
||||||
|
Map<String, List<String>> trackInfos = new ConcurrentHashMap<>();
|
||||||
|
for (Map.Entry<String, List<String>> entry : allPlayerTracks.entrySet()) {
|
||||||
|
String player = entry.getKey();
|
||||||
|
List<String> tracks = entry.getValue();
|
||||||
|
if (tracks.isEmpty()) continue; // Keine Tracks, skip
|
||||||
|
try {
|
||||||
|
List<String> trackInfo = authService.getTrackInfos(tracks);
|
||||||
|
trackInfos.put(player, trackInfo);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("Fehler beim Abrufen der Track-Infos für Spieler {}: {}", player, e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return trackInfos;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
public record Game(String id, List<String> players, Map<String,Integer> scores, String currentOwner,
|
public record Game(String id, List<String> players, Map<String,Integer> scores, String currentOwner,
|
||||||
String currentSong, List<String> allTracks, Map<String, List<String>> playerTracks) {
|
String currentSong, List<String> allTracks, Map<String, List<String>> playerTracks) {
|
||||||
public static Game create(String id) {
|
public static Game create(String id) {
|
||||||
return new Game(id, new CopyOnWriteArrayList<>(), new ConcurrentHashMap<>(), null, null, new ArrayList<>(), new ConcurrentHashMap<>());
|
return new Game(id, new CopyOnWriteArrayList<>(), new ConcurrentHashMap<>(), null, null, new ArrayList<>(), new ConcurrentHashMap<>());
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public Game getOrCreateGame(String gameId) {
|
public Game getOrCreateGame(String gameId) {
|
||||||
|
|
|
||||||
|
|
@ -36,6 +36,7 @@ public class SpotifyAuthService {
|
||||||
}
|
}
|
||||||
|
|
||||||
public URI getAuthorizationUri(String user) {
|
public URI getAuthorizationUri(String user) {
|
||||||
|
System.out.println("Erstelle Auth-URL für Benutzer: " + user);
|
||||||
// Temporäre API-Instanz nur für die Erstellung der Auth-URL
|
// Temporäre API-Instanz nur für die Erstellung der Auth-URL
|
||||||
SpotifyApi tempApi = new SpotifyApi.Builder()
|
SpotifyApi tempApi = new SpotifyApi.Builder()
|
||||||
.setClientId(clientId)
|
.setClientId(clientId)
|
||||||
|
|
@ -68,6 +69,7 @@ public class SpotifyAuthService {
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<String> getRecentTracks(String user) {
|
public List<String> getRecentTracks(String user) {
|
||||||
|
System.out.println("Hole kürzlich gespielte Tracks für Benutzer: " + user);
|
||||||
int limit = 2;
|
int limit = 2;
|
||||||
SpotifyApi userApi = userApis.get(user);
|
SpotifyApi userApi = userApis.get(user);
|
||||||
|
|
||||||
|
|
@ -112,6 +114,7 @@ public class SpotifyAuthService {
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<String> getSavedTracks(String user, int limit, int offset) {
|
private List<String> getSavedTracks(String user, int limit, int offset) {
|
||||||
|
System.out.println("Hole gespeicherte Tracks für Benutzer: " + user + ", Limit: " + limit + ", Offset: " + offset);
|
||||||
SpotifyApi userApi = userApis.get(user);
|
SpotifyApi userApi = userApis.get(user);
|
||||||
|
|
||||||
if (userApi == null) {
|
if (userApi == null) {
|
||||||
|
|
@ -143,10 +146,11 @@ public class SpotifyAuthService {
|
||||||
return Collections.emptyList();
|
return Collections.emptyList();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
public List<String> getTrackInfos(List<String> allTracks) {
|
public List<String> getTrackInfos(List<String> tracks) {
|
||||||
|
System.out.println("Hole Track-Infos für " + tracks.size() + " Tracks.");
|
||||||
//für jede URI den titel holen
|
//für jede URI den titel holen
|
||||||
List<String> trackInfos = new ArrayList<>();
|
List<String> trackInfos = new ArrayList<>();
|
||||||
for (String uri : allTracks) {
|
for (String uri : tracks) {
|
||||||
SpotifyApi userApi = userApis.values().stream().findFirst().orElse(null);
|
SpotifyApi userApi = userApis.values().stream().findFirst().orElse(null);
|
||||||
if (userApi == null) {
|
if (userApi == null) {
|
||||||
System.err.println("Kein SpotifyApi-Client gefunden.");
|
System.err.println("Kein SpotifyApi-Client gefunden.");
|
||||||
|
|
@ -176,6 +180,7 @@ public class SpotifyAuthService {
|
||||||
|
|
||||||
|
|
||||||
public String getAccessTokenForUser(String username) {
|
public String getAccessTokenForUser(String username) {
|
||||||
|
System.out.println("Hole AccessToken für Benutzer: " + username);
|
||||||
SpotifyApi userApi = userApis.get(username);
|
SpotifyApi userApi = userApis.get(username);
|
||||||
if (userApi == null) {
|
if (userApi == null) {
|
||||||
System.err.println("Kein SpotifyApi-Client für Benutzer gefunden: " + username);
|
System.err.println("Kein SpotifyApi-Client für Benutzer gefunden: " + username);
|
||||||
|
|
|
||||||
|
|
@ -25,8 +25,8 @@ public class GameWebSocketHandler {
|
||||||
private final Map<String, List<String>> trackInfoCache = new ConcurrentHashMap<>();
|
private final Map<String, List<String>> trackInfoCache = new ConcurrentHashMap<>();
|
||||||
private final Map<String, List<String>> allTracksCache = new ConcurrentHashMap<>();
|
private final Map<String, List<String>> allTracksCache = new ConcurrentHashMap<>();
|
||||||
//Map<gameId, Map<player, List<String>>
|
//Map<gameId, Map<player, List<String>>
|
||||||
private final Map<String, Map<String, List<String>>> playerTracksCache = new ConcurrentHashMap<>();
|
private Map<String, Map<String, List<String>>> playerTracksCache = new ConcurrentHashMap<>();
|
||||||
private final Map<String, Map<String, List<String>>> playerTrackInfoCache = new ConcurrentHashMap<>();
|
private Map<String, Map<String, List<String>>> playerTrackInfoCache = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
public GameWebSocketHandler(GameService gameService) {
|
public GameWebSocketHandler(GameService gameService) {
|
||||||
this.service = gameService;
|
this.service = gameService;
|
||||||
|
|
@ -84,14 +84,20 @@ public class GameWebSocketHandler {
|
||||||
var currentGame = service.getOrCreateGame(gameId);
|
var currentGame = service.getOrCreateGame(gameId);
|
||||||
if (currentGame.players().isEmpty()) return;
|
if (currentGame.players().isEmpty()) return;
|
||||||
// Tracks pro Spieler sammeln
|
// Tracks pro Spieler sammeln
|
||||||
Map<String, List<String>> playerTracks = currentGame.playerTracks();
|
Map<String, List<String>> allPlayerTracks = currentGame.playerTracks();
|
||||||
List<String> allTracks = playerTracks.values().stream()
|
// alle tracks sammeln
|
||||||
|
List<String> allTracks = allPlayerTracks.values().stream()
|
||||||
.flatMap(List::stream)
|
.flatMap(List::stream)
|
||||||
.toList();
|
.toList();
|
||||||
System.out.println("AlltracksCache für Spiel " + gameId + " hat " + allTracks.size() + " Songs (rundesnstart)");
|
System.out.println("AlltracksCache für Spiel " + gameId + " hat " + allTracks.size() + " Songs (rundenstart)");
|
||||||
if (!allTracks.isEmpty()) {
|
if (!allTracks.isEmpty()) {
|
||||||
service.startRound(gameId, allTracks);
|
service.startRound(gameId, allTracks);
|
||||||
}
|
}
|
||||||
|
// Trackinfos für alle Spieler sammeln
|
||||||
|
Map<String, List<String>> allTrackInfos = service.getTrackInfos(allPlayerTracks);
|
||||||
|
// Cache für Trackinfos pro Spiel-ID
|
||||||
|
playerTrackInfoCache.put(gameId, allTrackInfos);
|
||||||
|
System.out.println("TrackInfosCache für Spiel " + gameId + " hat " + allTrackInfos.size() + " Spieler (rundenstart)");
|
||||||
broadcastRoundStart(gameId);
|
broadcastRoundStart(gameId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -101,9 +107,7 @@ public class GameWebSocketHandler {
|
||||||
public void nextround(String gameId) {
|
public void nextround(String gameId) {
|
||||||
var game = service.getOrCreateGame(gameId);
|
var game = service.getOrCreateGame(gameId);
|
||||||
if (game.players().isEmpty()) return;
|
if (game.players().isEmpty()) return;
|
||||||
|
|
||||||
// Songs von allen Spielern sammeln
|
// Songs von allen Spielern sammeln
|
||||||
Map<String, List<String>> playerTracks = game.playerTracks();
|
|
||||||
List<String> allTracks = new ArrayList<>();
|
List<String> allTracks = new ArrayList<>();
|
||||||
for (String player : game.players()) {
|
for (String player : game.players()) {
|
||||||
allTracks.addAll(game.playerTracks().getOrDefault(player, List.of()));
|
allTracks.addAll(game.playerTracks().getOrDefault(player, List.of()));
|
||||||
|
|
@ -112,7 +116,7 @@ public class GameWebSocketHandler {
|
||||||
// TODO: Fehler an Client senden, dass keine Songs da sind
|
// TODO: Fehler an Client senden, dass keine Songs da sind
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
// TODO funktionalität bei neu joinenden Spielern überprüfen
|
||||||
// Runde im Service starten, um Song und Owner zu setzen
|
// Runde im Service starten, um Song und Owner zu setzen
|
||||||
service.startRound(gameId, allTracks);
|
service.startRound(gameId, allTracks);
|
||||||
// Jetzt Broadcast mit den aktuellen Daten
|
// Jetzt Broadcast mit den aktuellen Daten
|
||||||
|
|
@ -126,17 +130,8 @@ public class GameWebSocketHandler {
|
||||||
List<String> opts = game.players();
|
List<String> opts = game.players();
|
||||||
String songUri = game.currentSong();
|
String songUri = game.currentSong();
|
||||||
List<String> allTracks = game.allTracks();
|
List<String> allTracks = game.allTracks();
|
||||||
List<String> trackInfos = game.allTracks();
|
Map<String, List<String>> trackInfos = playerTrackInfoCache.get(gameId);
|
||||||
// Cache pro Spiel-ID nutzen
|
// Cache pro Spiel-ID nutzen
|
||||||
// List<String> trackInfos = trackInfoCache.get(gameId);
|
|
||||||
// if (trackInfos == null || trackInfos.isEmpty()) {
|
|
||||||
// System.out.println("TrackInfoCache ist leer, hole Infos von Spotify");
|
|
||||||
// trackInfos = authService.getTrackInfos(allTracks);
|
|
||||||
// trackInfoCache.put(gameId, trackInfos);
|
|
||||||
// System.out.println("TrackInfoCache für Spiel " + gameId + " hat " + trackInfos.size() + " Infos");
|
|
||||||
// } else {
|
|
||||||
// System.out.println("TrackInfoCache ist nicht leer, nutze gecachte Infos");
|
|
||||||
// }
|
|
||||||
|
|
||||||
String msg = JsonUtil.toJson(Map.of(
|
String msg = JsonUtil.toJson(Map.of(
|
||||||
"type", "round-start",
|
"type", "round-start",
|
||||||
|
|
|
||||||
|
|
@ -163,13 +163,13 @@ async function handleRoundStart({ownerOptions, songUri, allTracks, trackInfos})
|
||||||
|
|
||||||
const songList = document.getElementById("songList");
|
const songList = document.getElementById("songList");
|
||||||
songList.innerHTML = "";
|
songList.innerHTML = "";
|
||||||
if (Array.isArray(trackInfos)) {
|
//trackinfos ist eine map bestehend aus aus Spielername und Liste von Track-Infos
|
||||||
trackInfos.forEach(trackInfo => {
|
const userTracks = trackInfos?.[username] ?? [];
|
||||||
|
userTracks.forEach(trackInfo => {
|
||||||
const li = document.createElement("li");
|
const li = document.createElement("li");
|
||||||
li.textContent = trackInfo;
|
li.textContent = trackInfo;
|
||||||
songList.appendChild(li);
|
songList.appendChild(li);
|
||||||
});
|
});
|
||||||
}
|
|
||||||
//playLock = false;
|
//playLock = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue