diff --git a/packages/addons/service/bluetooth-audio/changelog.txt b/packages/addons/service/bluetooth-audio/changelog.txt index 572ffaee81..60d77b770d 100644 --- a/packages/addons/service/bluetooth-audio/changelog.txt +++ b/packages/addons/service/bluetooth-audio/changelog.txt @@ -1,3 +1,6 @@ +102 +- Avoid LibreELEC Settings addon crash + 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..f555f692c9 --- /dev/null +++ b/packages/addons/service/bluetooth-audio/source/bin/dbusservice.py @@ -0,0 +1,110 @@ +#!/usr/bin/python2 +# SPDX-License-Identifier: GPL-2.0 +# Copyright (C) 2016-present Team LibreELEC (https://libreelec.tv) + +from __future__ import print_function +import sys +import dbus +import dbus.mainloop.glib +import gobject +import time + +gobject.threads_init() + +class BluetoothAudioClient(object): + + def __init__(self): + + self.devices = {} + self.signal_added = None + self.signal_removed = None + + self._setup_loop() + self._setup_bus() + self._setup_signals() + + def quit(self): + + self.signal_added.remove() + self.signal_removed.remove() + + self._loop.quit() + + def _setup_loop(self): + + self._loop = gobject.MainLoop() + + def run(self): + self._loop.run() + + 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): + print('bluetooth') + sys.stdout.flush() + + 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) + + for path in self.devices: + if self.devices[path]['Connected'] and self.devices[path]['Class'] & (1 << 21): + return + + print('default') + sys.stdout.flush() + + except (TypeError, KeyError, dbus.exceptions.DBusException) as e: + print('%s: ' % unicode(e), file=sys.stderr) + + +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 e6e876b2b9..2926002860 100644 --- a/packages/addons/service/bluetooth-audio/source/default.py +++ b/packages/addons/service/bluetooth-audio/source/default.py @@ -1,19 +1,15 @@ # 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 xbmcaddon __addon__ = xbmcaddon.Addon() __addonid__ = __addon__.getAddonInfo('id') - -gobject.threads_init() +__addonpath__ = xbmc.translatePath(xbmcaddon.Addon().getAddonInfo('path')).decode('utf-8') class KodiFunctions(object): @@ -63,103 +59,38 @@ class BluetoothAudioClient(object): xbmc.log('%s: starting add-on' % __addonid__, xbmc.LOGNOTICE) - 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 == '': + break + if line == 'bluetooth\n': + self.kodi.select_pulse() + continue + if line == 'default\n': + 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.LOGNOTICE) + self.service.terminate() + self._thread.join() 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.LOGNOTICE) - xbmc.log('%s: switching to bluetooth audio device' % __addonid__, xbmc.LOGNOTICE) - 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.LOGNOTICE) - xbmc.log('%s: checking for other connected devices' % __addonid__, xbmc.LOGNOTICE) - - 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.LOGNOTICE) - return - - xbmc.log('%s: switching to default audio device' % __addonid__, xbmc.LOGNOTICE) - self.kodi.select_default() - - except (TypeError, KeyError, dbus.exceptions.DBusException) as e: - xbmc.log('%s: ' % __addonid__ + unicode(e), xbmc.LOGERROR) class BluetoothMonitor(xbmc.Monitor):