2
0
mirror of https://github.com/fhem/fhem-mirror.git synced 2025-03-10 03:06:37 +00:00

Sonos: Add feature "LoadSearchlist"

git-svn-id: https://svn.fhem.de/fhem/trunk@7896 2b470e98-0d58-463d-a4d8-8e2adae1ed80
This commit is contained in:
rleins 2015-02-06 19:27:46 +00:00
parent 40cd6846ef
commit acd60c293e
2 changed files with 483 additions and 125 deletions

View File

@ -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 .= '<li>'.++$i.'. Gruppe:<ul style="list-style-type: none; padding-left: 0px;"><li>'.join('</li><li>', @member).'</li></ul></li>';
}
@ -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/<container id="(A:.*?)".*?><dc:title>(.*?)<\/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/<container id="(A:.*?)".*?><dc:title>(.*?)<\/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 id="(A:.*?)".*?>.*?<\/container>/ig) { # Wenn überhaupt noch was zu suchen ist...
%resultHash = ();
while ($tmp =~ m/<container id="(A:.*?)".*?><dc:title>(.*?)<\/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.*?>.*?<\/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 id="(.*?)".*?>(.*?)<\/item>/ig) {
my $item = $2;
my $uri = $1 if ($item =~ m/<res.*?>(.*?)<\/res>/i);
$uri =~ s/&apos;/'/gi;
my $title = '';
$title = $1 if ($item =~ m/<dc:title>(.*?)<\/dc:title>/i);
my $album = '';
$album = $1 if ($item =~ m/<upnp:album>(.*?)<\/upnp:album>/i);
my $interpret = '';
$interpret = $1 if ($item =~ m/<dc:creator>(.*?)<\/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 <SearchField1=SearchValue1>[ <SearchFieldN=SearchValueN>] [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/.*(<ZoneGroup Coordinator="(RINCON_[0-9a-f]+)".*?>).*?(<(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/.*(<ZoneGroup Coordinator="(RINCON_[0-9a-f]+)".*?>).*?(<(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 &lt;packagename&
<li>SOAP::Lite-Special for Versions after 5.18:<ul>
<li>Add another Packagesource from suggestions or manual: Bribes de Perl (http://www.bribes.org/perl/ppm)</li>
<li>Install Package: SOAP::Lite</li></ul></li></ul>
<b>Windows ActivePerl 64Bit is currently not functioning due to missing SOAP::Lite</b></p>
<p><b>Attention!</b><br />This Module will not be functioning on any platform, because of the use of Threads and the neccessary Perl-modules.</p>
<b>Windows ActivePerl 5.20 does currently not work due to missing SOAP::Lite</b></p>
<p><b>Attention!</b><br />This Module will not work on any platform, because of the use of Threads and the neccessary Perl-modules.</p>
<p>More information is given in a (german) Wiki-article: <a href="http://www.fhemwiki.de/wiki/SONOS">http://www.fhemwiki.de/wiki/SONOS</a></p>
<p>The system consists of two different components:<br />
1. A UPnP-Client which runs as a standalone process in the background and takes the communications to the sonos-components.<br />
@ -7137,7 +7464,7 @@ Installation z.B. als Debian-Pakete (mittels "sudo apt-get install &lt;packagena
<li>SOAP::Lite-Special für Versionen nach 5.18:<ul>
<li>Eine andere Paketquelle von den Vorschlägen oder manuell hinzufügen: Bribes de Perl (http://www.bribes.org/perl/ppm)</li>
<li>Package: SOAP::Lite</li></ul></li></ul>
<b>Windows ActivePerl 64Bit kann momentan nicht verwendet werden, da es das Paket SOAP::Lite dort momentan nicht gibt.</b></p>
<b>Windows ActivePerl 5.20 kann momentan nicht verwendet werden, da es das Paket SOAP::Lite dort momentan nicht gibt.</b></p>
<p><b>Achtung!</b><br />Das Modul wird nicht auf jeder Plattform lauffähig sein, da Threads und die angegebenen Perl-Module verwendet werden.</p>
<p>Mehr Informationen im (deutschen) Wiki-Artikel: <a href="http://www.fhemwiki.de/wiki/SONOS">http://www.fhemwiki.de/wiki/SONOS</a></p>
<p>Das System besteht aus zwei Komponenten:<br />

View File

@ -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($$$) {
<li><a name="SONOSPLAYER_setter_StartRadio">
<b><code>StartRadio &lt;Radiostationname&gt;</code></b></a>
<br /> Loads the named radiostation (favorite) and starts playing immediately. For all Options have a look at "LoadRadio".</li>
<li><a name="SONOSPLAYER_setter_StartSearchlist">
<b><code>StartSearchlist &lt;Categoryname&gt; &lt;CategoryElement&gt; [[TitlefilterRegEx]/[AlbumfilterRegEx]/[ArtistfilterRegEx] [maxElem]]</code></b></a>
<br /> Loads the searchlist and starts playing immediately. For all Options have a look at "LoadSearchlist".</li>
<li><a name="SONOSPLAYER_setter_Stop">
<b><code>Stop</code></b></a>
<br /> Stops the playing</li>
@ -1089,6 +1105,9 @@ sub SONOSPLAYER_Log($$$) {
<li><a name="SONOSPLAYER_setter_LoadRadio">
<b><code>LoadRadio &lt;Radiostationname&gt;</code></b></a>
<br /> 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.<br />Additionally it's possible to use a regular expression as the name. The first hit will be used. The format is e.g. <code>/radio/</code>.</li>
<li><a name="SONOSPLAYER_setter_LoadSearchlist">
<b><code>LoadSearchlist &lt;Categoryname&gt; &lt;CategoryElement&gt; [[TitlefilterRegEx]/[AlbumfilterRegEx]/[ArtistfilterRegEx] [maxElem]]</code></b></a>
<br /> 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.</li>
<li><a name="SONOSPLAYER_setter_SavePlaylist">
<b><code>SavePlaylist &lt;Playlistname&gt;</code></b></a>
<br /> 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')</li>
@ -1147,10 +1166,13 @@ sub SONOSPLAYER_Log($$$) {
<br /> 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'}}</li>
<li><a name="SONOSPLAYER_getter_Radios">
<b><code>Radios</code></b></a>
<br /> 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"</li>
<br /> 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"</li>
<li><a name="SONOSPLAYER_getter_RadiosWithCovers">
<b><code>RadiosWithCovers</code></b></a>
<br /> 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'}}</li>
<li><a name="SONOSPLAYER_getter_SearchlistCategories">
<b><code>SearchlistCategories</code></b></a>
<br /> Retrieves a list with the possible categories for the setter "LoadSearchlist". The Format is a comma-separated list with quoted names of categories.</li>
</ul></li>
<li><b>Informations on the current Title</b><ul>
<li><a name="SONOSPLAYER_getter_CurrentTrackPosition">
@ -1291,6 +1313,9 @@ Here an event is defined, where in time of 2 seconds the Mute-Button has to be p
<li><a name="SONOSPLAYER_setter_StartRadio">
<b><code>StartRadio &lt;Radiostationname&gt;</code></b></a>
<br /> 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.</li>
<li><a name="SONOSPLAYER_setter_StartSearchlist">
<b><code>StartSearchlist &lt;Kategoriename&gt; &lt;KategorieElement&gt; [[TitelfilterRegEx]/[AlbumfilterRegEx]/[ArtistfilterRegEx] [maxElem]]</code></b></a>
<br /> Lädt die Searchlist und startet sofort die Wiedergabe. Für nähere Informationen bitte unter "LoadSearchlist" nachschlagen.</li>
<li><a name="SONOSPLAYER_setter_Stop">
<b><code>Stop</code></b></a>
<br /> Stoppt die Wiedergabe</li>
@ -1370,6 +1395,9 @@ Here an event is defined, where in time of 2 seconds the Mute-Button has to be p
<li><a name="SONOSPLAYER_setter_LoadRadio">
<b><code>LoadRadio &lt;Radiostationname&gt;</code></b></a>
<br /> 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.<br />Zusätzlich kann ein regulärer Ausdruck für den Namen verwendet werden. Der erste Treffer wird verwendet. Das Format ist z.B. <code>/radio/</code>.</li>
<li><a name="SONOSPLAYER_setter_LoadSearchlist">
<b><code>LoadSearchlist &lt;Kategoriename&gt; &lt;KategorieElement&gt; [[TitelfilterRegEx]/[AlbumfilterRegEx]/[ArtistfilterRegEx] [maxElem]]</code></b></a>
<br /> Lädt Titel nach diversen Kriterien in die aktuelle Abspielliste. Nähere Beschreibung bitte im Wiki nachlesen.</li>
<li><a name="SONOSPLAYER_setter_SavePlaylist">
<b><code>SavePlaylist &lt;Playlistname&gt;</code></b></a>
<br /> 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).</li>
@ -1432,6 +1460,9 @@ Here an event is defined, where in time of 2 seconds the Mute-Button has to be p
<li><a name="SONOSPLAYER_getter_RadiosWithCovers">
<b><code>RadiosWithCovers</code></b></a>
<br /> 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.</li>
<li><a name="SONOSPLAYER_getter_SearchlistCategories">
<b><code>SearchlistCategories</code></b></a>
<br /> 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.</li>
</ul></li>
<li><b>Informationen zum aktuellen Titel</b><ul>
<li><a name="SONOSPLAYER_getter_CurrentTrackPosition">