<!--?php
/******************* PHPSonos.inc.php **************************************
Sonos PHP Script
Copyright: Michael Maroszek
Version: 1.0, 09.07.2009
Comment:
- andre added setter functions
- 110108 - br added comments based on UPNP information from devicespy;
also added the function XMLsendPacket to get non filtered answers
- 110120 - br added Set and GetLEDState
- 110202 - br added GetZoneAttributes
- 110202 - br added GetZoneInfo
- 110203 - br added gestposinfo TrackURI (contains x-rincon of the zone master if we are slave)
- 110206 - br added AddMember(Rincon...) and RemoveMember(Rincon...)
- 110207 - br added RamptoVolume
- 110208 - br added calculation of Content-Length to some functions
- 110318 - br fiddled with Playmode (maybe fixed a bug)
- 110318 - br added Get and Set CrossfadeMode
- 110318 - br added SaveQueue
- 110328 - ta lun added GetPlaylist($value)
- 110328 - ta lun added GetImportedPlaylists()
- 110328 - ta lun added GetSonosPlaylists()
- 110328 - ta lun added GetCurrentPlaylist()
- 110328 - br corrected titel to title and other things...
- 110328 - br added optional parameter id to SaveQueue
- 110406 - br edited Seek to accept UPNP Unit parameter as option (sec. arg is Target then)
- 110406 - br edited GetPositionInfo to also reflect UPNP return value names
- 110406 - br fixed non valid soap request in seek()
- 110406 - br added return of CurrentURI and CurrentUriMetaData to GetMediaInfo (Current File or QUEUE)
This info is needed to restart a queue, pl or radiostation
- 110407 - br consolidated SetRadio, SetQueue and SetAVTransportURI
two last now also Support MetaData as 2nd parameter; SetRadio supports the name of a radiostation as second parameter
=============================================================================
Useful links:
------------------
http://travelmarx.blogspot.com/2010/06/exploring-sonos-via-upnp.html
http://travelmarx.blogspot.com/2011/01/extracting-sonos-playlist-simple-sonos.html,
http://opentools.homeip.net/dev-tools-for-upnp
http://www.ip-symcon.de/forum/f53/php-sonos-klasse-ansteuern-einzelner-player-7676/
http://your.sonos.i.p:1400/status (Cool!!)
http://play.er.i.p:1400/xml/zone_player.xml
***************************************************************************/
class PHPSonos {
private $address = "";
public function __construct( $address ) {
$this->address = $address;
}
/******************* urn:schemas-upnp-org:device:ZonePlayer:1 *************
* http://play.er.i.p:1400/xml/zone_player.xml
***************************************************************************/
/* urn:upnp-org:serviceId:AlarmClock */
// Not implemented
/* urn:upnp-org:serviceId:AudioIn */
// Not implemented
/* urn:upnp-org:serviceId:DeviceProperties */
public function GetZoneAttributes() // added br
{
/*
$content='POST /DeviceProperties/Control HTTP/1.1
SOAPACTION: "urn:schemas-upnp-org:service:DeviceProperties:1#GetZoneAttributes"
CONTENT-TYPE: text/xml; charset="utf-8"
HOST: '.$this->address.':1400
Content-Length: 295
<?xml version="1.0" encoding="utf-8"?-->
'
;
*/
$header='POST /DeviceProperties/Control HTTP/1.1
SOAPACTION: "urn:schemas-upnp-org:service:DeviceProperties:1#GetZoneAttributes"
CONTENT-TYPE: text/xml; charset="utf-8"
HOST: '.$this->address.':1400';
$xml='<!--?xml version="1.0" encoding="utf-8"?-->
';
$content=$header . '
Content-Length: '. strlen($xml) .'
'. $xml;
$returnContent = $this->XMLsendPacket($content);
$xmlParser = xml_parser_create("UTF-8");
xml_parser_set_option($xmlParser, XML_OPTION_TARGET_ENCODING, "ISO-8859-1");
xml_parse_into_struct($xmlParser, $returnContent, $vals, $index);
xml_parser_free($xmlParser);
$ZoneAttributes = Array();
$key="CurrentZoneName"; // Lookfor
if ( isset($index[strtoupper($key)][0]) and isset($vals[ $index[strtoupper($key)][0] ]['value'])) {$ZoneAttributes[$key] = $vals[ $index[strtoupper($key)][0] ]['value'];
} else { $ZoneAttributes[$key] = ""; }
$key="CurrentIcon"; // Lookfor
if ( isset($index[strtoupper($key)][0]) and isset($vals[ $index[strtoupper($key)][0] ]['value'])) {$ZoneAttributes[$key] = $vals[ $index[strtoupper($key)][0] ]['value'];
} else { $ZoneAttributes[$key] = ""; }
return $ZoneAttributes; //Assoziatives Array
}
public function GetZoneInfo() // added br
{
/*
$content='POST /DeviceProperties/Control HTTP/1.1
SOAPACTION: "urn:schemas-upnp-org:service:DeviceProperties:1#GetZoneInfo"
CONTENT-TYPE: text/xml; charset="utf-8"
HOST: '.$this->address.':1400
Content-Length: 295
<!--?xml version="1.0" encoding="utf-8"?-->
'
;
*/
$header='POST /DeviceProperties/Control HTTP/1.1
SOAPACTION: "urn:schemas-upnp-org:service:DeviceProperties:1#GetZoneInfo"
CONTENT-TYPE: text/xml; charset="utf-8"
HOST: '.$this->address.':1400';
$xml='<!--?xml version="1.0" encoding="utf-8"?-->
';
$content=$header . '
Content-Length: '. strlen($xml) .'
'. $xml;
$returnContent = $this->XMLsendPacket($content);
$xmlParser = xml_parser_create("UTF-8");
xml_parser_set_option($xmlParser, XML_OPTION_TARGET_ENCODING, "ISO-8859-1");
xml_parse_into_struct($xmlParser, $returnContent, $vals, $index);
xml_parser_free($xmlParser);
$ZoneInfo = Array();
$key="SerialNumber"; // Lookfor
if ( isset($index[strtoupper($key)][0]) and isset($vals[ $index[strtoupper($key)][0] ]['value'])) {$ZoneInfo[$key] = $vals[ $index[strtoupper($key)][0] ]['value'];
} else { $ZoneInfo[$key] = ""; }
$key="SoftwareVersion"; // Lookfor
if ( isset($index[strtoupper($key)][0]) and isset($vals[ $index[strtoupper($key)][0] ]['value'])) {$ZoneInfo[$key] = $vals[ $index[strtoupper($key)][0] ]['value'];
} else { $ZoneInfo[$key] = ""; }
$key="SoftwareVersion"; // Lookfor
if ( isset($index[strtoupper($key)][0]) and isset($vals[ $index[strtoupper($key)][0] ]['value'])) {$ZoneInfo[$key] = $vals[ $index[strtoupper($key)][0] ]['value'];
} else { $ZoneInfo[$key] = ""; }
$key="DisplaySoftwareVersion"; // Lookfor
if ( isset($index[strtoupper($key)][0]) and isset($vals[ $index[strtoupper($key)][0] ]['value'])) {$ZoneInfo[$key] = $vals[ $index[strtoupper($key)][0] ]['value'];
} else { $ZoneInfo[$key] = ""; }
$key="HardwareVersion"; // Lookfor
if ( isset($index[strtoupper($key)][0]) and isset($vals[ $index[strtoupper($key)][0] ]['value'])) {$ZoneInfo[$key] = $vals[ $index[strtoupper($key)][0] ]['value'];
} else { $ZoneInfo[$key] = ""; }
$key="IPAddress"; // Lookfor
if ( isset($index[strtoupper($key)][0]) and isset($vals[ $index[strtoupper($key)][0] ]['value'])) {$ZoneInfo[$key] = $vals[ $index[strtoupper($key)][0] ]['value'];
} else { $ZoneInfo[$key] = ""; }
$key="MACAddress"; // Lookfor
if ( isset($index[strtoupper($key)][0]) and isset($vals[ $index[strtoupper($key)][0] ]['value'])) {$ZoneInfo[$key] = $vals[ $index[strtoupper($key)][0] ]['value'];
} else { $ZoneInfo[$key] = ""; }
$key="CopyrightInfo"; // Lookfor
if ( isset($index[strtoupper($key)][0]) and isset($vals[ $index[strtoupper($key)][0] ]['value'])) {$ZoneInfo[$key] = $vals[ $index[strtoupper($key)][0] ]['value'];
} else { $ZoneInfo[$key] = ""; }
$key="ExtraInfo"; // Lookfor
if ( isset($index[strtoupper($key)][0]) and isset($vals[ $index[strtoupper($key)][0] ]['value'])) {$ZoneInfo[$key] = $vals[ $index[strtoupper($key)][0] ]['value'];
} else { $ZoneInfo[$key] = ""; }
return $ZoneInfo; //Assoziatives Array
}
public function SetLEDState($state) // added br
{
if($state=="On") { $state = "On"; } else
{ if($state=="Off") { $state = "Off"; } else {
if($state) { $state = "On"; } else { $state = "Off"; }
}
}
$content='POST /DeviceProperties/Control HTTP/1.1
CONNECTION: close
HOST: '.$this->address.':1400
CONTENT-LENGTH: 250
CONTENT-TYPE: text/xml; charset="utf-8"
SOAPACTION: "urn:schemas-upnp-org:service:DeviceProperties:1#SetLEDState"
' .$state. '';
return (bool)$this->sendPacket($content);
}
public function GetLEDState() // added br
{
$content='POST /DeviceProperties/Control HTTP/1.1
CONNECTION: close
HOST: '.$this->address.':1400
CONTENT-LENGTH: 250
CONTENT-TYPE: text/xml; charset="utf-8"
SOAPACTION: "urn:schemas-upnp-org:service:DeviceProperties:1#GetLEDState"
0';
if ($this->sendPacket($content)=="On") { return(true); }else return(false);
}
/* urn:upnp-org:serviceId:GroupManagement */
public function AddMember($MemberID) // added br
// Joins a group 1st Arg is RINCON_MAC1400
// Returns assoziative Array with CurrentSettings and GroupUUIDJoined
{
$header='POST /GroupManagement/Control HTTP/1.1
SOAPACTION: "urn:schemas-upnp-org:service:GroupManagement:1#AddMember"
CONTENT-TYPE: text/xml; charset="utf-8"
HOST: '.$this->address.':1400';
$xml='<!--?xml version="1.0" encoding="utf-8"?-->
' . $MemberID . '
';
$content=$header . '
Content-Length: '. strlen($xml) .'
'. $xml;
$returnContent = $this->XMLsendPacket($content);
$xmlParser = xml_parser_create("UTF-8");
xml_parser_set_option($xmlParser, XML_OPTION_TARGET_ENCODING, "ISO-8859-1");
xml_parse_into_struct($xmlParser, $returnContent, $vals, $index);
xml_parser_free($xmlParser);
$ZoneAttributes = Array();
$key="CurrentTransportSettings"; // Lookfor
if ( isset($index[strtoupper($key)][0]) and isset($vals[ $index[strtoupper($key)][0] ]['value'])) {$ZoneAttributes[$key] = $vals[ $index[strtoupper($key)][0] ]['value'];
} else { $ZoneAttributes[$key] = ""; }
$key="GroupUUIDJoined"; // Lookfor
if ( isset($index[strtoupper($key)][0]) and isset($vals[ $index[strtoupper($key)][0] ]['value'])) {$ZoneAttributes[$key] = $vals[ $index[strtoupper($key)][0] ]['value'];
} else { $ZoneAttributes[$key] = ""; }
return $ZoneAttributes; //Assoziatives Array
// set AVtransporturi ist notwendig
}
/* urn:upnp-org:serviceId:MusicServices */
// Not implemented
/* urn:upnp-org:serviceId:SystemProperties */
// Not implemented
/* urn:upnp-org:serviceId:ZoneGroupTopology */
// Not implemented
public function RemoveMember($MemberID) // added br
// Leaves a Group
// Returns None
{
$header='POST /GroupManagement/Control HTTP/1.1
SOAPACTION: "urn:schemas-upnp-org:service:GroupManagement:1#RemoveMember"
CONTENT-TYPE: text/xml; charset="utf-8"
HOST: '.$this->address.':1400';
$xml='<!--?xml version="1.0" encoding="utf-8"?-->
' . $MemberID . '
';
$content=$header . '
Content-Length: '. strlen($xml) .'
'. $xml;
echo $this->sendPacket($content);
// set AVtransporturi ist für STOP notwendig
}
/* urn:upnp-org:serviceId:MusicServices */
// Not implemented
/* urn:upnp-org:serviceId:SystemProperties */
// Not implemented
/* urn:upnp-org:serviceId:ZoneGroupTopology */
// Not implemented
/******************* urn:schemas-upnp-org:device:MediaRenderer:1 ***********
***************************************************************************/
/* urn:upnp-org:serviceId:RenderingControl */
public function RampToVolume($ramp_type, $volume) //added br
/* Ramps Vol to $volume using the Method mentioned in $ramp_type as string:
"SLEEP_TIMER_RAMP_TYPE" - mutes and ups Volume per default within 17 seconds to desiredVolume
"ALARM_RAMP_TYPE" -Switches audio off and slowly goes to volume
"AUTOPLAY_RAMP_TYPE" - very fast and smooth; Implemented from Sonos for the unofficial autoplay feature.
When you switch on a Sonos Device it plays its autoplay Command/Playlist and Settings. It´s all there but currently no GUI setting is possible.
*Should return Rampseconds* but this is not implemented! */
{
$header='POST /MediaRenderer/RenderingControl/Control HTTP/1.1
HOST: '.$this->address.':1400
CONTENT-TYPE: text/xml; charset="utf-8"
SOAPACTION: "urn:schemas-upnp-org:service:RenderingControl:1#RampToVolume"
';
$xml='<!--?xml version="1.0" encoding="utf-8"?-->
0Master'.$ramp_type.''.$volume.'
';
$content=$header . 'Content-Length: '. strlen($xml) .'
'. $xml;
return (int) $this->sendPacket($content);
}
/* urn:upnp-org:serviceId:AVTransport */
// If you don´t set $id you may get duplicate playlists!!!
public function SaveQueue($title,$id="") // added br
{
$header='POST /MediaRenderer/AVTransport/Control HTTP/1.1
SOAPACTION: "urn:schemas-upnp-org:service:AVTransport:1#SaveQueue"
CONTENT-TYPE: text/xml; charset="utf-8"
HOST: '.$this->address.':1400';
$xml='<!--?xml version="1.0" encoding="utf-8"?-->0'.$title.''.$id.'
';
$content=$header . '
Content-Length: '. strlen($xml) .'
'. $xml;
$returnContent = $this->sendPacket($content);
}
public function GetCrossfadeMode() // added br
{
$header='POST /MediaRenderer/AVTransport/Control HTTP/1.1
HOST: '.$this->address.':1400
CONTENT-TYPE: text/xml; charset="utf-8"
SOAPACTION: "urn:schemas-upnp-org:service:AVTransport:1#GetCrossfadeMode"
';
$xml='<!--?xml version="1.0" encoding="utf-8"?-->
0
';
$content=$header . 'Content-Length: '. strlen($xml) .'
'. $xml;
return (bool)$this->sendPacket($content);
}
public function SetCrossfadeMode($mode) // added br
{
if($mode) { $mode = "1"; } else { $mode = "0"; }
$header='POST /MediaRenderer/AVTransport/Control HTTP/1.1
HOST: '.$this->address.':1400
CONTENT-TYPE: text/xml; charset="utf-8"
SOAPACTION: "urn:schemas-upnp-org:service:AVTransport:1#SetCrossfadeMode"
';
$xml='<!--?xml version="1.0" encoding="utf-8"?-->
0'.$mode.'';
$content=$header . 'Content-Length: '. strlen($xml) .'
'. $xml;
$this->sendPacket($content);
}
public function Stop()
{
$content='POST /MediaRenderer/AVTransport/Control HTTP/1.1
CONNECTION: close
HOST: '.$this->address.':1400
CONTENT-LENGTH: 250
CONTENT-TYPE: text/xml; charset="utf-8"
SOAPACTION: "urn:schemas-upnp-org:service:AVTransport:1#Stop"
0';
$this->sendPacket($content);
}
public function Pause()
{
$content='POST /MediaRenderer/AVTransport/Control HTTP/1.1
CONNECTION: close
HOST: '.$this->address.':1400
CONTENT-LENGTH: 252
CONTENT-TYPE: text/xml; charset="utf-8"
SOAPACTION: "urn:schemas-upnp-org:service:AVTransport:1#Pause"
0';
$this->sendPacket($content);
}
public function Play()
{
$content='POST /MediaRenderer/AVTransport/Control HTTP/1.1
CONNECTION: close
HOST: '.$this->address.':1400
CONTENT-LENGTH: 266
CONTENT-TYPE: text/xml; charset="utf-8"
SOAPACTION: "urn:schemas-upnp-org:service:AVTransport:1#Play"
01';
$this->sendPacket($content);
}
public function Next()
{
$content='POST /MediaRenderer/AVTransport/Control HTTP/1.1
CONNECTION: close
HOST: '.$this->address.':1400
CONTENT-LENGTH: 250
CONTENT-TYPE: text/xml; charset="utf-8"
SOAPACTION: "urn:schemas-upnp-org:service:AVTransport:1#Next"
0';
$this->sendPacket($content);
}
public function Previous()
{
$content='POST /MediaRenderer/AVTransport/Control HTTP/1.1
CONNECTION: close
HOST: '.$this->address.':1400
CONTENT-LENGTH: 258
CONTENT-TYPE: text/xml; charset="utf-8"
SOAPACTION: "urn:schemas-upnp-org:service:AVTransport:1#Previous"
0';
$this->sendPacket($content);
}
public function Seek($arg1,$arg2="NONE")
{
// Abwärtskompatibel zu Paresys Original sein
if ($arg2=="NONE"){
$Unit="REL_TIME"; $position=$arg1;
} else {$Unit=$arg1; $position=$arg2;}
$header='POST /MediaRenderer/AVTransport/Control HTTP/1.1
SOAPACTION: "urn:schemas-upnp-org:service:AVTransport:1#Seek"
CONTENT-TYPE: text/xml; charset="utf-8"
CONNECTION: close
HOST: '.$this->address.':1400';
$xml='<!--?xml version="1.0" encoding="utf-8"?-->
0'. $Unit .''.$position.'';
$content=$header . '
Content-Length: '. strlen($xml) .'
'. $xml;
$returnContent = $this->sendPacket($content);
}
public function Rewind()
{
$content='POST /MediaRenderer/AVTransport/Control HTTP/1.1
CONNECTION: close
HOST: '.$this->address.':1400
CONTENT-LENGTH: 296
CONTENT-TYPE: text/xml; charset="utf-8"
SOAPACTION: "urn:schemas-upnp-org:service:AVTransport:1#Seek"
0REL_TIME00:00:00';
$this->sendPacket($content);
}
public function SetVolume($volume)
{
$content='POST /MediaRenderer/RenderingControl/Control HTTP/1.1
CONNECTION: close
HOST: '.$this->address.':1400
CONTENT-LENGTH: 32'.strlen($volume).'
CONTENT-TYPE: text/xml; charset="utf-8"
SOAPACTION: "urn:schemas-upnp-org:service:RenderingControl:1#SetVolume"
0Master'.$volume.'';
$this->sendPacket($content);
}
public function GetVolume()
{
$content='POST /MediaRenderer/RenderingControl/Control HTTP/1.1
CONNECTION: close
HOST: '.$this->address.':1400
CONTENT-LENGTH: 290
CONTENT-TYPE: text/xml; charset="utf-8"
SOAPACTION: "urn:schemas-upnp-org:service:RenderingControl:1#GetVolume"
0Master';
return (int)$this->sendPacket($content);
}
public function SetMute($mute)
{
if($mute) { $mute = "1"; } else { $mute = "0"; }
$content='POST /MediaRenderer/RenderingControl/Control HTTP/1.1
CONNECTION: close
HOST: '.$this->address.':1400
CONTENT-LENGTH: 314
CONTENT-TYPE: text/xml; charset="utf-8"
SOAPACTION: "urn:schemas-upnp-org:service:RenderingControl:1#SetMute"
0Master'.$mute.'';
$this->sendPacket($content);
}
public function GetMute()
{
$content='POST /MediaRenderer/RenderingControl/Control HTTP/1.1
CONNECTION: close
HOST: '.$this->address.':1400
CONTENT-LENGTH: 286
CONTENT-TYPE: text/xml; charset="utf-8"
SOAPACTION: "urn:schemas-upnp-org:service:RenderingControl:1#GetMute"
0Master';
return (bool)$this->sendPacket($content);
}
public function SetPlayMode($mode)
{
$content='POST /MediaRenderer/AVTransport/Control HTTP/1.1
CONNECTION: close
HOST: '.$this->address.':1400
CONTENT-LENGTH: '.(291+strlen($mode)).'
CONTENT-TYPE: text/xml; charset="utf-8"
SOAPACTION: "urn:schemas-upnp-org:service:AVTransport:1#SetPlayMode"
0'.$mode.'';
$this->sendPacket($content);
}
public function GetTransportSettings()
{
$content='POST /MediaRenderer/AVTransport/Control HTTP/1.1
CONNECTION: close
HOST: '.$this->address.':1400
CONTENT-LENGTH: 282
CONTENT-TYPE: text/xml; charset="utf-8"
SOAPACTION: "urn:schemas-upnp-org:service:AVTransport:1#GetTransportSettings"
0';
$returnContent = $this->sendPacket($content);
// echo "\n===" . $this->address. "====\n" . $returnContent . "\n===\n";
if (strstr($returnContent, "NORMAL") !== false) {
return Array (
"repeat" => false,
"shuffle" => false
);
} elseif (strstr($returnContent, "REPEAT_ALL") !== false) {
return Array (
"repeat" => true,
"shuffle" => false
);
} elseif (strstr($returnContent, "SHUFFLE_NOREPEAT") !== false) {
return Array (
"repeat" => false,
"shuffle" => true
);
} elseif (strstr($returnContent, "SHUFFLE") !== false) {
return Array (
"repeat" => true,
"shuffle" => true
);
}
/* what is PLAYING??? br */
/* } elseif (strstr($returnContent, "PLAYING") !== false) {
return Array (
"repeat" => false,
"shuffle" => true
);
} */
}
public function GetTransportInfo()
{
$content='POST /MediaRenderer/AVTransport/Control HTTP/1.1
CONNECTION: close
HOST: '.$this->address.':1400
CONTENT-LENGTH: 274
CONTENT-TYPE: text/xml; charset="utf-8"
SOAPACTION: "urn:schemas-upnp-org:service:AVTransport:1#GetTransportInfo"
0';
$returnContent = $this->sendPacket($content);
if (strstr($returnContent, "PLAYING") !== false) {
return 1;
} elseif (strstr($returnContent, "PAUSED_PLAYBACK") !== false) {
return 2;
} elseif (strstr($returnContent, "STOPPED") !== false) {
return 3;
}
}
public function GetMediaInfo()
{
// NOTEBR: diese F. ist aktuell noch buggy
$content='POST /MediaRenderer/AVTransport/Control HTTP/1.1
CONNECTION: close
HOST: '.$this->address.':1400
CONTENT-LENGTH: 266
CONTENT-TYPE: text/xml; charset="utf-8"
SOAPACTION: "urn:schemas-upnp-org:service:AVTransport:1#GetMediaInfo"
0';
$returnContent = $this->XMLsendPacket($content);
/* $returnContent = substr($returnContent, stripos($returnContent, '<'));
$returnContent = substr($returnContent, 0, strrpos($returnContent, '>') + 4);
$returnContent = str_replace(array("<", ">", """, "&", "%3a", "%2f", "%25"), array("<", ">", "\"", "&", ":", "/", "%"), $returnContent);
*/
$xmlParser = xml_parser_create("UTF-8");
xml_parser_set_option($xmlParser, XML_OPTION_TARGET_ENCODING, "ISO-8859-1");
xml_parse_into_struct($xmlParser, $returnContent, $vals, $index);
xml_parser_free($xmlParser);
$mediaInfo = Array();
if (isset($index["DC:TITLE"]) and isset($vals[$index["DC:TITLE"][0]]["value"])) {
$mediaInfo["title"] = $vals[$index["DC:TITLE"][0]]["value"];
} else {
$mediaInfo["title"] = "";
}
if (isset($vals[5]["value"])) {
$mediaInfo["CurrentURI"] = $vals[5]["value"];
} else {
$mediaInfo["CurrentURI"] = "";
}
if (isset($vals[6]["value"])) {
$mediaInfo["CurrentURIMetaData"] = $vals[6]["value"];
} else {
$mediaInfo["CurrentURIMetaData"] = "";
}
return $mediaInfo;
}
public function GetPositionInfo()
{
$content='POST /MediaRenderer/AVTransport/Control HTTP/1.1
CONNECTION: close
HOST: '.$this->address.':1400
CONTENT-LENGTH: 272
CONTENT-TYPE: text/xml; charset="utf-8"
SOAPACTION: "urn:schemas-upnp-org:service:AVTransport:1#GetPositionInfo"
0';
$returnContent = $this->sendPacket($content);
$position = substr($returnContent, stripos($returnContent, "NOT_IMPLEMENTED") - 7, 7);
$returnContent = substr($returnContent, stripos($returnContent, '<'));
$returnContent = substr($returnContent, 0, strrpos($returnContent, '>') + 4);
$returnContent = str_replace(array("<", ">", """, "&", "%3a", "%2f", "%25"), array("<", ">", "\"", "&", ":", "/", "%"), $returnContent);
$xmlParser = xml_parser_create("UTF-8");
xml_parser_set_option($xmlParser, XML_OPTION_TARGET_ENCODING, "UTF-8");
xml_parse_into_struct($xmlParser, $returnContent, $vals, $index);
xml_parser_free($xmlParser);
$positionInfo = Array ();
$positionInfo["position"] = $position;
$positionInfo["RelTime"] = $position;
if (isset($index["RES"]) and isset($vals[$index["RES"][0]]["attributes"]["DURATION"])) {
$positionInfo["duration"] = $vals[$index["RES"][0]]["attributes"]["DURATION"];
$positionInfo["TrackDuration"] = $vals[$index["RES"][0]]["attributes"]["DURATION"];
} else {
$positionInfo["duration"] = "";
$positionInfo["TrackDuration"] = "";
}
if (isset($index["RES"]) and isset($vals[$index["RES"][0]]["value"])) {
$positionInfo["URI"] = $vals[$index["RES"][0]]["value"];
$positionInfo["TrackURI"] = $vals[$index["RES"][0]]["value"];
} else {
$positionInfo["URI"] = "";
$positionInfo["TrackURI"] = "";
}
if (isset($index["DC:CREATOR"]) and isset($vals[$index["DC:CREATOR"][0]]["value"])) {
$positionInfo["artist"] = $vals[$index["DC:CREATOR"][0]]["value"];
} else {
$positionInfo["artist"] = "";
}
if (isset($index["DC:TITLE"]) and isset($vals[$index["DC:TITLE"][0]]["value"])) {
$positionInfo["title"] = $vals[$index["DC:TITLE"][0]]["value"];
} else {
$positionInfo["title"] = "";
}
if (isset($index["UPNP:ALBUM"]) and isset($vals[$index["UPNP:ALBUM"][0]]["value"])) {
$positionInfo["album"] = $vals[$index["UPNP:ALBUM"][0]]["value"];
} else {
$positionInfo["album"] = "";
}
if (isset($index["UPNP:ALBUMARTURI"]) and isset($vals[$index["UPNP:ALBUMARTURI"][0]]["value"])) {
$positionInfo["albumArtURI"] = "http://" . $this->address . ":1400" . $vals[$index["UPNP:ALBUMARTURI"][0]]["value"];
} else {
$positionInfo["albumArtURI"] = "";
}
if (isset($index["R:ALBUMARTIST"]) and isset($vals[$index["R:ALBUMARTIST"][0]]["value"])) {
$positionInfo["albumArtist"] = $vals[$index["R:ALBUMARTIST"][0]]["value"];
} else {
$positionInfo["albumArtist"] = "";
}
if (isset($index["UPNP:ORIGINALTRACKNUMBER"]) and isset($vals[$index["UPNP:ORIGINALTRACKNUMBER"][0]]["value"])) {
$positionInfo["albumTrackNumber"] = $vals[$index["UPNP:ORIGINALTRACKNUMBER"][0]]["value"];
} else {
$positionInfo["albumTrackNumber"] = "";
}
if (isset($index["R:STREAMCONTENT"]) and isset($vals[$index["R:STREAMCONTENT"][0]]["value"])) {
$positionInfo["streamContent"] = $vals[$index["R:STREAMCONTENT"][0]]["value"];
} else {
$positionInfo["streamContent"] = "";
}
// added br if this contains "rincon" we are slave to a coordinator mentioned in this field (otherwise path to the file is provided)!
// implemented via second XMLsendpacket to not break michaels current code
/* if (isset($index["RES"][0]) and isset($vals[($index["RES"][0])]["value"])) {
$positionInfo["trackURI"] = $vals[($index["RES"][0])]["value"];
} else {
*/
$returnContent = $this->XMLsendPacket($content);
$xmlParser = xml_parser_create("UTF-8");
xml_parser_set_option($xmlParser, XML_OPTION_TARGET_ENCODING, "ISO-8859-1");
xml_parse_into_struct($xmlParser, $returnContent, $vals, $index);
xml_parser_free($xmlParser);
// }
if (isset($index["TRACKURI"][0]) and isset($vals[($index["TRACKURI"][0])]["value"])) {
$positionInfo["trackURI"] = $vals[($index["TRACKURI"][0])]["value"];
$positionInfo["TrackURI"] = $vals[($index["TRACKURI"][0])]["value"];
} else {
$positionInfo["trackURI"] = "";
}
// Track Number in Playlist
if (isset($index["TRACK"][0]) and isset($vals[($index["TRACK"][0])]["value"])) {
$positionInfo["Track"] = $vals[($index["TRACK"][0])]["value"];;
} else {
$positionInfo["Track"] = "";
}
return $positionInfo;
}
public function SetRadio($radio,$Name="IP-Symcon Radio")
{
$Metadata="<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>".$Name."</dc:title><upnp:class>object.item.audioItem.audioBroadcast</upnp:class><desc id="cdudn" nameSpace="urn:schemas-rinconnetworks-com:metadata-1-0/">SA_RINCON65031_</desc></item></DIDL-Lite>";
$this->SetAVTransportURI($radio,$MetaData);
}
public function SetAVTransportURI($tspuri,$MetaData="")
{
$content='POST /MediaRenderer/AVTransport/Control HTTP/1.1
CONNECTION: close
HOST: '.$this->address.':1400
CONTENT-LENGTH: '.(342+strlen(htmlspecialchars($tspuri))+strlen(htmlspecialchars($MetaData))).'
CONTENT-TYPE: text/xml; charset="utf-8"
SOAPACTION: "urn:schemas-upnp-org:service:AVTransport:1#SetAVTransportURI"
0'.htmlspecialchars($tspuri).''.htmlspecialchars($MetaData).'.';
$this->sendPacket($content);
}
public function SetQueue($queue,$MetaData="")
{
$this->SetAVTransportURI($queue,$MetaData);
}
public function ClearQueue()
{
$content='POST /MediaRenderer/AVTransport/Control HTTP/1.1
CONNECTION: close
HOST: '.$this->address.':1400
CONTENT-LENGTH: 290
CONTENT-TYPE: text/xml; charset="utf-8"
SOAPACTION: "urn:schemas-upnp-org:service:AVTransport:1#RemoveAllTracksFromQueue"
0';
$this->sendPacket($content);
}
public function AddToQueue($file)
{
$content='POST /MediaRenderer/AVTransport/Control HTTP/1.1
CONNECTION: close
HOST: '.$this->address.':1400
CONTENT-LENGTH: '.(438+strlen(htmlspecialchars($file))).'
CONTENT-TYPE: text/xml; charset="utf-8"
SOAPACTION: "urn:schemas-upnp-org:service:AVTransport:1#AddURIToQueue"
0'.htmlspecialchars($file).'01';
$this->sendPacket($content);
}
public function RemoveFromQueue($track)
{
$content='POST /MediaRenderer/AVTransport/Control HTTP/1.1
CONNECTION: close
HOST: '.$this->address.':1400
CONTENT-LENGTH: '.(307+strlen($track)).'
CONTENT-TYPE: text/xml; charset="utf-8"
SOAPACTION: "urn:schemas-upnp-org:service:AVTransport:1#RemoveTrackFromQueue"
0Q:0/'.$track.'';
$this->sendPacket($content);
}
public function SetTrack($track)
{
$content='POST /MediaRenderer/AVTransport/Control HTTP/1.1
CONNECTION: close
HOST: '.$this->address.':1400
CONTENT-LENGTH: '.(288+strlen($track)).'
CONTENT-TYPE: text/xml; charset="utf-8"
SOAPACTION: "urn:schemas-upnp-org:service:AVTransport:1#Seek"
0TRACK_NR'.$track.'';
$this->sendPacket($content);
}
/******************* // urn:schemas-upnp-org:device:MediaServer:1 ***********
***************************************************************************/
/* urn:upnp-org:serviceId:ContentDirectory */
// MERKERTITLE
//Gibt ein Array mit den Songs der aktuellen Playlist
public function GetCurrentPlaylist()
{
$header='POST /MediaServer/ContentDirectory/Control HTTP/1.1
SOAPACTION: "urn:schemas-upnp-org:service:ContentDirectory:1#Browse"
CONTENT-TYPE: text/xml; charset="utf-8"
HOST: '.$this->address.':1400';
$xml='<!--?xml version="1.0" encoding="utf-8"?-->
Q:0BrowseDirectChildren01000
';
$content=$header . '
Content-Length: '. strlen($xml) .'
'. $xml;
$returnContent = $this->sendPacket($content);
$returnContent = substr($returnContent, stripos($returnContent, '<'));
$returnContent = substr($returnContent, 0, strrpos($returnContent, '>') + 4);
$returnContent = str_replace(array("<", ">", """, "&", "%3a", "%2f", "%25"), array("<", ">", "\"", "&", ":", "/", "%"), $returnContent);
$xml = new SimpleXMLElement($returnContent);
$liste = array();
for($i=0,$size=count($xml);$i<$size;$i++)
{
$aktrow = $xml->item[$i];
$albumart = $aktrow->xpath("upnp:albumArtURI");
$title = $aktrow->xpath("dc:title");
$artist = $aktrow->xpath("dc:creator");
$album = $aktrow->xpath("upnp:album");
$liste[$i]['listid']=$i+1;
if(isset($albumart[0])){
$liste[$i]['albumArtURI']="http://" . $this->address . ":1400".(string)$albumart[0];
}else{
$liste[$i]['albumArtURI'] ="";
}
$liste[$i]['title']=(string)$title[0];
if(isset($artist[0])){
$liste[$i]['artist']=(string)$artist[0];
}else{
$liste[$i]['artist']="";
}
if(isset($album[0])){
$liste[$i]['album']=(string)$album[0];
}else{
$liste[$i]['album']="";
}
}
return $liste;
}
//Liefert ein Array mit allen Sonos Wiedergabelisten und deren Aufrufinformationen
public function GetSonosPlaylists()
{
$header='POST /MediaServer/ContentDirectory/Control HTTP/1.1
SOAPACTION: "urn:schemas-upnp-org:service:ContentDirectory:1#Browse"
CONTENT-TYPE: text/xml; charset="utf-8"
HOST: '.$this->address.':1400';
$xml='<!--?xml version="1.0" encoding="utf-8"?-->
SQ:BrowseDirectChildren0100
';
$content=$header . '
Content-Length: '. strlen($xml) .'
'. $xml;
$returnContent = $this->sendPacket($content);
$returnContent = substr($returnContent, stripos($returnContent, '<'));
$returnContent = substr($returnContent, 0, strrpos($returnContent, '>') + 4);
$returnContent = str_replace(array("<", ">", """, "&", "%3a", "%2f", "%25"), array("<", ">", "\"", "&", ":", "/", "%"), $returnContent);
$xml = new SimpleXMLElement($returnContent);
$liste = array();
for($i=0,$size=count($xml);$i<$size;$i++)
{
$attr = $xml->container[$i]->attributes();
$liste[$i]['id'] = (string)$attr['id'];
$title = $xml->container[$i];
$title = $title->xpath('dc:title');
$liste[$i]['title'] = (string)$title[0];
$liste[$i]['typ'] = "Sonos";
$liste[$i]['file'] = (string)$xml->container[$i]->res;
}
return $liste;
}
//Liefert ein Array mit allen "Importierte Playlisten" Wiedergabelisten und deren Aufrufinformationen
public function GetImportedPlaylists()
{
$header='POST /MediaServer/ContentDirectory/Control HTTP/1.1
SOAPACTION: "urn:schemas-upnp-org:service:ContentDirectory:1#Browse"
CONTENT-TYPE: text/xml; charset="utf-8"
HOST: '.$this->address.':1400';
$xml='<!--?xml version="1.0" encoding="utf-8"?-->
A:PLAYLISTSBrowseDirectChildren0100
';
$content=$header . '
Content-Length: '. strlen($xml) .'
'. $xml;
$returnContent = $this->sendPacket($content);
$returnContent = substr($returnContent, stripos($returnContent, '<'));
$returnContent = substr($returnContent, 0, strrpos($returnContent, '>') + 4);
$returnContent = str_replace(array("<", ">", """, "&", "%3a", "%2f", "%25"), array("<", ">", "\"", "&", ":", "/", "%"), $returnContent);
$xml = new SimpleXMLElement($returnContent);
$liste = array();
for($i=0,$size=count($xml);$i<$size;$i++)
{
$attr = $xml->container[$i]->attributes();
$liste[$i]['id'] = (string)$attr['id'];
$title = $xml->container[$i];
$title = $title->xpath('dc:title');
// br substring use cuts my playlist names at the 4th char
$liste[$i]['title'] = (string)$title[0];
$liste[$i]['title']=preg_replace("/^(.+)\.m3u$/i","\\1",$liste[$i]['title']);
$liste[$i]['typ'] = "Import";
$liste[$i]['file'] = (string)$xml->container[$i]->res;
}
return $liste;
}
//Gibt ein Array mit den einzelnen Songs der Playlist wieder
//ObjektID aus GetSonosPlaylists() oder GetImportetPlaylists()
public function GetPlaylist($value)
{
$header='POST /MediaServer/ContentDirectory/Control HTTP/1.1
SOAPACTION: "urn:schemas-upnp-org:service:ContentDirectory:1#Browse"
CONTENT-TYPE: text/xml; charset="utf-8"
HOST: '.$this->address.':1400';
$xml='<!--?xml version="1.0" encoding="utf-8"?-->
'.$value.'BrowseDirectChildren01000
';
$content=$header . '
Content-Length: '. strlen($xml) .'
'. $xml;
$returnContent = $this->sendPacket($content);
$xmlParser = xml_parser_create();
$returnContent = substr($returnContent, stripos($returnContent, '<'));
$returnContent = substr($returnContent, 0, strrpos($returnContent, '>') + 4);
$returnContent = str_replace(array("<", ">", """, "&", "%3a", "%2f", "%25"), array("<", ">", "\"", "&", ":", "/", "%"), $returnContent);
$xml = new SimpleXMLElement($returnContent);
$liste = array();
for($i=0,$size=count($xml);$i<$size;$i++)
{
$aktrow = $xml->item[$i];
$albumart = $aktrow->xpath("upnp:albumArtURI");
$title = $aktrow->xpath("dc:title");
$artist = $aktrow->xpath("dc:creator");
$album = $aktrow->xpath("upnp:album");
$liste[$i]['listid']=$i+1;
if(isset($albumart[0])){
$liste[$i]['albumArtURI']="http://" . $this->address . ":1400".(string)$albumart[0];
}else{
$liste[$i]['albumArtURI'] ="";
}
$liste[$i]['title']=(string)$title[0];
if(isset($interpret[0])){
$liste[$i]['artist']=(string)$artist[0];
}else{
$liste[$i]['artist']="";
}
if(isset($album[0])){
$liste[$i]['album']=(string)$album[0];
}else{
$liste[$i]['album']="";
}
}
return $liste;
}
/***************************************************************************
Helper / sendPacket
***************************************************************************/
/*
Unter LocalUID findet sich der String, den man im $sonos->SetQueue Befehl braucht.
Den String kann man z.B. über folgendes Codeschnipsel bekommen:
$url = "http://"..":1400/status/zp";
$xml = simpleXML_load_file($url);
$result = $xml->ZPInfo->LocalUID;
Quelle: http://www.ip-symcon.de/forum/f53/php-sonos-klasse-ansteuern-einzelner-player-7676/#post87054
*/
// do not filter xml answer
private function XMLsendPacket( $content )
{
$fp = fsockopen($this->address, 1400 /* Port */, $errno, $errstr, 10);
if (!$fp)
throw new Exception("Error opening socket: ".$errstr." (".$errno.")");
fputs ($fp, $content);
$ret = "";
$buffer = "";
while (!feof($fp)) {
$buffer = fgets($fp,128);
// echo "\n;" . $buffer . ";\n"; //DEBUG
$ret.= $buffer;
}
// echo "\n\nReturn:" . $ret . "!!\n";
fclose($fp);
if(strpos($ret, "200 OK") === false)
throw new Exception("Error sending command: ".$ret);
$array = preg_split("/\n/", $ret);
return $array[count($array) - 1];
}
private function sendPacket( $content )
{
$fp = fsockopen($this->address, 1400 /* Port */, $errno, $errstr, 10);
if (!$fp)
throw new Exception("Error opening socket: ".$errstr." (".$errno.")");
fputs ($fp, $content);
$ret = "";
while (!feof($fp)) {
$ret.= fgetss($fp,128); // filter xml answer
}
fclose($fp);
if(strpos($ret, "200 OK") === false)
throw new Exception("Error sending command: ".$ret);
// echo "sendPacketDebug: "; //DEBUG
// print_r($ret);
$array = preg_split("/\n/", $ret);
return $array[count($array) - 1];
}
}
?>