diff --git a/packages/addons/service/bluetooth-audio/changelog.txt b/packages/addons/service/bluetooth-audio/changelog.txt index 572ffaee81..4653d548a3 100644 --- a/packages/addons/service/bluetooth-audio/changelog.txt +++ b/packages/addons/service/bluetooth-audio/changelog.txt @@ -1,3 +1,7 @@ +102 +- Python 3 support +- Using separate dbus process + 101 - Fix log errors diff --git a/packages/addons/service/bluetooth-audio/package.mk b/packages/addons/service/bluetooth-audio/package.mk index 34a15ce135..a536e89143 100644 --- a/packages/addons/service/bluetooth-audio/package.mk +++ b/packages/addons/service/bluetooth-audio/package.mk @@ -3,7 +3,7 @@ PKG_NAME="bluetooth-audio" PKG_VERSION="0" -PKG_REV="101" +PKG_REV="102" PKG_ARCH="any" PKG_LICENSE="GPL" PKG_SITE="" diff --git a/packages/addons/service/bluetooth-audio/source/bin/dbusservice.py b/packages/addons/service/bluetooth-audio/source/bin/dbusservice.py new file mode 100755 index 0000000000..3b9035fe47 --- /dev/null +++ b/packages/addons/service/bluetooth-audio/source/bin/dbusservice.py @@ -0,0 +1,74 @@ +#!/usr/bin/python3 +# SPDX-License-Identifier: GPL-2.0 +# Copyright (C) 2016-present Team LibreELEC (https://libreelec.tv) + +import sys +import time + +import asyncio +import ravel + +class BluetoothAudioClient(object): + + def __init__(self): + + self.devices = {} + self.signal_added = None + self.signal_removed = None + + self._setup_loop() + + def _setup_loop(self): + + self._loop = asyncio.new_event_loop() + + self.bus = ravel.system_bus() + self.bus.attach_asyncio(self._loop) + self.bus.listen_objects_added(self.object_added) + self.bus.listen_objects_removed(self.object_removed) + + def run(self): + self._loop.run_forever() + + @ravel.signal(name = "InterfacesAdded", in_signature = "oa{sa{sv}}", + arg_keys = ("device_path", "args")) + def object_added(self, device_path, args) : + if 'org.bluez.MediaTransport1' in args: + self.devices[device_path] = { + 'Connected': '', + 'Device': str(args['org.bluez.MediaTransport1']['Device'][1]), + 'Class': '', + } + + audio_device_iface = self.bus['org.bluez'][self.devices[device_path]['Device']].get_interface('org.bluez.Device1') + self.devices[device_path]['Class'] = audio_device_iface.Class + self.devices[device_path]['Connected'] = audio_device_iface.Connected + + if self.devices[device_path]['Class'] & (1 << 21): + print('bluetooth') + sys.stdout.flush() + + @ravel.signal(name = "InterfacesRemoved", in_signature = "oas", + arg_keys = ("device_path", "args")) + def object_removed(self, device_path, args) : + if device_path in self.devices and self.devices[device_path]['Class'] & (1 << 21): + audio_device_iface = self.bus['org.bluez'][self.devices[device_path]['Device']].get_interface('org.bluez.Device1') + self.devices[device_path]['Connected'] = audio_device_iface.Connected + + while self.devices[device_path]['Connected']: + self.devices[device_path]['Connected'] = audio_device_iface.Connected + time.sleep(0.1) + + for path in self.devices: + if self.devices[path]['Connected'] and self.devices[path]['Class'] & (1 << 21): + return + + print('default') + sys.stdout.flush() + + +client = BluetoothAudioClient() + +client.run() + +del BluetoothAudioClient diff --git a/packages/addons/service/bluetooth-audio/source/default.py b/packages/addons/service/bluetooth-audio/source/default.py index d99403b7b2..f45527591d 100644 --- a/packages/addons/service/bluetooth-audio/source/default.py +++ b/packages/addons/service/bluetooth-audio/source/default.py @@ -1,19 +1,16 @@ # SPDX-License-Identifier: GPL-2.0 # Copyright (C) 2016-present Team LibreELEC (https://libreelec.tv) -import dbus -import dbus.mainloop.glib -import gobject import json +import subprocess import threading -import time import xbmc +import xbmcvfs import xbmcaddon __addon__ = xbmcaddon.Addon() __addonid__ = __addon__.getAddonInfo('id') - -gobject.threads_init() +__addonpath__ = xbmcvfs.translatePath(xbmcaddon.Addon().getAddonInfo('path')) class KodiFunctions(object): @@ -45,6 +42,7 @@ class KodiFunctions(object): self.audiodevice = __addon__.getSetting('audiodevice') self.pulsedevice = 'PULSE:Default' + xbmc.log('%s: setting default audio device "%s" on start' % (__addonid__, self.audiodevice), xbmc.LOGINFO) self.select_default() def select_default(self): @@ -63,103 +61,41 @@ class BluetoothAudioClient(object): xbmc.log('%s: starting add-on' % __addonid__, xbmc.LOGINFO) - self.devices = {} - self.signal_added = None - self.signal_removed = None - self.kodi = KodiFunctions() + self.path = __addonpath__ + 'bin/dbusservice.py' + + self.service = subprocess.Popen([self.path], stdout=subprocess.PIPE) + + self._thread = threading.Thread(target=self.loop) + self._thread.start() + + + def loop(self): + + while True: + line = self.service.stdout.readline() + if line == b'': + break + if line == b'bluetooth\n': + xbmc.log('%s: switching to bluetooth audio device' % __addonid__, xbmc.LOGINFO) + self.kodi.select_pulse() + continue + if line == b'default\n': + xbmc.log('%s: switching to default audio device' % __addonid__, xbmc.LOGINFO) + self.kodi.select_default() + continue + xbmc.log('%s: unexpected input: %s' % (__addonid__, line), xbmc.LOGERROR) - self._setup_loop() - self._setup_bus() - self._setup_signals() def quit(self): xbmc.log('%s: stopping add-on' % __addonid__, xbmc.LOGINFO) + self.service.terminate() + self._thread.join() + del self.service self.kodi.select_default() - self.signal_added.remove() - self.signal_removed.remove() - - self._loop.quit() - - def _setup_loop(self): - - self._loop = gobject.MainLoop() - - self._thread = threading.Thread(target=self._loop.run) - self._thread.start() - - def _setup_bus(self): - - dbus.mainloop.glib.DBusGMainLoop(set_as_default=True) - self._bus = dbus.SystemBus() - - def _setup_signals(self): - - self.signal_added = self._bus.add_signal_receiver(handler_function=self.switch_audio, - signal_name='InterfacesAdded', - dbus_interface='org.freedesktop.DBus.ObjectManager', - bus_name='org.bluez', - member_keyword='signal') - - self.signal_removed = self._bus.add_signal_receiver(handler_function=self.switch_audio, - signal_name='InterfacesRemoved', - dbus_interface='org.freedesktop.DBus.ObjectManager', - bus_name='org.bluez', - member_keyword='signal') - - def switch_audio(self, *args, **kwargs): - - device_path = args[0] - - try: - if kwargs['signal'] == 'InterfacesAdded': - - self.devices[device_path] = { - 'Connected': '', - 'Device': '', - 'Class': '', - } - - device = self._bus.get_object('org.bluez', device_path) - device_iface = dbus.Interface(device, dbus.PROPERTIES_IFACE) - self.devices[device_path]['Device'] = device_iface.Get('org.bluez.MediaTransport1', 'Device') - - audio_device_path = self._bus.get_object('org.bluez', self.devices[device_path]['Device']) - audio_device_iface = dbus.Interface(audio_device_path, dbus.PROPERTIES_IFACE) - self.devices[device_path]['Class'] = audio_device_iface.Get('org.bluez.Device1', 'Class') - self.devices[device_path]['Connected'] = audio_device_iface.Get('org.bluez.Device1', 'Connected') - - if self.devices[device_path]['Class'] & (1 << 21): - xbmc.log('%s: bluetooth audio device connected' % __addonid__, xbmc.LOGINFO) - xbmc.log('%s: switching to bluetooth audio device' % __addonid__, xbmc.LOGINFO) - self.kodi.select_pulse() - - elif kwargs['signal'] == 'InterfacesRemoved': - if self.devices[device_path]['Device'] is not None and self.devices[device_path]['Class'] & (1 << 21): - audio_device_path = self._bus.get_object('org.bluez', self.devices[device_path]['Device']) - audio_device_iface = dbus.Interface(audio_device_path, dbus.PROPERTIES_IFACE) - self.devices[device_path]['Connected'] = audio_device_iface.Get('org.bluez.Device1', 'Connected') - - while self.devices[device_path]['Connected']: - self.devices[device_path]['Connected'] = audio_device_iface.Get('org.bluez.Device1', 'Connected') - time.sleep(0.1) - - xbmc.log('%s: bluetooth audio device disconnected' % __addonid__, xbmc.LOGINFO) - xbmc.log('%s: checking for other connected devices' % __addonid__, xbmc.LOGINFO) - - for path in self.devices: - if self.devices[path]['Connected'] and self.devices[path]['Class'] & (1 << 21): - xbmc.log('%s: found connected bluetooth audio device' % __addonid__, xbmc.LOGINFO) - return - - xbmc.log('%s: switching to default audio device' % __addonid__, xbmc.LOGINFO) - self.kodi.select_default() - - except (TypeError, KeyError, dbus.exceptions.DBusException) as e: - xbmc.log('%s: ' % __addonid__ + unicode(e), xbmc.LOGERROR) class BluetoothMonitor(xbmc.Monitor):