IP = $Sonos_IP;
$this->PORT = $Sonos_Port;
}
protected function Upnp($url,$SOAP_service,$SOAP_action,$SOAP_arguments = '',$XML_filter = '')
{
$POST_xml = '';
$POST_xml .= '';
$POST_xml .= '';
$POST_xml .= $SOAP_arguments;
$POST_xml .= '';
$POST_xml .= '';
$POST_xml .= '';
$POST_url = $this->IP.":".$this->PORT.$url;
$ch = curl_init();
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 0);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 0);
curl_setopt($ch, CURLOPT_URL, $POST_url);
curl_setopt($ch, CURLOPT_HEADER, 0);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_TIMEOUT, 30);
curl_setopt($ch, CURLOPT_HTTPHEADER, array("Content-Type: text/xml", "SOAPAction: ".$SOAP_service."#".$SOAP_action));
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_POSTFIELDS, $POST_xml);
$r = curl_exec($ch);
curl_close($ch);
if ($XML_filter != '')
return $this->Filter($r,$XML_filter);
else
return $r;
}
protected function Filter($subject,$pattern)
{
preg_match('/\<'.$pattern.'\>(.+)\<\/'.$pattern.'\>/',$subject,$matches); ///'/\<'.$pattern.'\>(.+)\<\/'.$pattern.'\>/'
return $matches[1];
}
/**
* Play
*/
public function Play()
{
$url = '/MediaRenderer/AVTransport/Control';
$action = 'Play';
$service = 'urn:schemas-upnp-org:service:AVTransport:1';
$args = '01';
return $this->Upnp($url,$service,$action,$args);
}
/**
* Pause
*/
public function Pause()
{
$url = '/MediaRenderer/AVTransport/Control';
$action = 'Pause';
$service = 'urn:schemas-upnp-org:service:AVTransport:1';
$args = '0';
return $this->Upnp($url,$service,$action,$args);
}
/**
* Stop
*/
public function Stop()
{
$url = '/MediaRenderer/AVTransport/Control';
$action = 'Stop';
$service = 'urn:schemas-upnp-org:service:AVTransport:1';
$args = '0';
return $this->Upnp($url,$service,$action,$args);
}
/**
* Next
*/
public function Next()
{
$url = '/MediaRenderer/AVTransport/Control';
$action = 'Next';
$service = 'urn:schemas-upnp-org:service:AVTransport:1';
$args = '0';
return $this->Upnp($url,$service,$action,$args);
}
/**
* Previous
*/
public function Previous()
{
$url = '/MediaRenderer/AVTransport/Control';
$action = 'Previous';
$service = 'urn:schemas-upnp-org:service:AVTransport:1';
$args = '0';
return $this->Upnp($url,$service,$action,$args);
}
/**
* Seek to position xx:xx:xx or track number x
* @param string 'REL_TIME' for time position (xx:xx:xx) or 'TRACK_NR' for track in actual queue
* @param string
*/
public function Seek($type,$position)
{
$url = '/MediaRenderer/AVTransport/Control';
$action = 'Seek';
$service = 'urn:schemas-upnp-org:service:AVTransport:1';
$args = '0'.$type.''.$position.'';
return $this->Upnp($url,$service,$action,$args);
}
/**
* Seek to time xx:xx:xx
*/
public function SeekTime($time)
{
return $this->Seek("REL_TIME",$time);
}
/**
* Change to track number
*/
public function ChangeTrack($number)
{
return $this->Seek("TRACK_NR",$number);
}
/**
* Restart actual track
*/
public function RestartTrack()
{
return $this->Seek("REL_TIME","00:00:00");
}
/**
* Restart actual queue
*/
public function RestartQueue()
{
return $this->Seek("TRACK_NR","1");
}
/**
* Get volume value (0-100)
*/
public function GetVolume()
{
$url = '/MediaRenderer/RenderingControl/Control';
$action = 'GetVolume';
$service = 'urn:schemas-upnp-org:service:RenderingControl:1';
$args = '0Master';
$filter = 'CurrentVolume';
return $this->Upnp($url,$service,$action,$args,$filter);
}
/**
* Set volume value (0-100)
*/
public function SetVolume($volume)
{
$url = '/MediaRenderer/RenderingControl/Control';
$action = 'SetVolume';
$service = 'urn:schemas-upnp-org:service:RenderingControl:1';
$args = '0Master'.$volume.'';
return $this->Upnp($url,$service,$action,$args);
}
/**
* Get mute status
*/
public function GetMute()
{
$url = '/MediaRenderer/RenderingControl/Control';
$action = 'GetMute';
$service = 'urn:schemas-upnp-org:service:RenderingControl:1';
$args = '0Master';
$filter = 'CurrentMute';
return $this->Upnp($url,$service,$action,$args,$filter);
}
/**
* Set mute
* @param integer mute active=1
*/
public function SetMute($mute = 0)
{
$url = '/MediaRenderer/RenderingControl/Control';
$action = 'SetMute';
$service = 'urn:schemas-upnp-org:service:RenderingControl:1';
$args = '0Master'.$mute.'';
return $this->Upnp($url,$service,$action,$args);
}
/**
* Get Transport Info : get status about player
*/
public function GetTransportInfo()
{
$url = '/MediaRenderer/AVTransport/Control';
$action = 'GetTransportInfo';
$service = 'urn:schemas-upnp-org:service:AVTransport:1';
$args = '0';
$filter = 'CurrentTransportState';
return $this->Upnp($url,$service,$action,$args,$filter);
}
/**
* Get Media Info : get informations about media
*/
public function GetMediaInfo()
{
$url = '/MediaRenderer/AVTransport/Control';
$action = 'GetMediaInfo';
$service = 'urn:schemas-upnp-org:service:AVTransport:1';
$args = '0';
$filter = 'CurrentURI';
return $this->Upnp($url,$service,$action,$args,$filter);
}
/**
* Get Position Info : get some informations about track
*/
public function GetPositionInfo()
{
$url = '/MediaRenderer/AVTransport/Control';
$action = 'GetPositionInfo';
$service = 'urn:schemas-upnp-org:service:AVTransport:1';
$args = '0';
$xml = $this->Upnp($url,$service,$action,$args);
$data["TrackNumberInQueue"] = $this->Filter($xml,"Track");
$data["TrackURI"] = $this->Filter($xml,"TrackURI");
$data["TrackDuration"] = $this->Filter($xml,"TrackDuration");
$data["RelTime"] = $this->Filter($xml,"RelTime");
$TrackMetaData = $this->Filter($xml,"TrackMetaData");
$xml = substr($xml, stripos($TrackMetaData, '<'));
$xml = substr($xml, 0, strrpos($xml, '>') + 4);
$xml = str_replace(array("<", ">", """, "&", "%3a", "%2f", "%25"), array("<", ">", "\"", "&", ":", "/", "%"), $xml);
$data["Title"] = $this->Filter($xml,"dc:title"); // Track Title
$data["AlbumArtist"] = $this->Filter($xml,"r:albumArtist"); // Album Artist
$data["Album"] = $this->Filter($xml,"upnp:album"); // Album Title
$data["TitleArtist"] = $this->Filter($xml,"dc:creator"); // Track Artist
return $data;
}
/**
* Add URI to Queue
* @param string track/radio URI
* @param bool added next (=1) or end queue (=0)
*/
public function AddURIToQueue($URI,$next=0)
{
$url = '/MediaRenderer/AVTransport/Control';
$action = 'AddURIToQueue';
$service = 'urn:schemas-upnp-org:service:AVTransport:1';
$next = (int)$next;
$args = '0'.$URI.'0'.$next.'';
$filter = 'FirstTrackNumberEnqueued';
return $this->Upnp($url,$service,$action,$args,$filter);
}
/**
* Remove a track from Queue
*
*/
public function RemoveTrackFromQueue($tracknumber)
{
$url = '/MediaRenderer/AVTransport/Control';
$action = 'RemoveTrackFromQueue';
$service = 'urn:schemas-upnp-org:service:AVTransport:1';
$args = '0Q:0/'.$tracknumber.'';
return $this->Upnp($url,$service,$action,$args);
}
/**
* Clear Queue
*
*/
public function RemoveAllTracksFromQueue()
{
$url = '/MediaRenderer/AVTransport/Control';
$action = 'RemoveAllTracksFromQueue';
$service = 'urn:schemas-upnp-org:service:AVTransport:1';
$args = '0';
return $this->Upnp($url,$service,$action,$args);
}
/**
* Set Queue
* @param string URI of new track
*/
public function SetQueue($URI)
{
$url = '/MediaRenderer/AVTransport/Control';
$action = 'SetAVTransportURI';
$service = 'urn:schemas-upnp-org:service:AVTransport:1';
$args = '0'.$URI.'';
return $this->Upnp($url,$service,$action,$args);
}
/**
* Refresh music library
*
*/
public function RefreshShareIndex()
{
$url = '/MediaServer/ContentDirectory/Control';
$action = 'RefreshShareIndex';
$service = 'urn:schemas-upnp-org:service:ContentDirectory:1';
return $this->Upnp($url,$service,$action,$args);
}
/**
* Split string in several strings
*
*/
protected function CutString($string,$intmax)
{
$i = 0;
while (strlen($string) > $intmax)
{
$string_cut = substr($string, 0, $intmax);
$last_space = strrpos($string_cut, "+");
$strings[$i] = substr($string, 0, $last_space);
$string = substr($string, $last_space, strlen($string));
$i++;
}
$strings[$i] = $string;
return $strings;
}
/**
* Convert Words (text) to Speech (MP3)
*
*/
protected function TTSToMp3($words,$lang)
{
// Directory
$folder = "audio/".$lang;
// Replace the non-alphanumeric characters
// The spaces in the sentence are replaced with the Plus symbol
$words = urlencode($words);
// Name of the MP3 file generated using the MD5 hash
$file = md5($words);
// If folder doesn't exists, create it
if (!file_exists($folder))
mkdir($folder, 0755, true);
// Save the MP3 file in this folder with the .mp3 extension
$file = $folder."/TTS-".$file.".mp3";
// If the MP3 file exists, do not create a new request
if (!file_exists($file))
{
// Google Translate API cannot handle strings > 100 characters
$words = $this->CutString($words,100);
ini_set('user_agent', 'Mozilla/5.0 (Windows NT 6.1; WOW64; rv:21.0) Gecko/20100101 Firefox/21.0');
$mp3 = "";
for ($i = 0; $i < count($words); $i++)
$mp3[$i] = file_get_contents('http://translate.google.com/translate_tts?q='.$words[$i].'&tl='.$lang);
file_put_contents($file, $mp3);
}
return $file;
}
/**
* Say song name via TTS message
* @param string message
* @param string radio name display on sonos controller
* @param int volume
* @param string language
*/
public function SongNameTTS($directory,$volume=0,$unmute=0,$lang='fr')
{
$ThisSong = "Cette chanson s'appelle ";
$By = " de ";
$actual['track'] = $this->GetPositionInfo();
$SongName = $actual['track']['Title'];
$Artist = $actual['track']['TitleArtist'];
$message = $ThisSong . $SongName . $By . $Artist ;
$this->PlayTTS($message,$directory,$volume,$unmute,$lang);
return true;
}
/**
* Play a TTS message
* @param string message
* @param string radio name display on sonos controller
* @param int volume
* @param string language
*/
public function PlayTTS($message,$directory,$volume=0,$unmute=0,$lang='fr')
{
$actual['track'] = $this->GetPositionInfo();
$actual['volume'] = $this->GetVolume();
$actual['mute'] = $this->GetMute();
$actual['status'] = $this->GetTransportInfo();
$this->Pause();
if ($unmute == 1)
$this->SetMute(0);
if ($volume != 0)
$this->SetVolume($volume);
$file = 'x-file-cifs://'.$directory.'/'.$this->TTSToMp3($message,$lang);
if (((stripos($actual['track']["TrackURI"],"x-file-cifs://")) != false) or ((stripos($actual['track']["TrackURI"],".mp3")) != false))
{
// It's a MP3 file
$TrackNumber = $this->AddURIToQueue($file);
$this->ChangeTrack($TrackNumber);
$this->Play();
sleep(2);
while ($this->GetTransportInfo() == "PLAYING") {}
$this->Pause();
$this->SetVolume($actual['volume']);
$this->SetMute($actual['mute']);
$this->ChangeTrack($actual['track']["TrackNumberInQueue"]);
$this->SeekTime($actual['track']["RelTime"]);
$this->RemoveTrackFromQueue($TrackNumber);
}
else
{
//It's a radio / or TV (playbar) / or nothing
$this->SetQueue($file);
$this->Play();
sleep(2);
while ($this->GetTransportInfo() == "PLAYING") {}
$this->Pause();
$this->SetVolume($actual['volume']);
$this->SetMute($actual['mute']);
$this->SetQueue($actual['track']["TrackURI"]);
}
if (strcmp($actual['status'],"PLAYING") == 0)
$this->Play();
return true;
}
public function AddSpotifyToQueue($spotify_id, $next = false) {
$rand = mt_rand(10000000, 99999999);
$meta = '
-
object.item.audioItem.musicTrack
SA_RINCON2311_X_#Svc2311-0-Token
';
$meta = htmlentities($meta);
$url = '/MediaRenderer/AVTransport/Control';
$action = 'AddURIToQueue';
$service = 'urn:schemas-upnp-org:service:AVTransport:1';
$next = (int)$next;
$args = "
0
x-sonos-spotify:spotify%3atrack%3a{$spotify_id}
{$meta}
0
{$next}
";
$filter = 'FirstTrackNumberEnqueued';
return $this->Upnp($url, $service, $action, $args, $filter);
}
public function device_info() {
$xml = $this->_device_info_raw('/xml/device_description.xml');
$out = array(
'friendlyName' => (string)$xml->device->friendlyName,
'modelNumber' => (string)$xml->device->modelNumber,
'modelName' => (string)$xml->device->modelName,
'softwareVersion' => (string)$xml->device->softwareVersion,
'hardwareVersion' => (string)$xml->device->hardwareVersion,
'roomName' => (string)$xml->device->roomName,
);
return $out;
}
public function get_coordinator() {
$topology = $this->_device_info_raw('/status/topology');
$myself = null;
$coordinators = array();
// Loop players, build map of coordinators and find myself
foreach ($topology->ZonePlayers->ZonePlayer as $player) {
$player_data = $player->attributes();
$url = parse_url((string)$player_data->location);
$ip = $url['host'];
if ($ip == $this->IP) {
$myself = $player_data;
}
if ((string)$player_data->coordinator == 'true') {
$coordinators[(string)$player_data->group] = $ip;
}
}
$coordinator = $coordinators[(string)$myself->group];
return new static($coordinator);
}
protected function _device_info_raw($url) {
$url = "http://{$this->IP}:{$this->PORT}{$url}";
if (!isset($this->_raw[$url])) {
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
$data = curl_exec($ch);
curl_close($ch);
$this->_raw[$url] = simplexml_load_string($data);
}
return $this->_raw[$url];
}
public static function detect($ip = '239.255.255.250', $port = 1900) {
$sock = socket_create(AF_INET, SOCK_DGRAM, SOL_UDP);
socket_set_option($sock, getprotobyname('ip'), IP_MULTICAST_TTL, 2);
$data = <<device_info();
if ($dev['roomName'] == $room_name) {
return $device->get_coordinator();
}
}
return false;
}
}