mirror of
https://github.com/Yetangitu/ampache
synced 2025-10-03 17:59:21 +02:00
UPnP localplay implementation
This commit is contained in:
parent
5d09e7dcad
commit
7463e75204
11 changed files with 1578 additions and 65 deletions
|
@ -923,7 +923,7 @@ abstract class Catalog extends database_object
|
||||||
* @param array|null $catalogs
|
* @param array|null $catalogs
|
||||||
* @return \Artist[]
|
* @return \Artist[]
|
||||||
*/
|
*/
|
||||||
public static function get_artists($catalogs = null)
|
public static function get_artists($size = 0, $offset = 0, $catalogs = null)
|
||||||
{
|
{
|
||||||
$sql_where = "";
|
$sql_where = "";
|
||||||
if (is_array($catalogs) && count($catalogs)) {
|
if (is_array($catalogs) && count($catalogs)) {
|
||||||
|
@ -931,7 +931,18 @@ abstract class Catalog extends database_object
|
||||||
$sql_where = "WHERE `song`.`catalog` IN $catlist";
|
$sql_where = "WHERE `song`.`catalog` IN $catlist";
|
||||||
}
|
}
|
||||||
|
|
||||||
$sql = "SELECT `artist`.id, `artist`.`name`, `artist`.`summary` FROM `song` LEFT JOIN `artist` ON `artist`.`id` = `song`.`artist` $sql_where GROUP BY `song`.artist ORDER BY `artist`.`name`";
|
$sql_limit = "";
|
||||||
|
if ($offset > 0 && $size > 0) {
|
||||||
|
$sql_limit = "LIMIT $offset, $size";
|
||||||
|
} elseif ($size > 0) {
|
||||||
|
$sql_limit = "LIMIT $size";
|
||||||
|
} elseif ($offset > 0) {
|
||||||
|
// MySQL doesn't have notation for last row, so we have to use the largest possible BIGINT value
|
||||||
|
// https://dev.mysql.com/doc/refman/5.0/en/select.html
|
||||||
|
$sql_limit = "LIMIT $offset, 18446744073709551615";
|
||||||
|
}
|
||||||
|
|
||||||
|
$sql = "SELECT `artist`.id, `artist`.`name`, `artist`.`summary` FROM `song` LEFT JOIN `artist` ON `artist`.`id` = `song`.`artist` $sql_where GROUP BY `song`.artist ORDER BY `artist`.`name` $sql_limit";
|
||||||
|
|
||||||
$results = array();
|
$results = array();
|
||||||
$db_results = Dba::read($sql);
|
$db_results = Dba::read($sql);
|
||||||
|
|
|
@ -55,10 +55,10 @@ class Upnp_Api
|
||||||
socket_set_option($socket, SOL_SOCKET, SO_BROADCAST, 1);
|
socket_set_option($socket, SOL_SOCKET, SO_BROADCAST, 1);
|
||||||
socket_sendto($socket, $buf, strlen($buf), 0, $host, $port);
|
socket_sendto($socket, $buf, strlen($buf), 0, $host, $port);
|
||||||
socket_close($socket);
|
socket_close($socket);
|
||||||
usleep($delay*1000);
|
usleep($delay * 1000);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function sddpSend($delay=15, $host="239.255.255.250", $port=1900)
|
public static function sddpSend($delay=15, $host="239.255.255.250", $port=1900, $prefix="NT")
|
||||||
{
|
{
|
||||||
$strHeader = 'NOTIFY * HTTP/1.1' . "\r\n";
|
$strHeader = 'NOTIFY * HTTP/1.1' . "\r\n";
|
||||||
$strHeader .= 'HOST: ' . $host . ':' . $port . "\r\n";
|
$strHeader .= 'HOST: ' . $host . ':' . $port . "\r\n";
|
||||||
|
@ -66,28 +66,28 @@ class Upnp_Api
|
||||||
$strHeader .= 'SERVER: DLNADOC/1.50 UPnP/1.0 Ampache/3.7' . "\r\n";
|
$strHeader .= 'SERVER: DLNADOC/1.50 UPnP/1.0 Ampache/3.7' . "\r\n";
|
||||||
$strHeader .= 'CACHE-CONTROL: max-age=1800' . "\r\n";
|
$strHeader .= 'CACHE-CONTROL: max-age=1800' . "\r\n";
|
||||||
$strHeader .= 'NTS: ssdp:alive' . "\r\n";
|
$strHeader .= 'NTS: ssdp:alive' . "\r\n";
|
||||||
$rootDevice = 'NT: upnp:rootdevice' . "\r\n";
|
|
||||||
$rootDevice .= 'USN: uuid:' . self::UUIDSTR . '::upnp:rootdevice' . "\r\n". "\r\n";
|
|
||||||
|
|
||||||
|
$rootDevice = $prefix . ': upnp:rootdevice' . "\r\n";
|
||||||
|
$rootDevice .= 'USN: uuid:' . self::UUIDSTR . '::upnp:rootdevice' . "\r\n". "\r\n";
|
||||||
$buf = $strHeader . $rootDevice;
|
$buf = $strHeader . $rootDevice;
|
||||||
self::udpSend($buf, $delay, $host, $port);
|
self::udpSend($buf, $delay, $host, $port);
|
||||||
|
|
||||||
$uuid = 'NT: uuid:' . self::UUIDSTR . "\r\n";
|
$uuid = $prefix . ': uuid:' . self::UUIDSTR . "\r\n";
|
||||||
$uuid .= 'USN: uuid:' . self::UUIDSTR . "\r\n". "\r\n";
|
$uuid .= 'USN: uuid:' . self::UUIDSTR . "\r\n". "\r\n";
|
||||||
$buf = $strHeader . $uuid;
|
$buf = $strHeader . $uuid;
|
||||||
self::udpSend($buf, $delay, $host, $port);
|
self::udpSend($buf, $delay, $host, $port);
|
||||||
|
|
||||||
$deviceType = 'NT: urn:schemas-upnp-org:device:MediaServer:1' . "\r\n";
|
$deviceType = $prefix . ': urn:schemas-upnp-org:device:MediaServer:1' . "\r\n";
|
||||||
$deviceType .= 'USN: uuid:' . self::UUIDSTR . '::urn:schemas-upnp-org:device:MediaServer:1' . "\r\n". "\r\n";
|
$deviceType .= 'USN: uuid:' . self::UUIDSTR . '::urn:schemas-upnp-org:device:MediaServer:1' . "\r\n". "\r\n";
|
||||||
$buf = $strHeader . $deviceType;
|
$buf = $strHeader . $deviceType;
|
||||||
self::udpSend($buf, $delay, $host, $port);
|
self::udpSend($buf, $delay, $host, $port);
|
||||||
|
|
||||||
$serviceCM = 'NT: urn:schemas-upnp-org:service:ConnectionManager:1' . "\r\n";
|
$serviceCM = $prefix . ': urn:schemas-upnp-org:service:ConnectionManager:1' . "\r\n";
|
||||||
$serviceCM .= 'USN: uuid:' . self::UUIDSTR . '::urn:schemas-upnp-org:service:ConnectionManager:1' . "\r\n". "\r\n";
|
$serviceCM .= 'USN: uuid:' . self::UUIDSTR . '::urn:schemas-upnp-org:service:ConnectionManager:1' . "\r\n". "\r\n";
|
||||||
$buf = $strHeader . $serviceCM;
|
$buf = $strHeader . $serviceCM;
|
||||||
self::udpSend($buf, $delay, $host, $port);
|
self::udpSend($buf, $delay, $host, $port);
|
||||||
|
|
||||||
$serviceCD = 'NT: urn:schemas-upnp-org:service:ContentDirectory:1' . "\r\n";
|
$serviceCD = $prefix . ': urn:schemas-upnp-org:service:ContentDirectory:1' . "\r\n";
|
||||||
$serviceCD .= 'USN: uuid:' . self::UUIDSTR . '::urn:schemas-upnp-org:service:ContentDirectory:1' . "\r\n". "\r\n";
|
$serviceCD .= 'USN: uuid:' . self::UUIDSTR . '::urn:schemas-upnp-org:service:ContentDirectory:1' . "\r\n". "\r\n";
|
||||||
$buf = $strHeader . $serviceCD;
|
$buf = $strHeader . $serviceCD;
|
||||||
self::udpSend($buf, $delay, $host, $port);
|
self::udpSend($buf, $delay, $host, $port);
|
||||||
|
@ -466,8 +466,10 @@ class Upnp_Api
|
||||||
case 'artists':
|
case 'artists':
|
||||||
switch (count($pathreq)) {
|
switch (count($pathreq)) {
|
||||||
case 1: // Get artists list
|
case 1: // Get artists list
|
||||||
$artists = Catalog::get_artists();
|
//$artists = Catalog::get_artists();
|
||||||
list($maxCount, $artists) = self::_slice($artists, $start, $count);
|
//list($maxCount, $artists) = self::_slice($artists, $start, $count);
|
||||||
|
$artists = Catalog::get_artists($count, $start);
|
||||||
|
list($maxCount, $artists) = array(999999, $artists);
|
||||||
foreach ($artists as $artist) {
|
foreach ($artists as $artist) {
|
||||||
$artist->format();
|
$artist->format();
|
||||||
$mediaItems[] = self::_itemArtist($artist, $parent);
|
$mediaItems[] = self::_itemArtist($artist, $parent);
|
||||||
|
@ -491,8 +493,10 @@ class Upnp_Api
|
||||||
case 'albums':
|
case 'albums':
|
||||||
switch (count($pathreq)) {
|
switch (count($pathreq)) {
|
||||||
case 1: // Get albums list
|
case 1: // Get albums list
|
||||||
$album_ids = Catalog::get_albums();
|
//!!$album_ids = Catalog::get_albums();
|
||||||
list($maxCount, $album_ids) = self::_slice($album_ids, $start, $count);
|
//!!list($maxCount, $album_ids) = self::_slice($album_ids, $start, $count);
|
||||||
|
$album_ids = Catalog::get_albums($count, $start);
|
||||||
|
list($maxCount, $album_ids) = array(999999, $album_ids);
|
||||||
foreach ($album_ids as $album_id) {
|
foreach ($album_ids as $album_id) {
|
||||||
$album = new Album($album_id);
|
$album = new Album($album_id);
|
||||||
$album->format();
|
$album->format();
|
||||||
|
@ -849,10 +853,10 @@ class Upnp_Api
|
||||||
|
|
||||||
private static function _replaceSpecialSymbols($title)
|
private static function _replaceSpecialSymbols($title)
|
||||||
{
|
{
|
||||||
debug_event('upnp_class', 'replace <<< ' . $title, 5);
|
///debug_event('upnp_class', 'replace <<< ' . $title, 5);
|
||||||
// replace non letter or digits
|
// replace non letter or digits
|
||||||
$title = preg_replace('~[^\\pL\d\.\s\(\)]+~u', '-', $title);
|
$title = preg_replace('~[^\\pL\d\.\s\(\)\.\,\'\"]+~u', '-', $title);
|
||||||
debug_event('upnp_class', 'replace >>> ' . $title, 5);
|
///debug_event('upnp_class', 'replace >>> ' . $title, 5);
|
||||||
|
|
||||||
if ($title == "")
|
if ($title == "")
|
||||||
$title = '(no title)';
|
$title = '(no title)';
|
||||||
|
@ -912,7 +916,7 @@ class Upnp_Api
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static function _itemSong($song, $parent)
|
public static function _itemSong($song, $parent)
|
||||||
{
|
{
|
||||||
$api_session = (AmpConfig::get('require_session')) ? Stream::get_session() : false;
|
$api_session = (AmpConfig::get('require_session')) ? Stream::get_session() : false;
|
||||||
$art_url = Art::url($song->album, 'album', $api_session);
|
$art_url = Art::url($song->album, 'album', $api_session);
|
||||||
|
@ -927,8 +931,8 @@ class Upnp_Api
|
||||||
'dc:title' => self::_replaceSpecialSymbols($song->f_title),
|
'dc:title' => self::_replaceSpecialSymbols($song->f_title),
|
||||||
'upnp:class' => (isset($arrFileType['class'])) ? $arrFileType['class'] : 'object.item.unknownItem',
|
'upnp:class' => (isset($arrFileType['class'])) ? $arrFileType['class'] : 'object.item.unknownItem',
|
||||||
'upnp:albumArtURI' => $art_url,
|
'upnp:albumArtURI' => $art_url,
|
||||||
'upnp:artist' => $song->f_artist,
|
'upnp:artist' => self::_replaceSpecialSymbols($song->f_artist),
|
||||||
'upnp:album' => $song->f_album,
|
'upnp:album' => self::_replaceSpecialSymbols($song->f_album),
|
||||||
'upnp:genre' => Tag::get_display($song->tags, false, 'song'),
|
'upnp:genre' => Tag::get_display($song->tags, false, 'song'),
|
||||||
//'dc:date' => date("c", $song->addition_time),
|
//'dc:date' => date("c", $song->addition_time),
|
||||||
'upnp:originalTrackNumber' => $song->track,
|
'upnp:originalTrackNumber' => $song->track,
|
||||||
|
@ -940,7 +944,7 @@ class Upnp_Api
|
||||||
'bitrate' => $song->bitrate,
|
'bitrate' => $song->bitrate,
|
||||||
'sampleFrequency' => $song->rate,
|
'sampleFrequency' => $song->rate,
|
||||||
//'nrAudioChannels' => '1',
|
//'nrAudioChannels' => '1',
|
||||||
'description' => $song->comment,
|
'description' => self::_replaceSpecialSymbols($song->comment),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
519
modules/localplay/upnp.controller.php
Normal file
519
modules/localplay/upnp.controller.php
Normal file
|
@ -0,0 +1,519 @@
|
||||||
|
<?php
|
||||||
|
/* vim:set softtabstop=4 shiftwidth=4 expandtab: */
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* LICENSE: GNU General Public License, version 2 (GPLv2)
|
||||||
|
* Copyright 2001 - 2014 Ampache.org
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or
|
||||||
|
* modify it under the terms of the GNU General Public License v2
|
||||||
|
* as published by the Free Software Foundation.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program; if not, write to the Free Software
|
||||||
|
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* AmpacheUPnp Class
|
||||||
|
*
|
||||||
|
* This is the class for the UPnP localplay method to remote control
|
||||||
|
* a UPnP player Instance
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
class AmpacheUPnP extends localplay_controller
|
||||||
|
{
|
||||||
|
/* Variables */
|
||||||
|
private $_version = '000001';
|
||||||
|
|
||||||
|
private $_description = 'Controls a UPnP instance';
|
||||||
|
|
||||||
|
/* @var UPnPPlayer $object */
|
||||||
|
private $_upnp;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor
|
||||||
|
* This returns the array map for the localplay object
|
||||||
|
* REQUIRED for Localplay
|
||||||
|
*/
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
/* Do a Require Once On the needed Libraries */
|
||||||
|
require_once AmpConfig::get('prefix') . '/modules/upnp/upnpplayer.class.php';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* get_description
|
||||||
|
* This returns the description of this localplay method
|
||||||
|
*/
|
||||||
|
public function get_description()
|
||||||
|
{
|
||||||
|
return $this->_description;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* get_version
|
||||||
|
* This returns the current version
|
||||||
|
*/
|
||||||
|
public function get_version()
|
||||||
|
{
|
||||||
|
return $this->_version;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* is_installed
|
||||||
|
* This returns true or false if upnp controller is installed
|
||||||
|
*/
|
||||||
|
public function is_installed()
|
||||||
|
{
|
||||||
|
$sql = "SHOW TABLES LIKE 'localplay_upnp'";
|
||||||
|
$db_results = Dba::query($sql);
|
||||||
|
|
||||||
|
return (Dba::num_rows($db_results) > 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* install
|
||||||
|
* This function installs the upnp localplay controller
|
||||||
|
*/
|
||||||
|
public function install()
|
||||||
|
{
|
||||||
|
$sql = "CREATE TABLE `localplay_upnp` (`id` INT( 11 ) UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY , ".
|
||||||
|
"`name` VARCHAR( 128 ) COLLATE utf8_unicode_ci NOT NULL , " .
|
||||||
|
"`owner` INT( 11 ) NOT NULL, " .
|
||||||
|
"`url` VARCHAR( 255 ) COLLATE utf8_unicode_ci NOT NULL " .
|
||||||
|
") ENGINE = MYISAM DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci";
|
||||||
|
$db_results = Dba::query($sql);
|
||||||
|
|
||||||
|
// Add an internal preference for the users current active instance
|
||||||
|
Preference::insert('upnp_active', 'UPnP Active Instance', '0', '25', 'integer', 'internal');
|
||||||
|
User::rebuild_all_preferences();
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* uninstall
|
||||||
|
* This removes the localplay controller
|
||||||
|
*/
|
||||||
|
public function uninstall()
|
||||||
|
{
|
||||||
|
$sql = "DROP TABLE `localplay_upnp`";
|
||||||
|
$db_results = Dba::query($sql);
|
||||||
|
|
||||||
|
// Remove the pref we added for this
|
||||||
|
Preference::delete('upnp_active');
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* add_instance
|
||||||
|
* This takes key'd data and inserts a new upnp instance
|
||||||
|
*/
|
||||||
|
public function add_instance($data)
|
||||||
|
{
|
||||||
|
$sql = "INSERT INTO `localplay_upnp` (`name`,`url`, `owner`) " .
|
||||||
|
"VALUES (?, ?, ?)";
|
||||||
|
$db_results = Dba::query($sql, array($data['name'], $data['url'], $GLOBALS['user']->id));
|
||||||
|
|
||||||
|
return $db_results;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* delete_instance
|
||||||
|
* This takes a UID and deletes the instance in question
|
||||||
|
*/
|
||||||
|
public function delete_instance($uid)
|
||||||
|
{
|
||||||
|
$sql = "DELETE FROM `localplay_upnp` WHERE `id` = ?";
|
||||||
|
$db_results = Dba::query($sql, array($uid));
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* get_instances
|
||||||
|
* This returns a key'd array of the instance information with
|
||||||
|
* [UID]=>[NAME]
|
||||||
|
*/
|
||||||
|
public function get_instances()
|
||||||
|
{
|
||||||
|
$sql = "SELECT * FROM `localplay_upnp` ORDER BY `name`";
|
||||||
|
$db_results = Dba::query($sql);
|
||||||
|
|
||||||
|
$results = array();
|
||||||
|
|
||||||
|
while ($row = Dba::fetch_assoc($db_results)) {
|
||||||
|
$results[$row['id']] = $row['name'];
|
||||||
|
}
|
||||||
|
|
||||||
|
return $results;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* update_instance
|
||||||
|
* This takes an ID and an array of data and updates the instance specified
|
||||||
|
*/
|
||||||
|
public function update_instance($uid, $data)
|
||||||
|
{
|
||||||
|
$sql = "UPDATE `localplay_upnp` SET `url` = ?, `name` = ? WHERE `id` = ?";
|
||||||
|
$db_results = Dba::query($sql, array($data['url'], $data['name'], $uid));
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* instance_fields
|
||||||
|
* This returns a key'd array of [NAME]=>array([DESCRIPTION]=>VALUE,[TYPE]=>VALUE) for the
|
||||||
|
* fields so that we can on-the-fly generate a form
|
||||||
|
*/
|
||||||
|
public function instance_fields()
|
||||||
|
{
|
||||||
|
$fields['name'] = array('description' => T_('Instance Name'), 'type'=>'textbox');
|
||||||
|
$fields['url'] = array('description' => T_('URL'), 'type'=>'textbox');
|
||||||
|
|
||||||
|
return $fields;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* get_instance
|
||||||
|
* This returns a single instance and all it's variables
|
||||||
|
*/
|
||||||
|
public function get_instance($instance='')
|
||||||
|
{
|
||||||
|
$instance = $instance ? $instance : AmpConfig::get('upnp_active');
|
||||||
|
|
||||||
|
$sql = "SELECT * FROM `localplay_upnp` WHERE `id` = ?";
|
||||||
|
$db_results = Dba::query($sql, array($instance));
|
||||||
|
$row = Dba::fetch_assoc($db_results);
|
||||||
|
|
||||||
|
return $row;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* set_active_instance
|
||||||
|
* This sets the specified instance as the 'active' one
|
||||||
|
*/
|
||||||
|
public function set_active_instance($uid, $user_id='')
|
||||||
|
{
|
||||||
|
// Not an admin? bubkiss!
|
||||||
|
if (!$GLOBALS['user']->has_access('100')) {
|
||||||
|
$user_id = $GLOBALS['user']->id;
|
||||||
|
}
|
||||||
|
$user_id = $user_id ? $user_id : $GLOBALS['user']->id;
|
||||||
|
debug_event('upnp', 'set_active_instance userid: ' . $user_id, 5);
|
||||||
|
|
||||||
|
Preference::update('upnp_active', $user_id, intval($uid));
|
||||||
|
AmpConfig::set('upnp_active', intval($uid), true);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* get_active_instance
|
||||||
|
* This returns the UID of the current active instance
|
||||||
|
* false if none are active
|
||||||
|
*/
|
||||||
|
public function get_active_instance()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public function add_url(Stream_URL $url)
|
||||||
|
{
|
||||||
|
debug_event('upnp', 'add_url: ' . $url->title . " | " . $url->url, 5);
|
||||||
|
|
||||||
|
if (!$this->_upnp) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->_upnp->PlaylistAdd($url->title, $url->url);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* delete_track
|
||||||
|
* Delete a track from the upnp playlist
|
||||||
|
*/
|
||||||
|
public function delete_track($track)
|
||||||
|
{
|
||||||
|
if (!$this->_upnp) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->_upnp->PlaylistRemove($track);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* clear_playlist
|
||||||
|
* This deletes the entire upnp playlist.
|
||||||
|
*/
|
||||||
|
public function clear_playlist()
|
||||||
|
{
|
||||||
|
if (!$this->_upnp) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->_upnp->PlaylistClear();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* play
|
||||||
|
* This just tells upnp to start playing, it does not
|
||||||
|
* take any arguments
|
||||||
|
*/
|
||||||
|
public function play()
|
||||||
|
{
|
||||||
|
if (!$this->_upnp) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->_upnp->Play();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* pause
|
||||||
|
* This tells upnp to pause the current song
|
||||||
|
*/
|
||||||
|
public function pause()
|
||||||
|
{
|
||||||
|
if (!$this->_upnp) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->_upnp->Pause();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* stop
|
||||||
|
* This just tells upnp to stop playing, it does not take
|
||||||
|
* any arguments
|
||||||
|
*/
|
||||||
|
public function stop()
|
||||||
|
{
|
||||||
|
if (!$this->_upnp) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->_upnp->Stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* skip
|
||||||
|
* This tells upnp to skip to the specified song
|
||||||
|
*/
|
||||||
|
public function skip($pos)
|
||||||
|
{
|
||||||
|
if (!$this->_upnp) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->_upnp->Skip($pos);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* next
|
||||||
|
* This just tells upnp to skip to the next song
|
||||||
|
*/
|
||||||
|
public function next()
|
||||||
|
{
|
||||||
|
if (!$this->_upnp) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->_upnp->Next();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* prev
|
||||||
|
* This just tells upnp to skip to the prev song
|
||||||
|
*/
|
||||||
|
public function prev()
|
||||||
|
{
|
||||||
|
if (!$this->_upnp) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->_upnp->Prev();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* volume
|
||||||
|
* This tells upnp to set the volume to the specified amount
|
||||||
|
*/
|
||||||
|
public function volume($volume)
|
||||||
|
{
|
||||||
|
if (!$this->_upnp) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->_upnp->SetVolume($volume);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This tells upnp to increase the volume
|
||||||
|
*/
|
||||||
|
public function volume_up()
|
||||||
|
{
|
||||||
|
if (!$this->_upnp) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->_upnp->VolumeUp();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This tells upnp to decrease the volume
|
||||||
|
*/
|
||||||
|
public function volume_down()
|
||||||
|
{
|
||||||
|
if (!$this->_upnp) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->_upnp->VolumeDown();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* repeat
|
||||||
|
* This tells upnp to set the repeating the playlist (i.e. loop) to either on or off
|
||||||
|
*/
|
||||||
|
public function repeat($state)
|
||||||
|
{
|
||||||
|
debug_event('upnp', 'repeat: ' . $state, 5);
|
||||||
|
|
||||||
|
if (!$this->_upnp) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->_upnp->Repeat(array(
|
||||||
|
'repeat' => ($state ? 'all' : 'off')
|
||||||
|
));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* random
|
||||||
|
* This tells upnp to turn on or off the playing of songs from the playlist in random order
|
||||||
|
*/
|
||||||
|
public function random($onoff)
|
||||||
|
{
|
||||||
|
debug_event('upnp', 'random: ' . $onoff, 5);
|
||||||
|
|
||||||
|
if (!$this->_upnp) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->_upnp->PlayShuffle($onoff);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* get
|
||||||
|
* This functions returns an array containing information about
|
||||||
|
* The songs that upnp currently has in it's playlist. This must be
|
||||||
|
* done in a standardized fashion
|
||||||
|
*/
|
||||||
|
public function get()
|
||||||
|
{
|
||||||
|
debug_event('upnp', 'get', 5);
|
||||||
|
|
||||||
|
if (!$this->_upnp) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$playlist = $this->_upnp->GetPlaylistItems();
|
||||||
|
|
||||||
|
$results = array();
|
||||||
|
$idx = 1;
|
||||||
|
foreach ($playlist as $key => $item) {
|
||||||
|
$data = array();
|
||||||
|
$data['link'] = $item['link'];
|
||||||
|
$data['id'] = $idx;
|
||||||
|
$data['track'] = $idx;
|
||||||
|
|
||||||
|
$url_data = Stream_URL::parse($item['link']);
|
||||||
|
if ($url_data != null) {
|
||||||
|
$song = new Song($url_data['id']);
|
||||||
|
if ($song != null) {
|
||||||
|
$data['name'] = $song->get_artist_name() . ' - ' . $song->title;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!$data['name']) {
|
||||||
|
$data['name'] = $item['name'];
|
||||||
|
}
|
||||||
|
|
||||||
|
$results[] = $data;
|
||||||
|
$idx++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $results;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* status
|
||||||
|
* This returns bool/int values for features, loop, repeat and any other features
|
||||||
|
* that this localplay method supports.
|
||||||
|
* This works as in requesting the upnp properties
|
||||||
|
*/
|
||||||
|
public function status()
|
||||||
|
{
|
||||||
|
debug_event('upnp', 'status', 5);
|
||||||
|
|
||||||
|
if (!$this->_upnp) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$item = $this->_upnp->GetCurrentItem();
|
||||||
|
|
||||||
|
$status = array();
|
||||||
|
|
||||||
|
$status['state'] = $this->_upnp->GetState();
|
||||||
|
$status['volume'] = $this->_upnp->GetVolume();
|
||||||
|
$status['repeat'] = false;
|
||||||
|
$status['random'] = false;
|
||||||
|
$status['track'] = $item['link'];
|
||||||
|
$status['track_title'] = $item['name'];
|
||||||
|
|
||||||
|
$url_data = Stream_URL::parse($item['link']);
|
||||||
|
if ($url_data != null) {
|
||||||
|
$song = new Song($url_data['id']);
|
||||||
|
if ($song != null) {
|
||||||
|
$status['track_artist'] = $song->get_artist_name();
|
||||||
|
$status['track_album'] = $song->get_album_name();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $status;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* connect
|
||||||
|
* This functions creates the connection to upnp and returns
|
||||||
|
* a boolean value for the status, to save time this handle
|
||||||
|
* is stored in this class
|
||||||
|
*/
|
||||||
|
public function connect()
|
||||||
|
{
|
||||||
|
$options = self::get_instance();
|
||||||
|
debug_event('upnp', 'Trying to connect upnp instance ' . $options['name'] . ' ( ' . $options['url'] . ' )', '5');
|
||||||
|
$this->_upnp = new UPnPPlayer($options['name'], $options['url']);
|
||||||
|
debug_event('upnp', 'Connected.', '5');
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
259
modules/upnp/upnpdevice.php
Normal file
259
modules/upnp/upnpdevice.php
Normal file
|
@ -0,0 +1,259 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
class UPnPDevice
|
||||||
|
{
|
||||||
|
private $_settings = array(
|
||||||
|
"descriptionURL" => "",
|
||||||
|
"host" => "",
|
||||||
|
"controlURLs" => array(),
|
||||||
|
"eventURLs" => array()
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
public function UPnPDevice($descriptionUrl)
|
||||||
|
{
|
||||||
|
if (! $this->restoreDescriptionUrl($descriptionUrl))
|
||||||
|
$this->parseDescriptionUrl($descriptionUrl);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Reads description URL from session
|
||||||
|
*/
|
||||||
|
private function restoreDescriptionUrl($descriptionUrl)
|
||||||
|
{
|
||||||
|
debug_event('upnpdevice', 'readDescriptionUrl: ' . $descriptionUrl, 5);
|
||||||
|
$this->_settings = unserialize(Session::read('upnp_dev_' . $descriptionUrl));
|
||||||
|
|
||||||
|
if ($this->_settings['descriptionURL'] == $descriptionUrl) {
|
||||||
|
debug_event('upnpdevice', 'service Urls restored from session.', 5);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function parseDescriptionUrl($descriptionUrl)
|
||||||
|
{
|
||||||
|
debug_event('upnpdevice', 'parseDescriptionUrl: ' . $descriptionUrl, 5);
|
||||||
|
|
||||||
|
$ch = curl_init();
|
||||||
|
curl_setopt($ch, CURLOPT_URL, $descriptionUrl);
|
||||||
|
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
|
||||||
|
$response = curl_exec($ch);
|
||||||
|
curl_close($ch);
|
||||||
|
//!!debug_event('upnpdevice', 'parseDescriptionUrl response: ' . $response, 5);
|
||||||
|
|
||||||
|
$responseXML = simplexml_load_string($response);
|
||||||
|
$services = $responseXML->device->serviceList->service;
|
||||||
|
foreach ($services as $service)
|
||||||
|
{
|
||||||
|
$serviceType = $service->serviceType;
|
||||||
|
$serviceTypeNames = explode(":", $serviceType);
|
||||||
|
$serviceTypeName = $serviceTypeNames[3];
|
||||||
|
$this->_settings['controlURLs'][$serviceTypeName] = (string)$service->controlURL;
|
||||||
|
$this->_settings['eventURLs'][$serviceTypeName] = (string)$service->eventSubURL;
|
||||||
|
}
|
||||||
|
|
||||||
|
$urldata = parse_url($descriptionUrl);
|
||||||
|
$this->_settings['host'] = $urldata['scheme'] . '://' . $urldata['host'] . ':' . $urldata['port'];
|
||||||
|
|
||||||
|
$this->_settings['descriptionURL'] = $descriptionUrl;
|
||||||
|
|
||||||
|
Session::create(array(
|
||||||
|
'type' => 'api',
|
||||||
|
'sid' => 'upnp_dev_' . $descriptionUrl,
|
||||||
|
'value' => serialize($this->_settings)
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sending HTTP-Request and returns parsed response
|
||||||
|
*
|
||||||
|
* @param string $method Method name
|
||||||
|
* @param array $arguments Key-Value array
|
||||||
|
*/
|
||||||
|
public function sendRequestToDevice( $method, $arguments, $type = 'RenderingControl')
|
||||||
|
{
|
||||||
|
$body ='<?xml version="1.0" encoding="utf-8"?>';
|
||||||
|
$body .='<s:Envelope s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/" xmlns:s="http://schemas.xmlsoap.org/soap/envelope/"><s:Body>';
|
||||||
|
$body .=' <u:' . $method . ' xmlns:u="urn:schemas-upnp-org:service:' . $type . ':1">';
|
||||||
|
foreach( $arguments as $arg=>$value ) {
|
||||||
|
$body .=' <'.$arg.'>'.$value.'</'.$arg.'>';
|
||||||
|
}
|
||||||
|
$body .=' </u:' . $method . '>';
|
||||||
|
$body .='</s:Body></s:Envelope>';
|
||||||
|
|
||||||
|
$controlUrl = $this->_settings['host'] . ((substr($this->_settings['controlURLs'][$type], 0, 1) != "/") ? "/" : "") . $this->_settings['controlURLs'][$type];
|
||||||
|
|
||||||
|
//!! TODO - need to use scheme in header ??
|
||||||
|
$header = array(
|
||||||
|
'SOAPACTION: "urn:schemas-upnp-org:service:' . $type . ':1#' . $method . '"',
|
||||||
|
'CONTENT-TYPE: text/xml; charset="utf-8"',
|
||||||
|
'HOST: ' . $this->_settings['host'],
|
||||||
|
'Connection: close',
|
||||||
|
'Content-Length: ' . mb_strlen($body),
|
||||||
|
);
|
||||||
|
//debug_event('upnpdevice', 'sendRequestToDevice Met: ' . $method . ' | ' . $controlUrl, 5);
|
||||||
|
//debug_event('upnpdevice', 'sendRequestToDevice Body: ' . $body, 5);
|
||||||
|
//debug_event('upnpdevice', 'sendRequestToDevice Hdr: ' . print_r($header, true), 5);
|
||||||
|
|
||||||
|
$ch = curl_init();
|
||||||
|
curl_setopt( $ch, CURLOPT_URL, $controlUrl );
|
||||||
|
curl_setopt( $ch, CURLOPT_POST, 1 );
|
||||||
|
curl_setopt( $ch, CURLOPT_POSTFIELDS, $body );
|
||||||
|
curl_setopt( $ch, CURLOPT_RETURNTRANSFER, TRUE );
|
||||||
|
curl_setopt( $ch, CURLOPT_HEADER, TRUE );
|
||||||
|
curl_setopt( $ch, CURLOPT_HTTPHEADER, $header );
|
||||||
|
curl_setopt( $ch, CURLOPT_FOLLOWLOCATION, TRUE );
|
||||||
|
|
||||||
|
$response = curl_exec( $ch );
|
||||||
|
curl_close( $ch );
|
||||||
|
//debug_event('upnpdevice', 'sendRequestToDevice response: ' . $response, 5);
|
||||||
|
|
||||||
|
$headers = array();
|
||||||
|
$tmp = explode("\r\n\r\n", $response);
|
||||||
|
|
||||||
|
foreach($tmp as $key => $value)
|
||||||
|
{
|
||||||
|
if(substr($value, 0, 8) == 'HTTP/1.1')
|
||||||
|
{
|
||||||
|
$headers[] = $tmp[$key];
|
||||||
|
unset($tmp[$key]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$response = join("\r\n", $tmp);
|
||||||
|
|
||||||
|
/*
|
||||||
|
$lastHeaders = $headers[count($headers) - 1];
|
||||||
|
$responseCode = $this->getResponseCode($lastHeaders);
|
||||||
|
debug_event('upnpdevice', 'sendRequestToDevice responseCode: ' . $responseCode, 5);
|
||||||
|
if ($responseCode == 500)
|
||||||
|
{
|
||||||
|
debug_event('upnpdevice', 'sendRequestToDevice HTTP-Code 500 - Create error response', 5);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
debug_event('upnpdevice', 'sendRequestToDevice HTTP-Code OK - Create response', 5);
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
return $response;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Filters response HTTP-Code from response headers
|
||||||
|
* @param string $headers HTTP response headers
|
||||||
|
* @return mixed Response code (int) or null if not found
|
||||||
|
*/
|
||||||
|
/*
|
||||||
|
private function getResponseCode($headers)
|
||||||
|
{
|
||||||
|
$tmp = explode("\n", $headers);
|
||||||
|
$firstLine = array_shift($tmp);
|
||||||
|
|
||||||
|
if(substr($headers, 0, 8) == 'HTTP/1.1') {
|
||||||
|
return substr($headers, 9, 3);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
// helper function for calls that require only an instance id
|
||||||
|
public function instanceOnly($command, $type = 'AVTransport', $id = 0)
|
||||||
|
{
|
||||||
|
$args = array( 'InstanceID' => $id );
|
||||||
|
$response = $this->sendRequestToDevice($command, $args, $type);
|
||||||
|
|
||||||
|
///$response = \Format::forge($response,'xml:ns')->to_array();
|
||||||
|
///return $response['s:Body']['u:' . $command . 'Response'];
|
||||||
|
|
||||||
|
return $response;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//!! UPNP subscription work not for all renderers, and works strange
|
||||||
|
//!! so now is not used
|
||||||
|
/**
|
||||||
|
* Subscribe
|
||||||
|
* Subscribe to UPnP event
|
||||||
|
*/
|
||||||
|
/*
|
||||||
|
public function Subscribe($type = 'AVTransport')
|
||||||
|
{
|
||||||
|
$web_path = AmpConfig::get('web_path');
|
||||||
|
$eventSubsUrl = $web_path . '/upnp/play-event.php?device=' . urlencode($this->_descrUrl);
|
||||||
|
$eventUrl = $this->_host . $this->_eventURLs[$type];
|
||||||
|
|
||||||
|
$header = array(
|
||||||
|
'HOST: ' . $this->_host,
|
||||||
|
'CALLBACK: <' . $eventSubsUrl . '>',
|
||||||
|
'NT: upnp:event',
|
||||||
|
'TIMEOUT: Second-180',
|
||||||
|
);
|
||||||
|
debug_event('upnpdevice', 'Subscribe with: ' . print_r($header, true), 5);
|
||||||
|
|
||||||
|
$ch = curl_init();
|
||||||
|
curl_setopt($ch, CURLOPT_URL, $eventUrl);
|
||||||
|
curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE);
|
||||||
|
curl_setopt($ch, CURLOPT_HEADER, TRUE);
|
||||||
|
curl_setopt($ch, CURLOPT_HTTPHEADER, $header);
|
||||||
|
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, TRUE);
|
||||||
|
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'SUBSCRIBE');
|
||||||
|
|
||||||
|
$response = curl_exec($ch);
|
||||||
|
curl_close( $ch );
|
||||||
|
debug_event('upnpdevice', 'Subscribe response: ' . $response, 5);
|
||||||
|
|
||||||
|
$lines = explode("\r\n", trim($response));
|
||||||
|
foreach($lines as $line) {
|
||||||
|
$tmp = explode(':', $line);
|
||||||
|
$key = strtoupper(trim(array_shift($tmp)));
|
||||||
|
$value = trim(join(':', $tmp));
|
||||||
|
|
||||||
|
if ($key == 'SID')
|
||||||
|
{
|
||||||
|
debug_event('upnpdevice', 'Subscribtion SID: ' . $value, 5);
|
||||||
|
return $value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
//!! UPNP subscription work not for all renderers, and works strange
|
||||||
|
//!! so now is not used
|
||||||
|
/**
|
||||||
|
* UnSubscribe
|
||||||
|
* Unsubscribe from UPnP event
|
||||||
|
*/
|
||||||
|
/*
|
||||||
|
public function UnSubscribe($sid, $type = 'AVTransport')
|
||||||
|
{
|
||||||
|
if (empty($sid))
|
||||||
|
return;
|
||||||
|
|
||||||
|
$eventUrl = $this->_host . $this->_eventURLs[$type];
|
||||||
|
|
||||||
|
$header = array(
|
||||||
|
'HOST: ' . $this->_host,
|
||||||
|
'SID: ' . $sid,
|
||||||
|
);
|
||||||
|
|
||||||
|
debug_event('upnpdevice', 'Unsubscribe from SID: ' . $sid . ' with: ' . "\n" . print_r($header, true), 5);
|
||||||
|
|
||||||
|
$ch = curl_init();
|
||||||
|
curl_setopt($ch, CURLOPT_URL, $eventUrl);
|
||||||
|
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||||
|
curl_setopt($ch, CURLOPT_HEADER, true);
|
||||||
|
curl_setopt($ch, CURLOPT_HTTPHEADER, $header);
|
||||||
|
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
|
||||||
|
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'UNSUBSCRIBE');
|
||||||
|
|
||||||
|
$response = curl_exec($ch);
|
||||||
|
curl_close( $ch );
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
}
|
384
modules/upnp/upnpplayer.class.php
Normal file
384
modules/upnp/upnpplayer.class.php
Normal file
|
@ -0,0 +1,384 @@
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* LICENSE: GNU General Public License, version 2 (GPLv2)
|
||||||
|
* Copyright 2001 - 2015 Ampache.org
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or
|
||||||
|
* modify it under the terms of the GNU General Public License v2
|
||||||
|
* as published by the Free Software Foundation.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program; if not, write to the Free Software
|
||||||
|
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* UPnPPlayer Class
|
||||||
|
*
|
||||||
|
* This player controls an instance of UPnP player
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
class UPnPPlayer
|
||||||
|
{
|
||||||
|
/* @var UPnPPlaylist $object */
|
||||||
|
private $_playlist = null;
|
||||||
|
|
||||||
|
/* @var UPnPDevice $object */
|
||||||
|
private $_device;
|
||||||
|
|
||||||
|
private $_description_url = null;
|
||||||
|
|
||||||
|
// 0 - stopped, 1 - playing
|
||||||
|
private $_intState = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Lazy initialization for UPNP device property
|
||||||
|
* @return UPnPDevice
|
||||||
|
*/
|
||||||
|
private function Device()
|
||||||
|
{
|
||||||
|
if (is_null($this->_device))
|
||||||
|
$this->_device = new UPnPDevice($this->_description_url);
|
||||||
|
return $this->_device;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Lazy initialization for UPNP playlist property
|
||||||
|
* @return UPnPPlaylist
|
||||||
|
*/
|
||||||
|
private function Playlist()
|
||||||
|
{
|
||||||
|
if (is_null($this->_playlist))
|
||||||
|
$this->_playlist = new UPnPPlaylist($this->_description_url);
|
||||||
|
return $this->_playlist;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* UPnPPlayer
|
||||||
|
* This is the constructor,
|
||||||
|
*/
|
||||||
|
public function UPnPPlayer($name = "noname", $description_url = "http://localhost")
|
||||||
|
{
|
||||||
|
require_once AmpConfig::get('prefix') . '/modules/upnp/upnpdevice.php';
|
||||||
|
require_once AmpConfig::get('prefix') . '/modules/upnp/upnpplaylist.php';
|
||||||
|
|
||||||
|
debug_event('upnpPlayer', 'constructor: ' . $name . ' | ' . $description_url, 5);
|
||||||
|
$this->_description_url = $description_url;
|
||||||
|
|
||||||
|
$this->ReadIndState();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* add
|
||||||
|
* append a song to the playlist
|
||||||
|
* $name Name to be shown in the playlist
|
||||||
|
* $link URL of the song
|
||||||
|
*/
|
||||||
|
public function PlayListAdd($name, $link)
|
||||||
|
{
|
||||||
|
$this->Playlist()->Add($name, $link);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* delete_pos
|
||||||
|
* This deletes a specific track
|
||||||
|
*/
|
||||||
|
public function PlaylistRemove($track)
|
||||||
|
{
|
||||||
|
$this->Playlist()->RemoveTrack($track);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function PlaylistClear()
|
||||||
|
{
|
||||||
|
$this->Playlist()->Clear();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* GetPlayListItems
|
||||||
|
* This returns a delimited string of all of the filenames
|
||||||
|
* current in your playlist, only url's at the moment
|
||||||
|
*/
|
||||||
|
public function GetPlaylistItems()
|
||||||
|
{
|
||||||
|
return $this->Playlist()->AllItems();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function GetCurrentItem()
|
||||||
|
{
|
||||||
|
return $this->Playlist()->CurrentItem();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function GetState()
|
||||||
|
{
|
||||||
|
$response = $this->Device()->instanceOnly('GetTransportInfo');
|
||||||
|
$responseXML = simplexml_load_string($response);
|
||||||
|
list($state) = $responseXML->xpath('//CurrentTransportState');
|
||||||
|
|
||||||
|
//!!debug_event('upnpPlayer', 'GetState = ' . $state, 5);
|
||||||
|
|
||||||
|
return $state;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* next
|
||||||
|
* go to next song
|
||||||
|
*/
|
||||||
|
public function Next($forcePlay = true)
|
||||||
|
{
|
||||||
|
// get current internal play state, for case if someone has changed it
|
||||||
|
if (! $forcePlay)
|
||||||
|
$this->ReadIndState();
|
||||||
|
if (($forcePlay || ($this->_intState == 1)) && ($this->Playlist()->Next()))
|
||||||
|
{
|
||||||
|
$this->Play();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* prev
|
||||||
|
* go to previous song
|
||||||
|
*/
|
||||||
|
public function Prev()
|
||||||
|
{
|
||||||
|
if ($this->Playlist()->Prev()) {
|
||||||
|
$this->Play();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* skip
|
||||||
|
* This skips to POS in the playlist
|
||||||
|
*/
|
||||||
|
public function Skip($pos)
|
||||||
|
{
|
||||||
|
if ($this->Playlist()->Skip($pos)) {
|
||||||
|
$this->Play();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function prepareURIRequest($song, $prefix)
|
||||||
|
{
|
||||||
|
if ($song == null) return null;
|
||||||
|
|
||||||
|
$songUrl = $song['link'];
|
||||||
|
$songId = preg_replace('/(.+)\/oid\/(\d+)\/(.+)/i', '${2}', $songUrl);
|
||||||
|
|
||||||
|
$song = new song($songId);
|
||||||
|
$song->format();
|
||||||
|
$songItem = Upnp_Api::_itemSong($song, '');
|
||||||
|
$domDIDL = Upnp_Api::createDIDL($songItem);
|
||||||
|
$xmlDIDL = $domDIDL->saveXML();
|
||||||
|
|
||||||
|
return array(
|
||||||
|
'InstanceID' => 0,
|
||||||
|
$prefix . 'URI' => $songUrl,
|
||||||
|
$prefix . 'URIMetaData' => htmlentities($xmlDIDL)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function CallAsyncURL($url) {
|
||||||
|
$ch = curl_init();
|
||||||
|
curl_setopt( $ch, CURLOPT_URL, $url );
|
||||||
|
curl_setopt( $ch, CURLOPT_FRESH_CONNECT, true );
|
||||||
|
curl_setopt( $ch, CURLOPT_HEADER, false );
|
||||||
|
curl_exec( $ch );
|
||||||
|
curl_close( $ch );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* play
|
||||||
|
* play the current song
|
||||||
|
*/
|
||||||
|
public function Play()
|
||||||
|
{
|
||||||
|
//!!$this->Stop();
|
||||||
|
|
||||||
|
$this->SetIntState(1);
|
||||||
|
|
||||||
|
$currentSongArgs = $this->prepareURIRequest($this->Playlist()->CurrentItem(), "Current");
|
||||||
|
$response = $this->Device()->sendRequestToDevice('SetAVTransportURI', $currentSongArgs, 'AVTransport');
|
||||||
|
|
||||||
|
$args = array( 'InstanceID' => 0, 'Speed' => 1);
|
||||||
|
$response = $this->Device()->sendRequestToDevice('Play', $args, 'AVTransport');
|
||||||
|
|
||||||
|
//!! UPNP subscription work not for all renderers, and works strange
|
||||||
|
//!! so now is not used
|
||||||
|
//$sid = $this->Device()->Subscribe();
|
||||||
|
//$_SESSION['upnp_SID'] = $sid;
|
||||||
|
|
||||||
|
// launch special page in background for periodically check play status
|
||||||
|
$url = AmpConfig::get('local_web_path') . "/upnp/playstatus.php";
|
||||||
|
$this->CallAsyncURL($url);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stop
|
||||||
|
* stops the current song amazing!
|
||||||
|
*/
|
||||||
|
public function Stop()
|
||||||
|
{
|
||||||
|
$this->SetIntState(0);
|
||||||
|
$response = $this->Device()->instanceOnly('Stop');
|
||||||
|
|
||||||
|
//!! UPNP subscription work not for all renderers, and works strange
|
||||||
|
//!! so now is not used
|
||||||
|
//$sid = $_SESSION['upnp_SID'];
|
||||||
|
//$_SESSION['upnp_SID'] = "";
|
||||||
|
//$this->Device()->UnSubscribe($sid);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* pause
|
||||||
|
* toggle pause mode on current song
|
||||||
|
*/
|
||||||
|
public function Pause()
|
||||||
|
{
|
||||||
|
$state = $this->GetState();
|
||||||
|
debug_event('upnpPlayer', 'Pause. prev state = ' . $state, 5);
|
||||||
|
|
||||||
|
if ($state == 'PLAYING') {
|
||||||
|
$response = $this->Device()->instanceOnly('Pause');
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$args = array( 'InstanceID' => 0, 'Speed' => 1);
|
||||||
|
$response = $this->Device()->sendRequestToDevice('Play', $args, 'AVTransport');
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Repeat
|
||||||
|
* This toggles the repeat state
|
||||||
|
*/
|
||||||
|
public function Repeat($value)
|
||||||
|
{
|
||||||
|
//!! TODO not implemented yet
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Random
|
||||||
|
* this toggles the random state
|
||||||
|
*/
|
||||||
|
public function Random($value)
|
||||||
|
{
|
||||||
|
//!! TODO not implemented yet
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public function FullState()
|
||||||
|
{
|
||||||
|
//!! TODO not implemented yet
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* VolumeUp
|
||||||
|
* increases the volume
|
||||||
|
*/
|
||||||
|
public function VolumeUp()
|
||||||
|
{
|
||||||
|
$volume = $this->GetVolume() + 2;
|
||||||
|
return $this->SetVolume($volume);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* VolumeDown
|
||||||
|
* decreases the volume
|
||||||
|
*/
|
||||||
|
public function VolumeDown()
|
||||||
|
{
|
||||||
|
$volume = $this->GetVolume() - 2;
|
||||||
|
return $this->SetVolume($volume);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* SetVolume
|
||||||
|
*/
|
||||||
|
public function SetVolume($value)
|
||||||
|
{
|
||||||
|
$desiredVolume = Max(0, Min(100, $value));
|
||||||
|
$instanceId = 0;
|
||||||
|
$channel = 'Master';
|
||||||
|
|
||||||
|
$response = $this->Device()->sendRequestToDevice( 'SetVolume', array(
|
||||||
|
'InstanceID' => $instanceId,
|
||||||
|
'Channel' => $channel,
|
||||||
|
'DesiredVolume' => $desiredVolume
|
||||||
|
));
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* GetVolume
|
||||||
|
*/
|
||||||
|
public function GetVolume()
|
||||||
|
{
|
||||||
|
$instanceId = 0;
|
||||||
|
$channel = 'Master';
|
||||||
|
|
||||||
|
$response = $this->Device()->sendRequestToDevice( 'GetVolume', array(
|
||||||
|
'InstanceID' => $instanceId,
|
||||||
|
'Channel' => $channel
|
||||||
|
));
|
||||||
|
|
||||||
|
$responseXML = simplexml_load_string($response);
|
||||||
|
list($volume) = ($responseXML->xpath('//CurrentVolume'));
|
||||||
|
debug_event('upnpPlayer', 'GetVolume:' . $volume, 5);
|
||||||
|
|
||||||
|
return $volume;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private function SetIntState($state)
|
||||||
|
{
|
||||||
|
$this->_intState = $state;
|
||||||
|
|
||||||
|
$sid = 'upnp_ply_' . $this->_description_url;
|
||||||
|
$data = serialize($this->_intState);
|
||||||
|
if (! Session::exists('api', $sid))
|
||||||
|
Session::create(array('type' => 'api', 'sid' => $sid, 'value' => $data ));
|
||||||
|
else
|
||||||
|
Session::write($sid, $data);
|
||||||
|
debug_event('upnpPlayer', 'SetIntState:' . $this->_intState, 5);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function ReadIndState()
|
||||||
|
{
|
||||||
|
$sid = 'upnp_ply_' . $this->_description_url;
|
||||||
|
$data = Session::read($sid);
|
||||||
|
|
||||||
|
$this->_intState = unserialize($data);
|
||||||
|
debug_event('upnpPlayer', 'ReadIndState:' . $this->_intState, 5);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // End UPnPPlayer Class
|
||||||
|
?>
|
141
modules/upnp/upnpplaylist.php
Normal file
141
modules/upnp/upnpplaylist.php
Normal file
|
@ -0,0 +1,141 @@
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* LICENSE: GNU General Public License, version 2 (GPLv2)
|
||||||
|
* Copyright 2001 - 2015 Ampache.org
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or
|
||||||
|
* modify it under the terms of the GNU General Public License v2
|
||||||
|
* as published by the Free Software Foundation.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program; if not, write to the Free Software
|
||||||
|
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* UPnPPlaylist Class
|
||||||
|
*/
|
||||||
|
class UPnPPlaylist
|
||||||
|
{
|
||||||
|
private $_deviceGUID = "";
|
||||||
|
private $_songs;
|
||||||
|
private $_current = 0;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Playlist is its own for each UPnP device
|
||||||
|
*/
|
||||||
|
public function UPnPPlaylist($deviceGUID)
|
||||||
|
{
|
||||||
|
$this->_deviceGUID = $deviceGUID;
|
||||||
|
$this->PlayListRead();
|
||||||
|
if (! is_array($this->_songs))
|
||||||
|
$this->Clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function Add($name, $link)
|
||||||
|
{
|
||||||
|
$this->_songs[] = array('name' => $name, 'link' => $link);
|
||||||
|
$this->PlayListSave();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function RemoveTrack($track)
|
||||||
|
{
|
||||||
|
unset($this->_songs[$track - 1]);
|
||||||
|
$this->PlayListSave();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function Clear()
|
||||||
|
{
|
||||||
|
$this->_songs = array();
|
||||||
|
$this->_current = 0;
|
||||||
|
$this->PlayListSave();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function AllItems()
|
||||||
|
{
|
||||||
|
return $this->_songs;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function CurrentItem()
|
||||||
|
{
|
||||||
|
$item = $this->_songs[$this->_current];
|
||||||
|
return $item;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function CurrentPos()
|
||||||
|
{
|
||||||
|
return $this->_current;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function Next()
|
||||||
|
{
|
||||||
|
if ($this->_current < count($this->_songs) - 1) {
|
||||||
|
$this->_current++;
|
||||||
|
$this->PlayListSave();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function NextItem()
|
||||||
|
{
|
||||||
|
if ($this->_current < count($this->_songs) - 1) {
|
||||||
|
$nxt = $this->_current + 1;
|
||||||
|
return $this->_songs[$nxt];
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function Prev()
|
||||||
|
{
|
||||||
|
if ($this->_current > 0) {
|
||||||
|
$this->_current--;
|
||||||
|
$this->PlayListSave();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function Skip($pos)
|
||||||
|
{
|
||||||
|
// note that pos is started from 1 not from zero
|
||||||
|
if (($pos >= 1) && ($pos <= count($this->_songs))) {
|
||||||
|
$this->_current = $pos - 1;
|
||||||
|
$this->PlayListSave();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function PlayListRead()
|
||||||
|
{
|
||||||
|
$sid = 'upnp_pls_' . $this->_deviceGUID;
|
||||||
|
$pls_data = unserialize(Session::read($sid));
|
||||||
|
|
||||||
|
$this->_songs = $pls_data['upnp_playlist'];
|
||||||
|
$this->_current = $pls_data['upnp_current'];
|
||||||
|
}
|
||||||
|
|
||||||
|
private function PlayListSave()
|
||||||
|
{
|
||||||
|
$sid = 'upnp_pls_' . $this->_deviceGUID;
|
||||||
|
$pls_data = serialize(array(
|
||||||
|
'upnp_playlist' => $this->_songs,
|
||||||
|
'upnp_current' => $this->_current
|
||||||
|
));
|
||||||
|
if (! Session::exists('api', $sid))
|
||||||
|
Session::create(array('type' => 'api', 'sid' => $sid, 'value' => $pls_data ));
|
||||||
|
else
|
||||||
|
Session::write($sid, $pls_data);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
?>
|
|
@ -18,7 +18,7 @@ $rootMediaItems[] = Upnp_Api::_videoMetadata('');
|
||||||
$requestRaw = file_get_contents('php://input');
|
$requestRaw = file_get_contents('php://input');
|
||||||
if ($requestRaw != '') {
|
if ($requestRaw != '') {
|
||||||
$upnpRequest = Upnp_Api::parseUPnPRequest($requestRaw);
|
$upnpRequest = Upnp_Api::parseUPnPRequest($requestRaw);
|
||||||
debug_event('upnp', 'Request: ' . $requestRaw, '5');
|
//!!debug_event('upnp', 'Request: ' . $requestRaw, '5');
|
||||||
} else {
|
} else {
|
||||||
echo 'Error: no UPnP request.';
|
echo 'Error: no UPnP request.';
|
||||||
debug_event('upnp', 'No request', '5');
|
debug_event('upnp', 'No request', '5');
|
||||||
|
@ -100,5 +100,5 @@ $rootMediaItems[] = Upnp_Api::_videoMetadata('');
|
||||||
$soapXML = $domSOAP->saveXML();
|
$soapXML = $domSOAP->saveXML();
|
||||||
|
|
||||||
echo $soapXML;
|
echo $soapXML;
|
||||||
debug_event('upnp', 'Response: ' . $soapXML, '5');
|
//!!debug_event('upnp', 'Response: ' . $soapXML, '5');
|
||||||
?>
|
?>
|
||||||
|
|
140
upnp/find.php
Normal file
140
upnp/find.php
Normal file
|
@ -0,0 +1,140 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
error_reporting( E_ALL );
|
||||||
|
ini_set('display_errors', 1);
|
||||||
|
|
||||||
|
class UPnPFind
|
||||||
|
{
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find devices by UPnP multicast message and stores them to cache
|
||||||
|
*
|
||||||
|
* @return array Parsed device list
|
||||||
|
*/
|
||||||
|
public static function findDevices()
|
||||||
|
{
|
||||||
|
$discover = self::discover(10);
|
||||||
|
|
||||||
|
return($discover); //!!
|
||||||
|
|
||||||
|
/*
|
||||||
|
$devices = array();
|
||||||
|
flush();
|
||||||
|
foreach ($discover as $response) {
|
||||||
|
|
||||||
|
$device = new Device();
|
||||||
|
if ($device->initByDiscoveryReponse($response)) {
|
||||||
|
|
||||||
|
$device->saveToCache();
|
||||||
|
|
||||||
|
try {
|
||||||
|
$client = $device->getClient('ConnectionManager');
|
||||||
|
$protocolInfo = $client->call('GetProtocolInfo');
|
||||||
|
|
||||||
|
$sink = $protocolInfo['Sink'];
|
||||||
|
$tmp = explode(',', $sink);
|
||||||
|
|
||||||
|
$protocols = array();
|
||||||
|
|
||||||
|
foreach ($tmp as $protocol) {
|
||||||
|
$t = explode(':', $protocol);
|
||||||
|
if ($t[0] == 'http-get') {
|
||||||
|
$protocols[] = $t[2];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (UPnPException $upnpe) {
|
||||||
|
$protocols = array();
|
||||||
|
}
|
||||||
|
|
||||||
|
$device->protocolInfo = $protocols;
|
||||||
|
|
||||||
|
$cache[$device->getId()] = array(
|
||||||
|
'name' => $device->getName(),
|
||||||
|
'services' => $device->getServices(),
|
||||||
|
'icons' => $device->getIcons(),
|
||||||
|
'protocols' => $device->getProtocolInfo()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $cache;
|
||||||
|
*/
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Performs a standardized UPnP multicast request to 239.255.255.250:1900
|
||||||
|
* and listens $timeout seconds for responses
|
||||||
|
*
|
||||||
|
* Thanks to artheus (https://github.com/artheus/PHP-UPnP/blob/master/phpupnp.class.php)
|
||||||
|
*
|
||||||
|
* @param int $timeout Timeout to wait for responses
|
||||||
|
*
|
||||||
|
* @return array Response
|
||||||
|
*/
|
||||||
|
private static function discover($timeout = 2)
|
||||||
|
{
|
||||||
|
$msg = 'M-SEARCH * HTTP/1.1' . "\r\n";
|
||||||
|
$msg .= 'HOST: 239.255.255.250:1900' . "\r\n";
|
||||||
|
$msg .= 'MAN: "ssdp:discover"' . "\r\n";
|
||||||
|
$msg .= "MX: 3\r\n";
|
||||||
|
$msg .= "ST: upnp:rootdevice\r\n";
|
||||||
|
$msg .= '' . "\r\n";
|
||||||
|
|
||||||
|
$socket = socket_create(AF_INET, SOCK_DGRAM, SOL_UDP);
|
||||||
|
socket_set_option($socket, SOL_SOCKET, SO_BROADCAST, 1);
|
||||||
|
socket_sendto($socket, $msg, strlen($msg), 0, '239.255.255.250', 1900);
|
||||||
|
|
||||||
|
socket_set_option($socket, SOL_SOCKET, SO_RCVTIMEO, array('sec' => $timeout, 'usec' => 0));
|
||||||
|
|
||||||
|
$response = array();
|
||||||
|
do {
|
||||||
|
$buf = null;
|
||||||
|
@socket_recvfrom($socket, $buf, 1024, MSG_WAITALL, $from, $port);
|
||||||
|
|
||||||
|
if (!is_null($buf))
|
||||||
|
$response[] = self::discoveryReponse2Array($buf);
|
||||||
|
|
||||||
|
} while (!is_null($buf));
|
||||||
|
//socket_close($socket);
|
||||||
|
|
||||||
|
return $response;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Transforms discovery response string to key/value array
|
||||||
|
*
|
||||||
|
* @param string $res discovery response
|
||||||
|
*
|
||||||
|
* @return \stdObj
|
||||||
|
*/
|
||||||
|
private static function discoveryReponse2Array($res)
|
||||||
|
{
|
||||||
|
$result = array();
|
||||||
|
$lines = explode("\n", trim($res));
|
||||||
|
|
||||||
|
if (trim($lines[0]) == 'HTTP/1.1 200 OK') {
|
||||||
|
array_shift($lines);
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($lines as $line) {
|
||||||
|
$tmp = explode(':', trim($line));
|
||||||
|
|
||||||
|
$key = strtoupper(array_shift($tmp));
|
||||||
|
$value = (count($tmp) > 0 ? trim(join(':', $tmp)) : null);
|
||||||
|
|
||||||
|
$result[$key] = $value;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (Object)$result;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
$devices = UPnPFind::findDevices();
|
||||||
|
|
||||||
|
?>
|
||||||
|
|
||||||
|
<pre>
|
||||||
|
<?php print_r($devices); ?>
|
||||||
|
</pre>
|
|
@ -8,6 +8,7 @@ if (!AmpConfig::get('upnp_backend')) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (($_GET['btnSend']) || ($_GET['btnSendAuto'])) {
|
if (($_GET['btnSend']) || ($_GET['btnSendAuto'])) {
|
||||||
|
$msIP = 1;
|
||||||
Upnp_Api::sddpSend($msIP);
|
Upnp_Api::sddpSend($msIP);
|
||||||
}
|
}
|
||||||
?>
|
?>
|
||||||
|
|
50
upnp/playstatus.php
Normal file
50
upnp/playstatus.php
Normal file
|
@ -0,0 +1,50 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
// Send response to client to close connection
|
||||||
|
header('Connection: Close');
|
||||||
|
|
||||||
|
set_time_limit(0);
|
||||||
|
|
||||||
|
$path = dirname(__FILE__);
|
||||||
|
$prefix = realpath($path . '/../');
|
||||||
|
require_once $prefix . '/lib/init.php';
|
||||||
|
require_once $prefix . '/modules/localplay/upnp.controller.php';
|
||||||
|
require_once $prefix . '/modules/upnp/upnpplayer.class.php';
|
||||||
|
|
||||||
|
if (!AmpConfig::get('upnp_backend')) {
|
||||||
|
die("UPnP backend disabled..");
|
||||||
|
}
|
||||||
|
|
||||||
|
// get current UPnP player instance
|
||||||
|
$controller = new AmpacheUPnP();
|
||||||
|
$instance = $controller->get_instance();
|
||||||
|
echo "UPnP instance = " . $instance['name'] . "\n";
|
||||||
|
|
||||||
|
$deviceDescr = $instance['url'];
|
||||||
|
//!!echo "UPnP device = " . $deviceDescr . "\n";
|
||||||
|
$player = new UPnPPlayer("background controller", $deviceDescr);
|
||||||
|
|
||||||
|
//!!echo "Current playlist: \n" . print_r($player->GetPlaylistItems(), true);
|
||||||
|
//!!echo "Current item: \n" . print_r($player->GetCurrentItem(), true);
|
||||||
|
|
||||||
|
// periodically (every second) checking state of renderer, until it is STOPPED
|
||||||
|
$played = false;
|
||||||
|
while (($state = $player->GetState()) == "PLAYING") {
|
||||||
|
$played = true;
|
||||||
|
echo ".";
|
||||||
|
sleep(1);
|
||||||
|
}
|
||||||
|
echo "STATE = " . $state . "\n";
|
||||||
|
|
||||||
|
// If the song was played and then finished, start to play next song in list.
|
||||||
|
// Do not start anything if playback was stopped from beginning
|
||||||
|
if ($played)
|
||||||
|
{
|
||||||
|
echo "UPnP play next" . "\n";
|
||||||
|
if ($player->Next(false))
|
||||||
|
echo "Next song started" . "\n";
|
||||||
|
else
|
||||||
|
echo "Next song FAILED!" . "\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
?>
|
4
upnp/readme.txt
Normal file
4
upnp/readme.txt
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
playstatus.php - ...
|
||||||
|
|
||||||
|
find.php - ...
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue