![JAR search and dependency download from the Maven repository](/logo.png)
dev.arbjerg.lavalink.client.LavalinkNode.kt Maven / Gradle / Ivy
package dev.arbjerg.lavalink.client
import dev.arbjerg.lavalink.client.event.ClientEvent
import dev.arbjerg.lavalink.client.http.HttpBuilder
import dev.arbjerg.lavalink.client.player.*
import dev.arbjerg.lavalink.client.player.Track
import dev.arbjerg.lavalink.client.player.toCustom
import dev.arbjerg.lavalink.internal.*
import dev.arbjerg.lavalink.internal.error.RestException
import dev.arbjerg.lavalink.internal.loadbalancing.Penalties
import dev.arbjerg.lavalink.internal.toLavalinkPlayer
import dev.arbjerg.lavalink.protocol.v4.*
import kotlinx.serialization.DeserializationStrategy
import kotlinx.serialization.ExperimentalSerializationApi
import kotlinx.serialization.json.decodeFromStream
import kotlinx.serialization.serializer
import okhttp3.Call
import okhttp3.OkHttpClient
import okhttp3.Response
import reactor.core.Disposable
import reactor.core.publisher.Flux
import reactor.core.publisher.Mono
import reactor.core.publisher.Sinks
import reactor.core.publisher.Sinks.Many
import reactor.kotlin.core.publisher.toMono
import java.io.Closeable
import java.io.IOException
import java.util.concurrent.ConcurrentHashMap
import java.util.concurrent.TimeUnit
import java.util.function.Consumer
import java.util.function.UnaryOperator
/**
* The Node is a physical instance of the lavalink server software.
*/
class LavalinkNode(
private val nodeOptions: NodeOptions,
val lavalink: LavalinkClient
) : Disposable, Closeable {
// "safe" uri with all paths removed
val baseUri = "${nodeOptions.serverUri.scheme}://${nodeOptions.serverUri.host}:${nodeOptions.serverUri.port}"
val name = nodeOptions.name
val regionFilter = nodeOptions.regionFilter
val password = nodeOptions.password
var sessionId: String? = null
internal set
internal val httpClient = OkHttpClient.Builder().callTimeout(nodeOptions.httpTimeout, TimeUnit.MILLISECONDS).build()
internal val sink: Many = Sinks.many().multicast().onBackpressureBuffer()
val flux: Flux = sink.asFlux()
private val reference: Disposable = flux.subscribe()
internal val rest = LavalinkRestClient(this)
val ws = LavalinkSocket(this)
// Stuff for load balancing
val penalties = Penalties(this)
var stats: Stats? = null
internal set
var available: Boolean = false
internal set
/**
* A local player cache, allows us to not call the rest client every time we need a player.
*/
internal val playerCache = ConcurrentHashMap()
override fun dispose() {
close()
}
override fun close() {
available = false
ws.close()
httpClient.dispatcher.executorService.shutdown()
httpClient.connectionPool.evictAll()
httpClient.cache?.close()
reference.dispose()
}
// For the java people
/**
* Listen to events from the node. Please note that uncaught exceptions will cause the listener to stop emitting events.
*
* @param type the [ClientEvent] to listen for
*
* @return a [Flux] of [ClientEvent]s
*/
fun on(type: Class): Flux {
return flux.ofType(type)
}
/**
* Listen to events from the node. Please note that uncaught exceptions will cause the listener to stop emitting events.
*
* @return a [Flux] of [ClientEvent]s
*/
inline fun on() = on(T::class.java)
/**
* Retrieves a list of all players from the lavalink node.
*
* @return A list of all players from the node.
*/
fun getPlayers(): Mono> {
if (!available) return Mono.error(IllegalStateException("Node is not available"))
return rest.getPlayers()
.map { it.players.map { pl -> pl.toLavalinkPlayer(this) } }
.doOnSuccess {
it.forEach { player ->
playerCache[player.guildId] = player
}
}
}
/**
* Gets the player from the guild id. If the player is not cached, it will be retrieved from the server.
* If the player does not exist on the node, it will be created.
*
* @param guildId The guild id of the player.
*
* @Returns The player from the guild
*/
fun getPlayer(guildId: Long): Mono {
if (!available) return Mono.error(IllegalStateException("Node is not available"))
if (playerCache.containsKey(guildId)) {
return playerCache[guildId].toMono()
}
return rest.getPlayer(guildId)
.map { it.toLavalinkPlayer(this) }
// Create the player if it doesn't exist on the node.
.onErrorResume {
if (it is RestException && it.code == 404) {
createOrUpdatePlayer(guildId)
} else {
it.toMono()
}
}
.doOnSuccess {
// Update the player internally upon retrieving it.
playerCache[it.guildId] = it
}
}
fun updatePlayer(guildId: Long, updateConsumer: Consumer): Mono {
val update = createOrUpdatePlayer(guildId)
updateConsumer.accept(update)
return update
}
/**
* Creates or updates a player.
*
* @param guildId The guild id that you want to create or update the player for.
*
* @return The newly created or updated player.
*/
fun createOrUpdatePlayer(guildId: Long) = PlayerUpdateBuilder(this, guildId)
/**
* Destroy a guild's player and remove it from the cache. This will also remove the associated link from the client.
*
* @param guildId The guild id of the player AND link to destroy.
*/
fun destroyPlayerAndLink(guildId: Long): Mono {
if (!available) return Mono.error(IllegalStateException("Node is not available"))
return rest.destroyPlayer(guildId)
.doOnSuccess {
removeCachedPlayer(guildId)
lavalink.removeDestroyedLink(guildId)
}
}
internal fun removeCachedPlayer(guildId: Long) {
playerCache.remove(guildId)
}
/**
* Load an item for the player.
*
* @param identifier The identifier (E.G. youtube url) to load.
*
* @return The [LoadResult] of whatever you tried to load.
*/
fun loadItem(identifier: String): Mono {
if (!available) return Mono.error(IllegalStateException("Node is not available"))
return rest.loadItem(identifier).map { it.toLavalinkLoadResult() }
}
/**
* Uses the node to decode a base64 encoded track.
*
* @param encoded The base64 encoded track to decode.
*
* @return The decoded track.
*/
fun decodeTrack(encoded: String): Mono
© 2015 - 2025 Weber Informatics LLC | Privacy Policy