diff --git a/blueman/bluez/AVRemote.py b/blueman/bluez/AVRemote.py
new file mode 100644
index 000000000..655476400
--- /dev/null
+++ b/blueman/bluez/AVRemote.py
@@ -0,0 +1,40 @@
+# coding=utf-8
+from __future__ import print_function
+from __future__ import division
+from __future__ import absolute_import
+from __future__ import unicode_literals
+
+from blueman.bluez.PropertiesBase import PropertiesBase
+from gi.repository import GObject
+
+
+class AVRemote(PropertiesBase):
+ __gsignals__ = {
+ str('status-changed'): (GObject.SignalFlags.NO_HOOKS, None, (GObject.TYPE_PYOBJECT,))
+ }
+
+ _interface_name = 'org.bluez.MediaPlayer1'
+
+ def _init(self, obj_path):
+ super(AVRemote, self)._init(self._interface_name, obj_path)
+
+ def play(self):
+ self._call('Play')
+
+ def pause(self):
+ self._call('Pause')
+
+ def stop(self):
+ self._call('Stop')
+
+ def next(self):
+ self._call('Next')
+
+ def previous(self):
+ self._call('Previous')
+
+ def fast_forward(self):
+ self._call('FastForward')
+
+ def rewind(self):
+ self._call('Rewind')
diff --git a/blueman/bluez/Makefile.am b/blueman/bluez/Makefile.am
index bade75583..733addb4c 100644
--- a/blueman/bluez/Makefile.am
+++ b/blueman/bluez/Makefile.am
@@ -6,6 +6,7 @@ blueman_PYTHON = \
Adapter.py \
Agent.py \
AgentManager.py \
+ AVRemote.py \
Base.py \
Device.py \
errors.py \
diff --git a/blueman/plugins/applet/AVRemote.py b/blueman/plugins/applet/AVRemote.py
new file mode 100644
index 000000000..f230d4223
--- /dev/null
+++ b/blueman/plugins/applet/AVRemote.py
@@ -0,0 +1,237 @@
+# coding=utf-8
+from __future__ import print_function
+from __future__ import division
+from __future__ import absolute_import
+from __future__ import unicode_literals
+
+from blueman.Functions import *
+from blueman.Constants import *
+from blueman.Sdp import uuid128_to_uuid16, AV_REMOTE_TARGET_SVCLASS_ID
+from blueman.plugins.AppletPlugin import AppletPlugin
+from blueman.bluez.AVRemote import AVRemote
+from gi.repository import GLib
+import blueman.bluez as Bluez
+
+import gi
+gi.require_version("Gtk", "3.0")
+from gi.repository import Gtk, Pango, GLib
+import gettext
+import os
+import time
+from locale import bind_textdomain_codeset
+import cgi
+
+
+class PlayerWindow(Gtk.Window):
+ running = False
+
+ def __init__(self):
+ if not PlayerWindow.running:
+ PlayerWindow.running = True
+ else:
+ return
+
+ super(PlayerWindow, self).__init__(title="MediaPlayer")
+
+ builder = Gtk.Builder()
+ builder.add_from_file(UI_PATH + "/media-player.ui")
+ builder.set_translation_domain("blueman")
+ bind_textdomain_codeset("blueman", "UTF-8")
+
+ self.add(builder.get_object("media_player"))
+
+ self._control_buttons = {}
+ self._control_buttons['play'] = builder.get_object("media_play")
+ self._control_buttons['pause'] = builder.get_object("media_pause")
+ self._control_buttons['stop'] = builder.get_object("media_stop")
+ self._control_buttons['next'] = builder.get_object("media_next")
+ self._control_buttons['prev'] = builder.get_object("media_previous")
+
+ self._control_buttons['play'].connect("clicked", self._on_media_button_clicked, "play")
+ self._control_buttons['pause'].connect("clicked", self._on_media_button_clicked, "pause")
+ self._control_buttons['stop'].connect("clicked", self._on_media_button_clicked, "stop")
+ self._control_buttons['next'].connect("clicked", self._on_media_button_clicked, "next")
+ self._control_buttons['prev'].connect("clicked", self._on_media_button_clicked, "prev")
+
+ self._artist = builder.get_object("media_artist")
+ self._track = builder.get_object("media_track")
+ self._time = builder.get_object("media_time")
+
+ self.set_icon(get_icon("multimedia-player"))
+ self.show()
+ self.connect("delete-event", self._on_window_delete)
+
+ cr_name_address = builder.get_object("name_address")
+ cr_name_address.props.ellipsize = Pango.EllipsizeMode.END
+
+ cr_connected = builder.get_object("connected")
+ cr_connected.props.sensitive = False
+ cr_connected.props.style = Pango.Style.ITALIC
+
+ self.liststore = Gtk.ListStore(str, str, object)
+ self.device_combo = builder.get_object("device_combo")
+ self.device_combo.set_model(self.liststore)
+ self.device_combo.connect("changed", self._on_device_combo_changed)
+
+ self._manager = Bluez.Manager()
+
+ self._remote_control_devices = {}
+ self.active_remote = None
+
+ for adapter in self._manager.list_adapters():
+ for device in adapter.list_devices():
+ if self._has_remote_control(device['UUIDs']):
+ object_path = device.get_object_path()
+ player_path = os.path.join(object_path, 'player0')
+ remote_control = AVRemote(player_path)
+ sig = remote_control.connect_signal("property-changed", self._on_property_changed)
+ self._remote_control_devices[object_path] = (remote_control, sig)
+
+ self.add_to_list(device)
+ dprint("Added device: ", player_path)
+
+ GLib.timeout_add_seconds(1, self._update_time)
+
+ def add_to_list(self, device):
+ dev_info = "%s\n%s" % (device['Alias'], device['Address'])
+ if device['Connected']:
+ conn_info = "Connected"
+ else:
+ conn_info = "Not Connected"
+
+ titer = self.liststore.append([dev_info, conn_info, device.get_object_path()])
+
+ def _has_remote_control(self, uuids):
+ for uuid in uuids:
+ uuid16 = uuid128_to_uuid16(uuid)
+ if uuid16 == AV_REMOTE_TARGET_SVCLASS_ID:
+ return True
+ return False
+
+ def update_track_info(self, remote):
+ self._update_artist()
+ self._update_time()
+ self._update_track()
+
+ def _update_time(self):
+ if self.active_remote is None:
+ return True
+
+ pos = self.active_remote['Position']
+ track_lenght = float(self.active_remote['Track']['Duration'])
+ time_left = (track_lenght - pos) / 1000
+ left_string = time.strftime('%M:%S', time.gmtime(time_left))
+ self._time.set_markup("%s" % left_string)
+
+ return True
+
+ def _update_artist(self):
+ artist = self.active_remote['Track']['Artist']
+ artist_markup = "%s" % cgi.escape(artist)
+ self._artist.set_markup(artist_markup)
+
+ def _update_track(self):
+ track = self.active_remote['Track']['Title']
+ self._track.set_markup(cgi.escape(track))
+
+ def _on_device_combo_changed(self, combo):
+ titer = combo.get_active_iter()
+ object_path = self.liststore[titer][2]
+ self.active_remote = self._remote_control_devices[object_path][0]
+ self.update_track_info(self.active_remote)
+ dprint(object_path)
+
+ def _on_media_button_clicked(self, button, action):
+ dprint(action)
+ if self.active_remote is None:
+ return
+
+ if action == "play":
+ self.active_remote.play()
+ elif action == "pause":
+ self.active_remote.pause()
+ elif action == "next":
+ self.active_remote.next()
+ elif action == "prev":
+ self.active_remote.previous()
+ elif action == "stop":
+ self.active_remote.stop()
+
+ def _on_window_delete(self, win, event):
+ for path in list(self._remote_control_devices.keys()):
+ remote, sig = self._remote_control_devices.pop(path)
+ remote.disconnect_signal(sig)
+
+ self.active_remote = None
+ self._remote_control_devices = {}
+ PlayerWindow.running = False
+ self.destroy()
+
+ def _update_track_times(self, track_lenght, time_left):
+ position = self.active_remote['Position']
+ self.time_label.set_markup("%s")
+
+ def _on_device_created(self, object_path):
+ dprint(object_path)
+
+ def _on_device_removed(self, object_path):
+ dprint(object_path)
+
+ def _on_property_changed(self, remote, name, val, path):
+ if not path == self.active_remote.get_object_path():
+ return
+
+ dprint(name, val)
+ if name == "Track":
+ self._update_artist()
+ self._update_track()
+ elif name == "Position":
+ self._update_time()
+ elif name == "Status":
+ if val == "stopped":
+ self._control_buttons['play'].props.sensitive = True
+ self._control_buttons['stop'].props.sensitive = False
+ self._control_buttons['pause'].props.sensitive = False
+ elif val == "playing":
+ self._control_buttons['play'].props.sensitive = False
+ self._control_buttons['stop'].props.sensitive = True
+ self._control_buttons['pause'].props.sensitive = True
+ elif val == "paused":
+ self._control_buttons['play'].props.sensitive = True
+ self._control_buttons['stop'].props.sensitive = False
+ self._control_buttons['pause'].props.sensitive = False
+
+
+class RemoteMediaPlayer(AppletPlugin):
+ __depends__ = ["Menu"]
+ __icon__ = "multimedia-player"
+ __description__ = _(
+ "Allows you to control remote A/V media devices.")
+ __author__ = "Sander Sweers (infirit)"
+ __autoload__ = False
+
+ def on_load(self, applet):
+ item = create_menuitem(_("Media _Player"), get_icon("multimedia-player", 16))
+ item.props.tooltip_text = _("Show a media player remote control")
+ item.connect("activate", self.activate_ui)
+
+ self.Applet.Plugins.Menu.Register(self, item, 85, True)
+
+ self.player_window = None
+
+ def on_unload(self):
+ self.Applet.Plugins.Menu.Unregister(self)
+ if self.player_window is not None:
+ self.player_window.destroy()
+
+ def activate_ui(self, item):
+ if not self.player_window:
+ self.player_window = PlayerWindow()
+ elif self.player_window.running:
+ self.player_window.present()
+
+ def on_device_created(self, device):
+ self.player_window._on_device_created(device)
+
+ def on_device_removed(self, device):
+ self.player_window._on_device_removed(device)
diff --git a/blueman/plugins/applet/Makefile.am b/blueman/plugins/applet/Makefile.am
index ef99f8edf..663a6dff9 100644
--- a/blueman/plugins/applet/Makefile.am
+++ b/blueman/plugins/applet/Makefile.am
@@ -3,6 +3,7 @@ bluemandir = $(pythondir)/blueman/plugins/applet
blueman_PYTHON = \
__init__.py \
AuthAgent.py \
+ AVRemote.py \
DBusService.py \
DhcpClient.py \
DiscvManager.py \
diff --git a/blueman/services/AVRemote.py b/blueman/services/AVRemote.py
new file mode 100644
index 000000000..1c4b6137e
--- /dev/null
+++ b/blueman/services/AVRemote.py
@@ -0,0 +1,16 @@
+# coding=utf-8
+from __future__ import print_function
+from __future__ import division
+from __future__ import absolute_import
+from __future__ import unicode_literals
+
+from blueman.Service import Service
+from blueman.Sdp import AV_REMOTE_TARGET_SVCLASS_ID
+
+
+class AudioSink(Service):
+ __group__ = 'audio'
+ __svclass_id__ = AV_REMOTE_TARGET_SVCLASS_ID
+ __description__ = _("Allows to control media player device")
+ __icon__ = "multimedia-player"
+ __priority__ = 90
diff --git a/blueman/services/Makefile.am b/blueman/services/Makefile.am
index 86f6d0622..d86b70c23 100644
--- a/blueman/services/Makefile.am
+++ b/blueman/services/Makefile.am
@@ -5,6 +5,7 @@ blueman_PYTHON = \
__init__.py \
AudioSink.py \
AudioSource.py \
+ AVRemote.py \
DialupNetwork.py \
Functions.py \
GroupNetwork.py \
diff --git a/data/ui/Makefile.am b/data/ui/Makefile.am
index e41f6326c..e97839414 100644
--- a/data/ui/Makefile.am
+++ b/data/ui/Makefile.am
@@ -4,6 +4,7 @@ ui_DATA = \
adapters-tab.ui \
applet-passkey.ui \
manager-main.ui \
+ media-player.ui \
device-list-widget.ui \
services-network.ui \
services-transfer.ui \
diff --git a/data/ui/media-player.ui b/data/ui/media-player.ui
new file mode 100644
index 000000000..420d5d5e2
--- /dev/null
+++ b/data/ui/media-player.ui
@@ -0,0 +1,164 @@
+
+
+
+
+
+
+
+
+
+
+