Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow setting user data on tracks #983

Merged
merged 9 commits into from
Dec 2, 2023
Original file line number Diff line number Diff line change
Expand Up @@ -59,8 +59,24 @@ class PlayerRestHandler(
): ResponseEntity<Player> {
val context = socketContext(socketServer, sessionId)

val encodedTrack = playerUpdate.encodedTrack
if (encodedTrack is Omissible.Present && playerUpdate.identifier is Omissible.Present) {
val track = if (playerUpdate.track.isPresent()) {
playerUpdate.track
} else {
topi314 marked this conversation as resolved.
Show resolved Hide resolved
if (playerUpdate.encodedTrack is Omissible.Present || playerUpdate.identifier is Omissible.Present) {
PlayerUpdateTrack(
playerUpdate.encodedTrack,
playerUpdate.identifier
).toOmissible()
} else {
Omissible.Omitted()
}
}

val encodedTrack = track.ifPresent { it.encoded } ?: Omissible.Omitted()
val identifier = track.ifPresent { it.identifier } ?: Omissible.Omitted()
val userData = track.ifPresent { it.userData } ?: Omissible.Omitted()

if (encodedTrack is Omissible.Present && identifier is Omissible.Present) {
throw ResponseStatusException(HttpStatus.BAD_REQUEST, "Cannot specify both encodedTrack and identifier")
}

Expand Down Expand Up @@ -112,25 +128,31 @@ class PlayerRestHandler(

// we handle pause differently for playing new tracks
val paused = playerUpdate.paused
paused.takeIfPresent { encodedTrack is Omissible.Omitted && playerUpdate.identifier is Omissible.Omitted }
paused.takeIfPresent { encodedTrack is Omissible.Omitted && identifier is Omissible.Omitted }
?.let {
player.setPause(it)
}

// we handle userData differently for playing new tracks
userData.takeIfPresent { encodedTrack is Omissible.Omitted && identifier is Omissible.Omitted }
?.let {
player.track?.userData = it
}

playerUpdate.volume.ifPresent {
player.setVolume(it)
}

// we handle position differently for playing new tracks
playerUpdate.position.takeIfPresent { encodedTrack is Omissible.Omitted && playerUpdate.identifier is Omissible.Omitted }
playerUpdate.position.takeIfPresent { encodedTrack is Omissible.Omitted && identifier is Omissible.Omitted }
?.let {
if (player.isPlaying) {
player.seekTo(it)
SocketServer.sendPlayerUpdate(context, player)
}
}

playerUpdate.endTime.takeIfPresent { encodedTrack is Omissible.Omitted && playerUpdate.identifier is Omissible.Omitted }
playerUpdate.endTime.takeIfPresent { encodedTrack is Omissible.Omitted && identifier is Omissible.Omitted }
?.let { endTime ->
val marker = TrackMarker(endTime, TrackEndMarkerHandler(player))
player.track?.setMarker(marker)
Expand All @@ -141,72 +163,75 @@ class PlayerRestHandler(
SocketServer.sendPlayerUpdate(context, player)
}

if (encodedTrack is Omissible.Present || playerUpdate.identifier is Omissible.Present) {
if (encodedTrack is Omissible.Present || identifier is Omissible.Present) {

if (noReplace && player.track != null) {
log.info("Skipping play request because of noReplace")
return ResponseEntity.ok(player.toPlayer(context, pluginInfoModifiers))
}
player.setPause(if (paused is Omissible.Present) paused.value else false)

val track: AudioTrack? = if (encodedTrack is Omissible.Present) {
val newTrack: AudioTrack? = if (encodedTrack is Omissible.Present) {
encodedTrack.value?.let {
decodeTrack(context.audioPlayerManager, it)
}
} else {
val trackFuture = CompletableFuture<AudioTrack>()
val identifier = playerUpdate.identifier as Omissible.Present
context.audioPlayerManager.loadItem(identifier.value, object : AudioLoadResultHandler {
override fun trackLoaded(track: AudioTrack) {
trackFuture.complete(track)
}

override fun playlistLoaded(playlist: AudioPlaylist) {
trackFuture.completeExceptionally(
ResponseStatusException(
HttpStatus.BAD_REQUEST,
"Cannot play a playlist or search result"
context.audioPlayerManager.loadItemSync(
(identifier as Omissible.Present).value,
object : AudioLoadResultHandler {
override fun trackLoaded(track: AudioTrack) {
trackFuture.complete(track)
}

override fun playlistLoaded(playlist: AudioPlaylist) {
trackFuture.completeExceptionally(
ResponseStatusException(
HttpStatus.BAD_REQUEST,
"Cannot play a playlist or search result"
)
)
)
}

override fun noMatches() {
trackFuture.completeExceptionally(
ResponseStatusException(
HttpStatus.BAD_REQUEST,
"No matches found for identifier"
}

override fun noMatches() {
trackFuture.completeExceptionally(
ResponseStatusException(
HttpStatus.BAD_REQUEST,
"No matches found for identifier"
)
)
)
}

override fun loadFailed(exception: FriendlyException) {
trackFuture.completeExceptionally(
ResponseStatusException(
HttpStatus.INTERNAL_SERVER_ERROR,
exception.message,
getRootCause(exception)
}

override fun loadFailed(exception: FriendlyException) {
trackFuture.completeExceptionally(
ResponseStatusException(
HttpStatus.INTERNAL_SERVER_ERROR,
exception.message,
getRootCause(exception)
)
)
)
}
})
}
})

trackFuture.exceptionally {
throw it
}.join()
trackFuture.join()
}

track?.let {
newTrack?.let {
playerUpdate.position.ifPresent { position ->
track.position = position
newTrack.position = position
}

userData.ifPresent { userData ->
newTrack.userData = userData
}

playerUpdate.endTime.ifPresent { endTime ->
if (endTime != null) {
track.setMarker(TrackMarker(endTime, TrackEndMarkerHandler(player)))
newTrack.setMarker(TrackMarker(endTime, TrackEndMarkerHandler(player)))
}
}

player.play(track)
player.play(newTrack)
player.provideTo(context.getMediaConnection(player))
} ?: player.stop()
}
Expand Down
2 changes: 1 addition & 1 deletion LavalinkServer/src/main/java/lavalink/server/util/util.kt
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ fun AudioTrack.toTrack(encoded: String, pluginInfoModifiers: List<AudioPluginInf
acc + jsonObject
}

return Track(encoded, this.toInfo(), pluginInfo)
return Track(encoded, this.toInfo(), pluginInfo, this.userData as? JsonObject ?: JsonObject(emptyMap()))
}

private operator fun JsonObject.plus(other: JsonObject) = JsonObject(toMap() + other.toMap())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,13 @@ data class Player(
data class Track(
val encoded: String,
val info: TrackInfo,
val pluginInfo: JsonObject
) : LoadResult.Data
val pluginInfo: JsonObject,
val userData: JsonObject
) : LoadResult.Data {
fun copyWithUserData(userData: JsonObject): Track {
topi314 marked this conversation as resolved.
Show resolved Hide resolved
return copy(userData = userData)
}
}

@Serializable
@JvmInline
Expand Down Expand Up @@ -64,10 +69,20 @@ data class PlayerState(
val ping: Long
)

@Serializable
data class PlayerUpdateTrack(
val encoded: Omissible<String?> = Omissible.Omitted(),
val identifier: Omissible<String> = Omissible.Omitted(),
val userData: Omissible<JsonObject> = Omissible.Omitted()
)

@Serializable
data class PlayerUpdate(
@Deprecated("Use PlayerUpdateTrack#encoded instead", ReplaceWith("encoded"))
val encodedTrack: Omissible<String?> = Omissible.Omitted(),
@Deprecated("Use PlayerUpdateTrack#identifier instead")
val identifier: Omissible<String> = Omissible.Omitted(),
val track: Omissible<PlayerUpdateTrack> = Omissible.Omitted(),
val position: Omissible<Long> = Omissible.Omitted(),
val endTime: Omissible<Long?> = Omissible.Omitted(),
val volume: Omissible<Int> = Omissible.Omitted(),
Expand Down
3 changes: 2 additions & 1 deletion protocol/src/commonTest/kotlin/LoadResultSerializerTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,8 @@ class LoadResultSerializerTest {
"isrc": null,
"sourceName": "youtube"
},
"pluginInfo": {}
"pluginInfo": {},
"userData": {}
},
"exception": null
}
Expand Down
12 changes: 8 additions & 4 deletions protocol/src/commonTest/kotlin/MessageSerializerTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,8 @@ class MessageSerializerTest {
"isrc": null,
"sourceName": "youtube"
},
"pluginInfo": {}
"pluginInfo": {},
"userData": {}
}
}
""".trimIndent()
Expand Down Expand Up @@ -183,7 +184,8 @@ class MessageSerializerTest {
"isrc": null,
"sourceName": "youtube"
},
"pluginInfo": {}
"pluginInfo": {},
"userData": {}
},
"reason": "finished"
}
Expand Down Expand Up @@ -238,7 +240,8 @@ class MessageSerializerTest {
"isrc": null,
"sourceName": "youtube"
},
"pluginInfo": {}
"pluginInfo": {},
"userData": {}
},
"exception": {
"message": "...",
Expand Down Expand Up @@ -301,7 +304,8 @@ class MessageSerializerTest {
"isrc": null,
"sourceName": "youtube"
},
"pluginInfo": {}
"pluginInfo": {},
"userData": {}
},
"thresholdMs": 123456789
}
Expand Down
16 changes: 10 additions & 6 deletions protocol/src/commonTest/kotlin/PlayerSerializerTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,8 @@ private const val json = """
"artworkUrl": null,
"isrc": null
},
"pluginInfo": {}
"pluginInfo": {},
"userData": {}
},
"volume": 100,
"paused": false,
Expand All @@ -52,7 +53,9 @@ private const val json = """
//language=json
const val updateJson = """
{
"identifier": "...",
"track": {
"identifier": "..."
},
"endTime": 0,
"volume": 100,
"position": 32400,
Expand Down Expand Up @@ -112,8 +115,7 @@ class PlayerSerializerTest {
val json = """{}"""

test<PlayerUpdate>(json) {
assertIs<Omissible.Omitted<*>>(encodedTrack)
assertIs<Omissible.Omitted<*>>(identifier)
assertIs<Omissible.Omitted<*>>(track)
assertIs<Omissible.Omitted<*>>(position)
assertIs<Omissible.Omitted<*>>(endTime)
assertIs<Omissible.Omitted<*>>(volume)
Expand All @@ -127,7 +129,7 @@ class PlayerSerializerTest {
@JsName("test3")
fun `test encodedTrack and identifier exclusivity`() {
//language=json
val json = """{"encodedTrack": "", "identifier": ""}"""
val json = """{"track": {"encoded": "", "identifier": ""}}"""

assertFailsWith<IllegalArgumentException> { Json.decodeFromString(json) }
}
Expand All @@ -136,7 +138,9 @@ class PlayerSerializerTest {
@JsName("test4")
fun `test update player serialization`() {
test<PlayerUpdate>(updateJson) {
identifier shouldBe "..."
track.requirePresent {
identifier shouldBe "..."
}
endTime shouldBe 0
volume shouldBe 100
position shouldBe 32400
Expand Down