From acd60c293e205b77d7d993977a5f6d8431dcf24a Mon Sep 17 00:00:00 2001
From: rleins <>
Date: Fri, 6 Feb 2015 19:27:46 +0000
Subject: [PATCH] Sonos: Add feature "LoadSearchlist"
git-svn-id: https://svn.fhem.de/fhem/trunk@7896 2b470e98-0d58-463d-a4d8-8e2adae1ed80
---
fhem/FHEM/00_SONOS.pm | 559 ++++++++++++++++++++++++++++--------
fhem/FHEM/21_SONOSPLAYER.pm | 49 +++-
2 files changed, 483 insertions(+), 125 deletions(-)
diff --git a/fhem/FHEM/00_SONOS.pm b/fhem/FHEM/00_SONOS.pm
index 4972a803b..765b7f459 100755
--- a/fhem/FHEM/00_SONOS.pm
+++ b/fhem/FHEM/00_SONOS.pm
@@ -27,7 +27,7 @@
# * Add another Packagesource from suggestions or manual: Bribes de Perl (http://www.bribes.org/perl/ppm)
# * Install Package: SOAP::Lite
#
-# Windows ActivePerl 64Bit is currently not functioning due to missing SOAP::Lite
+# Windows ActivePerl 5.20 does currently not work due to missing SOAP::Lite
#
########################################################################################
# Configuration:
@@ -47,6 +47,11 @@
# Changelog
#
# SVN-History:
+# 06.02.2015
+# Es wurde im Standard-RemoteControl-Design ein :blank zwischen den Steuerbefehlen und den drei Umschaltbefehlen ("MuteT", "ShuffleT" und "RepeatT") eingefügt.
+# Es gibt ein neues Reading "roomNameAlias", das den Namen enthält, der für das Attribut "alias" beim Erkennen des Players verwendet werden würde (z.B. "Wohnzimmer - Rechts"). Wird zu Laufzeit mit aktualisiert.
+# Es gibt zwei neue Setter-Befehle "LoadSearchlist" und "StartSearchlist". Mit diesen kann eine dynamisch erzeugte Playliste mit Titeln aus der Sonos-Bibliothek geladen werden. Nähere Informationen dazu im Wiki.
+# Es gibt einen neuen Getter-Befehl "SearchlistCategories", mit dem die möglichen Kategorien für den Aufruf von "LoadSearchlist" oder "StartSearchlist" ermittelt werden können.
# 01.02.2015
# Es gibt nun zwei neue Befehle "ShuffleT" und "RepeatT", die jeweils den aktuellen Zustand von "Shuffle" und "Repeat" umschalten
# Das angelegte RemoteControl sowie die RemoteControl Vorlagen enthalten nun zwei neue Icons für Shuffle-Umschaltung und Repeat-Umschaltung
@@ -397,7 +402,7 @@ my %sets = (
my @SONOS_PossibleDefinitions = qw(NAME INTERVAL);
my @SONOS_PossibleAttributes = qw(targetSpeakFileHashCache targetSpeakFileTimestamp targetSpeakDir targetSpeakURL Speak0 Speak1 Speak2 Speak3 Speak4 SpeakCover Speak1Cover Speak2Cover Speak3Cover Speak4Cover minVolume maxVolume minVolumeHeadphone maxVolumeHeadphone getAlarms disable generateVolumeEvent buttonEvents characterDecoding generateProxyAlbumArtURLs proxyCacheTime);
-my @SONOS_PossibleReadings = qw(AlarmList AlarmListIDs UserID_Spotify UserID_Napster location SleepTimerVersion Mute HeadphoneConnected Balance Volume Loudness Bass Treble AlarmListVersion ZonePlayerUUIDsInGroup ZoneGroupID fieldType ZoneGroupName roomName roomIcon LineInConnected currentAlbum currentArtist currentTitle);
+my @SONOS_PossibleReadings = qw(AlarmList AlarmListIDs UserID_Spotify UserID_Napster location SleepTimerVersion Mute HeadphoneConnected Balance Volume Loudness Bass Treble AlarmListVersion ZonePlayerUUIDsInGroup ZoneGroupID fieldType ZoneGroupName roomName roomNameAlias roomIcon LineInConnected currentAlbum currentArtist currentTitle);
# Obsolete Einstellungen...
my $SONOS_UseTelnetForQuestions = 1;
@@ -527,7 +532,7 @@ sub SONOS_Initialize ($) {
sub SONOS_RCLayout() {
my @rows = ();
- push @rows, "Play:PLAY,Pause:PAUSE,Previous:REWIND,Next:FF,VolumeD:VOLDOWN,VolumeU:VOLUP,MuteT:MUTE,ShuffleT:SHUFFLE,RepeatT:REPEAT";
+ push @rows, "Play:PLAY,Pause:PAUSE,Previous:REWIND,Next:FF,:blank,VolumeD:VOLDOWN,VolumeU:VOLUP,:blank,MuteT:MUTE,ShuffleT:SHUFFLE,RepeatT:REPEAT";
push @rows, "attr rc_iconpath icons/remotecontrol";
push @rows, "attr rc_iconprefix black_btn_";
@@ -542,7 +547,7 @@ sub SONOS_RCLayout() {
sub SONOS_RCLayoutSVG1() {
my @rows = ();
- push @rows, "Play:rc_PLAY.svg,Pause:rc_PAUSE.svg,Previous:rc_PREVIOUS.svg,Next:rc_NEXT.svg,VolumeD:rc_VOLDOWN.svg,VolumeU:rc_VOLUP.svg,MuteT:rc_MUTE.svg,ShuffleT:rc_SHUFFLE.svg,RepeatT:rc_REPEAT.svg";
+ push @rows, "Play:rc_PLAY.svg,Pause:rc_PAUSE.svg,Previous:rc_PREVIOUS.svg,Next:rc_NEXT.svg,:blank,VolumeD:rc_VOLDOWN.svg,VolumeU:rc_VOLUP.svg,:blank,MuteT:rc_MUTE.svg,ShuffleT:rc_SHUFFLE.svg,RepeatT:rc_REPEAT.svg";
push @rows, "attr rc_iconpath icons/remotecontrol";
push @rows, "attr rc_iconprefix black_btn_";
@@ -557,7 +562,7 @@ sub SONOS_RCLayoutSVG1() {
sub SONOS_RCLayoutSVG2() {
my @rows = ();
- push @rows, "Play:audio_play.svg,Pause:audio_pause.svg,Previous:audio_rew.svg,Next:audio_ff.svg,VolumeD:audio_volume_low.svg,VolumeU:audio_volume_high.svg,MuteT:audio_volume_mute.svg,ShuffleT:audio_shuffle.svg,RepeatT:audio_repeat.svg";
+ push @rows, "Play:audio_play.svg,Pause:audio_pause.svg,Previous:audio_rew.svg,Next:audio_ff.svg,:blank,VolumeD:audio_volume_low.svg,VolumeU:audio_volume_high.svg,:blank,MuteT:audio_volume_mute.svg,ShuffleT:audio_shuffle.svg,RepeatT:audio_repeat.svg";
push @rows, "attr rc_iconpath icons/remotecontrol";
push @rows, "attr rc_iconprefix black_btn_";
@@ -776,7 +781,7 @@ sub SONOS_getGroupsRG() {
my $i = 0;
while ($groups =~ m/\[(.*?)\]/ig) {
my @member = split(/, /, $1);
- @member = map FW_makeImage('icoSONOSPLAYER_icon-'.ReadingsVal($_, 'playerType', '').'.png', '', '').ReadingsVal($_, 'roomName', $_), @member;
+ @member = map FW_makeImage('icoSONOSPLAYER_icon-'.ReadingsVal($_, 'playerType', '').'.png', '', '').ReadingsVal($_, 'roomNameAlias', $_), @member;
$result .= '
'.++$i.'. Gruppe:';
}
@@ -2547,6 +2552,302 @@ sub SONOS_Discover() {
SONOS_MakeSigHandlerReturnValue($udn, 'LastActionResult', ucfirst($workType).': '.Dumper(\%resultHash));
$Data::Dumper::Indent = 2;
}
+ } elsif ($workType eq 'getSearchlistCategories') {
+ if (SONOS_CheckProxyObject($udn, $SONOS_ContentDirectoryControlProxy{$udn})) {
+ my $result = $SONOS_ContentDirectoryControlProxy{$udn}->Browse('A:', 'BrowseDirectChildren', '', 0, 0, '');
+ my $tmp = $result->getValue('Result');
+
+ SONOS_Log $udn, 5, 'getSearchlistCategories BrowseResult: '.$tmp;
+
+ my %resultHash;
+ while ($tmp =~ m/(.*?)<\/dc:title>.*?<\/container>/ig) {
+ $resultHash{$1} = $2;
+ }
+
+ $Data::Dumper::Indent = 0;
+ SONOS_MakeSigHandlerReturnValue($udn, 'LastActionResult', ucfirst($workType).': "'.join('","', sort values %resultHash).'"');
+ $Data::Dumper::Indent = 2;
+ }
+ } elsif ($workType eq 'loadSearchlist') {
+ # Category holen
+ my $regSearch = ($params[0] =~ m/^ *\/(.*)\/ *$/);
+ my $searchlistName = $1 if ($regSearch);
+ $searchlistName = uri_unescape($params[0]) if (!$regSearch);
+
+ # RegEx prüfen...
+ if ($regSearch) {
+ eval { "" =~ m/$searchlistName/ };
+ if($@) {
+ SONOS_MakeSigHandlerReturnValue($udn, 'LastActionResult', ucfirst($workType).': Bad Category RegExp "'.$searchlistName.'": '.$@);
+ return;
+ }
+ }
+
+ # Element holen
+ $params[1] = '' if (!$params[1]);
+ my $regSearchElement = ($params[1] =~ m/^ *\/(.*)\/ *$/);
+ my $searchlistElement = $1 if ($regSearchElement);
+ $searchlistElement = uri_unescape($params[1]) if (!$regSearchElement);
+
+ # RegEx prüfen...
+ if ($regSearchElement) {
+ eval { "" =~ m/$searchlistElement/ };
+ if($@) {
+ SONOS_MakeSigHandlerReturnValue($udn, 'LastActionResult', ucfirst($workType).': Bad CategoryElement RegExp "'.$searchlistElement.'": '.$@);
+ return;
+ }
+ }
+
+ # Filter angegeben?
+ my $filter = '//';
+ $filter = $params[2] if ($params[2]);
+ $filter .= '/' while ((SONOS_CountInString('/', $filter) - SONOS_CountInString('\/', $filter)) < 2);
+ my ($filterTitle, $filterAlbum, $filterArtist) = ($1, $3, $5) if ($filter =~ m/((.*?[^\\])|.{0})\/((.*?[^\\])|.{0})\/(.*)/);
+ $filterTitle = '.*' if (!$filterTitle);
+ $filterAlbum = '.*' if (!$filterAlbum);
+ $filterArtist = '.*' if (!$filterArtist);
+ SONOS_Log $udn, 4, 'getSearchlist filterTitle: '.$filterTitle;
+ SONOS_Log $udn, 4, 'getSearchlist filterAlbum: '.$filterAlbum;
+ SONOS_Log $udn, 4, 'getSearchlist filterArtist: '.$filterArtist;
+
+ # RegEx prüfen...
+ eval { "" =~ m/$filterTitle/ };
+ if($@) {
+ SONOS_MakeSigHandlerReturnValue($udn, 'LastActionResult', ucfirst($workType).': Bad FilterTitle RegExp "'.$filterTitle.'": '.$@);
+ return;
+ }
+
+ # RegEx prüfen...
+ eval { "" =~ m/$filterAlbum/ };
+ if($@) {
+ SONOS_MakeSigHandlerReturnValue($udn, 'LastActionResult', ucfirst($workType).': Bad FilterAlbum RegExp "'.$filterAlbum.'": '.$@);
+ return;
+ }
+
+ # RegEx prüfen...
+ eval { "" =~ m/$filterArtist/ };
+ if($@) {
+ SONOS_MakeSigHandlerReturnValue($udn, 'LastActionResult', ucfirst($workType).': Bad FilterArtist RegExp "'.$filterArtist.'": '.$@);
+ return;
+ }
+
+ # Menge angegeben? Hier kann auch mit einem '*' eine zufällige Reihenfolge bestimmt werden...
+ my $maxElems = '0-';
+ $maxElems = $params[3] if ($params[3]);
+
+ # Anfragen durchführen...
+ if (SONOS_CheckProxyObject($udn, $SONOS_ContentDirectoryControlProxy{$udn})) {
+ my $result = $SONOS_ContentDirectoryControlProxy{$udn}->Browse('A:', 'BrowseDirectChildren', '', 0, 0, '');
+ my $tmp = $result->getValue('Result');
+
+ SONOS_Log $udn, 5, 'getSearchlistCategories BrowseResult: '.$tmp;
+
+ # Category heraussuchen
+ my %resultHash;
+ while ($tmp =~ m/(.*?)<\/dc:title>.*?<\/container>/ig) {
+ next if (SONOS_Trim($2) eq ''); # Wenn kein Titel angegeben ist, dann überspringen
+
+ my $name = $2;
+ $resultHash{$name} = $1;
+
+ # Den ersten Match ermitteln, und sich den echten Namen für die Zukunft merken...
+ if ($regSearch) {
+ if ($name =~ m/$searchlistName/) {
+ $searchlistName = $name;
+ $regSearch = 0;
+ }
+ }
+ }
+
+ # Wenn RegSearch gesetzt war, und nichts gefunden wurde...
+ if (!$resultHash{$searchlistName} || $regSearch) {
+ SONOS_MakeSigHandlerReturnValue($udn, 'LastActionResult', ucfirst($workType).': Category "'.$searchlistName.'" not found. Choose one of: "'.join('","', sort keys %resultHash).'"');
+ return;
+ }
+ my $searchlistTitle = $searchlistName;
+ $searchlistName = $resultHash{$searchlistName};
+
+ ###############################################
+ # Elemente der Category heraussuchen
+ ###############################################
+ $result = $SONOS_ContentDirectoryControlProxy{$udn}->Browse($searchlistName, 'BrowseDirectChildren', '', 0, 0, '');
+ $tmp = $result->getValue('Result');
+
+ my $numberReturned = $result->getValue('NumberReturned');
+ my $totalMatches = $result->getValue('TotalMatches');
+ SONOS_Log $udn, 4, 'getSearchlistCategoriesElements StepInfo_0 - NumberReturned: '.$numberReturned.' - Totalmatches: '.$totalMatches;
+ while ($numberReturned < $totalMatches) {
+ $result = $SONOS_ContentDirectoryControlProxy{$udn}->Browse($searchlistName, 'BrowseDirectChildren', '', $numberReturned, 0, '');
+ $tmp .= $result->getValue('Result');
+
+ $numberReturned += $result->getValue('NumberReturned');
+ $totalMatches = $result->getValue('TotalMatches');
+
+ SONOS_Log $udn, 4, 'getSearchlistCategoriesElements StepInfo - NumberReturned: '.$numberReturned.' - Totalmatches: '.$totalMatches;
+ }
+
+ SONOS_Log $udn, 4, 'getSearchlistCategoriesElements Totalmatches: '.$totalMatches;
+ SONOS_Log $udn, 5, 'getSearchlistCategoriesElements BrowseResult: '.$tmp;
+
+ # Category heraussuchen
+ my $searchlistElementTitle = $searchlistElement;
+ if ($tmp =~ m/.*?<\/container>/ig) { # Wenn überhaupt noch was zu suchen ist...
+ %resultHash = ();
+ while ($tmp =~ m/(.*?)<\/dc:title>.*?<\/container>/ig) {
+ next if (SONOS_Trim($2) eq ''); # Wenn kein Titel angegeben ist, dann überspringen
+
+ my $name = $2;
+ $resultHash{$name} = $1;
+
+ # Den ersten Match ermitteln, und sich den echten Namen für die Zukunft merken...
+ if ($regSearchElement) {
+ if ($name =~ m/$searchlistElement/) {
+ $searchlistElement = $name;
+ $regSearchElement = 0;
+ }
+ }
+ }
+
+ # Wenn RegSearch gesetzt war, und nichts gefunden wurde...
+ if (!$resultHash{$searchlistElement} || $regSearchElement) {
+ SONOS_MakeSigHandlerReturnValue($udn, 'LastActionResult', ucfirst($workType).': Element "'.$searchlistElement.'" not found. Choose one of: "'.join('","', sort keys %resultHash).'"');
+ return;
+ }
+ $searchlistElementTitle = $searchlistElement;
+ $searchlistElement = $resultHash{$searchlistElement};
+
+
+ ###############################################
+ # Ziel-Elemente ermitteln und filtern
+ ###############################################
+ $result = $SONOS_ContentDirectoryControlProxy{$udn}->Browse($searchlistElement, 'BrowseDirectChildren', '', 0, 0, '');
+ $tmp = $result->getValue('Result');
+
+ # Wenn hier noch eine Schicht Container enthalten ist, dann nochmal tiefer gehen...
+ while ($tmp && ($tmp =~ m/.*?<\/container>/i)) {
+ $searchlistElement .= '/';
+ $result = $SONOS_ContentDirectoryControlProxy{$udn}->Browse($searchlistElement, 'BrowseDirectChildren', '', 0, 0, '');
+ $tmp = $result->getValue('Result');
+ }
+
+ $numberReturned = $result->getValue('NumberReturned');
+ $totalMatches = $result->getValue('TotalMatches');
+ SONOS_Log $udn, 4, 'getSearchlistCategoriesElementsEl StepInfo_0 - NumberReturned: '.$numberReturned.' - Totalmatches: '.$totalMatches;
+ while ($numberReturned < $totalMatches) {
+ $result = $SONOS_ContentDirectoryControlProxy{$udn}->Browse($searchlistElement, 'BrowseDirectChildren', '', $numberReturned, 0, '');
+ $tmp .= $result->getValue('Result');
+
+ $numberReturned += $result->getValue('NumberReturned');
+ $totalMatches = $result->getValue('TotalMatches');
+
+ SONOS_Log $udn, 4, 'getSearchlistCategoriesElementsEl StepInfo - NumberReturned: '.$numberReturned.' - Totalmatches: '.$totalMatches;
+ }
+
+ SONOS_Log $udn, 4, 'getSearchlistCategoriesElementsEl Totalmatches: '.$totalMatches;
+ SONOS_Log $udn, 5, 'getSearchlistCategoriesElementsEl BrowseResult: '.$tmp;
+ }
+
+ # Elemente heraussuchen
+ %resultHash = ();
+ my @URIs = ();
+ my @Metas = ();
+ while ($tmp =~ m/- (.*?)<\/item>/ig) {
+ my $item = $2;
+
+ my $uri = $1 if ($item =~ m/(.*?)<\/res>/i);
+ $uri =~ s/'/'/gi;
+
+ my $title = '';
+ $title = $1 if ($item =~ m/(.*?)<\/dc:title>/i);
+
+ my $album = '';
+ $album = $1 if ($item =~ m/(.*?)<\/upnp:album>/i);
+
+ my $interpret = '';
+ $interpret = $1 if ($item =~ m/(.*?)<\/dc:creator>/i);
+
+ # Die Matches merken...
+ if (($title =~ m/$filterTitle/) && ($album =~ m/$filterAlbum/) && ($interpret =~ m/$filterArtist/)) {
+ my ($res, $meta) = SONOS_CreateURIMeta(SONOS_ExpandURIForQueueing($uri));
+
+ push(@URIs, $res);
+ push(@Metas, $meta);
+ }
+ }
+
+ my $answer = 'Retrieved all titles of category "'.$searchlistTitle.'" with searchvalue "'.$searchlistElementTitle.'" and filter "'.$filterTitle.'/'.$filterAlbum.'/'.$filterArtist.'" (#'.($#URIs + 1).'). ';
+
+ # Liste u.U. vermischen...
+ my @matches = (0..$#URIs);
+ if ($maxElems =~ m/^\*/) {
+ SONOS_Fisher_Yates_Shuffle(\@matches);
+ $answer .= 'Shuffled the searchlist. ';
+ }
+
+ # Nicht alle übernehmen?
+ if ($maxElems =~ m/^\*{0,1}(\d+)-{0,1}$/) {
+ splice(@matches, $1) if ($1 && ($1 <= $#matches));
+ SONOS_Log $udn, 4, 'getSearchlist maxElems('.$maxElems.'): '.$1;
+ }
+ SONOS_Log $udn, 4, 'getSearchlist Count Matches: '.($#matches + 1);
+
+ # Wenn der AVTransportProxy existiert weitermachen...
+ if (SONOS_CheckProxyObject($udn, $SONOS_AVTransportControlProxy{$udn})) {
+ # Playlist vorher leeren?
+ if ($maxElems =~ m/-$/) {
+ $SONOS_AVTransportControlProxy{$udn}->RemoveAllTracksFromQueue();
+ $answer .= 'Queue successfully emptied. ';
+ }
+
+ my $currentInsertPos = $SONOS_AVTransportControlProxy{$udn}->GetPositionInfo(0)->getValue('Track') + 1;
+
+ # Die Matches in die Playlist laden...
+ my $sliceSize = 16;
+ my $count = 0;
+
+ SONOS_Log $udn, 4, "Start-Adding: Count ".scalar(@matches)." / $sliceSize";
+
+ if (scalar(@matches)) {
+ for my $i (0..int(scalar(@matches) / $sliceSize)) { # Da hier Nullbasiert vorgegangen wird, brauchen wir die letzte Runde nicht noch hinzuaddieren
+ my $startIndex = $i * $sliceSize;
+ my $endIndex = $startIndex + $sliceSize - 1;
+ $endIndex = SONOS_Min(scalar(@matches) - 1, $endIndex);
+
+ SONOS_Log $udn, 4, "Add($i) von $startIndex bis $endIndex (".($endIndex - $startIndex + 1)." Elemente)";
+
+ my $uri = '';
+ my $meta = '';
+ for my $index (@matches[$startIndex..$endIndex]) {
+ $uri .= ' '.$URIs[$index];
+ $meta .= ' '.$Metas[$index];
+ }
+ $uri = substr($uri, 1) if (length($uri) > 0);
+ $meta = substr($meta, 1) if (length($meta) > 0);
+
+ $result = $SONOS_AVTransportControlProxy{$udn}->AddMultipleURIsToQueue(0, 0, $endIndex - $startIndex + 1, $uri, $meta, '', '', $currentInsertPos, 0);
+ if (!$result->isSuccessful()) {
+ $answer .= 'Adding-Error: '.SONOS_UPnPAnswerMessage($result).' ';
+ }
+
+ $currentInsertPos += $endIndex - $startIndex + 1;
+ $count = $endIndex + 1;
+ }
+
+ if ($result->isSuccessful()) {
+ $answer .= 'Added '.$count.' entries from searchlist. There are now '.$result->getValue('NewQueueLength').' entries in Queue. ';
+ } else {
+ $answer .= 'Adding-Error: '.SONOS_UPnPAnswerMessage($result).' ';
+ }
+ }
+
+ # Die Liste als aktuelles Abspielstück einstellen
+ my $queueMetadata = $SONOS_ContentDirectoryControlProxy{$udn}->Browse('Q:0', 'BrowseMetadata', '', 0, 0, '');
+ my $result = $SONOS_AVTransportControlProxy{$udn}->SetAVTransportURI(0, SONOS_GetTagData('res', $queueMetadata->getValue('Result')), '');
+ $answer .= 'Startlist: '.SONOS_UPnPAnswerMessage($result).'. ';
+
+ SONOS_MakeSigHandlerReturnValue($udn, 'LastActionResult', ucfirst($workType).': '.$answer);
+ }
+ }
} elsif ($workType eq 'getRadios') {
if (SONOS_CheckProxyObject($udn, $SONOS_ContentDirectoryControlProxy{$udn})) {
my $result = $SONOS_ContentDirectoryControlProxy{$udn}->Browse('R:0/0', 'BrowseDirectChildren', '', 0, 0, '');
@@ -2985,64 +3286,6 @@ sub SONOS_Discover() {
}
}
}
- } elsif ($workType eq 'createThemeList') {
- # set Player CreateThemeList [ ] [ShuffleList] [EmptyList] [Play]
- # set Player CreateThemeList ARTIST=*{1} EmptyList Play
- # set Player CreateThemeList ARTIST=Herbert%20Grönemeyer ShuffleList EmptyList Play
- # set Player CreateThemeList ARTIST=Herbert%20Grönemeyer ALBUM=Zwölf ShuffleList EmptyList Play
- # ARTIST, ALBUMARTIST, ALBUM, GENRE, COMPOSER, TRACKS
- # SearchValue: * -> Beliebiger Wert, {N} -> Anzahl einschränken
-
- my $shuffleList = 0;
- my $emptyList = 0;
- my $play = 0;
- my %searches;
-
- my $answer = '';
-
- #while ($SONOS_ComObjectTransportQueue->pending() > 0) {
- # my $tmp = $SONOS_ComObjectTransportQueue->dequeue();
- #
- # if ($tmp =~ /ShuffleList/i) {
- # $shuffleList = 1;
- # } elsif ($tmp =~ /EmptyList/i) {
- # $emptyList = 1;
- # } elsif ($tmp =~ /Play/i) {
- # $play = 1;
- # } elsif ($tmp =~ /(.*?)=(.*?)/) {
- # $searches{$1} = $2;
- # } else {
- # SONOS_Log $udn, 1, 'Error during parsing of CreateThemeList-Parameter: "'.$tmp.'". Ignoring it!';
- # }
- #}
-
- if (SONOS_CheckProxyObject($udn, $SONOS_AVTransportControlProxy{$udn}) && SONOS_CheckProxyObject($udn, $SONOS_ContentDirectoryControlProxy{$udn})) {
- # EmptyList before adding new elements
- if ($emptyList) {
- $answer .= ', EmptyList: '.SONOS_UPnPAnswerMessage($SONOS_AVTransportControlProxy{$udn}->RemoveAllTracksFromQueue());
- }
-
- # Search and Load
-
- # Shuffle retrieved list
- if ($shuffleList) {
- # Do shuffeling here
-
- $answer .= ', ShuffleList: '.SONOS_UPnPAnswerMessage(0);
- }
-
- # Die Liste als aktuelles Abspielstück einstellen
- my $queueMetadata = $SONOS_ContentDirectoryControlProxy{$udn}->Browse('Q:0', 'BrowseMetadata', '', 0, 0, '');
- my $result = $SONOS_AVTransportControlProxy{$udn}->SetAVTransportURI(0, SONOS_GetTagData('res', $queueMetadata->getValue('Result')), '');
- $answer .= ', Startlist: '.SONOS_UPnPAnswerMessage($result);
-
- # Play afterwards?
- if ($play) {
- $answer .= ', Play: '.SONOS_UPnPAnswerMessage($SONOS_AVTransportControlProxy{$udn}->Play(0, 1));
- }
-
- SONOS_MakeSigHandlerReturnValue($udn, 'LastActionResult', ucfirst($workType).': '.substr($answer, 2)); # Das führende Komma wieder entfernen
- }
} elsif ($workType eq 'deleteProxyObjects') {
# Wird vom Sonos-Device selber in IsAlive benötigt
SONOS_DeleteProxyObjects($udn);
@@ -3302,6 +3545,54 @@ sub SONOS_Discover() {
return 1;
}
+########################################################################################
+#
+# SONOS_Fisher_Yates_Shuffle - Shuffles the given array
+#
+########################################################################################
+sub SONOS_Fisher_Yates_Shuffle($) {
+ my ($deck) = @_; # $deck is a reference to an array
+ my $i = @$deck;
+
+ while ($i--) {
+ my $j = int rand ($i+1);
+ @$deck[$i,$j] = @$deck[$j,$i];
+ }
+}
+
+########################################################################################
+#
+# SONOS_Trim - Trim the given string
+#
+########################################################################################
+sub SONOS_Trim($) {
+ my ($str) = @_;
+
+ return $1 if ($str =~ m/^\W*(.*?)\W*$/);
+ return $str;
+}
+
+########################################################################################
+#
+# SONOS_CountInString - Count the occurences of the first string in the second string
+#
+########################################################################################
+sub SONOS_CountInString($$) {
+ my ($search, $str) = @_;
+
+ my $pos = 0;
+ my $matches = 0;
+
+ while (1) {
+ $pos = index($str, $search, $pos);
+ last if($pos < 0);
+ $matches++;
+ $pos++;
+ }
+
+ return $matches;
+}
+
########################################################################################
#
# SONOS_MakeCoverURL - Generates the approbriate cover-url incl. the use of an Fhem-Proxy
@@ -4320,49 +4611,7 @@ sub SONOS_Discover_Callback($$$) {
SONOS_Log undef, 4, 'ControlProxies wurden gesichert';
# ZoneTopology laden, um die Benennung der Fhem-Devices besser an die Realität anpassen zu können
- my $topoType = '';
- my $fieldType = '';
- my $master = 1;
- if ($SONOS_ZoneGroupTopologyProxy{$udn}) {
- my $zoneGroupState = $SONOS_ZoneGroupTopologyProxy{$udn}->GetZoneGroupState()->getValue('ZoneGroupState');
- SONOS_Log undef, 5, 'ZoneGroupState: '.$zoneGroupState;
-
- if ($zoneGroupState =~ m/.*().*?(<(ZoneGroupMember|Satellite) UUID="$udnShort".*?(>|\/>))/is) {
- my $coordinator = $2;
- my $member = $3;
-
- # Ist dieser Player in einem ChannelMapSet (also einer Paarung) enthalten?
- if ($member =~ m/ChannelMapSet=".*?$udnShort:(.*?),(.*?)[;"]/is) {
- $topoType = '_'.$1;
- }
-
- # Ist dieser Player in einem HTSatChanMapSet (also einem Surround-System) enthalten?
- if ($member =~ m/HTSatChanMapSet=".*?$udnShort:(.*?)[;"]/is) {
- $topoType = '_'.$1;
- $topoType =~ s/,/_/g;
- }
-
- SONOS_Log undef, 4, 'Retrieved TopoType: '.$topoType;
- $fieldType = substr($topoType, 1) if ($topoType);
-
- my $invisible = 0;
- $invisible = 1 if ($member =~ m/Invisible="1"/i);
-
- my $isZoneBridge = 0;
- $isZoneBridge = 1 if ($member =~ m/IsZoneBridge="1"/i);
-
- $master = !$invisible || $isZoneBridge;
- }
- }
-
- # Für den Aliasnamen schöne Bezeichnungen ermitteln...
- my $aliasSuffix = '';
- $aliasSuffix = ' - Hinten Links' if ($topoType eq '_LR');
- $aliasSuffix = ' - Hinten Rechts' if ($topoType eq '_RR');
- $aliasSuffix = ' - Links' if ($topoType eq '_LF');
- $aliasSuffix = ' - Rechts' if ($topoType eq '_RF');
- $aliasSuffix = ' - Subwoofer' if ($topoType eq '_SW');
- $aliasSuffix = ' - Mitte' if ($topoType eq '_LF_RF');
+ my ($topoType, $fieldType, $master, $aliasSuffix) = SONOS_AnalyzeZoneGroupTopology($udn, $udnShort);
# Wenn der aktuelle Player der Master ist, dann kein Kürzel anhängen,
# damit gibt es immer einen Player, der den Raumnamen trägt, und die anderen enthalten Kürzel
@@ -4451,14 +4700,14 @@ sub SONOS_Discover_Callback($$$) {
# Define ReadingsGroup
if ($master) {
- SONOS_Client_Notifier('CommandDefine:'.$name.'RG ReadingsGroup '.$name.':<{SONOS_getCoverTitleRG($DEVICE)}@infoSummarize2>');
+ SONOS_Client_Notifier('CommandDefine:'.$name.'RG readingsGroup '.$name.':<{SONOS_getCoverTitleRG($DEVICE)}@infoSummarize2>');
SONOS_Client_Notifier('CommandAttr:'.$name.'RG room '.$SONOS_Client_Data{SonosDeviceName});
SONOS_Client_Notifier('CommandAttr:'.$name.'RG group '.$groupName);
SONOS_Client_Notifier('CommandAttr:'.$name.'RG sortby 2');
SONOS_Client_Notifier('CommandAttr:'.$name.'RG noheading 1');
SONOS_Client_Notifier('CommandAttr:'.$name.'RG nonames 1');
- #SONOS_Client_Notifier('CommandDefine:'.$name.'RG2 ReadingsGroup '.$name.':infoSummarize2@{SONOSPLAYER_GetMasterPlayerName($DEVICE)}');
+ #SONOS_Client_Notifier('CommandDefine:'.$name.'RG2 readingsGroup '.$name.':infoSummarize2@{SONOSPLAYER_GetMasterPlayerName($DEVICE)}');
#SONOS_Client_Notifier('CommandAttr:'.$name.'RG2 valueFormat {" "}');
#SONOS_Client_Notifier('CommandAttr:'.$name.'RG2 valuePrefix {SONOS_getCoverTitleRG(SONOSPLAYER_GetMasterPlayerName($DEVICE))}');
#SONOS_Client_Notifier('CommandAttr:'.$name.'RG2 room '.$SONOS_Client_Data{SonosDeviceName});
@@ -4471,9 +4720,9 @@ sub SONOS_Discover_Callback($$$) {
# Define Readingsgroup Listen
if ($master) {
- SONOS_Client_Notifier('CommandDefine:'.$name.'RG_Favourites ReadingsGroup '.$name.':<{SONOS_getListRG($DEVICE,"Favourites",1)}@Favourites>');
- SONOS_Client_Notifier('CommandDefine:'.$name.'RG_Radios ReadingsGroup '.$name.':<{SONOS_getListRG($DEVICE,"Radios",1)}@Radios>');
- SONOS_Client_Notifier('CommandDefine:'.$name.'RG_Playlists ReadingsGroup '.$name.':<{SONOS_getListRG($DEVICE,"Playlists")}@Playlists>');
+ SONOS_Client_Notifier('CommandDefine:'.$name.'RG_Favourites readingsGroup '.$name.':<{SONOS_getListRG($DEVICE,"Favourites",1)}@Favourites>');
+ SONOS_Client_Notifier('CommandDefine:'.$name.'RG_Radios readingsGroup '.$name.':<{SONOS_getListRG($DEVICE,"Radios",1)}@Radios>');
+ SONOS_Client_Notifier('CommandDefine:'.$name.'RG_Playlists readingsGroup '.$name.':<{SONOS_getListRG($DEVICE,"Playlists")}@Playlists>');
}
# Define RemoteControl
@@ -4483,7 +4732,7 @@ sub SONOS_Discover_Callback($$$) {
SONOS_Client_Notifier('CommandAttr:'.$name.'RC group '.$SONOS_Client_Data{SonosDeviceName});
SONOS_Client_Notifier('CommandAttr:'.$name.'RC rc_iconpath icons/remotecontrol');
SONOS_Client_Notifier('CommandAttr:'.$name.'RC rc_iconprefix black_btn_');
- SONOS_Client_Notifier('CommandAttr:'.$name.'RC row00 Play:rc_PLAY.svg,Pause:rc_PAUSE.svg,Previous:rc_PREVIOUS.svg,Next:rc_NEXT.svg,VolumeD:rc_VOLDOWN.svg,VolumeU:rc_VOLUP.svg,MuteT:rc_MUTE.svg,ShuffleT:rc_SHUFFLE.svg,RepeatT:rc_REPEAT.svg');
+ SONOS_Client_Notifier('CommandAttr:'.$name.'RC row00 Play:rc_PLAY.svg,Pause:rc_PAUSE.svg,Previous:rc_PREVIOUS.svg,Next:rc_NEXT.svg,:blank,VolumeD:rc_VOLDOWN.svg,VolumeU:rc_VOLUP.svg,:blank,MuteT:rc_MUTE.svg,ShuffleT:rc_SHUFFLE.svg,RepeatT:rc_REPEAT.svg');
SONOS_Client_Notifier('CommandDefine:'.$name.'RC_Notify notify '.$name.'RC set '.$name.' $EVENT');
@@ -4509,6 +4758,7 @@ sub SONOS_Discover_Callback($$$) {
SONOS_Client_Data_Refresh('ReadingsBulkUpdateIfChanged', $udn, 'Volume', $currentVolume);
SONOS_Client_Data_Refresh('ReadingsBulkUpdateIfChanged', $udn, 'Balance', $balance);
SONOS_Client_Data_Refresh('ReadingsBulkUpdateIfChanged', $udn, 'roomName', $roomName);
+ SONOS_Client_Data_Refresh('ReadingsBulkUpdateIfChanged', $udn, 'roomNameAlias', $roomName.$aliasSuffix);
SONOS_Client_Data_Refresh('ReadingsBulkUpdateIfChanged', $udn, 'saveRoomName', $saveRoomName);
SONOS_Client_Data_Refresh('ReadingsBulkUpdateIfChanged', $udn, 'playerType', $modelNumber);
SONOS_Client_Data_Refresh('ReadingsBulkUpdateIfChanged', $udn, 'Volume', $currentVolume);
@@ -4635,6 +4885,57 @@ sub SONOS_Discover_Callback($$$) {
return 0;
}
+sub SONOS_AnalyzeZoneGroupTopology($$) {
+ my ($udn, $udnShort) = @_;
+
+ # ZoneTopology laden, um die Benennung der Fhem-Devices besser an die Realität anpassen zu können
+ my $topoType = '';
+ my $fieldType = '';
+ my $master = 1;
+ if ($SONOS_ZoneGroupTopologyProxy{$udn}) {
+ my $zoneGroupState = $SONOS_ZoneGroupTopologyProxy{$udn}->GetZoneGroupState()->getValue('ZoneGroupState');
+ SONOS_Log undef, 5, 'ZoneGroupState: '.$zoneGroupState;
+
+ if ($zoneGroupState =~ m/.*().*?(<(ZoneGroupMember|Satellite) UUID="$udnShort".*?(>|\/>))/is) {
+ my $coordinator = $2;
+ my $member = $3;
+
+ # Ist dieser Player in einem ChannelMapSet (also einer Paarung) enthalten?
+ if ($member =~ m/ChannelMapSet=".*?$udnShort:(.*?),(.*?)[;"]/is) {
+ $topoType = '_'.$1;
+ }
+
+ # Ist dieser Player in einem HTSatChanMapSet (also einem Surround-System) enthalten?
+ if ($member =~ m/HTSatChanMapSet=".*?$udnShort:(.*?)[;"]/is) {
+ $topoType = '_'.$1;
+ $topoType =~ s/,/_/g;
+ }
+
+ SONOS_Log undef, 4, 'Retrieved TopoType: '.$topoType;
+ $fieldType = substr($topoType, 1) if ($topoType);
+
+ my $invisible = 0;
+ $invisible = 1 if ($member =~ m/Invisible="1"/i);
+
+ my $isZoneBridge = 0;
+ $isZoneBridge = 1 if ($member =~ m/IsZoneBridge="1"/i);
+
+ $master = !$invisible || $isZoneBridge;
+ }
+ }
+
+ # Für den Aliasnamen schöne Bezeichnungen ermitteln...
+ my $aliasSuffix = '';
+ $aliasSuffix = ' - Hinten Links' if ($topoType eq '_LR');
+ $aliasSuffix = ' - Hinten Rechts' if ($topoType eq '_RR');
+ $aliasSuffix = ' - Links' if ($topoType eq '_LF');
+ $aliasSuffix = ' - Rechts' if ($topoType eq '_RF');
+ $aliasSuffix = ' - Subwoofer' if ($topoType eq '_SW');
+ $aliasSuffix = ' - Mitte' if ($topoType eq '_LF_RF');
+
+ return ($topoType, $fieldType, $master, $aliasSuffix);
+}
+
########################################################################################
#
# SONOS_IsAlive - Checks if the given Device is alive or not and triggers the proper event if status changed
@@ -4841,6 +5142,7 @@ sub SONOS_GetReadingsToCurrentHash($$) {
$current{AlarmRunningID} = ReadingsVal($name, 'AlarmRunningID', '');
$current{Presence} = ReadingsVal($name, 'presence', '');
$current{RoomName} = ReadingsVal($name, 'roomName', '');
+ $current{RoomNameAlias} = ReadingsVal($name, 'roomNameAlias', '');
$current{SaveRoomName} = ReadingsVal($name, 'saveRoomName', '');
$current{PlayerType} = ReadingsVal($name, 'playerType', '');
$current{Location} = ReadingsVal($name, 'location', '');
@@ -5585,6 +5887,18 @@ sub SONOS_ZoneGroupTopologyCallback($$) {
SONOS_Log undef, 4, 'Retrieved TopoType: '.$topoType;
$fieldType = substr($topoType, 1) if ($topoType ne '');
+
+ # Für den Aliasnamen schöne Bezeichnungen ermitteln...
+ my $aliasSuffix = '';
+ $aliasSuffix = ' - Hinten Links' if ($topoType eq '_LR');
+ $aliasSuffix = ' - Hinten Rechts' if ($topoType eq '_RR');
+ $aliasSuffix = ' - Links' if ($topoType eq '_LF');
+ $aliasSuffix = ' - Rechts' if ($topoType eq '_RF');
+ $aliasSuffix = ' - Subwoofer' if ($topoType eq '_SW');
+ $aliasSuffix = ' - Mitte' if ($topoType eq '_LF_RF');
+
+ my $roomName = SONOS_Client_Data_Retreive($udn, 'reading', 'roomName', '');
+ SONOS_Client_Data_Refresh('ReadingsSingleUpdateIfChanged', $udn, 'roomNameAlias', $roomName.$aliasSuffix);
}
SONOS_Client_Data_Refresh('ReadingsSingleUpdateIfChanged', $udn, 'ZoneGroupID', $zoneGroupID.':__');
SONOS_Client_Data_Refresh('ReadingsSingleUpdateIfChanged', $udn, 'fieldType', $fieldType);
@@ -5653,6 +5967,19 @@ sub SONOS_DevicePropertiesCallback($$) {
};
$saveRoomName =~ s/[^a-zA-Z0-9]/_/g;
SONOS_Client_Data_Refresh('ReadingsSingleUpdateIfChanged', $udn, 'saveRoomName', $saveRoomName);
+
+ my $topoType = '_'.SONOS_Client_Data_Retreive($udn, 'reading', 'fieldType', '');
+
+ # Für den Aliasnamen schöne Bezeichnungen ermitteln...
+ my $aliasSuffix = '';
+ $aliasSuffix = ' - Hinten Links' if ($topoType eq '_LR');
+ $aliasSuffix = ' - Hinten Rechts' if ($topoType eq '_RR');
+ $aliasSuffix = ' - Links' if ($topoType eq '_LF');
+ $aliasSuffix = ' - Rechts' if ($topoType eq '_RF');
+ $aliasSuffix = ' - Subwoofer' if ($topoType eq '_SW');
+ $aliasSuffix = ' - Mitte' if ($topoType eq '_LF_RF');
+
+ SONOS_Client_Data_Refresh('ReadingsSingleUpdateIfChanged', $udn, 'roomNameAlias', $roomName.$aliasSuffix);
}
# Icon wurde angepasst?
@@ -7000,8 +7327,8 @@ Installation e.g. as Debian-Packages (via "sudo apt-get install <packagename&
SOAP::Lite-Special for Versions after 5.18:
- Add another Packagesource from suggestions or manual: Bribes de Perl (http://www.bribes.org/perl/ppm)
- Install Package: SOAP::Lite
-Windows ActivePerl 64Bit is currently not functioning due to missing SOAP::Lite
-Attention!
This Module will not be functioning on any platform, because of the use of Threads and the neccessary Perl-modules.
+Windows ActivePerl 5.20 does currently not work due to missing SOAP::Lite
+Attention!
This Module will not work on any platform, because of the use of Threads and the neccessary Perl-modules.
More information is given in a (german) Wiki-article: http://www.fhemwiki.de/wiki/SONOS
The system consists of two different components:
1. A UPnP-Client which runs as a standalone process in the background and takes the communications to the sonos-components.
@@ -7137,7 +7464,7 @@ Installation z.B. als Debian-Pakete (mittels "sudo apt-get install <packagena
SOAP::Lite-Special für Versionen nach 5.18:
- Eine andere Paketquelle von den Vorschlägen oder manuell hinzufügen: Bribes de Perl (http://www.bribes.org/perl/ppm)
- Package: SOAP::Lite
-Windows ActivePerl 64Bit kann momentan nicht verwendet werden, da es das Paket SOAP::Lite dort momentan nicht gibt.
+Windows ActivePerl 5.20 kann momentan nicht verwendet werden, da es das Paket SOAP::Lite dort momentan nicht gibt.
Achtung!
Das Modul wird nicht auf jeder Plattform lauffähig sein, da Threads und die angegebenen Perl-Module verwendet werden.
Mehr Informationen im (deutschen) Wiki-Artikel: http://www.fhemwiki.de/wiki/SONOS
Das System besteht aus zwei Komponenten:
diff --git a/fhem/FHEM/21_SONOSPLAYER.pm b/fhem/FHEM/21_SONOSPLAYER.pm
index 207c15e5a..b870bd6c7 100755
--- a/fhem/FHEM/21_SONOSPLAYER.pm
+++ b/fhem/FHEM/21_SONOSPLAYER.pm
@@ -106,7 +106,8 @@ my %gets = (
'RadiosWithCovers' => '',
'Alarm' => 'ID',
'EthernetPortStatus' => 'PortNum',
- 'PossibleRoomIcons' => ''
+ 'PossibleRoomIcons' => '',
+ 'SearchlistCategories' => ''
);
my %sets = (
@@ -121,7 +122,6 @@ my %sets = (
'CurrentPlaylist' => '',
'EmptyPlaylist' => '',
'StartFavourite' => 'favouritename',
- 'CreateThemeList' => 'searchField=searchValue',
'LoadRadio' => 'radioname',
'StartRadio' => 'radioname',
'PlayURI' => 'songURI',
@@ -160,7 +160,9 @@ my %sets = (
'Reboot' => '',
'Wifi' => 'state',
'Name' => 'roomName',
- 'RoomIcon' => 'iconName'
+ 'RoomIcon' => 'iconName',
+ 'LoadSearchlist' => 'category categoryElem titleFilter/albumFilter/artistFilter maxElems',
+ 'StartSearchlist' => 'category categoryElem titleFilter/albumFilter/artistFilter maxElems'
);
my @possibleRoomIcons = qw(bathroom library office foyer dining tvroom hallway garage garden guestroom den bedroom kitchen portable media family pool masterbedroom playroom patio living);
@@ -307,7 +309,7 @@ sub SONOSPLAYER_Get($@) {
}
return "SONOSPLAYER: Get with unknown argument $a[1], choose one of ".join(" ", sort keys %gets) if(!$found);
- # some argument needs parameter(s), some not
+ # some arguments needs parameter(s), some not
return "SONOSPLAYER: $a[1] needs parameter(s): ".$gets{$reading} if (scalar(split(',', $gets{$reading})) > scalar(@a) - 2);
# getter
@@ -325,6 +327,8 @@ sub SONOSPLAYER_Get($@) {
SONOS_DoWork($udn, 'getRadios');
} elsif (lc($reading) eq 'radioswithcovers') {
SONOS_DoWork($udn, 'getRadiosWithCovers');
+ } elsif (lc($reading) eq 'searchlistcategories') {
+ SONOS_DoWork($udn, 'getSearchlistCategories');
} elsif (lc($reading) eq 'ethernetportstatus') {
my $portNum = $a[2];
@@ -348,7 +352,7 @@ sub SONOSPLAYER_Get($@) {
return eval(ReadingsVal($name, 'AlarmList', ()))->{$id};
}
} elsif (lc($reading) eq 'possibleroomicons') {
- return join(', ', @possibleRoomIcons);
+ return '"'.join('", "', @possibleRoomIcons).'"';
}
return undef;
@@ -431,7 +435,7 @@ sub SONOSPLAYER_Set($@) {
}
return "SONOSPLAYER: Set with unknown argument $a[1], choose one of ".join(" ", sort keys %sets) if(!$found);
- # some argument needs parameter(s), some not
+ # some arguments needs parameter(s), some not
return "SONOSPLAYER: $a[1] needs parameter(s): ".$sets{$a[1]} if (scalar(split(',', $sets{$a[1]})) > scalar(@a) - 2);
# define vars
@@ -612,8 +616,17 @@ sub SONOSPLAYER_Set($@) {
$udn = $hash->{UDN};
SONOS_DoWork($udn, 'setCurrentPlaylist');
- } elsif (lc($key) eq 'createthemelist') {
- SONOS_DoWork($udn, 'createThemelist');
+ } elsif (lc($key) eq 'loadsearchlist') {
+ $hash = SONOSPLAYER_GetRealTargetPlayerHash($hash);
+ $udn = $hash->{UDN};
+
+ SONOS_DoWork($udn, 'loadSearchlist', $value, $value2, $a[4], $a[5]);
+ } elsif (lc($key) eq 'startsearchlist') {
+ $hash = SONOSPLAYER_GetRealTargetPlayerHash($hash);
+ $udn = $hash->{UDN};
+
+ SONOS_DoWork($udn, 'loadSearchlist', $value, $value2, $a[4], $a[5]);
+ SONOS_DoWork($udn, 'play');
} elsif (lc($key) eq 'playuri') {
$hash = SONOSPLAYER_GetRealTargetPlayerHash($hash);
$udn = $hash->{UDN};
@@ -1010,6 +1023,9 @@ sub SONOSPLAYER_Log($$$) {
StartRadio <Radiostationname>
Loads the named radiostation (favorite) and starts playing immediately. For all Options have a look at "LoadRadio".
+
+StartSearchlist <Categoryname> <CategoryElement> [[TitlefilterRegEx]/[AlbumfilterRegEx]/[ArtistfilterRegEx] [maxElem]]
+
Loads the searchlist and starts playing immediately. For all Options have a look at "LoadSearchlist".
Stop
Stops the playing
@@ -1089,6 +1105,9 @@ sub SONOSPLAYER_Log($$$) {
LoadRadio <Radiostationname>
Loads the named radiostation (favorite). The current queue will not be touched but deactivated. The parameter should be URL-encoded for proper naming of lists with special characters.
Additionally it's possible to use a regular expression as the name. The first hit will be used. The format is e.g. /radio/
.
+
+LoadSearchlist <Categoryname> <CategoryElement> [[TitlefilterRegEx]/[AlbumfilterRegEx]/[ArtistfilterRegEx] [maxElem]]
+
Loads titles from the Sonos-Bibliothek into the current playlist according to the given category and filtervalues. Please consult the (german) Wiki for detailed informations.
SavePlaylist <Playlistname>
Saves the current queue as a playlist with the given name. An existing playlist with the same name will be overwritten. The parameter should be URL-encoded for proper naming of lists with special characters. The Playlistname can be a filename and then must be startet with 'file:' (e.g. 'file:c:/Test.m3u')
@@ -1147,10 +1166,13 @@ sub SONOSPLAYER_Log($$$) {
Retrieves a list with the stringrepresentation of a perl-hash which can easily be converted with "eval". It consists of the names and coverlinks of all of the playlists stored in Sonos e.g. {'SQ:14' => {'Cover' => 'urlzumcover', 'Title' => '1. Playlist'}}
Radios
-
Retrieves a list woth the names of all saved radiostations (favorites). This getter retrieves the same list on all Zoneplayer. The format is a comma-separated list with quoted names of radiostations. e.g. "Sender 1","Sender 2","Test"
+
Retrieves a list with the names of all saved radiostations (favorites). This getter retrieves the same list on all Zoneplayer. The format is a comma-separated list with quoted names of radiostations. e.g. "Sender 1","Sender 2","Test"
RadiosWithCovers
Retrieves a list with the stringrepresentation of a perl-hash which can easily be converted with "eval". It consists of the names and coverlinks of all of the radiofavourites stored in Sonos e.g. {'R:0/0/2' => {'Cover' => 'urlzumcover', 'Title' => '1. Radiosender'}}
+
+SearchlistCategories
+
Retrieves a list with the possible categories for the setter "LoadSearchlist". The Format is a comma-separated list with quoted names of categories.
Informations on the current Title
-
@@ -1291,6 +1313,9 @@ Here an event is defined, where in time of 2 seconds the Mute-Button has to be p
-
StartRadio <Radiostationname>
Lädt den benannten Radiosender, genauer gesagt, den benannten Radiofavoriten und startet sofort die Wiedergabe. Dabei wird die bestehende Abspielliste beibehalten, aber deaktiviert. Der Parameter kann/muss URL-Encoded sein, um auch Leer- und Sonderzeichen angeben zu können.
+-
+
StartSearchlist <Kategoriename> <KategorieElement> [[TitelfilterRegEx]/[AlbumfilterRegEx]/[ArtistfilterRegEx] [maxElem]]
+
Lädt die Searchlist und startet sofort die Wiedergabe. Für nähere Informationen bitte unter "LoadSearchlist" nachschlagen.
-
Stop
Stoppt die Wiedergabe
@@ -1370,6 +1395,9 @@ Here an event is defined, where in time of 2 seconds the Mute-Button has to be p
-
LoadRadio <Radiostationname>
Startet den angegebenen Radiostream. Der Name bezeichnet einen Sender in der Radiofavoritenliste. Die aktuelle Abspielliste wird nicht verändert. Der Parameter sollte/kann URL-Encoded werden um auch Spezialzeichen zu ermöglichen.
Zusätzlich kann ein regulärer Ausdruck für den Namen verwendet werden. Der erste Treffer wird verwendet. Das Format ist z.B. /radio/
.
+-
+
LoadSearchlist <Kategoriename> <KategorieElement> [[TitelfilterRegEx]/[AlbumfilterRegEx]/[ArtistfilterRegEx] [maxElem]]
+
Lädt Titel nach diversen Kriterien in die aktuelle Abspielliste. Nähere Beschreibung bitte im Wiki nachlesen.
-
SavePlaylist <Playlistname>
Speichert die aktuelle Abspielliste unter dem angegebenen Namen. Eine bestehende Playlist mit diesem Namen wird überschrieben. Der Parameter sollte/kann URL-Encoded werden um auch Spezialzeichen zu ermöglichen. Der Playlistname kann auch ein Dateiname sein. Dann muss dieser mit 'file:' beginnen (z.B. 'file:c:/Test.m3u).
@@ -1432,6 +1460,9 @@ Here an event is defined, where in time of 2 seconds the Mute-Button has to be p
-
RadiosWithCovers
Liefert die Stringrepräsentation eines Hash mit den Namen und Covern aller gespeicherten Sonos-Radiofavoriten. Z.B.: {'R:0/0/2' => {'Cover' => 'urlzumcover', 'Title' => '1. Radiosender'}}. Dieser String kann einfach mit '''eval''' in eine Perl-Datenstruktur umgewandelt werden.
+-
+
SearchlistCategories
+
Liefert eine Liste mit den Namen alle möglichen Kategorien für den Aufruf von "LoadSearchlist". Das Format der Liste ist eine Komma-Separierte Liste, bei der die Namen in doppelten Anführungsstrichen stehen.
Informationen zum aktuellen Titel