Skip to content

Commit

Permalink
Improve Readme & Documentation & Fix test MPRISPlayer
Browse files Browse the repository at this point in the history
  • Loading branch information
Xerus committed Mar 22, 2019
1 parent 639ed1e commit 1736c84
Show file tree
Hide file tree
Showing 8 changed files with 73 additions and 47 deletions.
12 changes: 6 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
# mpris-java

Simple MPRIS 2 implementation with Kotlin for Java.
Code is mainly done, but largely untested. No guarantees for it to work.
Simple [MPRIS 2.2](https://specifications.freedesktop.org/mpris-spec/2.2/) implementation with Kotlin for the JVM.
Code is basically done, but largely untested. No guarantees for it to work.

Look into extensions/test/xerus.mpris.MPRISPlayer for a test implementation.
A test implementation can be found in [extensions/test/xerus/mpris/MPRISPlayer.kt](extensions/test/xerus/mpris/MPRISPlayer.kt).

It has two modules, the project itself and mpris-extensions,
which is meant to ease the use of this library by providing reference
implementations and abstractions.
The project is separated into two modules, the base/root module and [mpris-extensions](extensions), which is meant to ease the use of this library by providing reference implementations and abstractions.

The base module should be compatible with any JVM language, while the extensions module is specifically fitted to Kotlin.
2 changes: 1 addition & 1 deletion extensions/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ sourceSets.main.get().java.srcDir("src")
sourceSets.main.get().java.srcDir("test")

application {
mainClassName = "xerus.mpris.DBusPropertyDelegateKt"
mainClassName = "xerus.mpris.MPRISPlayerKt"
}

dependencies {
Expand Down
4 changes: 4 additions & 0 deletions extensions/src/xerus/mpris/AbstractMPRISPlayer.kt
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,9 @@ abstract class AbstractMPRISPlayer: MediaPlayerX, PlayerX, DefaultDBus {
propertyListeners[property_name]?.invoke(value)
}

/** Requests the bus name for this player and exports it, so that it can be called from DBus.
*
* Blocks the current Thread as long as the object is exported to DBus. */
fun exportAs(playerName: String) {
connection.requestBusName("org.mpris.MediaPlayer2.$playerName")
connection.exportObject("/org/mpris/MediaPlayer2", this)
Expand Down Expand Up @@ -74,6 +77,7 @@ interface PlayerX: Player {

}

/** Extension of the [MediaPlayer2] interface which adds its properties. */
interface MediaPlayerX: MediaPlayer2 {
val supportedUriSchemes: Array<String>
val supportedMimeTypes: Array<String>
Expand Down
28 changes: 18 additions & 10 deletions extensions/src/xerus/mpris/DBusPropertyDelegate.kt
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,12 @@ package xerus.mpris

import javafx.beans.value.ObservableValue
import org.freedesktop.dbus.DBusInterfaceName
import org.mpris.MediaPlayer2.PlaylistOrdering
import org.slf4j.LoggerFactory
import kotlin.properties.ReadOnlyProperty
import kotlin.properties.ReadWriteProperty
import kotlin.reflect.KClass
import kotlin.reflect.KProperty

fun main(args: Array<String>) {
println(arrayOf(PlaylistOrdering.Alphabetical).variant())
}

val logger = LoggerFactory.getLogger("xerus.mpris.properties")

private fun findInterface(clazz: Class<*>, name: String): Class<*>? =
Expand Down Expand Up @@ -50,9 +46,15 @@ class DBusProperty<T: Any>(private val initial: T, private val observable: Obser

}

class DBusMapProperty<K, V>(private val initial: Map<K, V> = HashMap<K, V>(), private val observable: ObservableValue<Map<K, V>>? = null, private val onSet: ((Map<K, V>) -> Unit)? = null) {
class DBusMapProperty<K: Any, V: Any>(
private val keyClass: KClass<K>,
private val valueClass: KClass<V>,
private val initial: Map<K, V> = HashMap(),
private val observable: ObservableValue<Map<K, V>>? = null,
private val onSet: ((Map<K, V>) -> Unit)? = null) {

constructor(observable: ObservableValue<Map<K, V>>, onSet: ((Map<K, V>) -> Unit)? = null): this(observable.value, observable, onSet)
constructor(keyClass: KClass<K>, valueClass: KClass<V>, observable: ObservableValue<Map<K, V>>, onSet: ((Map<K, V>) -> Unit)? = null):
this(keyClass, valueClass, observable.value, observable, onSet)

operator fun provideDelegate(
thisRef: AbstractMPRISPlayer,
Expand All @@ -64,7 +66,7 @@ class DBusMapProperty<K, V>(private val initial: Map<K, V> = HashMap<K, V>(), pr
val interfaceName = (clazz.annotations.find { it is DBusInterfaceName } as? DBusInterfaceName)?.value
?: clazz.name
logger.trace("Registered Property ${prop.name} for $interfaceName")
//thisRef.properties.getOrPut(interfaceName) { HashMap() }[name] = initial.variant()
thisRef.properties.getOrPut(interfaceName) { HashMap() }[name] = initial.variant(keyClass, valueClass)
val property = Property<Map<K, V>>(interfaceName, name)
if(onSet != null)
thisRef.propertyListeners[name] = onSet as ((Any) -> Unit)
Expand Down Expand Up @@ -94,8 +96,14 @@ class DBusConstant<out T: Any>(private val value: T) {
private class Property<T: Any>(private val interfaceName: String, private val name: String): ReadWriteProperty<AbstractMPRISPlayer, T> {

override fun setValue(thisRef: AbstractMPRISPlayer, property: KProperty<*>, value: T) {
if(thisRef.properties.getValue(interfaceName).put(name, value.variant())?.value != value)
thisRef.connection.sendSignal(thisRef.propertyChanged(interfaceName, name))
println("setting $name in $interfaceName to $value")
try {
if(thisRef.properties.getValue(interfaceName).put(name, value.variant())?.value != value)
thisRef.connection.sendSignal(thisRef.propertyChanged(interfaceName, name))
} catch(t: Throwable) {
t.printStackTrace()
}
println("set $value")
}

override fun getValue(thisRef: AbstractMPRISPlayer, property: KProperty<*>) =
Expand Down
12 changes: 9 additions & 3 deletions extensions/src/xerus/mpris/DefaultDBus.kt
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,24 @@ import org.freedesktop.dbus.Variant
import org.freedesktop.dbus.types.DBusMapType
import java.security.InvalidParameterException
import java.util.*
import kotlin.reflect.KClass

fun Any.variant(): Variant<*> {
if(this is Variant<*>)
return this
if(this is Map<*, *>)
throw InvalidParameterException("Use Map.variant for Maps!")
throw InvalidParameterException("Map.variant has to be used for Maps")
return Variant(this)
}

inline fun <reified K, reified V> Map<K, V>.variant() =
Variant(this, DBusMapType(K::class.java, V::class.java))

fun <K: Any, V: Any> Map<K, V>.variant(
keyClass: KClass<K>,
valueClass: KClass<V>) =
Variant(this, DBusMapType(keyClass.java, valueClass.java))

interface DefaultDBus: Properties {

override fun isRemote() = false
Expand All @@ -26,12 +32,12 @@ interface DefaultDBus: Properties {
GetAll(interface_name)[property_name]?.value as A

override fun <A: Any> Set(interface_name: String, property_name: String, value: A) {
GetAll(interface_name).put(property_name, value.variant())
GetAll(interface_name)[property_name] = value.variant()
propertyChanged(interface_name, property_name)
}

/** returns a new [Properties.PropertiesChanged] signal */
fun propertyChanged(interface_name: String, property_name: String) =
Properties.PropertiesChanged(objectPath, interface_name, Collections.singletonMap(property_name, Get<Any>(interface_name, property_name).variant()) as Map<String, Variant<*>>, null)
Properties.PropertiesChanged(objectPath, interface_name, Collections.singletonMap<String, Variant<*>>(property_name, Get<Any>(interface_name, property_name).variant()), null)

}
18 changes: 9 additions & 9 deletions extensions/src/xerus/mpris/PropertyMap.kt
Original file line number Diff line number Diff line change
Expand Up @@ -3,25 +3,25 @@ package xerus.mpris
import org.freedesktop.dbus.Variant
import java.util.*

class PropertyMap private constructor(initial: PropertyMap.() -> Unit, private val map: HashMap<String, Variant<*>>): Map<String, Variant<*>> by map {
class PropertyMap private constructor(private val map: MutableMap<String, Variant<*>>): MutableMap<String, Variant<*>> by map {

@JvmOverloads
constructor(initial: PropertyMap.() -> Unit = {}): this(initial, HashMap())
constructor(initializer: PropertyMap.() -> Unit): this(HashMap()) {
initializer(this)
}

constructor(vararg elements: Pair<String, Variant<*>>): this(mutableMapOf(*elements))

override val entries: Set<Map.Entry<String, Variant<*>>>
override val entries: MutableSet<MutableMap.MutableEntry<String, Variant<*>>>
get() = refresh().entries
override val values: Collection<Variant<*>>
override val values: MutableCollection<Variant<*>>
get() = refresh().values

override fun containsValue(value: Variant<*>) = refresh().containsValue(value)

private val mapCallable = HashMap<String, () -> Any?>()

init {
initial(this)
}

fun refresh(): Map<String, Variant<*>> {
fun refresh(): MutableMap<String, Variant<*>> {
mapCallable.forEach { key, cal -> refresh(key, cal) }
return map
}
Expand Down
12 changes: 7 additions & 5 deletions extensions/src/xerus/mpris/Track.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,16 @@ package xerus.mpris

import org.freedesktop.dbus.Variant

/** Represents a Track with Metadata.
* This is not an actual DBus-object, but rather a convenience wrapper around the metadata. */
interface Track {
/** the TrackID, must be a valid DBus-Path */
/** The TrackID, must be a valid DBus-Path. */
val id: String
get() = metadata["mpris:trackid"]!!.value as String
/** Metadata of the Track */
val metadata: Map<String, Variant<*>?>
get() = metadata.getValue("mpris:trackid").value as String
/** Metadata of the Track. */
val metadata: Map<String, Variant<*>>
}

class SimpleTrack(override val metadata: Map<String, Variant<*>?>): Track {
class SimpleTrack(override val metadata: Map<String, Variant<*>>): Track {
constructor(createMetadata: PropertyMap.() -> Unit): this(PropertyMap(createMetadata))
}
32 changes: 19 additions & 13 deletions extensions/test/xerus/mpris/MPRISPlayer.kt
Original file line number Diff line number Diff line change
@@ -1,30 +1,36 @@
package xerus.mpris

import org.freedesktop.DBus
import org.freedesktop.DBus.Properties.PropertiesChanged
import org.freedesktop.dbus.Variant
import org.freedesktop.dbus.types.DBusMapType
import org.mpris.MediaPlayer2.LoopStatus
import org.mpris.MediaPlayer2.MediaPlayer2
import org.mpris.MediaPlayer2.PlaybackStatus
import org.mpris.MediaPlayer2.Player
import java.io.File
import java.util.*

operator fun <K, V> HashMap<K, V>.set(k: K, value: V) = put(k, value)
val playerName = "TestPlayer"
val cover = "file://" + File("extensions/test/cover.png").absoluteFile.toString()

fun main(args: Array<String>) {
fun main() {
println("Connecting to DBus")
MPRISPlayer().exportAs("TestPlayer")
MPRISPlayer().exportAs(playerName)
}

class MPRISPlayer: AbstractMPRISPlayer() {
class MPRISPlayer: AbstractMPRISPlayer(), MediaPlayer2, Player, DBus.Properties {

override var playbackStatus by DBusProperty(PlaybackStatus.Stopped)
override var loopStatus: LoopStatus = LoopStatus.None
override var rate by DBusProperty(1.0)
override var shuffle: Boolean = false
override val metadata by DBusMapProperty(PropertyMap {
put("mpris:trackid", "/playerfx/songs/untiltheend")
put("mpris:length", 10_000000)
put("mpris:artUrl", "file:///home/janek/daten/musik/Monstercat/Aero Chord - Love & Hate EP/cover.jpeg")
override val metadata by DBusMapProperty(String::class, Variant::class, PropertyMap {
put("mpris:trackid", "/playerfx/songs/monstercat")
put("mpris:length", 10_000_000)
put("mpris:artUrl", cover)
put("xesam:artist", arrayOf("Aero Chord", "Fractal"))
put("xesam:title", "Until The End (feat. Q'AILA)")
put("xesam:title", "Monstercat")
})
override var volume: Double
get() = TODO("not implemented")
Expand Down Expand Up @@ -71,7 +77,6 @@ class MPRISPlayer: AbstractMPRISPlayer() {

init {
println("Constructing MPRISPlayer")
/*
properties["org.mpris.MediaPlayer2"] = PropertyMap {
put("CanSeek", true)
put("CanQuit", true)
Expand All @@ -82,7 +87,7 @@ class MPRISPlayer: AbstractMPRISPlayer() {
put("SupportedMimeTypes", arrayOf("audio/mpeg", "audio/mp4"))
//put("DesktopEntry")
}
properties["org.mpris.MediaPlayer2.Player"] = PropertyMap {
put("PlaybackStatus", PlaybackStatus.Stopped)
put("LoopStatus", LoopStatus.None)
Expand All @@ -100,7 +105,6 @@ class MPRISPlayer: AbstractMPRISPlayer() {
put("CanSeek", true)
put("CanControl", true)
}
*/
println("MPRISPlayer constructed")

}
Expand All @@ -119,8 +123,10 @@ class MPRISPlayer: AbstractMPRISPlayer() {
}

fun updateStatus(status: PlaybackStatus) {
println(status)
playbackStatus = status
updateProperty("org.mpris.MediaPlayer2.Player", "PlaybackStatus", status.name)
println(status)
updateProperty("org.mpris.MediaPlayer2.$playerName", "PlaybackStatus", status.name)
}

override fun PlayPause() {
Expand Down

0 comments on commit 1736c84

Please sign in to comment.