The branch, frodo has been updated
via 50a117274492d2b7e4dc4fa9156a0b482c6df508 (commit)
from 8dacbefee34295973c204f1369dca7ba6231e32b (commit)
- Log -----------------------------------------------------------------
http://xbmc.git.sourceforge.net/git/gitweb.cgi?p=xbmc/scripts;a=commit;h=50a117274492d2b7e4dc4fa9156a0b482c6df508
commit 50a117274492d2b7e4dc4fa9156a0b482c6df508
Author: Martijn Kaijser <[email protected]>
Date: Sun Aug 24 17:36:26 2014 +0200
[script.sonos] 1.0.10
diff --git a/script.sonos/addon.xml b/script.sonos/addon.xml
index e9e4479..544ee44 100644
--- a/script.sonos/addon.xml
+++ b/script.sonos/addon.xml
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
-<addon id="script.sonos" name="Sonos" version="1.0.9"
provider-name="robwebset">
+<addon id="script.sonos" name="Sonos" version="1.0.10"
provider-name="robwebset">
<requires>
<import addon="xbmc.python" version="2.1.0"/>
<import addon="script.module.requests" version="1.1.0"/>
@@ -15,7 +15,6 @@
<extension point="xbmc.python.pluginsource" library="plugin.py">
<provides>audio</provides>
</extension>
- <extension point="xbmc.python.module" library="lib" />
<extension point="xbmc.addon.metadata">
<summary lang="de">Sonos Controller</summary>
diff --git a/script.sonos/changelog.txt b/script.sonos/changelog.txt
index 6322651..feab768 100644
--- a/script.sonos/changelog.txt
+++ b/script.sonos/changelog.txt
@@ -1,3 +1,6 @@
+v1.0.10
+- Highlight which speakers are group coordinators
+
v1.0.9
- Add option to Switch Sonos To Line-In On Media Start
- Add check that Artist Slideshow has transparent image enabled
diff --git a/script.sonos/discovery.py b/script.sonos/discovery.py
index bf38ec6..ffc6ec8 100644
--- a/script.sonos/discovery.py
+++ b/script.sonos/discovery.py
@@ -73,7 +73,17 @@ if __name__ == '__main__':
displayName = "%s [%s]" % (ip, zone_name)
else:
log("SonosDiscovery: No zone for IP address %s" % ip)
- speakers[displayName] = (ip, zone_name)
+ # Record if this is the group coordinator, as when there are
several
+ # speakers in the group, we need to send messages to the group
+ # coordinator for things to work correctly
+ isCoordinator = device.is_coordinator
+ if isCoordinator:
+ log("SonosDiscovery: %s is the group coordinator" % ip)
+ displayName = "%s - %s" % (displayName,
__addon__.getLocalizedString(32031))
+ else:
+ log("SonosDiscovery: %s is not the group coordinator" % ip)
+
+ speakers[displayName] = (ip, zone_name, isCoordinator)
# Remove the busy dialog
xbmc.executebuiltin("Dialog.Close(busydialog)")
@@ -90,6 +100,14 @@ if __name__ == '__main__':
log("SonosDiscovery: Entry chosen = %s" % selectedDisplayName)
chosenIPAddress = speakers.get(selectedDisplayName)[0]
chosenZoneName = speakers.get(selectedDisplayName)[1]
+ chosenIsCoordinator = speakers.get(selectedDisplayName)[2]
+
+ # Warn the user if they have selected something that is not the
zone coordinator
+ if not chosenIsCoordinator:
+ xbmcgui.Dialog().ok(__addon__.getLocalizedString(32001),
+ "%s %s:" % (chosenIPAddress,
__addon__.getLocalizedString(32032)),
+ " \"%s\"" % chosenZoneName,
+ __addon__.getLocalizedString(32033))
# Set the selected item into the settings
Settings.setIPAddress(chosenIPAddress)
Settings.setZoneName(chosenZoneName)
diff --git a/script.sonos/resources/language/English/strings.po
b/script.sonos/resources/language/English/strings.po
index d827079..345063c 100644
--- a/script.sonos/resources/language/English/strings.po
+++ b/script.sonos/resources/language/English/strings.po
@@ -144,7 +144,19 @@ msgctxt "#32030"
msgid "Switch Sonos To Line-In On Media Start"
msgstr ""
-#empty strings from id 32031 to 32059
+msgctxt "#32031"
+msgid "Coordinator"
+msgstr ""
+
+msgctxt "#32032"
+msgid "is not the group coordinator for zone"
+msgstr ""
+
+msgctxt "#32033"
+msgid "Some functions may not work correctly if the speaker is not a
coordinator"
+msgstr ""
+
+#empty strings from id 32034 to 32059
msgctxt "#32060"
msgid "The following setting in ArtistSlideshow must be enabled"
diff --git a/script.sonos/resources/lib/mocksonos.py
b/script.sonos/resources/lib/mocksonos.py
index d5b1f8d..018dd46 100644
--- a/script.sonos/resources/lib/mocksonos.py
+++ b/script.sonos/resources/lib/mocksonos.py
@@ -1,103 +1,103 @@
-# -*- coding: utf-8 -*-
-import xbmc
-
-
-#########################################################################
-# Mock Sonos class to support testing when there is no live Sonos system
-#########################################################################
-class TestMockSonos():
- def __init__(self):
- self.currentPlayState = 'PLAYING'
- self.trackNumber = 100
- self.isMuted = False
- self.currentVolume = 50
- self.duration = "00:05:47"
- self.position = "00:02:25"
-
- def get_current_track_info(self, forcedTrackNum=None):
- displayTrackNum = self.trackNumber
- if forcedTrackNum is not None:
- displayTrackNum = forcedTrackNum
-
- # Test code to test the dialog without a Sonos Speaker connected
- track = {'title': "Money Money Money %d" % displayTrackNum,
- 'artist': "ABBA %d" % displayTrackNum,
- 'album': "Gold %d" % displayTrackNum,
- 'album_art': '',
- 'position': self.position,
- 'duration': self.duration,
- 'uri': "%d" % self.trackNumber,
- 'playlist_position': "%d" % self.trackNumber}
- return track
-
- def get_current_transport_info(self):
- playStatus = {'current_transport_state': self.currentPlayState}
- return playStatus
-
- def play(self):
- self.currentPlayState = 'PLAYING'
- self._displayOperation("Play")
-
- def pause(self):
- self.currentPlayState = 'PAUSED_PLAYBACK'
- self._displayOperation("Paused")
-
- def stop(self):
- self.currentPlayState = 'STOPPED'
- self._displayOperation("Stopped")
-
- def next(self):
- self.trackNumber = self.trackNumber + 1
- self._displayOperation("Next Track")
-
- def previous(self):
- self.trackNumber = self.trackNumber - 1
- self._displayOperation("Previous Track")
-
- @property
- def mute(self):
- return self.isMuted
-
- @mute.setter
- def mute(self, mute):
- if mute is True:
- self.isMuted = True
- self._displayOperation("Volume Muted")
- else:
- self.isMuted = False
- self._displayOperation("Volume Unmuted")
- return True
-
- def get_queue(self, start=0, max_items=100):
- queue = []
- for num in range(start, start + max_items):
- queue.append(self.get_current_track_info(num))
- return queue
-
- def get_tracks(self, start=0, max_items=100):
- # TODO: THis is not currently returning what the sonos system does
- out = {'item_list': []}
- for num in range(start, start + max_items):
-
out['item_list'].append(self.get_current_track_info(self.trackNumber + num + 1))
- return out
-
- @property
- def volume(self):
- return self.currentVolume
-
- @volume.setter
- def volume(self, newVolume):
- if newVolume is not None:
- self.currentVolume = int(newVolume)
- self._displayOperation("Volume set to %d" % newVolume)
- return self.currentVolume
-
- def seek(self, timestamp):
- self.position = timestamp
- self._displayOperation("Seek Time to %s" % timestamp)
-
- def _displayOperation(self, textStr):
- xbmc.executebuiltin('Notification("Test Mock Sonos", %s, %d)' %
(textStr, 3))
-
- def switch_to_line_in(self):
- xbmc.executebuiltin('Notification("Test Mock Sonos", "Switched to
Line-In", %d)' % 3)
+# -*- coding: utf-8 -*-
+import xbmc
+
+
+#########################################################################
+# Mock Sonos class to support testing when there is no live Sonos system
+#########################################################################
+class TestMockSonos():
+ def __init__(self):
+ self.currentPlayState = 'PLAYING'
+ self.trackNumber = 100
+ self.isMuted = False
+ self.currentVolume = 50
+ self.duration = "00:05:47"
+ self.position = "00:02:25"
+
+ def get_current_track_info(self, forcedTrackNum=None):
+ displayTrackNum = self.trackNumber
+ if forcedTrackNum is not None:
+ displayTrackNum = forcedTrackNum
+
+ # Test code to test the dialog without a Sonos Speaker connected
+ track = {'title': "Money Money Money %d" % displayTrackNum,
+ 'artist': "ABBA %d" % displayTrackNum,
+ 'album': "Gold %d" % displayTrackNum,
+ 'album_art': '',
+ 'position': self.position,
+ 'duration': self.duration,
+ 'uri': "%d" % self.trackNumber,
+ 'playlist_position': "%d" % self.trackNumber}
+ return track
+
+ def get_current_transport_info(self):
+ playStatus = {'current_transport_state': self.currentPlayState}
+ return playStatus
+
+ def play(self):
+ self.currentPlayState = 'PLAYING'
+ self._displayOperation("Play")
+
+ def pause(self):
+ self.currentPlayState = 'PAUSED_PLAYBACK'
+ self._displayOperation("Paused")
+
+ def stop(self):
+ self.currentPlayState = 'STOPPED'
+ self._displayOperation("Stopped")
+
+ def next(self):
+ self.trackNumber = self.trackNumber + 1
+ self._displayOperation("Next Track")
+
+ def previous(self):
+ self.trackNumber = self.trackNumber - 1
+ self._displayOperation("Previous Track")
+
+ @property
+ def mute(self):
+ return self.isMuted
+
+ @mute.setter
+ def mute(self, mute):
+ if mute is True:
+ self.isMuted = True
+ self._displayOperation("Volume Muted")
+ else:
+ self.isMuted = False
+ self._displayOperation("Volume Unmuted")
+ return True
+
+ def get_queue(self, start=0, max_items=100):
+ queue = []
+ for num in range(start, start + max_items):
+ queue.append(self.get_current_track_info(num))
+ return queue
+
+ def get_tracks(self, start=0, max_items=100):
+ # TODO: THis is not currently returning what the sonos system does
+ out = {'item_list': []}
+ for num in range(start, start + max_items):
+
out['item_list'].append(self.get_current_track_info(self.trackNumber + num + 1))
+ return out
+
+ @property
+ def volume(self):
+ return self.currentVolume
+
+ @volume.setter
+ def volume(self, newVolume):
+ if newVolume is not None:
+ self.currentVolume = int(newVolume)
+ self._displayOperation("Volume set to %d" % newVolume)
+ return self.currentVolume
+
+ def seek(self, timestamp):
+ self.position = timestamp
+ self._displayOperation("Seek Time to %s" % timestamp)
+
+ def _displayOperation(self, textStr):
+ xbmc.executebuiltin('Notification("Test Mock Sonos", %s, %d)' %
(textStr, 3))
+
+ def switch_to_line_in(self):
+ xbmc.executebuiltin('Notification("Test Mock Sonos", "Switched to
Line-In", %d)' % 3)
diff --git a/script.sonos/resources/lib/soco/event_structures.py
b/script.sonos/resources/lib/soco/event_structures.py
index 6a4f8d4..345895e 100644
--- a/script.sonos/resources/lib/soco/event_structures.py
+++ b/script.sonos/resources/lib/soco/event_structures.py
@@ -1,315 +1,315 @@
-# -*- coding: utf-8 -*-
-
-""" This module contains all the event structures for the events that
-can be returned
-
-"""
-from __future__ import unicode_literals
-
-import logging
-from .xml import XML
-from .utils import really_utf8
-
-log = logging.getLogger(__name__) # pylint: disable=C0103
-
-
-# pylint: disable=too-many-public-methods,too-many-statements
-class LastChangeEvent(object):
- """
- Class to handle the Last Change event XML
- """
-
- def __init__(self, contents):
- """ Initialize the class with the contents of the event
-
- :param contents: Dictionary of the data
- """
- self.content = contents
-
- # pylint: disable=bare-except,broad-except,too-many-locals
- @classmethod
- def from_xml(cls, xmlstr):
- """Return an instance of this class, created from xml.
-
- :param xmlStr: The xml in string form to create the class from
- """
- try:
- # Need to handle character encoding
- xmlstr = really_utf8(xmlstr)
- # Read in the XML
- last_change_xml = XML.fromstring(xmlstr)
- except Exception as exc:
- # Not valid XML
- log.exception(xmlstr)
- log.exception(str(exc))
- return None
-
- # All the namespaces used in the event XML
- avtns = '{urn:schemas-upnp-org:metadata-1-0/AVT/}'
- didlns = '{urn:schemas-upnp-org:metadata-1-0/DIDL-Lite/}'
- rns = '{urn:schemas-rinconnetworks-com:metadata-1-0/}'
- upnpns = '{urn:schemas-upnp-org:metadata-1-0/upnp/}'
- nsdc = '{http://purl.org/dc/elements/1.1/}'
-
- instanceid = last_change_xml.find('{0}InstanceID'.format(avtns))
- if instanceid is None:
- log.debug("No InstanceID found %s", xmlstr)
- return None
-
- result = {}
- result['transportState'] = LastChangeEvent._get_val_data(
- avtns, instanceid, 'TransportState')
- result['currentPlayMode'] = LastChangeEvent._get_val_data(
- avtns, instanceid, 'CurrentPlayMode')
- result['currentCrossfadeMode'] = LastChangeEvent._get_val_data(
- avtns, instanceid, 'CurrentCrossfadeMode')
- result['numberOfTracks'] = LastChangeEvent._get_val_data(
- avtns, instanceid, 'NumberOfTracks')
- result['currentTrack'] = LastChangeEvent._get_val_data(
- avtns, instanceid, 'CurrentTrack')
- result['currentSection'] = LastChangeEvent._get_val_data(
- avtns, instanceid, 'CurrentSection')
- result['currentTrackURI'] = LastChangeEvent._get_val_data(
- avtns, instanceid, 'CurrentTrackURI')
- result['currentTrackDuration'] = LastChangeEvent._get_val_data(
- avtns, instanceid, 'CurrentTrackDuration')
-
- # The current track meta data is embedded XML
- current_track_md = LastChangeEvent._get_val_data(
- avtns, instanceid, 'CurrentTrackMetaData')
- if (current_track_md is not None) and (current_track_md != ""):
- log.debug("CurrentTrackMetaData: %s", current_track_md)
- try:
- current_track_md = current_track_md.encode('utf-8')
- ctrack_metadata_xml = XML.fromstring(current_track_md)
- except Exception as exc:
- # Not valid XML
- log.exception(current_track_md)
- log.exception(str(exc))
- return None
-
- item = ctrack_metadata_xml.find('{0}item'.format(didlns))
-
- if item is not None:
- result['title'] = LastChangeEvent._get_element_data(
- nsdc, item, 'title')
- result['creator'] = LastChangeEvent._get_element_data(
- nsdc, item, 'creator')
- result['album'] = LastChangeEvent._get_element_data(
- upnpns, item, 'album')
- result['originalTrackNumber'] =\
- LastChangeEvent._get_element_data(
- upnpns, item, 'originalTrackNumber')
- result['albumArtist'] = LastChangeEvent._get_element_data(
- rns, item, 'albumArtist')
- result['albumArtURI'] = LastChangeEvent._get_element_data(
- upnpns, item, 'albumArtURI')
- result['radioShowMd'] = LastChangeEvent._get_element_data(
- rns, item, 'radioShowMd')
-
- result['nextTrackURI'] = LastChangeEvent._get_val_data(
- rns, instanceid, 'NextTrackURI')
-
- # The next track meta data is embedded XML
- next_track_metadata = LastChangeEvent._get_val_data(
- rns, instanceid, 'NextTrackMetaData')
- if (next_track_metadata is not None) and (next_track_metadata != ""):
- log.debug("NextTrackMetaData: %s", next_track_metadata)
- try:
- next_track_metadata = next_track_metadata.encode('utf-8')
- next_track_metadata_xml = XML.fromstring(next_track_metadata)
- except Exception as exc:
- # Not valid XML
- log.exception(next_track_metadata)
- log.exception(str(exc))
- return None
-
- item = next_track_metadata_xml.find('{0}item'.format(didlns))
-
- if item is not None:
- result['nextTitle'] = LastChangeEvent._get_element_data(
- nsdc, item, 'title')
- result['nextCreator'] = LastChangeEvent._get_element_data(
- nsdc, item, 'creator')
- result['nextAlbum'] = LastChangeEvent._get_element_data(
- upnpns, item, 'album')
- result['nextOriginalTrackNumber'] =\
- LastChangeEvent._get_element_data(
- upnpns, item, 'originalTrackNumber')
- result['nextAlbumArtist'] = LastChangeEvent._get_element_data(
- rns, item, 'albumArtist')
- result['nextAlbumArtURI'] = LastChangeEvent._get_element_data(
- upnpns, item, 'albumArtURI')
-
- # The transport meta data is embedded XML
- transportmetadata = LastChangeEvent._get_val_data(
- rns, instanceid, 'EnqueuedTransportURIMetaData')
- if (transportmetadata is not None) and (transportmetadata != ""):
- log.debug("EnqueuedTransportURIMetaData: %s", transportmetadata)
- try:
- transportmetadata = transportmetadata.encode('utf-8')
- transport_xml = XML.fromstring(transportmetadata)
- except Exception as exc:
- # Not valid XML
- log.exception(transportmetadata)
- log.exception(str(exc))
- return None
-
- item = transport_xml.find('{0}item'.format(didlns))
-
- if item is not None:
- result['transportTitle'] = LastChangeEvent._get_element_data(
- nsdc, item, 'title')
-
- return cls(result)
-
- @staticmethod
- def _get_val_data(namesp, container, elem_name):
- """Returns the string from the val attribute
-
- :param ns: Namespace the element is in
- :param container: Parent object the element is in
- :param elem_name: Name of the to get the attribute of
- """
- value = None
- if container is not None:
- element = container.find('{0}{1}'.format(namesp, elem_name))
- if element is not None:
- value = element.get('val')
- return value
-
- @staticmethod
- def _get_element_data(namesp, container, elem_name):
- """Returns the string from the element
-
- :param ns: Namespace the element is in
- :param container: Parent object the element is in
- :param elem_name: Name of the to get the value of
- """
- value = None
- if container is not None:
- element = container.find('{0}{1}'.format(namesp, elem_name))
- if element is not None:
- value = element.text
- return value
-
- def _get_integer_content(self, keyval):
- """Gets the content value as an integer"""
- int_val = self.content.get(keyval, None)
- if int_val is not None:
- try:
- # Try and convert to an integer
- int_val = int(int_val)
- except ValueError:
- pass
- return int_val
-
- @property
- def transport_state(self):
- """Get the transport state"""
- return self.content.get('transportState', None)
-
- @property
- def current_play_mode(self):
- """Get the current play mode"""
- return self.content.get('currentPlayMode', None)
-
- @property
- def current_crossfade_mode(self):
- """Get the current cross fade mode"""
- return self.content.get('currentCrossfadeMode', None)
-
- @property
- def number_of_tracks(self):
- """Get the number of tracks"""
- return self._get_integer_content('numberOfTracks')
-
- @property
- def current_track(self):
- """Get the current track number"""
- return self._get_integer_content('currentTrack')
-
- @property
- def current_track_uri(self):
- """Get the track URI"""
- return self.content.get('currentTrackURI', None)
-
- @property
- def current_track_duration(self):
- """Get the current track duration"""
- return self.content.get('currentTrackDuration', None)
-
- @property
- def title(self):
- """Get the title"""
- return self.content.get('title', None)
-
- @property
- def creator(self):
- """Get the creator"""
- return self.content.get('creator', None)
-
- @property
- def album(self):
- """Get the album"""
- return self.content.get('album', None)
-
- @property
- def original_track_number(self):
- """Get the original track number"""
- return self._get_integer_content('originalTrackNumber')
-
- @property
- def album_artist(self):
- """Get the album artist"""
- return self.content.get('albumArtist', None)
-
- @property
- def album_art_uri(self):
- """Get the albumArtURI"""
- return self.content.get('albumArtURI', None)
-
- @property
- def radio_show_md(self):
- """Get the radio show name"""
- return self.content.get('radioShowMd', None)
-
- @property
- def next_track_uri(self):
- """Get the next track URI"""
- return self.content.get('nextTrackURI', None)
-
- @property
- def next_title(self):
- """Get the next title"""
- return self.content.get('nextTitle', None)
-
- @property
- def next_creator(self):
- """Get the next creator"""
- return self.content.get('nextCreator', None)
-
- @property
- def next_album(self):
- """Get the next album name"""
- return self.content.get('nextAlbum', None)
-
- @property
- def next_original_track_number(self):
- """Get the next original track number"""
- return self._get_integer_content('nextOriginalTrackNumber')
-
- @property
- def next_album_artist(self):
- """Get the next album artist"""
- return self.content.get('nextAlbumArtist', None)
-
- @property
- def next_album_art_uri(self):
- """Get the next albumArtURI"""
- return self.content.get('nextAlbumArtURI', None)
-
- @property
- def transport_title(self):
- """Get the transport title"""
- return self.content.get('transportTitle', None)
+# -*- coding: utf-8 -*-
+
+""" This module contains all the event structures for the events that
+can be returned
+
+"""
+from __future__ import unicode_literals
+
+import logging
+from .xml import XML
+from .utils import really_utf8
+
+log = logging.getLogger(__name__) # pylint: disable=C0103
+
+
+# pylint: disable=too-many-public-methods,too-many-statements
+class LastChangeEvent(object):
+ """
+ Class to handle the Last Change event XML
+ """
+
+ def __init__(self, contents):
+ """ Initialize the class with the contents of the event
+
+ :param contents: Dictionary of the data
+ """
+ self.content = contents
+
+ # pylint: disable=bare-except,broad-except,too-many-locals
+ @classmethod
+ def from_xml(cls, xmlstr):
+ """Return an instance of this class, created from xml.
+
+ :param xmlStr: The xml in string form to create the class from
+ """
+ try:
+ # Need to handle character encoding
+ xmlstr = really_utf8(xmlstr)
+ # Read in the XML
+ last_change_xml = XML.fromstring(xmlstr)
+ except Exception as exc:
+ # Not valid XML
+ log.exception(xmlstr)
+ log.exception(str(exc))
+ return None
+
+ # All the namespaces used in the event XML
+ avtns = '{urn:schemas-upnp-org:metadata-1-0/AVT/}'
+ didlns = '{urn:schemas-upnp-org:metadata-1-0/DIDL-Lite/}'
+ rns = '{urn:schemas-rinconnetworks-com:metadata-1-0/}'
+ upnpns = '{urn:schemas-upnp-org:metadata-1-0/upnp/}'
+ nsdc = '{http://purl.org/dc/elements/1.1/}'
+
+ instanceid = last_change_xml.find('{0}InstanceID'.format(avtns))
+ if instanceid is None:
+ log.debug("No InstanceID found %s", xmlstr)
+ return None
+
+ result = {}
+ result['transportState'] = LastChangeEvent._get_val_data(
+ avtns, instanceid, 'TransportState')
+ result['currentPlayMode'] = LastChangeEvent._get_val_data(
+ avtns, instanceid, 'CurrentPlayMode')
+ result['currentCrossfadeMode'] = LastChangeEvent._get_val_data(
+ avtns, instanceid, 'CurrentCrossfadeMode')
+ result['numberOfTracks'] = LastChangeEvent._get_val_data(
+ avtns, instanceid, 'NumberOfTracks')
+ result['currentTrack'] = LastChangeEvent._get_val_data(
+ avtns, instanceid, 'CurrentTrack')
+ result['currentSection'] = LastChangeEvent._get_val_data(
+ avtns, instanceid, 'CurrentSection')
+ result['currentTrackURI'] = LastChangeEvent._get_val_data(
+ avtns, instanceid, 'CurrentTrackURI')
+ result['currentTrackDuration'] = LastChangeEvent._get_val_data(
+ avtns, instanceid, 'CurrentTrackDuration')
+
+ # The current track meta data is embedded XML
+ current_track_md = LastChangeEvent._get_val_data(
+ avtns, instanceid, 'CurrentTrackMetaData')
+ if (current_track_md is not None) and (current_track_md != ""):
+ log.debug("CurrentTrackMetaData: %s", current_track_md)
+ try:
+ current_track_md = current_track_md.encode('utf-8')
+ ctrack_metadata_xml = XML.fromstring(current_track_md)
+ except Exception as exc:
+ # Not valid XML
+ log.exception(current_track_md)
+ log.exception(str(exc))
+ return None
+
+ item = ctrack_metadata_xml.find('{0}item'.format(didlns))
+
+ if item is not None:
+ result['title'] = LastChangeEvent._get_element_data(
+ nsdc, item, 'title')
+ result['creator'] = LastChangeEvent._get_element_data(
+ nsdc, item, 'creator')
+ result['album'] = LastChangeEvent._get_element_data(
+ upnpns, item, 'album')
+ result['originalTrackNumber'] =\
+ LastChangeEvent._get_element_data(
+ upnpns, item, 'originalTrackNumber')
+ result['albumArtist'] = LastChangeEvent._get_element_data(
+ rns, item, 'albumArtist')
+ result['albumArtURI'] = LastChangeEvent._get_element_data(
+ upnpns, item, 'albumArtURI')
+ result['radioShowMd'] = LastChangeEvent._get_element_data(
+ rns, item, 'radioShowMd')
+
+ result['nextTrackURI'] = LastChangeEvent._get_val_data(
+ rns, instanceid, 'NextTrackURI')
+
+ # The next track meta data is embedded XML
+ next_track_metadata = LastChangeEvent._get_val_data(
+ rns, instanceid, 'NextTrackMetaData')
+ if (next_track_metadata is not None) and (next_track_metadata != ""):
+ log.debug("NextTrackMetaData: %s", next_track_metadata)
+ try:
+ next_track_metadata = next_track_metadata.encode('utf-8')
+ next_track_metadata_xml = XML.fromstring(next_track_metadata)
+ except Exception as exc:
+ # Not valid XML
+ log.exception(next_track_metadata)
+ log.exception(str(exc))
+ return None
+
+ item = next_track_metadata_xml.find('{0}item'.format(didlns))
+
+ if item is not None:
+ result['nextTitle'] = LastChangeEvent._get_element_data(
+ nsdc, item, 'title')
+ result['nextCreator'] = LastChangeEvent._get_element_data(
+ nsdc, item, 'creator')
+ result['nextAlbum'] = LastChangeEvent._get_element_data(
+ upnpns, item, 'album')
+ result['nextOriginalTrackNumber'] =\
+ LastChangeEvent._get_element_data(
+ upnpns, item, 'originalTrackNumber')
+ result['nextAlbumArtist'] = LastChangeEvent._get_element_data(
+ rns, item, 'albumArtist')
+ result['nextAlbumArtURI'] = LastChangeEvent._get_element_data(
+ upnpns, item, 'albumArtURI')
+
+ # The transport meta data is embedded XML
+ transportmetadata = LastChangeEvent._get_val_data(
+ rns, instanceid, 'EnqueuedTransportURIMetaData')
+ if (transportmetadata is not None) and (transportmetadata != ""):
+ log.debug("EnqueuedTransportURIMetaData: %s", transportmetadata)
+ try:
+ transportmetadata = transportmetadata.encode('utf-8')
+ transport_xml = XML.fromstring(transportmetadata)
+ except Exception as exc:
+ # Not valid XML
+ log.exception(transportmetadata)
+ log.exception(str(exc))
+ return None
+
+ item = transport_xml.find('{0}item'.format(didlns))
+
+ if item is not None:
+ result['transportTitle'] = LastChangeEvent._get_element_data(
+ nsdc, item, 'title')
+
+ return cls(result)
+
+ @staticmethod
+ def _get_val_data(namesp, container, elem_name):
+ """Returns the string from the val attribute
+
+ :param ns: Namespace the element is in
+ :param container: Parent object the element is in
+ :param elem_name: Name of the to get the attribute of
+ """
+ value = None
+ if container is not None:
+ element = container.find('{0}{1}'.format(namesp, elem_name))
+ if element is not None:
+ value = element.get('val')
+ return value
+
+ @staticmethod
+ def _get_element_data(namesp, container, elem_name):
+ """Returns the string from the element
+
+ :param ns: Namespace the element is in
+ :param container: Parent object the element is in
+ :param elem_name: Name of the to get the value of
+ """
+ value = None
+ if container is not None:
+ element = container.find('{0}{1}'.format(namesp, elem_name))
+ if element is not None:
+ value = element.text
+ return value
+
+ def _get_integer_content(self, keyval):
+ """Gets the content value as an integer"""
+ int_val = self.content.get(keyval, None)
+ if int_val is not None:
+ try:
+ # Try and convert to an integer
+ int_val = int(int_val)
+ except ValueError:
+ pass
+ return int_val
+
+ @property
+ def transport_state(self):
+ """Get the transport state"""
+ return self.content.get('transportState', None)
+
+ @property
+ def current_play_mode(self):
+ """Get the current play mode"""
+ return self.content.get('currentPlayMode', None)
+
+ @property
+ def current_crossfade_mode(self):
+ """Get the current cross fade mode"""
+ return self.content.get('currentCrossfadeMode', None)
+
+ @property
+ def number_of_tracks(self):
+ """Get the number of tracks"""
+ return self._get_integer_content('numberOfTracks')
+
+ @property
+ def current_track(self):
+ """Get the current track number"""
+ return self._get_integer_content('currentTrack')
+
+ @property
+ def current_track_uri(self):
+ """Get the track URI"""
+ return self.content.get('currentTrackURI', None)
+
+ @property
+ def current_track_duration(self):
+ """Get the current track duration"""
+ return self.content.get('currentTrackDuration', None)
+
+ @property
+ def title(self):
+ """Get the title"""
+ return self.content.get('title', None)
+
+ @property
+ def creator(self):
+ """Get the creator"""
+ return self.content.get('creator', None)
+
+ @property
+ def album(self):
+ """Get the album"""
+ return self.content.get('album', None)
+
+ @property
+ def original_track_number(self):
+ """Get the original track number"""
+ return self._get_integer_content('originalTrackNumber')
+
+ @property
+ def album_artist(self):
+ """Get the album artist"""
+ return self.content.get('albumArtist', None)
+
+ @property
+ def album_art_uri(self):
+ """Get the albumArtURI"""
+ return self.content.get('albumArtURI', None)
+
+ @property
+ def radio_show_md(self):
+ """Get the radio show name"""
+ return self.content.get('radioShowMd', None)
+
+ @property
+ def next_track_uri(self):
+ """Get the next track URI"""
+ return self.content.get('nextTrackURI', None)
+
+ @property
+ def next_title(self):
+ """Get the next title"""
+ return self.content.get('nextTitle', None)
+
+ @property
+ def next_creator(self):
+ """Get the next creator"""
+ return self.content.get('nextCreator', None)
+
+ @property
+ def next_album(self):
+ """Get the next album name"""
+ return self.content.get('nextAlbum', None)
+
+ @property
+ def next_original_track_number(self):
+ """Get the next original track number"""
+ return self._get_integer_content('nextOriginalTrackNumber')
+
+ @property
+ def next_album_artist(self):
+ """Get the next album artist"""
+ return self.content.get('nextAlbumArtist', None)
+
+ @property
+ def next_album_art_uri(self):
+ """Get the next albumArtURI"""
+ return self.content.get('nextAlbumArtURI', None)
+
+ @property
+ def transport_title(self):
+ """Get the transport title"""
+ return self.content.get('transportTitle', None)
diff --git a/script.sonos/resources/lib/sonos.py
b/script.sonos/resources/lib/sonos.py
index 2f4cd5a..876a21c 100644
--- a/script.sonos/resources/lib/sonos.py
+++ b/script.sonos/resources/lib/sonos.py
@@ -1,201 +1,201 @@
-# -*- coding: utf-8 -*-
-import cgi
-import traceback
-import logging
-
-# Load the Soco classes
-from soco import SoCo
-from soco.event_structures import LastChangeEvent
-
-# Use the SoCo logger
-LOGGER = logging.getLogger('soco')
-
-
-#########################################################################
-# Sonos class to add extra support on top of SoCo
-#########################################################################
-class Sonos(SoCo):
- # Format of the meta data (borrowed from sample code)
- meta_template = '<DIDL-Lite xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:upnp="urn:schemas-upnp-org:metadata-1-0/upnp/"
xmlns:r="urn:schemas-rinconnetworks-com:metadata-1-0/"
xmlns="urn:schemas-upnp-org:metadata-1-0/DIDL-Lite/"><item id="R:0/0/0"
parentID="R:0/0"
restricted="true"><dc:title>{title}</dc:title><upnp:class>object.item.audioItem.audioBroadcast</upnp:class><desc
id="cdudn"
nameSpace="urn:schemas-rinconnetworks-com:metadata-1-0/">{service}</desc></item></DIDL-Lite>'
- tunein_service = 'SA_RINCON65031_'
-
- # Converts non complete URIs to complete URIs with IP address
- def _updateAlbumArtToFullUri(self, musicInfo):
- if hasattr(musicInfo, 'album_art_uri'):
- # Add on the full album art link, as the URI version does not
include the ipaddress
- if (musicInfo.album_art_uri is not None) and
(musicInfo.album_art_uri != ""):
- if not musicInfo.album_art_uri.startswith(('http:', 'https:')):
- musicInfo.album_art_uri = 'http://' + self.ip_address +
':1400' + musicInfo.album_art_uri
-
- # Override method so that the album art http reference can be added
- def get_music_library_information(self, search_type, start=0,
max_items=100, sub_category=''):
- # Make sure the sub category is valid for the message, escape invalid
characters
- sub_category = cgi.escape(sub_category)
-
- # Call the base version
- musicInfo = SoCo.get_music_library_information(self, search_type,
start, max_items, sub_category)
-
- if musicInfo is not None:
- for anItem in musicInfo['item_list']:
- # Make sure the album art URI is the full path
- self._updateAlbumArtToFullUri(anItem)
-
- return musicInfo
-
- # Override method so that the album art http reference can be added
- def get_queue(self, start=0, max_items=100):
- list = SoCo.get_queue(self, start=start, max_items=max_items)
-
- if list is not None:
- for anItem in list:
- # Make sure the album art URI is the full path
- self._updateAlbumArtToFullUri(anItem)
-
- return list
-
- # For radio playing a title is required
- def play_uri(self, uri='', title=None, metadata=''):
- # Radio stations need to have at least a title to play
- if (metadata == '') and (title is not None):
- title_esc = cgi.escape(title)
- metadata = Sonos.meta_template.format(title=title_esc,
service=Sonos.tunein_service)
-
- # Need to replace any special characters in the URI
- uri = cgi.escape(uri)
- # Now play the track
- SoCo.play_uri(self, uri, metadata)
-
- # Reads the current Random and repeat status
- def getPlayMode(self):
- isRandom = False
- isLoop = False
- # Check what the play mode is
- playMode = self.play_mode
- if playMode.upper() == "REPEAT_ALL":
- isLoop = True
- elif playMode.upper() == "SHUFFLE":
- isLoop = True
- isRandom = True
- elif playMode.upper() == "SHUFFLE_NOREPEAT":
- isRandom = True
-
- return isRandom, isLoop
-
- # Sets the current Random and repeat status
- def setPlayMode(self, isRandom, isLoop):
- playMode = "NORMAL"
-
- # Convert the booleans into a playmode
- if isRandom and isLoop:
- playMode = "SHUFFLE"
- elif isRandom and (not isLoop):
- playMode = "SHUFFLE_NOREPEAT"
- elif (not isRandom) and isLoop:
- playMode = "REPEAT_ALL"
-
- # Now set the playmode on the Sonos speaker
- self.play_mode = playMode
-
- def hasTrackChanged(self, track1, track2):
- if track2 is None:
- return False
- if track1 is None:
- return True
- if track1['uri'] != track2['uri']:
- return True
- # Don't update if the URI is the same but the new version does
- # not have event info
- if (track2['lastEventDetails'] is None) and
(track1['lastEventDetails'] is not None):
- return False
- if track1['title'] != track2['title']:
- return True
- if track1['album_art'] != track2['album_art']:
- return True
- if track1['artist'] != track2['artist']:
- return True
- if track1['album'] != track2['album']:
- return True
-
- return False
-
- # Gets the most recent event from the event queue
- def getLastEventDetails(self, sub):
- lastChangeDetails = None
- try:
- queueItem = None
- # Get the most recent event received
- while not sub.events.empty():
- try:
- # Get the next event - but do not block or wait for an
event
- # if there is not already one there
- queueItem = sub.events.get(False)
- except:
- LOGGER.debug("Sonos: Queue get failed: %s" %
traceback.format_exc())
-
- # Now get the details of an event if there is one there
- lastChangeDetails = None
- if queueItem is not None:
- lastChangeXmlStr = queueItem.variables['LastChange']
- if lastChangeXmlStr is not None:
- LOGGER.debug("Event details: %s" % lastChangeXmlStr)
- # Convert the XML into an object
- lastChangeDetails =
LastChangeEvent.from_xml(lastChangeXmlStr)
- except:
- LOGGER.debug("Sonos: Failed to get latest event details: %s" %
traceback.format_exc())
-
- return lastChangeDetails
-
- # When given a track info structure and an event, will merge the data
- # together so that it is complete and accurate
- def mergeTrackInfoAndEvent(self, track, eventDetails, previousTrack=None):
- # If there is no event data, then just return the track unchanged
- if eventDetails is None:
- # Check to see if the track has changed, if it has not, then we can
- # safely use the previous event we stored
- if (previousTrack is not None) and (track['uri'] ==
previousTrack['uri']) and (previousTrack['lastEventDetails'] is not None):
- LOGGER.debug("Sonos: Using previous Event details for merge")
- track['lastEventDetails'] = previousTrack['lastEventDetails']
- eventDetails = previousTrack['lastEventDetails']
- else:
- LOGGER.debug("Sonos: Event details not set for merge")
- track['lastEventDetails'] = None
- return track
- else:
- LOGGER.debug("Sonos: Event details set for merge")
- track['lastEventDetails'] = eventDetails
-
- # If the track has no album art, use the event one (if it exists)
- if (track['album_art'] is None) or (track['album_art'] == ""):
- if (eventDetails.album_art_uri is not None) and
(eventDetails.album_art_uri != ""):
- track['album_art'] = eventDetails.album_art_uri
- # Make sure the Album art is fully qualified
- if not track['album_art'].startswith(('http:', 'https:')):
- track['album_art'] = 'http://' + self.ip_address + ':1400'
+ track['album_art']
-
- if (track['artist'] is None) or (track['artist'] == ""):
- if (eventDetails.album_artist is not None) and
(eventDetails.album_artist != ""):
- track['artist'] = eventDetails.album_artist
-
- # Check if this is radio stream, in which case use that as the title
- if (eventDetails.transport_title is not None) and
(eventDetails.transport_title != ""):
- if (track['title'] is None) or (track['title'] == ""):
- track['title'] = eventDetails.transport_title
- # Otherwise treat as a normal title
- elif (track['title'] is None) or (track['title'] == ""):
- if (eventDetails.title is not None) and (eventDetails.title != ""):
- track['title'] = eventDetails.title
-
- # Check if this is radio stream, in which case use that as the album
title
- if (eventDetails.radio_show_md is not None) and
(eventDetails.radio_show_md != ""):
- if (track['album'] is None) or (track['album'] == ""):
- track['album'] = eventDetails.radio_show_md
- # This may be something like: Drivetime,p239255 so need to
remove the last section
- trimmed = track['album'].rpartition(',p')[0]
- if (trimmed is not None) and (trimmed != ""):
- track['album'] = trimmed
- # Otherwise treat as a album title
- elif (track['album'] is None) or (track['album'] == ""):
- if (eventDetails.album is not None) and (eventDetails.album != ""):
- track['album'] = eventDetails.album
-
- return track
+# -*- coding: utf-8 -*-
+import cgi
+import traceback
+import logging
+
+# Load the Soco classes
+from soco import SoCo
+from soco.event_structures import LastChangeEvent
+
+# Use the SoCo logger
+LOGGER = logging.getLogger('soco')
+
+
+#########################################################################
+# Sonos class to add extra support on top of SoCo
+#########################################################################
+class Sonos(SoCo):
+ # Format of the meta data (borrowed from sample code)
+ meta_template = '<DIDL-Lite xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:upnp="urn:schemas-upnp-org:metadata-1-0/upnp/"
xmlns:r="urn:schemas-rinconnetworks-com:metadata-1-0/"
xmlns="urn:schemas-upnp-org:metadata-1-0/DIDL-Lite/"><item id="R:0/0/0"
parentID="R:0/0"
restricted="true"><dc:title>{title}</dc:title><upnp:class>object.item.audioItem.audioBroadcast</upnp:class><desc
id="cdudn"
nameSpace="urn:schemas-rinconnetworks-com:metadata-1-0/">{service}</desc></item></DIDL-Lite>'
+ tunein_service = 'SA_RINCON65031_'
+
+ # Converts non complete URIs to complete URIs with IP address
+ def _updateAlbumArtToFullUri(self, musicInfo):
+ if hasattr(musicInfo, 'album_art_uri'):
+ # Add on the full album art link, as the URI version does not
include the ipaddress
+ if (musicInfo.album_art_uri is not None) and
(musicInfo.album_art_uri != ""):
+ if not musicInfo.album_art_uri.startswith(('http:', 'https:')):
+ musicInfo.album_art_uri = 'http://' + self.ip_address +
':1400' + musicInfo.album_art_uri
+
+ # Override method so that the album art http reference can be added
+ def get_music_library_information(self, search_type, start=0,
max_items=100, sub_category=''):
+ # Make sure the sub category is valid for the message, escape invalid
characters
+ sub_category = cgi.escape(sub_category)
+
+ # Call the base version
+ musicInfo = SoCo.get_music_library_information(self, search_type,
start, max_items, sub_category)
+
+ if musicInfo is not None:
+ for anItem in musicInfo['item_list']:
+ # Make sure the album art URI is the full path
+ self._updateAlbumArtToFullUri(anItem)
+
+ return musicInfo
+
+ # Override method so that the album art http reference can be added
+ def get_queue(self, start=0, max_items=100):
+ list = SoCo.get_queue(self, start=start, max_items=max_items)
+
+ if list is not None:
+ for anItem in list:
+ # Make sure the album art URI is the full path
+ self._updateAlbumArtToFullUri(anItem)
+
+ return list
+
+ # For radio playing a title is required
+ def play_uri(self, uri='', title=None, metadata=''):
+ # Radio stations need to have at least a title to play
+ if (metadata == '') and (title is not None):
+ title_esc = cgi.escape(title)
+ metadata = Sonos.meta_template.format(title=title_esc,
service=Sonos.tunein_service)
+
+ # Need to replace any special characters in the URI
+ uri = cgi.escape(uri)
+ # Now play the track
+ SoCo.play_uri(self, uri, metadata)
+
+ # Reads the current Random and repeat status
+ def getPlayMode(self):
+ isRandom = False
+ isLoop = False
+ # Check what the play mode is
+ playMode = self.play_mode
+ if playMode.upper() == "REPEAT_ALL":
+ isLoop = True
+ elif playMode.upper() == "SHUFFLE":
+ isLoop = True
+ isRandom = True
+ elif playMode.upper() == "SHUFFLE_NOREPEAT":
+ isRandom = True
+
+ return isRandom, isLoop
+
+ # Sets the current Random and repeat status
+ def setPlayMode(self, isRandom, isLoop):
+ playMode = "NORMAL"
+
+ # Convert the booleans into a playmode
+ if isRandom and isLoop:
+ playMode = "SHUFFLE"
+ elif isRandom and (not isLoop):
+ playMode = "SHUFFLE_NOREPEAT"
+ elif (not isRandom) and isLoop:
+ playMode = "REPEAT_ALL"
+
+ # Now set the playmode on the Sonos speaker
+ self.play_mode = playMode
+
+ def hasTrackChanged(self, track1, track2):
+ if track2 is None:
+ return False
+ if track1 is None:
+ return True
+ if track1['uri'] != track2['uri']:
+ return True
+ # Don't update if the URI is the same but the new version does
+ # not have event info
+ if (track2['lastEventDetails'] is None) and
(track1['lastEventDetails'] is not None):
+ return False
+ if track1['title'] != track2['title']:
+ return True
+ if track1['album_art'] != track2['album_art']:
+ return True
+ if track1['artist'] != track2['artist']:
+ return True
+ if track1['album'] != track2['album']:
+ return True
+
+ return False
+
+ # Gets the most recent event from the event queue
+ def getLastEventDetails(self, sub):
+ lastChangeDetails = None
+ try:
+ queueItem = None
+ # Get the most recent event received
+ while not sub.events.empty():
+ try:
+ # Get the next event - but do not block or wait for an
event
+ # if there is not already one there
+ queueItem = sub.events.get(False)
+ except:
+ LOGGER.debug("Sonos: Queue get failed: %s" %
traceback.format_exc())
+
+ # Now get the details of an event if there is one there
+ lastChangeDetails = None
+ if queueItem is not None:
+ lastChangeXmlStr = queueItem.variables['LastChange']
+ if lastChangeXmlStr is not None:
+ LOGGER.debug("Event details: %s" % lastChangeXmlStr)
+ # Convert the XML into an object
+ lastChangeDetails =
LastChangeEvent.from_xml(lastChangeXmlStr)
+ except:
+ LOGGER.debug("Sonos: Failed to get latest event details: %s" %
traceback.format_exc())
+
+ return lastChangeDetails
+
+ # When given a track info structure and an event, will merge the data
+ # together so that it is complete and accurate
+ def mergeTrackInfoAndEvent(self, track, eventDetails, previousTrack=None):
+ # If there is no event data, then just return the track unchanged
+ if eventDetails is None:
+ # Check to see if the track has changed, if it has not, then we can
+ # safely use the previous event we stored
+ if (previousTrack is not None) and (track['uri'] ==
previousTrack['uri']) and (previousTrack['lastEventDetails'] is not None):
+ LOGGER.debug("Sonos: Using previous Event details for merge")
+ track['lastEventDetails'] = previousTrack['lastEventDetails']
+ eventDetails = previousTrack['lastEventDetails']
+ else:
+ LOGGER.debug("Sonos: Event details not set for merge")
+ track['lastEventDetails'] = None
+ return track
+ else:
+ LOGGER.debug("Sonos: Event details set for merge")
+ track['lastEventDetails'] = eventDetails
+
+ # If the track has no album art, use the event one (if it exists)
+ if (track['album_art'] is None) or (track['album_art'] == ""):
+ if (eventDetails.album_art_uri is not None) and
(eventDetails.album_art_uri != ""):
+ track['album_art'] = eventDetails.album_art_uri
+ # Make sure the Album art is fully qualified
+ if not track['album_art'].startswith(('http:', 'https:')):
+ track['album_art'] = 'http://' + self.ip_address + ':1400'
+ track['album_art']
+
+ if (track['artist'] is None) or (track['artist'] == ""):
+ if (eventDetails.album_artist is not None) and
(eventDetails.album_artist != ""):
+ track['artist'] = eventDetails.album_artist
+
+ # Check if this is radio stream, in which case use that as the title
+ if (eventDetails.transport_title is not None) and
(eventDetails.transport_title != ""):
+ if (track['title'] is None) or (track['title'] == ""):
+ track['title'] = eventDetails.transport_title
+ # Otherwise treat as a normal title
+ elif (track['title'] is None) or (track['title'] == ""):
+ if (eventDetails.title is not None) and (eventDetails.title != ""):
+ track['title'] = eventDetails.title
+
+ # Check if this is radio stream, in which case use that as the album
title
+ if (eventDetails.radio_show_md is not None) and
(eventDetails.radio_show_md != ""):
+ if (track['album'] is None) or (track['album'] == ""):
+ track['album'] = eventDetails.radio_show_md
+ # This may be something like: Drivetime,p239255 so need to
remove the last section
+ trimmed = track['album'].rpartition(',p')[0]
+ if (trimmed is not None) and (trimmed != ""):
+ track['album'] = trimmed
+ # Otherwise treat as a album title
+ elif (track['album'] is None) or (track['album'] == ""):
+ if (eventDetails.album is not None) and (eventDetails.album != ""):
+ track['album'] = eventDetails.album
+
+ return track
diff --git a/script.sonos/resources/settings.xml
b/script.sonos/resources/settings.xml
index 3d77f90..0181787 100644
--- a/script.sonos/resources/settings.xml
+++ b/script.sonos/resources/settings.xml
@@ -1,36 +1,36 @@
-<?xml version="1.0" encoding="utf-8" standalone="yes"?>
-<settings>
- <category label="32010">
- <setting label="32013" type="action"
action="RunScript($CWD/discovery.py)"/>
- <setting id="ipAddress" type="ipaddress" label="32002"
default="0.0.0.0"/>
- <setting id="zoneName" type="text" label="32028" default=""/>
- <setting id="autoIPUpdate" subsetting="true" enable="!eq(-1,)"
type="bool" label="32029" default="false"/>
- <setting label="32012" type="lsep"/>
- <setting id="logEnabled" type="bool" label="32009"
default="false"/>
- <setting id="useTestData" type="bool" label="32017"
default="false"/>
- </category>
- <category label="32015">
- <setting id="refreshInterval" label="32016" type="slider"
default="2" range="0.5,0.5,5" option="float"/>
- <setting id="displayArtistInfo" type="bool" label="32021"
default="false"/>
- <setting id="avoidDuplicateCommands" label="32022"
type="slider" default="1.5" range="0.5,0.5,5" option="float"/>
- <setting id="volumeChangeIncrements" label="32025"
type="slider" default="3" range="1,1,10" option="int"/>
-
- <setting id="linkAudioWithSonos" type="bool" label="32023"
default="false"/>
- <setting id="switchSonosToLineIn" subsetting="true"
enable="eq(-1,true)" type="bool" label="32024" default="false"/>
- <setting id="switchSonosToLineInOnMediaStart" subsetting="true"
enable="eq(-2,true)" type="bool" label="32030" default="false"/>
-
- <setting id="autoPauseSonos" enable="eq(-2,false)" type="bool"
label="32026" default="false"/>
- <setting id="autoResumeSonos" subsetting="true"
enable="eq(-1,true)" label="32027" type="slider" default="0" range="0,3,60"
option="int"/>
- </category>
- <category label="32011">
- <setting id="notifEnabled" type="bool" label="32003"
default="true"/>
- <setting id="notifDisplayDuration" enable="eq(-1,true)"
label="32004" type="slider" default="3" range="0,1,60" option="int"/>
- <setting id="notifCheckFrequency" enable="eq(-2,true)"
label="32005" type="slider" default="10" range="0,1,60" option="int"/>
- <setting id="notifNotIfVideoPlaying" enable="eq(-3,true)"
type="bool" label="32006" default="true"/>
- <setting id="xbmcNotifDialog" enable="eq(-4,true)" type="bool"
label="32007" default="false"/>
- </category>
- <category label="32018">
- <setting id="batchSize" label="32019" type="slider"
default="100" range="10,10,1000" option="int"/>
- <setting id="maxListEntries" label="32020" type="slider"
default="1000" range="0,100,3000" option="int"/>
- </category>
-</settings>
+<?xml version="1.0" encoding="utf-8" standalone="yes"?>
+<settings>
+ <category label="32010">
+ <setting label="32013" type="action"
action="RunScript($CWD/discovery.py)"/>
+ <setting id="ipAddress" type="ipaddress" label="32002"
default="0.0.0.0"/>
+ <setting id="zoneName" type="text" label="32028" default=""/>
+ <setting id="autoIPUpdate" subsetting="true" enable="!eq(-1,)"
type="bool" label="32029" default="false"/>
+ <setting label="32012" type="lsep"/>
+ <setting id="logEnabled" type="bool" label="32009"
default="false"/>
+ <setting id="useTestData" type="bool" label="32017"
default="false"/>
+ </category>
+ <category label="32015">
+ <setting id="refreshInterval" label="32016" type="slider"
default="2" range="0.5,0.5,5" option="float"/>
+ <setting id="displayArtistInfo" type="bool" label="32021"
default="false"/>
+ <setting id="avoidDuplicateCommands" label="32022"
type="slider" default="1.5" range="0.5,0.5,5" option="float"/>
+ <setting id="volumeChangeIncrements" label="32025"
type="slider" default="3" range="1,1,10" option="int"/>
+
+ <setting id="linkAudioWithSonos" type="bool" label="32023"
default="false"/>
+ <setting id="switchSonosToLineIn" subsetting="true"
enable="eq(-1,true)" type="bool" label="32024" default="false"/>
+ <setting id="switchSonosToLineInOnMediaStart" subsetting="true"
enable="eq(-2,true)" type="bool" label="32030" default="false"/>
+
+ <setting id="autoPauseSonos" enable="eq(-2,false)" type="bool"
label="32026" default="false"/>
+ <setting id="autoResumeSonos" subsetting="true"
enable="eq(-1,true)" label="32027" type="slider" default="0" range="0,3,60"
option="int"/>
+ </category>
+ <category label="32011">
+ <setting id="notifEnabled" type="bool" label="32003"
default="true"/>
+ <setting id="notifDisplayDuration" enable="eq(-1,true)"
label="32004" type="slider" default="3" range="0,1,60" option="int"/>
+ <setting id="notifCheckFrequency" enable="eq(-2,true)"
label="32005" type="slider" default="10" range="0,1,60" option="int"/>
+ <setting id="notifNotIfVideoPlaying" enable="eq(-3,true)"
type="bool" label="32006" default="true"/>
+ <setting id="xbmcNotifDialog" enable="eq(-4,true)" type="bool"
label="32007" default="false"/>
+ </category>
+ <category label="32018">
+ <setting id="batchSize" label="32019" type="slider"
default="100" range="10,10,1000" option="int"/>
+ <setting id="maxListEntries" label="32020" type="slider"
default="1000" range="0,100,3000" option="int"/>
+ </category>
+</settings>
diff --git
a/script.sonos/resources/skins/Default/720p/script-sonos-controller.xml
b/script.sonos/resources/skins/Default/720p/script-sonos-controller.xml
index 940d9c5..a42cbce 100644
--- a/script.sonos/resources/skins/Default/720p/script-sonos-controller.xml
+++ b/script.sonos/resources/skins/Default/720p/script-sonos-controller.xml
@@ -1,393 +1,393 @@
-<?xml version="1.0" encoding="utf-8"?>
-<window type="dialog" id="3002">
- <defaultcontrol always="true">100</defaultcontrol>
- <animation effect="fade" time="250">WindowOpen</animation>
- <animation effect="fade" time="250">WindowClose</animation>
- <coordinates>
- <system>1</system>
- <posx>390</posx>
- <posy>250</posy>
- </coordinates>
- <controls>
- <control type="image">
- <description>Main Dialog Background</description>
- <posx>0</posx>
- <posy>0</posy>
- <width>500</width>
- <height>225</height>
- <colordiffuse>CCFFFFFF</colordiffuse>
- <texture border="20">[email protected]</texture>
- </control>
- <control type="image">
- <description>gradient</description>
- <posx>5</posx>
- <posy>5</posy>
- <width>490</width>
- <height>215</height>
- <texture border="20">msNowPlayingBg_ipad.png</texture>
- </control>
-
- <!-- Music Info -->
- <control type="group" id="300">
- <control type="image" id="801">
- <description>Cover image</description>
- <posx>20</posx>
- <posy>17</posy>
- <width>130</width>
- <height>130</height>
- </control>
- <control type="fadelabel" id="802">
- <description>Artist label</description>
- <posx>160</posx>
- <posy>20</posy>
- <height>30</height>
- <width>325</width>
- <align>left</align>
- <aligny>center</aligny>
- <font>font12_title</font>
- <textcolor>FF999999</textcolor> <!--
grey2 -->
- <shadowcolor>FF000000</shadowcolor> <!-- black
-->
- <scrollout>false</scrollout>
- <pauseatend>2000</pauseatend>
- </control>
- <control type="fadelabel" id="803">
- <description>Title label</description>
- <posx>160</posx>
- <posy>43</posy>
- <height>30</height>
- <width>325</width>
- <align>left</align>
- <aligny>center</aligny>
- <font>font13_title</font>
- <textcolor>ffffffff</textcolor> <!--
white -->
- <shadowcolor>FF000000</shadowcolor> <!-- black
-->
- <scrollout>false</scrollout>
- <pauseatend>2000</pauseatend>
- </control>
- <control type="fadelabel" id="804">
- <description>Album Label</description>
- <posx>160</posx>
- <posy>70</posy>
- <height>30</height>
- <width>325</width>
- <align>left</align>
- <aligny>center</aligny>
- <font>font12</font>
- <textcolor>ffffffff</textcolor> <!--
white -->
- <shadowcolor>FF000000</shadowcolor> <!-- black
-->
- <scrollout>false</scrollout>
- <pauseatend>2000</pauseatend>
- </control>
- <control type="fadelabel" id="805">
- <description>Next Label</description>
- <posx>160</posx>
- <posy>100</posy>
- <height>30</height>
- <width>325</width>
- <align>right</align>
- <aligny>center</aligny>
- <font>font12</font>
- <textcolor>7fffffff</textcolor> <!--
grey -->
- <shadowcolor>FF000000</shadowcolor> <!-- black
-->
- <scrollout>false</scrollout>
- <pauseatend>2000</pauseatend>
- </control>
- <control type="label" id="810">
- <description>Current Time Position
Label</description>
- <posx>160</posx>
- <posy>130</posy>
- <height>15</height>
- <width>60</width>
- <label>-</label>
- <align>center</align>
- <aligny>center</aligny>
- <font>font10</font>
- <textcolor>ffffffff</textcolor> <!--
white -->
- <shadowcolor>FF000000</shadowcolor> <!-- black
-->
- <!-- Only display the start time if there is a
duration set -->
- <visible>Control.IsVisible(812)</visible>
- </control>
- <control type="slider" id="811">
- <description>Seek Slider</description>
- <posx>220</posx>
- <posy>135</posy>
- <width>215</width>
- <height>5</height>
- <texturefocus>-</texturefocus>
- <texturenofocus>-</texturenofocus>
-
<texturesliderbar>pmProgressTrackBar.png</texturesliderbar>
-
<textureslidernib>pmProgressTrackBarFillStretch.png</textureslidernib>
-
<textureslidernibfocus>pmProgressTrackBarFillStretch.png</textureslidernibfocus>
- <onup>100</onup>
- <ondown>900</ondown>
- <!-- Only display the slider if there is a
duration set -->
- <visible>Control.IsVisible(812)</visible>
- </control>
- <control type="label" id="812">
- <description>Duration Label</description>
- <posx>435</posx>
- <posy>130</posy>
- <height>15</height>
- <width>60</width>
- <label>-</label>
- <align>center</align>
- <aligny>center</aligny>
- <font>font10</font>
- <textcolor>ffffffff</textcolor> <!--
white -->
- <shadowcolor>FF000000</shadowcolor> <!-- black
-->
- <visible>True</visible>
- </control>
- </control>
-
- <control type="image">
- <description>Background For the controls</description>
- <posx>10</posx>
- <posy>165</posy>
- <width>480</width>
- <height>53</height>
- <colordiffuse>DDFFFFFF</colordiffuse>
- <texture flipy="false"
border="20,20,20,2">[email protected]</texture>
- </control>
-
- <control type="group" id="100">
- <posx>25</posx>
- <posy>172</posy>
- <defaultcontrol always="true">603</defaultcontrol>
- <control type="button" id="600">
- <description>Previous Button</description>
- <posx>0</posx>
- <posy>0</posy>
- <width>40</width>
- <height>40</height>
- <label>-</label>
-
<texturefocus>control_buttons/tbTransportBack_sel.png</texturefocus>
-
<texturenofocus>control_buttons/tbTransportBack.png</texturenofocus>
- <onleft>102</onleft>
- <onright>101</onright>
- <onup>900</onup>
- <ondown>622</ondown>
- </control>
- <!-- Group for the Play Pause Button to allow keyboard
navigation -->
- <control type="group" id="101">
- <control type="button" id="601">
- <description>Play Button</description>
- <posx>40</posx>
- <posy>0</posy>
- <width>40</width>
- <height>40</height>
- <label>-</label>
-
<texturefocus>control_buttons/tbPlay_sel.png</texturefocus>
-
<texturenofocus>control_buttons/tbPlay.png</texturenofocus>
- <onleft>600</onleft>
- <onright>603</onright>
- <onup>900</onup>
- <ondown>622</ondown>
- <visible>True</visible>
- </control>
- <control type="button" id="602">
- <description>Pause Button</description>
- <posx>40</posx>
- <posy>0</posy>
- <width>40</width>
- <height>40</height>
- <label>-</label>
-
<texturefocus>control_buttons/tbPause_sel.png</texturefocus>
-
<texturenofocus>control_buttons/tbPause.png</texturenofocus>
- <onleft>600</onleft>
- <onright>603</onright>
- <onup>900</onup>
- <ondown>622</ondown>
-
<visible>!Control.IsVisible(601)</visible>
- </control>
- </control>
- <control type="button" id="603">
- <description>Stop Button</description>
- <posx>80</posx>
- <posy>0</posy>
- <width>40</width>
- <height>40</height>
- <label>-</label>
-
<texturefocus>control_buttons/tbStop_sel.png</texturefocus>
-
<texturenofocus>control_buttons/tbStop.png</texturenofocus>
- <onleft>101</onleft>
- <onright>604</onright>
- <onup>900</onup>
- <ondown>622</ondown>
- </control>
- <control type="button" id="604">
- <description>Next Button</description>
- <posx>120</posx>
- <posy>0</posy>
- <width>40</width>
- <height>40</height>
- <label>-</label>
-
<texturefocus>control_buttons/tbTransportForward_sel.png</texturefocus>
-
<texturenofocus>control_buttons/tbTransportForward.png</texturenofocus>
- <onleft>603</onleft>
- <onright>104</onright>
- <onup>900</onup>
- <ondown>622</ondown>
- </control>
- <!-- Group the Repeat Buttons -->
- <control type="group" id="104">
- <control type="button" id="605">
- <description>Repeat Button</description>
- <posx>190</posx>
- <posy>10</posy>
- <width>20</width>
- <height>20</height>
- <label>-</label>
-
<texturefocus>control_buttons/[email protected]</texturefocus>
-
<texturenofocus>control_buttons/[email protected]</texturenofocus>
- <onleft>604</onleft>
- <onright>103</onright>
- <onup>900</onup>
- <ondown>622</ondown>
- </control>
- <control type="button" id="606">
- <description>Repeat Button
Enabled</description>
- <posx>190</posx>
- <posy>10</posy>
- <width>20</width>
- <height>20</height>
- <label>-</label>
-
<texturefocus>control_buttons/[email protected]</texturefocus>
-
<texturenofocus>control_buttons/[email protected]</texturenofocus>
- <onleft>604</onleft>
- <onright>103</onright>
- <onup>900</onup>
-
<visible>!Control.IsVisible(605)</visible>
- <ondown>622</ondown>
- </control>
- </control>
- <!-- Group the Random Buttons -->
- <control type="group" id="103">
- <control type="button" id="607">
- <description>Random Button</description>
- <posx>220</posx>
- <posy>10</posy>
- <width>20</width>
- <height>20</height>
- <label>-</label>
-
<texturefocus>control_buttons/[email protected]</texturefocus>
-
<texturenofocus>control_buttons/[email protected]</texturenofocus>
- <onleft>104</onleft>
- <onright>105</onright>
- <onup>900</onup>
- <ondown>622</ondown>
- </control>
- <control type="button" id="608">
- <description>Random Button
Enabled</description>
- <posx>220</posx>
- <posy>10</posy>
- <width>20</width>
- <height>20</height>
- <label>-</label>
-
<texturefocus>control_buttons/[email protected]</texturefocus>
-
<texturenofocus>control_buttons/[email protected]</texturenofocus>
- <onleft>104</onleft>
- <onright>105</onright>
- <onup>900</onup>
- <ondown>622</ondown>
-
<visible>!Control.IsVisible(607)</visible>
- </control>
- </control>
- <!-- Group the Crossfade Buttons -->
- <control type="group" id="105">
- <control type="button" id="609">
- <description>Crossfade
Button</description>
- <posx>250</posx>
- <posy>10</posy>
- <width>20</width>
- <height>20</height>
- <label>-</label>
-
<texturefocus>control_buttons/[email protected]</texturefocus>
-
<texturenofocus>control_buttons/[email protected]</texturenofocus>
- <onleft>103</onleft>
- <onright>102</onright>
- <onup>900</onup>
- <ondown>622</ondown>
- </control>
- <control type="button" id="610">
- <description>Crossfade Button
Enabled</description>
- <posx>250</posx>
- <posy>10</posy>
- <width>20</width>
- <height>20</height>
- <label>-</label>
-
<texturefocus>control_buttons/[email protected]</texturefocus>
-
<texturenofocus>control_buttons/[email protected]</texturenofocus>
- <onleft>103</onleft>
- <onright>102</onright>
- <onup>900</onup>
- <ondown>622</ondown>
-
<visible>!Control.IsVisible(609)</visible>
- </control>
- </control>
- <!-- Group for the Sound and mute Button to allow
keyboard navigation -->
- <control type="group" id="102">
- <control type="button" id="620">
- <description>Sound Volume
Button</description>
- <posx>290</posx>
- <posy>0</posy>
- <width>40</width>
- <height>40</height>
- <label>-</label>
-
<texturefocus>control_buttons/tbUnMute_sel.png</texturefocus>
-
<texturenofocus>control_buttons/tbMute.png</texturenofocus>
- <onleft>105</onleft>
- <onright>600</onright>
- <onup>900</onup>
- <ondown>622</ondown>
- <visible>True</visible>
- </control>
- <control type="button" id="621">
- <description>Sound Mute
Button</description>
- <posx>290</posx>
- <posy>0</posy>
- <width>40</width>
- <height>40</height>
- <label>-</label>
-
<texturefocus>control_buttons/tbMute_sel.png</texturefocus>
-
<texturenofocus>control_buttons/tbMute_on.png</texturenofocus>
- <onleft>105</onleft>
- <onright>600</onright>
- <onup>900</onup>
- <ondown>622</ondown>
-
<visible>!Control.IsVisible(620)</visible>
- </control>
- </control>
- <control type="slider" id="622">
- <description>Volume Slider</description>
- <posx>330</posx>
- <posy>10</posy>
- <width>120</width>
- <height>20</height>
- <texturefocus>-</texturefocus>
- <texturenofocus>-</texturenofocus>
-
<texturesliderbar>control_buttons/snAutoBtn.png</texturesliderbar>
-
<textureslidernib>control_buttons/tbVolumeScrubber_dis.png</textureslidernib>
-
<textureslidernibfocus>control_buttons/tbVolumeScrubber.png</textureslidernibfocus>
- <onup>100</onup>
- <ondown>900</ondown>
- </control>
- </control>
- <control type="button" id="900">
- <description>Close Window button</description>
- <posx>430</posx>
- <posy>5</posy>
- <width>52</width>
- <height>26</height>
- <label>-</label>
- <font>-</font>
- <onclick>back</onclick>
- <texturefocus>DialogCloseButton-focus.png</texturefocus>
- <texturenofocus>DialogCloseButton.png</texturenofocus>
- <onleft>100</onleft>
- <onright>100</onright>
- <onup>622</onup>
- <ondown>100</ondown>
- <visible>system.getbool(input.enablemouse)</visible>
- </control>
- </controls>
-</window>
+<?xml version="1.0" encoding="utf-8"?>
+<window type="dialog" id="3002">
+ <defaultcontrol always="true">100</defaultcontrol>
+ <animation effect="fade" time="250">WindowOpen</animation>
+ <animation effect="fade" time="250">WindowClose</animation>
+ <coordinates>
+ <system>1</system>
+ <posx>390</posx>
+ <posy>250</posy>
+ </coordinates>
+ <controls>
+ <control type="image">
+ <description>Main Dialog Background</description>
+ <posx>0</posx>
+ <posy>0</posy>
+ <width>500</width>
+ <height>225</height>
+ <colordiffuse>CCFFFFFF</colordiffuse>
+ <texture border="20">[email protected]</texture>
+ </control>
+ <control type="image">
+ <description>gradient</description>
+ <posx>5</posx>
+ <posy>5</posy>
+ <width>490</width>
+ <height>215</height>
+ <texture border="20">msNowPlayingBg_ipad.png</texture>
+ </control>
+
+ <!-- Music Info -->
+ <control type="group" id="300">
+ <control type="image" id="801">
+ <description>Cover image</description>
+ <posx>20</posx>
+ <posy>17</posy>
+ <width>130</width>
+ <height>130</height>
+ </control>
+ <control type="fadelabel" id="802">
+ <description>Artist label</description>
+ <posx>160</posx>
+ <posy>20</posy>
+ <height>30</height>
+ <width>325</width>
+ <align>left</align>
+ <aligny>center</aligny>
+ <font>font12_title</font>
+ <textcolor>FF999999</textcolor> <!--
grey2 -->
+ <shadowcolor>FF000000</shadowcolor> <!-- black
-->
+ <scrollout>false</scrollout>
+ <pauseatend>2000</pauseatend>
+ </control>
+ <control type="fadelabel" id="803">
+ <description>Title label</description>
+ <posx>160</posx>
+ <posy>43</posy>
+ <height>30</height>
+ <width>325</width>
+ <align>left</align>
+ <aligny>center</aligny>
+ <font>font13_title</font>
+ <textcolor>ffffffff</textcolor> <!--
white -->
+ <shadowcolor>FF000000</shadowcolor> <!-- black
-->
+ <scrollout>false</scrollout>
+ <pauseatend>2000</pauseatend>
+ </control>
+ <control type="fadelabel" id="804">
+ <description>Album Label</description>
+ <posx>160</posx>
+ <posy>70</posy>
+ <height>30</height>
+ <width>325</width>
+ <align>left</align>
+ <aligny>center</aligny>
+ <font>font12</font>
+ <textcolor>ffffffff</textcolor> <!--
white -->
+ <shadowcolor>FF000000</shadowcolor> <!-- black
-->
+ <scrollout>false</scrollout>
+ <pauseatend>2000</pauseatend>
+ </control>
+ <control type="fadelabel" id="805">
+ <description>Next Label</description>
+ <posx>160</posx>
+ <posy>100</posy>
+ <height>30</height>
+ <width>325</width>
+ <align>right</align>
+ <aligny>center</aligny>
+ <font>font12</font>
+ <textcolor>7fffffff</textcolor> <!--
grey -->
+ <shadowcolor>FF000000</shadowcolor> <!-- black
-->
+ <scrollout>false</scrollout>
+ <pauseatend>2000</pauseatend>
+ </control>
+ <control type="label" id="810">
+ <description>Current Time Position
Label</description>
+ <posx>160</posx>
+ <posy>130</posy>
+ <height>15</height>
+ <width>60</width>
+ <label>-</label>
+ <align>center</align>
+ <aligny>center</aligny>
+ <font>font10</font>
+ <textcolor>ffffffff</textcolor> <!--
white -->
+ <shadowcolor>FF000000</shadowcolor> <!-- black
-->
+ <!-- Only display the start time if there is a
duration set -->
+ <visible>Control.IsVisible(812)</visible>
+ </control>
+ <control type="slider" id="811">
+ <description>Seek Slider</description>
+ <posx>220</posx>
+ <posy>135</posy>
+ <width>215</width>
+ <height>5</height>
+ <texturefocus>-</texturefocus>
+ <texturenofocus>-</texturenofocus>
+
<texturesliderbar>pmProgressTrackBar.png</texturesliderbar>
+
<textureslidernib>pmProgressTrackBarFillStretch.png</textureslidernib>
+
<textureslidernibfocus>pmProgressTrackBarFillStretch.png</textureslidernibfocus>
+ <onup>100</onup>
+ <ondown>900</ondown>
+ <!-- Only display the slider if there is a
duration set -->
+ <visible>Control.IsVisible(812)</visible>
+ </control>
+ <control type="label" id="812">
+ <description>Duration Label</description>
+ <posx>435</posx>
+ <posy>130</posy>
+ <height>15</height>
+ <width>60</width>
+ <label>-</label>
+ <align>center</align>
+ <aligny>center</aligny>
+ <font>font10</font>
+ <textcolor>ffffffff</textcolor> <!--
white -->
+ <shadowcolor>FF000000</shadowcolor> <!-- black
-->
+ <visible>True</visible>
+ </control>
+ </control>
+
+ <control type="image">
+ <description>Background For the controls</description>
+ <posx>10</posx>
+ <posy>165</posy>
+ <width>480</width>
+ <height>53</height>
+ <colordiffuse>DDFFFFFF</colordiffuse>
+ <texture flipy="false"
border="20,20,20,2">[email protected]</texture>
+ </control>
+
+ <control type="group" id="100">
+ <posx>25</posx>
+ <posy>172</posy>
+ <defaultcontrol always="true">603</defaultcontrol>
+ <control type="button" id="600">
+ <description>Previous Button</description>
+ <posx>0</posx>
+ <posy>0</posy>
+ <width>40</width>
+ <height>40</height>
+ <label>-</label>
+
<texturefocus>control_buttons/tbTransportBack_sel.png</texturefocus>
+
<texturenofocus>control_buttons/tbTransportBack.png</texturenofocus>
+ <onleft>102</onleft>
+ <onright>101</onright>
+ <onup>900</onup>
+ <ondown>622</ondown>
+ </control>
+ <!-- Group for the Play Pause Button to allow keyboard
navigation -->
+ <control type="group" id="101">
+ <control type="button" id="601">
+ <description>Play Button</description>
+ <posx>40</posx>
+ <posy>0</posy>
+ <width>40</width>
+ <height>40</height>
+ <label>-</label>
+
<texturefocus>control_buttons/tbPlay_sel.png</texturefocus>
+
<texturenofocus>control_buttons/tbPlay.png</texturenofocus>
+ <onleft>600</onleft>
+ <onright>603</onright>
+ <onup>900</onup>
+ <ondown>622</ondown>
+ <visible>True</visible>
+ </control>
+ <control type="button" id="602">
+ <description>Pause Button</description>
+ <posx>40</posx>
+ <posy>0</posy>
+ <width>40</width>
+ <height>40</height>
+ <label>-</label>
+
<texturefocus>control_buttons/tbPause_sel.png</texturefocus>
+
<texturenofocus>control_buttons/tbPause.png</texturenofocus>
+ <onleft>600</onleft>
+ <onright>603</onright>
+ <onup>900</onup>
+ <ondown>622</ondown>
+
<visible>!Control.IsVisible(601)</visible>
+ </control>
+ </control>
+ <control type="button" id="603">
+ <description>Stop Button</description>
+ <posx>80</posx>
+ <posy>0</posy>
+ <width>40</width>
+ <height>40</height>
+ <label>-</label>
+
<texturefocus>control_buttons/tbStop_sel.png</texturefocus>
+
<texturenofocus>control_buttons/tbStop.png</texturenofocus>
+ <onleft>101</onleft>
+ <onright>604</onright>
+ <onup>900</onup>
+ <ondown>622</ondown>
+ </control>
+ <control type="button" id="604">
+ <description>Next Button</description>
+ <posx>120</posx>
+ <posy>0</posy>
+ <width>40</width>
+ <height>40</height>
+ <label>-</label>
+
<texturefocus>control_buttons/tbTransportForward_sel.png</texturefocus>
+
<texturenofocus>control_buttons/tbTransportForward.png</texturenofocus>
+ <onleft>603</onleft>
+ <onright>104</onright>
+ <onup>900</onup>
+ <ondown>622</ondown>
+ </control>
+ <!-- Group the Repeat Buttons -->
+ <control type="group" id="104">
+ <control type="button" id="605">
+ <description>Repeat Button</description>
+ <posx>190</posx>
+ <posy>10</posy>
+ <width>20</width>
+ <height>20</height>
+ <label>-</label>
+
<texturefocus>control_buttons/[email protected]</texturefocus>
+
<texturenofocus>control_buttons/[email protected]</texturenofocus>
+ <onleft>604</onleft>
+ <onright>103</onright>
+ <onup>900</onup>
+ <ondown>622</ondown>
+ </control>
+ <control type="button" id="606">
+ <description>Repeat Button
Enabled</description>
+ <posx>190</posx>
+ <posy>10</posy>
+ <width>20</width>
+ <height>20</height>
+ <label>-</label>
+
<texturefocus>control_buttons/[email protected]</texturefocus>
+
<texturenofocus>control_buttons/[email protected]</texturenofocus>
+ <onleft>604</onleft>
+ <onright>103</onright>
+ <onup>900</onup>
+
<visible>!Control.IsVisible(605)</visible>
+ <ondown>622</ondown>
+ </control>
+ </control>
+ <!-- Group the Random Buttons -->
+ <control type="group" id="103">
+ <control type="button" id="607">
+ <description>Random Button</description>
+ <posx>220</posx>
+ <posy>10</posy>
+ <width>20</width>
+ <height>20</height>
+ <label>-</label>
+
<texturefocus>control_buttons/[email protected]</texturefocus>
+
<texturenofocus>control_buttons/[email protected]</texturenofocus>
+ <onleft>104</onleft>
+ <onright>105</onright>
+ <onup>900</onup>
+ <ondown>622</ondown>
+ </control>
+ <control type="button" id="608">
+ <description>Random Button
Enabled</description>
+ <posx>220</posx>
+ <posy>10</posy>
+ <width>20</width>
+ <height>20</height>
+ <label>-</label>
+
<texturefocus>control_buttons/[email protected]</texturefocus>
+
<texturenofocus>control_buttons/[email protected]</texturenofocus>
+ <onleft>104</onleft>
+ <onright>105</onright>
+ <onup>900</onup>
+ <ondown>622</ondown>
+
<visible>!Control.IsVisible(607)</visible>
+ </control>
+ </control>
+ <!-- Group the Crossfade Buttons -->
+ <control type="group" id="105">
+ <control type="button" id="609">
+ <description>Crossfade
Button</description>
+ <posx>250</posx>
+ <posy>10</posy>
+ <width>20</width>
+ <height>20</height>
+ <label>-</label>
+
<texturefocus>control_buttons/[email protected]</texturefocus>
+
<texturenofocus>control_buttons/[email protected]</texturenofocus>
+ <onleft>103</onleft>
+ <onright>102</onright>
+ <onup>900</onup>
+ <ondown>622</ondown>
+ </control>
+ <control type="button" id="610">
+ <description>Crossfade Button
Enabled</description>
+ <posx>250</posx>
+ <posy>10</posy>
+ <width>20</width>
+ <height>20</height>
+ <label>-</label>
+
<texturefocus>control_buttons/[email protected]</texturefocus>
+
<texturenofocus>control_buttons/[email protected]</texturenofocus>
+ <onleft>103</onleft>
+ <onright>102</onright>
+ <onup>900</onup>
+ <ondown>622</ondown>
+
<visible>!Control.IsVisible(609)</visible>
+ </control>
+ </control>
+ <!-- Group for the Sound and mute Button to allow
keyboard navigation -->
+ <control type="group" id="102">
+ <control type="button" id="620">
+ <description>Sound Volume
Button</description>
+ <posx>290</posx>
+ <posy>0</posy>
+ <width>40</width>
+ <height>40</height>
+ <label>-</label>
+
<texturefocus>control_buttons/tbUnMute_sel.png</texturefocus>
+
<texturenofocus>control_buttons/tbMute.png</texturenofocus>
+ <onleft>105</onleft>
+ <onright>600</onright>
+ <onup>900</onup>
+ <ondown>622</ondown>
+ <visible>True</visible>
+ </control>
+ <control type="button" id="621">
+ <description>Sound Mute
Button</description>
+ <posx>290</posx>
+ <posy>0</posy>
+ <width>40</width>
+ <height>40</height>
+ <label>-</label>
+
<texturefocus>control_buttons/tbMute_sel.png</texturefocus>
+
<texturenofocus>control_buttons/tbMute_on.png</texturenofocus>
+ <onleft>105</onleft>
+ <onright>600</onright>
+ <onup>900</onup>
+ <ondown>622</ondown>
+
<visible>!Control.IsVisible(620)</visible>
+ </control>
+ </control>
+ <control type="slider" id="622">
+ <description>Volume Slider</description>
+ <posx>330</posx>
+ <posy>10</posy>
+ <width>120</width>
+ <height>20</height>
+ <texturefocus>-</texturefocus>
+ <texturenofocus>-</texturenofocus>
+
<texturesliderbar>control_buttons/snAutoBtn.png</texturesliderbar>
+
<textureslidernib>control_buttons/tbVolumeScrubber_dis.png</textureslidernib>
+
<textureslidernibfocus>control_buttons/tbVolumeScrubber.png</textureslidernibfocus>
+ <onup>100</onup>
+ <ondown>900</ondown>
+ </control>
+ </control>
+ <control type="button" id="900">
+ <description>Close Window button</description>
+ <posx>430</posx>
+ <posy>5</posy>
+ <width>52</width>
+ <height>26</height>
+ <label>-</label>
+ <font>-</font>
+ <onclick>back</onclick>
+ <texturefocus>DialogCloseButton-focus.png</texturefocus>
+ <texturenofocus>DialogCloseButton.png</texturenofocus>
+ <onleft>100</onleft>
+ <onright>100</onright>
+ <onup>622</onup>
+ <ondown>100</ondown>
+ <visible>system.getbool(input.enablemouse)</visible>
+ </control>
+ </controls>
+</window>
diff --git
a/script.sonos/resources/skins/Default/720p/script-sonos-notif-popup.xml
b/script.sonos/resources/skins/Default/720p/script-sonos-notif-popup.xml
index 2d68569..51d47a4 100644
--- a/script.sonos/resources/skins/Default/720p/script-sonos-notif-popup.xml
+++ b/script.sonos/resources/skins/Default/720p/script-sonos-notif-popup.xml
@@ -1,77 +1,77 @@
-<?xml version="1.0" encoding="utf-8"?>
-<window id="3001">
- <animation effect="fade" start="0" end="100"
time="200">WindowOpen</animation>
- <animation effect="fade" start="100" end="0"
time="200">WindowClose</animation>
- <coordinates>
- <posx>840</posx>
- <posy>620</posy>
- </coordinates>
- <controls>
- <control type="group">
- <animation effect="slide" start="0,0" end="-190,0"
time="200" condition="Window.IsVisible(BusyDialog)">conditional</animation>
- <control type="image">
- <posx>0</posx>
- <posy>0</posy>
- <width>420</width>
- <height>90</height>
- <colordiffuse>CCFFFFFF</colordiffuse>
- <texture
border="12">[email protected]</texture>
- </control>
- <control type="image">
- <posx>5</posx>
- <posy>5</posy>
- <width>410</width>
- <height>80</height>
- <texture
border="20">msNowPlayingBg_ipad.png</texture>
- </control>
- <control type="image" id="400">
- <description>avatar</description>
- <posx>20</posx>
- <posy>10</posy>
- <width>70</width>
- <height>70</height>
- <aspectratio>keep</aspectratio>
- <texture>DefaultFile.png</texture>
- </control>
- <control type="fadelabel" id="401">
- <description>Line 1 Label</description>
- <posx>95</posx>
- <posy>15</posy>
- <width>310</width>
- <height>18</height>
- <font>font12_title</font>
- <textcolor>ffffffff</textcolor> <!--
white -->
- <align>left</align>
- <aligny>center</aligny>
- <scrollout>false</scrollout>
- <pauseatend>2000</pauseatend>
- </control>
- <control type="fadelabel" id="402">
- <description>Line 2 Label</description>
- <posx>95</posx>
- <posy>35</posy>
- <width>310</width>
- <height>20</height>
- <font>font12_title</font>
- <textcolor>7fffffff</textcolor> <!--
grey -->
- <align>left</align>
- <aligny>center</aligny>
- <scrollout>false</scrollout>
- <pauseatend>2000</pauseatend>
- </control>
- <control type="fadelabel" id="403">
- <description>Line 3 Label</description>
- <posx>95</posx>
- <posy>55</posy>
- <width>310</width>
- <height>20</height>
- <font>font12_title</font>
- <textcolor>7fffffff</textcolor> <!--
grey -->
- <align>left</align>
- <aligny>center</aligny>
- <scrollout>false</scrollout>
- <pauseatend>2000</pauseatend>
- </control>
- </control>
- </controls>
-</window>
+<?xml version="1.0" encoding="utf-8"?>
+<window id="3001">
+ <animation effect="fade" start="0" end="100"
time="200">WindowOpen</animation>
+ <animation effect="fade" start="100" end="0"
time="200">WindowClose</animation>
+ <coordinates>
+ <posx>840</posx>
+ <posy>620</posy>
+ </coordinates>
+ <controls>
+ <control type="group">
+ <animation effect="slide" start="0,0" end="-190,0"
time="200" condition="Window.IsVisible(BusyDialog)">conditional</animation>
+ <control type="image">
+ <posx>0</posx>
+ <posy>0</posy>
+ <width>420</width>
+ <height>90</height>
+ <colordiffuse>CCFFFFFF</colordiffuse>
+ <texture
border="12">[email protected]</texture>
+ </control>
+ <control type="image">
+ <posx>5</posx>
+ <posy>5</posy>
+ <width>410</width>
+ <height>80</height>
+ <texture
border="20">msNowPlayingBg_ipad.png</texture>
+ </control>
+ <control type="image" id="400">
+ <description>avatar</description>
+ <posx>20</posx>
+ <posy>10</posy>
+ <width>70</width>
+ <height>70</height>
+ <aspectratio>keep</aspectratio>
+ <texture>DefaultFile.png</texture>
+ </control>
+ <control type="fadelabel" id="401">
+ <description>Line 1 Label</description>
+ <posx>95</posx>
+ <posy>15</posy>
+ <width>310</width>
+ <height>18</height>
+ <font>font12_title</font>
+ <textcolor>ffffffff</textcolor> <!--
white -->
+ <align>left</align>
+ <aligny>center</aligny>
+ <scrollout>false</scrollout>
+ <pauseatend>2000</pauseatend>
+ </control>
+ <control type="fadelabel" id="402">
+ <description>Line 2 Label</description>
+ <posx>95</posx>
+ <posy>35</posy>
+ <width>310</width>
+ <height>20</height>
+ <font>font12_title</font>
+ <textcolor>7fffffff</textcolor> <!--
grey -->
+ <align>left</align>
+ <aligny>center</aligny>
+ <scrollout>false</scrollout>
+ <pauseatend>2000</pauseatend>
+ </control>
+ <control type="fadelabel" id="403">
+ <description>Line 3 Label</description>
+ <posx>95</posx>
+ <posy>55</posy>
+ <width>310</width>
+ <height>20</height>
+ <font>font12_title</font>
+ <textcolor>7fffffff</textcolor> <!--
grey -->
+ <align>left</align>
+ <aligny>center</aligny>
+ <scrollout>false</scrollout>
+ <pauseatend>2000</pauseatend>
+ </control>
+ </control>
+ </controls>
+</window>
diff --git a/script.sonos/service.py b/script.sonos/service.py
index 02f9498..41bdfec 100644
--- a/script.sonos/service.py
+++ b/script.sonos/service.py
@@ -175,27 +175,34 @@ class SonosAutoPause():
# Check if the Sonos system should be paused or resumed
def check(self):
if Settings.autoPauseSonos() and not Settings.linkAudioWithSonos():
- # Check to see if something has started playing
- if xbmc.Player().isPlaying():
- # If this is a change in play state since the last time we
checked
- if self.xbmcPlayState is False:
- log("SonosAutoPause: Automatically pausing Sonos")
- self.xbmcPlayState = True
- # Pause the sonos if it is playing
- if self._isSonosPlaying():
- self.sonosDevice.pause()
- self.autoStopped = True
- self.resumeCountdown = Settings.autoResumeSonos()
- else:
- self.xbmcPlayState = False
- if Settings.autoResumeSonos() > 0 and self.autoStopped:
- if self.resumeCountdown > 0:
- self.resumeCountdown = self.resumeCountdown - 1
- else:
- log("SonosAutoPause: Automatically resuming Sonos")
- self.sonosDevice.play()
- self.autoStopped = False
- self.resumeCountdown = Settings.autoResumeSonos()
+ try:
+ # Check to see if something has started playing
+ if xbmc.Player().isPlaying():
+ # If this is a change in play state since the last time we
checked
+ if self.xbmcPlayState is False:
+ log("SonosAutoPause: Automatically pausing Sonos")
+ self.xbmcPlayState = True
+ # Pause the sonos if it is playing
+ if self._isSonosPlaying():
+ self.sonosDevice.pause()
+ self.autoStopped = True
+ self.resumeCountdown = Settings.autoResumeSonos()
+ else:
+ self.xbmcPlayState = False
+ if Settings.autoResumeSonos() > 0 and self.autoStopped:
+ if self.resumeCountdown > 0:
+ self.resumeCountdown = self.resumeCountdown - 1
+ else:
+ log("SonosAutoPause: Automatically resuming Sonos")
+ self.sonosDevice.play()
+ self.autoStopped = False
+ self.resumeCountdown = Settings.autoResumeSonos()
+ except:
+ # If we fail to stop the speaker playing, it may be because
+ # there is a network problem or the speaker is powered down
+ # So we just continue after logging the error
+ log("SonosAutoPause: Error from speaker %s" %
Settings.getIPAddress())
+ log("SonosAutoPause: %s" % traceback.format_exc())
# Works out if the Sonos system is playing
def _isSonosPlaying(self):
-----------------------------------------------------------------------
Summary of changes:
script.sonos/addon.xml | 3 +-
script.sonos/changelog.txt | 3 +
script.sonos/discovery.py | 20 +-
script.sonos/resources/language/English/strings.po | 14 +-
script.sonos/resources/lib/mocksonos.py | 206 +++---
.../resources/lib/soco/event_structures.py | 630 ++++++++--------
script.sonos/resources/lib/sonos.py | 402 +++++-----
script.sonos/resources/settings.xml | 72 +-
.../skins/Default/720p/script-sonos-controller.xml | 786 ++++++++++----------
.../Default/720p/script-sonos-notif-popup.xml | 154 ++--
script.sonos/service.py | 49 +-
11 files changed, 1189 insertions(+), 1150 deletions(-)
hooks/post-receive
--
Scripts
------------------------------------------------------------------------------
Slashdot TV.
Video for Nerds. Stuff that matters.
http://tv.slashdot.org/
_______________________________________________
Xbmc-addons mailing list
[email protected]
https://lists.sourceforge.net/lists/listinfo/xbmc-addons