From baedea15bd92b3442c710fa6ca798f05ad2ecd18 Mon Sep 17 00:00:00 2001 From: Xerus <27jf@web.de> Date: Tue, 13 Feb 2018 21:04:05 +0100 Subject: [PATCH] First Strike --- .gitignore | 5 + README.md | 10 ++ build.gradle.kts | 30 ++++ settings.gradle | 1 + src/main/org/mpris/MediaPlayer2/Player.kt | 43 +++++ src/main/org/mpris/MediaPlayer2/Playlists.kt | 30 ++++ src/main/org/mpris/MediaPlayer2/TrackList.kt | 31 ++++ src/main/xerus/mpris/MPRISPlayer.kt | 158 +++++++++++++++++++ src/main/xerus/mpris/Properties.kt | 19 +++ 9 files changed, 327 insertions(+) create mode 100644 .gitignore create mode 100644 README.md create mode 100644 build.gradle.kts create mode 100644 settings.gradle create mode 100644 src/main/org/mpris/MediaPlayer2/Player.kt create mode 100644 src/main/org/mpris/MediaPlayer2/Playlists.kt create mode 100644 src/main/org/mpris/MediaPlayer2/TrackList.kt create mode 100644 src/main/xerus/mpris/MPRISPlayer.kt create mode 100644 src/main/xerus/mpris/Properties.kt diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..65fcedf --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +.gradle +/build/ + +.idea +/out/ diff --git a/README.md b/README.md new file mode 100644 index 0000000..b185f02 --- /dev/null +++ b/README.md @@ -0,0 +1,10 @@ +# mpris-java +Simple MPRIS 2 implementation with Kotlin for Java + +This is not done, but usable. No guarantees whatsoever. + +Look into xerus.mpris.MPRISPlayer for a test implementation. + +### Todo +- TrackList +- better Properties integration diff --git a/build.gradle.kts b/build.gradle.kts new file mode 100644 index 0000000..9a35470 --- /dev/null +++ b/build.gradle.kts @@ -0,0 +1,30 @@ +import org.jetbrains.kotlin.gradle.tasks.KotlinCompile + +plugins { + application + kotlin("jvm") version "1.1.51" +} + +java.sourceSets["main"].java.srcDir("src/main") +java.sourceSets["test"].java.srcDir("src/test") + +group = "xerus.mpris" + +application { + mainClassName = "xerus.mpris.MPRISPlayerKt" +} + +repositories { + jcenter() +} + +dependencies { + compile("com.github.hypfvieh", "dbus-java", "2.7.4") + compile(kotlin("runtime")) +} + +tasks { + withType { + kotlinOptions.jvmTarget = "1.8" + } +} diff --git a/settings.gradle b/settings.gradle new file mode 100644 index 0000000..2996622 --- /dev/null +++ b/settings.gradle @@ -0,0 +1 @@ +rootProject.name = "mpris-java" \ No newline at end of file diff --git a/src/main/org/mpris/MediaPlayer2/Player.kt b/src/main/org/mpris/MediaPlayer2/Player.kt new file mode 100644 index 0000000..b3046c1 --- /dev/null +++ b/src/main/org/mpris/MediaPlayer2/Player.kt @@ -0,0 +1,43 @@ +@file: Suppress("UNUSED") +package org.mpris.MediaPlayer2 + +import org.freedesktop.DBus +import org.freedesktop.dbus.DBusInterface +import org.freedesktop.dbus.DBusInterfaceName +import org.freedesktop.dbus.DBusSignal +import org.freedesktop.dbus.Variant + +interface DefaultDBus: DBus.Properties { + override fun isRemote() = false + @Suppress("UNCHECKED_CAST") + override fun Get(interface_name: String, property_name: String): A { + return GetAll(interface_name)[property_name]?.value as A + } + override fun Set(interface_name: String, property_name: String, value: A) { + GetAll(interface_name).put(property_name, Variant(value)) + } +} + +@DBusInterfaceName("org.mpris.MediaPlayer2") +interface MediaPlayer2: DBusInterface { + fun Raise() + fun Quit() +} + +@DBusInterfaceName("org.mpris.MediaPlayer2.Player") +interface Player: DBusInterface { + class Seeked(path: String, val position: Long): DBusSignal(path, position) + + fun Next() + fun Previous() + fun Pause() + fun PlayPause() + fun Play() + fun Stop() + fun Seek(x: Long) + fun OpenUri(uri: String) +} + +enum class PlaybackStatus { + Playing, Paused, Stopped +} \ No newline at end of file diff --git a/src/main/org/mpris/MediaPlayer2/Playlists.kt b/src/main/org/mpris/MediaPlayer2/Playlists.kt new file mode 100644 index 0000000..56cc446 --- /dev/null +++ b/src/main/org/mpris/MediaPlayer2/Playlists.kt @@ -0,0 +1,30 @@ +@file: Suppress("UNUSED") +package org.mpris.MediaPlayer2 + +import org.freedesktop.dbus.DBusInterface +import org.freedesktop.dbus.DBusInterfaceName +import org.freedesktop.dbus.DBusSignal +import org.freedesktop.dbus.Position +import org.freedesktop.dbus.Struct +import org.freedesktop.dbus.UInt32 + +@DBusInterfaceName("org.mpris.MediaPlayer2.Playlists") +interface Playlists: DBusInterface { + class PlaylistChanged(path: String, val playlist: Struct2): DBusSignal(path, playlist) + fun ActivatePlaylist(playlist_id: DBusInterface) + fun GetPlaylists(index: UInt32, max_count: UInt32, order: String, reverse_order: Boolean): List +} + +class Playlist(@field:Position(0) + val a: DBusInterface, + @field:Position(1) + val b: String, + @field:Position(2) + val c: String): Struct() + +class Struct2(@field:Position(0) + val a: DBusInterface, + @field:Position(1) + val b: String, + @field:Position(2) + val c: String): Struct() diff --git a/src/main/org/mpris/MediaPlayer2/TrackList.kt b/src/main/org/mpris/MediaPlayer2/TrackList.kt new file mode 100644 index 0000000..b03a7d3 --- /dev/null +++ b/src/main/org/mpris/MediaPlayer2/TrackList.kt @@ -0,0 +1,31 @@ +@file: Suppress("UNUSED") +package org.mpris.MediaPlayer2 + +import org.freedesktop.dbus.DBusInterface +import org.freedesktop.dbus.DBusSignal +import org.freedesktop.dbus.Variant + +interface TrackList: DBusInterface { + + class TrackListReplacedr(path: String, val Tracks: List, + val CurrentTrack: DBusInterface): DBusSignal(path, Tracks, CurrentTrack) + + class TrackAdded(path: String, val Metadata: Map>, + val AfterTrack: DBusInterface): DBusSignal(path, Metadata, AfterTrack) + + class TrackRemoved(path: String, val TrackId: DBusInterface): DBusSignal(path, TrackId) + + class TrackMetadataChanged(path: String, val TrackId: DBusInterface, + val Metadata: Map>): DBusSignal(path, TrackId, Metadata) + + fun GetTracksMetadata( + TrackIds: List): List>> + + fun AddTrack(Uri: String, AfterTrack: DBusInterface, + SetAsCurrent: Boolean) + + fun RemoveTrack(TrackId: DBusInterface) + + fun GoTo(TrackId: DBusInterface) + +} \ No newline at end of file diff --git a/src/main/xerus/mpris/MPRISPlayer.kt b/src/main/xerus/mpris/MPRISPlayer.kt new file mode 100644 index 0000000..a7a1e88 --- /dev/null +++ b/src/main/xerus/mpris/MPRISPlayer.kt @@ -0,0 +1,158 @@ +package xerus.mpris + +import org.freedesktop.DBus.Properties.PropertiesChanged +import org.freedesktop.dbus.DBusConnection +import org.freedesktop.dbus.Variant +import org.freedesktop.dbus.types.DBusMapType +import org.mpris.MediaPlayer2.DefaultDBus +import org.mpris.MediaPlayer2.MediaPlayer2 +import org.mpris.MediaPlayer2.PlaybackStatus +import org.mpris.MediaPlayer2.Player +import java.util.Collections +import java.util.HashMap + +val conn = DBusConnection.getConnection(DBusConnection.SESSION) + +fun println(obj: String) = System.out.println(obj) +operator fun HashMap.set(k: K, value: V) = put(k, value) + +fun main(args: Array) { + println("Connecting to DBus") + conn.requestBusName("org.mpris.MediaPlayer2.PlayerFX") + conn.exportObject("/org/mpris/MediaPlayer2", MPRISPlayer()) +} + +class MPRISPlayer : MediaPlayer2, Player, DefaultDBus { + + val properties = HashMap() + + var status = PlaybackStatus.Stopped + + init { + println("Constructing MPRISPlayer") + // MediaPlayer2 + properties["org.mpris.MediaPlayer2"] = PropertyMap { + put("CanSeek", true) + put("CanSeek", true) + put("CanQuit", true) + put("CanRaise", false) + put("HasTrackList", true) + put("Identity", "PlayerFX") + put("SupportedUriSchemes", arrayOf("file")) + put("SupportedMimeTypes", arrayOf("audio/mpeg", "audio/mp4")) + //properties["DesktopEntry"] + } + + // Player + properties["org.mpris.MediaPlayer2.Player"] = PropertyMap { + put("PlaybackStatus", status) + put("LoopStatus", "None") + put("Rate", 1.0) + put("Shuffle", false) + put("Metadata", Variant( + PropertyMap { + put("mpris:trackid", "/song/1") + put("mpris:length", 10_000000) + put("mpris:artUrl", "file:///home/janek/Daten/Musik/Monstercat/Aero Chord - Love & Hate EP/cover.jpeg") + put("xesam:artist", arrayOf("Aero Chord", "Fractal")) + put("xesam:title", "Until The End (feat. Q'AILA)") + }, + DBusMapType(Class.forName("java.lang.String"), Class.forName("org.freedesktop.dbus.Variant")))) + put("Volume", 1.0) + put("Position", 0) + put("MinimumRate", 1.0) + put("MaximumRate", 1.0) + put("CanGoNext", true) + put("CanGoPrevious", true) + put("CanPlay", true) + put("CanPause", true) + put("CanSeek", true) + put("CanControl", true) + } + + // Playlist + /*properties["PlaylistCount"] = Variant() + // u Read only + properties["Orderings"] = Variant() + // as (Playlist_Ordering_List) Read only + properties["ActivePlaylist"] = Variant() + // (b(oss)) (Maybe_Playlist) Read only + */ + + // TrackList + /*properties["Tracks"] = Variant() + // ao (Track_Id_List) Read only + properties["CanEditTracks"] = Variant() + // b Read only*/ + println("MPRISPlayer constructed") + } + + override fun GetAll(interface_name: String): Map> = properties[interface_name]!! + + fun updateProperty(interface_name: String, name: String, value: A) { + println("Updating $name of $interface_name to $value") + val new = Variant(value) + properties[interface_name]!![name] = new + try { + conn.sendSignal(PropertiesChanged("/org/mpris/MediaPlayer2", interface_name, Collections.singletonMap(name, new) as Map>, Collections.emptyList())) + } catch (e: Throwable) { + e.printStackTrace() + } + } + + fun updateStatus(status: PlaybackStatus) { + this.status = status + updateProperty("org.mpris.MediaPlayer2.Player", "PlaybackStatus", status.name) + } + + override fun PlayPause() { + println("PlayPause called") + if(status == PlaybackStatus.Playing) + updateStatus(PlaybackStatus.Paused) + else + updateStatus(PlaybackStatus.Playing) + } + + override fun Play() { + println("Play called") + updateStatus(PlaybackStatus.Playing) + } + + override fun Pause() { + println("Pause called") + updateStatus(PlaybackStatus.Paused) + } + + override fun Stop() { + println("Stop called") + updateStatus(PlaybackStatus.Stopped) + } + + override fun Seek(x: Long) { + println("Seeking by $x") + } + + override fun Previous() { + println("Previous called") + } + + override fun Next() { + println("Next called") + } + + override fun Raise() { + println("open me!") + } + + override fun Quit() { + println("I'm quitting!") + conn.disconnect() + } + + override fun OpenUri(uri: String) { + println("OpenUri called") + } + + override fun getObjectPath() = "/org/mpris/MediaPlayer2" + +} diff --git a/src/main/xerus/mpris/Properties.kt b/src/main/xerus/mpris/Properties.kt new file mode 100644 index 0000000..b6aab05 --- /dev/null +++ b/src/main/xerus/mpris/Properties.kt @@ -0,0 +1,19 @@ +package xerus.mpris + +import org.freedesktop.dbus.Variant +import java.util.HashMap + +class PropertyMap(initial: PropertyMap.() -> Unit): HashMap>() { + + init { + initial(this) + } + + fun put(key: String, value: Any) { + var v = value + if(value is Enum<*>) + v = value.name + super.put(key, Variant(v)) + } + +}