diff --git a/.coveralls.yml b/.coveralls.yml new file mode 100755 index 0000000..71578e5 --- /dev/null +++ b/.coveralls.yml @@ -0,0 +1,3 @@ +service_name: travis-ci +coverage_clover: clover.xml +json_path: coveralls.json \ No newline at end of file diff --git a/.editorconfig b/.editorconfig old mode 100644 new mode 100755 diff --git a/.gitignore b/.gitignore old mode 100644 new mode 100755 index 3b77c7c..053d9bb --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ # Created by .ignore support plugin (hsz.mobi) /vendor/ -/test/env.yml +/tests/env.yml /phpunit.xml /composer.lock /.php_cs.cache +/.idea/ \ No newline at end of file diff --git a/.styleci.yml b/.styleci.yml old mode 100644 new mode 100755 diff --git a/.travis.yml b/.travis.yml old mode 100644 new mode 100755 index 4bb081c..1dae047 --- a/.travis.yml +++ b/.travis.yml @@ -4,12 +4,30 @@ php: - 5.6 - 7.0 - 7.1 + - 7.2 + - nightly -sudo: false +matrix: + allow_failures: + - php: nightly + +env: + - COMPOSER_OPTS="" + - COMPOSER_OPTS="--prefer-lowest" install: - - travis_retry composer install --no-interaction --prefer-source + - composer self-update --snapshot + - composer update $COMPOSER_OPTS script: - - cp test/env.yml.dist test/env.yml - - vendor/bin/phpunit --verbose --coverage-text + - vendor/bin/phpunit --coverage-clover=clover.xml + - tests/lint.sh + +after_success: + - vendor/bin/coveralls + +git: + depth: 5 + +dist: trusty +sudo: false \ No newline at end of file diff --git a/CREDITS.md b/CREDITS.md new file mode 100755 index 0000000..9c4f083 --- /dev/null +++ b/CREDITS.md @@ -0,0 +1,5 @@ +This api is highly inspired by the excellent Github project + +[KnpLabs/php-github-api](https://github.com/KnpLabs/php-github-api) + +[duncan3dc/sonos](https://github.com/duncan3dc/sonos) \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100755 index 0000000..0c95716 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2013 Thomas Park + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. \ No newline at end of file diff --git a/README.md b/README.md old mode 100644 new mode 100755 index f6b5ebd..ef1eec0 --- a/README.md +++ b/README.md @@ -1,9 +1,8 @@ # Yamaha MusicCast API PHP Library -A simple wrapper for the Yamaha MusicCast API. -Not all call's have been implemented yet so pull requests are welcome! +A php library for interacting with Yamaha MusicCast speakers. -[![Build Status](https://travis-ci.org/samvdb/php-musiccast-api.svg?branch=master)](https://travis-ci.org/samvdb/php-musiccast-api) +[![Build Status](https://travis-ci.org/grandDam/php-musiccast-api.svg?branch=master)](https://travis-ci.org/grandDam/php-musiccast-api) Based on the API specification found at [https://jayvee.com.au/downloads/commands/yamaha/YXC_API_Spec_Basic.pdf](https://jayvee.com.au/downloads/commands/yamaha/YXC_API_Spec_Basic.pdf) @@ -25,27 +24,24 @@ $ curl -s http://getcomposer.org/installer | php Via composer ```bash -$ composer require samvdb/php-musiccast-api php-http/guzzle6-adapter +$ composer require granddam/php-musiccast-api php-http/guzzle6-adapter ``` You can install any adapter you want but guzzle is probably fine for what you want to do. -## Creating a client +## Examples +Start all groups playing music ```php -$yamaha = new MusicCast\Client([ - 'host' => 'localhost', - 'port' => 80, // default value -]); -``` - -## Using the API - - -```php -$result = $yamaha->api('zone')->status('main'); -print_r($result); - +require 'vendor/autoload.php'; + +$musicCast = new MusicCast\Network(); +$controllers = $musicCast->getControllers(); +foreach ($controllers as $controller) { + echo $controller->getGroup() + "\n"; + echo "\tState: " . $controller->getStateName() . "\n"; + $controller->play(); +} ``` ## Enabling events @@ -89,11 +85,6 @@ while(true) { $ composer test ``` -## Credits - -This api is highly inspired by the excellent Github api client made by KnpLabs! - -[KnpLabs/php-github-api](https://github.com/KnpLabs/php-github-api) diff --git a/composer.json b/composer.json old mode 100644 new mode 100755 index 558f8ff..f341a64 --- a/composer.json +++ b/composer.json @@ -1,46 +1,65 @@ { - "name": "samvdb/php-musiccast-api", - "description": "PHP Wrapper for Yamaha MusicCast API", - "type": "library", - "keywords": ["yamaha", "musiccast", "api"], - "license": "MIT", - "authors": [ - { - "name": "Sam Van der Borght", - "email": "samvanderborght@gmail.com" - } - ], - "require": { - "php": "^5.5 || ^7.0", - "psr/http-message": "^1.0", - "psr/cache": "^1.0", - "php-http/httplug": "^1.1", - "php-http/discovery": "^1.0", - "php-http/client-implementation": "^1.0", - "php-http/client-common": "^1.3", - "php-http/cache-plugin": "^1.2", - "symfony/yaml": "^3.2", - "myclabs/php-enum": "^1.5" + "name": "granddam/php-musiccast-api", + "description": "A PHP library for interacting with Yamaha MusicCast speakers", + "type": "library", + "keywords": [ + "yamaha", + "musiccast", + "api", + "client" + ], + "license": "MIT", + "authors": [ + { + "name": "Sam Van der Borght", + "email": "samvanderborght@gmail.com" }, - "require-dev": { - "squizlabs/php_codesniffer": "^2.7", - "phpunit/phpunit": "^4.0 || ^5.5", - "phpunit/php-code-coverage": "^4", - "php-http/guzzle6-adapter": "^1.0", - "guzzlehttp/psr7": "^1.2", - "phpro/grumphp": "^0.11.1", - "nikic/php-parser": "^3.0", - "friendsofphp/php-cs-fixer": "^2.0" - }, - "autoload": { - "psr-4": { "MusicCast\\": "lib/MusicCast/" } - }, - "scripts": { - "test": "vendor/bin/phpunit" - }, - "extra": { - "branch-alias": { - "dev-master": "1.0-dev" - } + { + "name": "Damien SUROT", + "email": "damien@toxeek.com" + } + ], + "require": { + "php": "^5.6 || ^7.0", + "psr/http-message": "^1.0", + "psr/cache": "^1.0", + "php-http/httplug": "^1.1", + "php-http/discovery": "^1.0", + "php-http/client-implementation": "^1.0", + "php-http/client-common": "^1.3", + "php-http/cache-plugin": "^1.2", + "symfony/yaml": "^3.2", + "myclabs/php-enum": "^1.5", + "cache/doctrine-adapter": "^1.0", + "doctrine/cache": "^1.4" + }, + "require-dev": { + "squizlabs/php_codesniffer": "^2.7", + "mockery/mockery": "^0.9.4", + "phpunit/phpunit": "^5.7.14", + "phpunit/php-code-coverage": "^4", + "php-http/guzzle6-adapter": "^1.0", + "guzzlehttp/psr7": "^1.2", + "phpro/grumphp": "^0.11.1", + "nikic/php-parser": "^3.0", + "friendsofphp/php-cs-fixer": "^2.0", + "satooshi/php-coveralls": "^1.0" + }, + "autoload": { + "psr-4": { + "MusicCast\\": "src/", + "MusicCastTests\\": "tests/" + } + }, + "scripts": { + "test": "vendor/bin/phpunit" + }, + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" } + }, + "conflict": { + "php-http/message-factory": "< 1.0.2" + } } diff --git a/grumphp.yml b/grumphp.yml old mode 100644 new mode 100755 index f239ccb..7dc771f --- a/grumphp.yml +++ b/grumphp.yml @@ -10,7 +10,7 @@ parameters: standard: PSR2 ignore_patterns: - "spec/*Spec.php" - - "test/*.php" + - "tests/*.php" metadata: priority: 300 phpcsfixer: ~ @@ -18,7 +18,6 @@ parameters: phpparser: visitors: no_exit_statements: ~ - never_use_else: ~ forbidden_function_calls: blacklist: [var_dump] phpunit: diff --git a/lib/MusicCast/Api/Network.php b/lib/MusicCast/Api/Network.php deleted file mode 100644 index 1b1b998..0000000 --- a/lib/MusicCast/Api/Network.php +++ /dev/null @@ -1,11 +0,0 @@ - - */ -class Network extends AbstractApi -{ -} diff --git a/lib/MusicCast/Api/System.php b/lib/MusicCast/Api/System.php deleted file mode 100644 index 52a540b..0000000 --- a/lib/MusicCast/Api/System.php +++ /dev/null @@ -1,60 +0,0 @@ - - */ -class System extends AbstractApi -{ - /** - * For retrieving basic information of a Device - * - * @return array - */ - public function deviceInfo() - { - return $this->get('/getDeviceInfo'); - } - - /** - * For retrieving feature information equipped with a Device - */ - public function features() - { - return $this->get('/getFeatures'); - } - - public function networkStatus() - { - return $this->get('/getNetworkStatus'); - } - - public function functionStatus() - { - return $this->get('/getFuncStatus'); - } - - public function autoPowerStandby($enable = true) - { - throw new \Exception('Not implemented'); - } - - public function locationInfo() - { - return $this->get('/getLocationInfo'); - } - - public function sendIrCode($code) - { - throw new \Exception('Not implemented'); - - return $this->get('/sendIrCode?code=1'); - } - - public function get($path, array $parameters = array(), array $requestHeaders = array()) - { - return parent::get('/system' . $path, $parameters, $requestHeaders); - } -} diff --git a/lib/MusicCast/Api/Usb.php b/lib/MusicCast/Api/Usb.php deleted file mode 100644 index 0a55a5b..0000000 --- a/lib/MusicCast/Api/Usb.php +++ /dev/null @@ -1,11 +0,0 @@ - - */ -class Usb extends Network -{ -} diff --git a/lib/MusicCast/Api/Zone.php b/lib/MusicCast/Api/Zone.php deleted file mode 100644 index e2a4760..0000000 --- a/lib/MusicCast/Api/Zone.php +++ /dev/null @@ -1,25 +0,0 @@ -call($zone, 'getStatus'); - } - - - private function call($zone, $path) - { - return $this->get(rawurlencode($zone).'/'.$path); - } -} diff --git a/phpunit.xml.dist b/phpunit.xml.dist old mode 100644 new mode 100755 index 461db94..1a25369 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -1,31 +1,10 @@ - - - - - - ./test/MusicCast/ - - - - - - functional - - - + + + tests + - - ./lib/MusicCast/ + + src - + \ No newline at end of file diff --git a/lib/MusicCast/Api/AbstractApi.php b/src/Api/AbstractApi.php old mode 100644 new mode 100755 similarity index 93% rename from lib/MusicCast/Api/AbstractApi.php rename to src/Api/AbstractApi.php index 31715e5..a52697b --- a/lib/MusicCast/Api/AbstractApi.php +++ b/src/Api/AbstractApi.php @@ -28,12 +28,11 @@ public function configure() * Send a GET request with query parameters. * * @param string $path Request path. - * @param array $parameters GET parameters. * @param array $requestHeaders Request Headers. * * @return array|string */ - protected function get($path, array $parameters = array(), array $requestHeaders = array()) + protected function get($path, array $requestHeaders = array()) { $response = $this->client->getHttpClient()->get($path, $requestHeaders); diff --git a/lib/MusicCast/Api/ApiInterface.php b/src/Api/ApiInterface.php old mode 100644 new mode 100755 similarity index 100% rename from lib/MusicCast/Api/ApiInterface.php rename to src/Api/ApiInterface.php diff --git a/lib/MusicCast/Api/CD.php b/src/Api/CD.php old mode 100644 new mode 100755 similarity index 100% rename from lib/MusicCast/Api/CD.php rename to src/Api/CD.php diff --git a/src/Api/Distribution.php b/src/Api/Distribution.php new file mode 100755 index 0000000..fdd41ee --- /dev/null +++ b/src/Api/Distribution.php @@ -0,0 +1,87 @@ + + */ + +class Distribution extends AbstractApi +{ + /** + * @return array + */ + public function getDistributionInfo() + { + return $this->callGet('getDistributionInfo'); + } + + private function callGet($path) + { + return $this->get('/dist/' . $path); + } + + /** + * @param int $num + * @return array + */ + public function startDistribution($num = 0) + { + return $this->callGet('startDistribution?num=' . rawurlencode($num)); + } + + /** + * @param string $groupId + * @param string $zone + * @param string $server_ip_addr + * @return array + */ + public function setClientInfo($groupId = '', $zone = 'main', $server_ip_addr = null) + { + $info = array('group_id' => $groupId); + $info['zone'] = array($zone); + if ($server_ip_addr != null) { + $info['server_ip_address'] = $server_ip_addr; + } + return $this->callPost('setClientInfo', $info); + } + + /** + * @param $name + * @return array + */ + public function setGroupName($name) + { + return $this->callPost('setGroupName', array('name' => $name)); + } + + private function callPost($path, array $parameters = array()) + { + return $this->post('/dist/' . $path, $parameters); + } + + + /** + * @param $groupId + * @param $type + * @param $zone + * @param array $client_list Clients IP + * @return array + */ + public function setServerInfo($groupId, $type = null, $zone = null, array $client_list = null) + { + $info = array('group_id' => $groupId); + if ($type != null) { + $info['type'] = $type; + } + if ($zone != null) { + $info['zone'] = $zone; + } + if ($client_list != null) { + $info['client_list'] = $client_list; + } + return $this->callPost('setServerInfo', array($info)); + } +} diff --git a/lib/MusicCast/Api/Event.php b/src/Api/Event.php old mode 100644 new mode 100755 similarity index 73% rename from lib/MusicCast/Api/Event.php rename to src/Api/Event.php index e9ce4c9..f1d36fd --- a/lib/MusicCast/Api/Event.php +++ b/src/Api/Event.php @@ -1,6 +1,5 @@ sprintf('%s', $port), ]; -// $plugin = $this->client->getPlugin(AddBasePath::class); -// $this->client->removePlugin(AddBasePath::class); - return $this->get('', [], $headers); -// $this->client->addPlugin($plugin); + // $plugin = $this->client->getPlugin(AddBasePath::class); + // $this->client->removePlugin(AddBasePath::class); + return $this->get('', $headers); + // $this->client->addPlugin($plugin); } } diff --git a/src/Api/NetworkUSB.php b/src/Api/NetworkUSB.php new file mode 100755 index 0000000..7bec23b --- /dev/null +++ b/src/Api/NetworkUSB.php @@ -0,0 +1,310 @@ + + * @author Damien Surot + */ +class NetworkUSB extends AbstractApi +{ + + /** + * For retrieving preset information. Presets are common use among Net/USB related input sources + * + * @return array + */ + public function getPresetInfo() + { + return $this->callGet('getPresetInfo'); + } + + /** + *For retrieving playback information + * + * @return array + */ + public function getPlayInfo() + { + return $this->callGet('getPlayInfo'); + } + + /** + * For controlling playback status + * + * @param string $playback Specifies playback status + * Values: "play" / "stop" / "pause" / "play_pause" / "previous" / "next" / + * "fast_reverse_start" / "fast_reverse_end" / "fast_forward_start" / + * "fast_forward_end" + * @return array + */ + public function setPlayback($playback) + { + return $this->callGet('setPlayback?playback=' . rawurlencode($playback)); + } + + /** + * For toggling repeat setting. No direct / discrete setting commands available + * + * @return array + */ + public function toggleRepeat() + { + return $this->callGet('toggleRepeat'); + } + + /** + * For toggling shuffle setting. No direct / discrete setting commands available + * + * @return array + */ + public function toggleShuffle() + { + return $this->callGet('toggleShuffle'); + } + + /** + * For retrieving list information. Basically this info is available to all relevant inputs, not limited to + * or independent from current input + * + * @param string $list_id Specifies list ID. If nothing specified, "main" is chosen implicitly + * Values: "main" (common for all Net/USB sources) + * "auto_complete" (Pandora) + * "search_artist" (Pandora) + * "search_track" (Pandora) + * + * @param string $input Specifies target Input ID. Controls for setListControl are to work + * with the input specified here + * Values: Input IDs for Net/USB related sources + * + * @param integer $index Specifies the reference index (offset from the beginning of the list). + * Note that this index must be in multiple of 8. If nothing was + * specified, the reference index previously specified would be used + * Values: 0, 8, 16, 24, ..., 64984, 64992 + * + * @param integer $size Specifies max list size retrieved at a time + * Value Range: 1 - 8 + * + * @param string $lang Specifies list language. But menu names or text info are not + * always necessarily pulled in a language specified here. If nothing + * specified, English ("en") is used implicitly + * Values: "en" (English)/ "ja" (Japanese)/ "fr" (French)/ "de" + * (German)/ "es" (Spanish)/ "ru" (Russian)/ "it" (Italy)/ "zh" (Chinese) + * + * @return array + */ + public function getListInfo($input, $size, $list_id = 'main', $index = null, $lang = null) + { + return $this->callGet('getListInfo?list_id=' . rawurlencode($list_id) . '&input=' . rawurlencode($input) . + (isset($index) ? '&index=' . rawurlencode($index) : '') . '&size=' . rawurlencode($size) . + (isset($lang) ? '&lang=' . rawurlencode($lang) : '')); + } + + /** + * For control a list. Controllable list info is not limited to or independent from current input + * + * @param string $list_id Specifies list ID. If nothing specified, "main" is chosen implicitly + * Values: "main" (common for all Net/USB sources) + * "auto_complete" (Pandora) + * "search_artist" (Pandora) + * "search_track" (Pandora) + * + * @param string $type Specifies list transition type. + * "select" is to enter and get into one deeper layer than the current layer where the element specified by + * the index belongs to. + * "play" is to start playback current index element, + * "return" is to go back one upper layer than current. + * "select" and "play" needs to specify an index at the same time. + * In case to “select” an element with its attribute being "Capable of Search", specify search text using + * setSearchString in advance. (Or it is possible to specify search text and move layers at the same + * time by specifying an index in setSearchString) + * Values: "select" / "play" / "return" + * + * @param integer $index Specifies the reference index (offset from the beginning of the list). + * Note that this index must be in multiple of 8. If nothing was + * specified, the reference index previously specified would be used + * Values: 0, 8, 16, 24, ..., 64984, 64992 + * + * @param string $zone Specifies target zone to playback. In the specified zone, input + * change occurs at the same time of playback. + * This parameter is valid only when type "play" is specified. If + * nothing is specified, "main" is chosen implicitly + * Values: : "main" / "zone2" / "zone3" / "zone4" + * + * @return array + */ + public function setListControl($type, $list_id = 'main', $index = null, $zone = null) + { + return $this->callGet('setListControl?list_id=' . rawurlencode($list_id) . '&type=' . rawurlencode($type) . + (isset($index) ? '&index=' . rawurlencode($index) : '') . + (isset($zone) ? '&zone=' . rawurlencode($zone) : '')); + } + + /** + * For setting search text. Specifies string executing this API before select an element with its + * attribute being “Capable of Search” or retrieve info about searching list(Pandora). + * + * @param string $list_id Specifies list ID. If nothing specified, "main" is chosen implicitly + * Values: "main" (common for all Net/USB sources) + * "auto_complete" (Pandora) + * "search_artist" (Pandora) + * "search_track" (Pandora) + * + * @param string $string Setting search text + * + * @param integer $index Specifies an element position in the list being selected (offset from + * the beginning of the list).Valid only when the list_id is "main" + * Specifies index an element with its attribute being "Capable of + * Search" Controls same as setListControl "select" are to work with + * the index an element specified. If no index is specified, non-actions + * of select + * Values : 0 ~ 64999 + * @return array + */ + public function setSearchString($string, $list_id = 'main', $index = null) + { + return $this->callGet('setSearchString?list_id=' . rawurlencode($list_id) . '&string=' . rawurlencode($string) . + (isset($index) ? '&index=' . rawurlencode($index) : '')); + } + + /** + * For recalling a content preset + * + * @param string $zone Specifies station recalling zone. This causes input change in specified zone + * Values: "main" / "zone2" / "zone3" / "zone4" + * + * @param integer $num Specifies Preset number + * Value: one in the range gotten via /system/getFeatures + * @return array + */ + public function recallPreset($zone, $num) + { + return $this->callGet('recallPreset?zone=' . rawurlencode($zone) . '&num=' . rawurlencode($num)); + } + + /** + * For registering current content to a preset. Presets are common use among Net/USB related input sources. + * + * @param integer $num Specifying a preset number + * Value: one in the range gotten via /system/getFeatures + * + * @return array + */ + public function storePreset($num) + { + return $this->callGet('storePreset?num=' . rawurlencode($num)); + } + + /** + * For retrieving account information registered on Device + * + * @return array + */ + public function getAccountStatus() + { + return $this->callGet('getAccountStatus'); + } + + /** + * For switching account for service corresponding multi account + * + * @param string $input Specifies target Input ID. + * Value: "pandora" + * @param integer $index Specifies switch account index + * Value : 0 ~ 7 (Pandora) + * @param integer $timeout Specifies timeout duration(ms) for this API process. If specifies 0, + * treat as maximum value. + * Value: 0 ~ 60000 + * @return array + */ + public function switchAccount($input, $index, $timeout) + { + return $this->callGet('switchAccount?input=' . rawurlencode($input) . '&index=' . rawurlencode($index) + . '&timeout=' . rawurlencode($timeout)); + } + + /** + * @param $input + * @param $type + * @param $timeout + * @return array|string + */ + public function getServiceInfo($input, $type, $timeout) + { + return $this->callGet('getServiceInfo?input=' . rawurlencode($input) . '&type=' . rawurlencode($type) + . '&timeout=' . rawurlencode($timeout)); + } + + /** + * @return array + */ + public function getMcPlaylistName() + { + return $this->callGet('getMcPlaylistName'); + } + + /** + * @param $bank + * @param $type + * @param int $index + * @param string $zone + * @return array|string + */ + public function manageMcPlaylist($bank, $type, $index = 0, $zone = 'main') + { + return $this->callGet('manageMcPlaylist?bank=' . rawurlencode($bank) . '&type=' . rawurlencode($type) + . '&index=' . rawurlencode($index) . '&zone=' . rawurlencode($zone)); + } + + /** + * @param $bank + * @param int $index + * @return array|string + */ + public function getMcPlaylist($bank, $index = 0) + { + return $this->callGet('getMcPlaylist?bank=' . rawurlencode($bank) . '&index=' . rawurlencode($index)); + } + + /** + * @param integer $index + * @return array + */ + public function getPlayQueue($index = 0) + { + return $this->callGet('getPlayQueue?index=' . rawurlencode($index)); + } + + /** + * @return array + */ + public function getRecentInfo() + { + return $this->callGet('getRecentInfo'); + } + + /** + * @param $zone + * @param $uri + * @return array + */ + public function setYmapUri($zone, $uri) + { + return $this->callPost('setYmapUri', array('zone' => $zone, "uri" => $uri)); + } + + + private function callGet($path) + { + return $this->get('/netusb/' . $path); + } + + private function callPost($path, array $parameters = array()) + { + return $this->post('/netusb/' . $path, $parameters); + } +} diff --git a/src/Api/System.php b/src/Api/System.php new file mode 100755 index 0000000..4254460 --- /dev/null +++ b/src/Api/System.php @@ -0,0 +1,138 @@ + + * @author Damien Surot + */ +/** + * @author Sam Van der Borght + */ +class System extends AbstractApi +{ + /** + * For retrieving basic information of a Device + * + * @return array + */ + public function getDeviceInfo() + { + return $this->call('getDeviceInfo'); + } + + /** + * For retrieving feature information equipped with a Device + * + * @return array + */ + public function getFeatures() + { + return $this->call('getFeatures'); + } + + /** + * For retrieving network related setup / information + * + * @return array + */ + public function getNetworkStatus() + { + return $this->call('getNetworkStatus'); + } + + /** + * For retrieving setup/information of overall system function. Parameters are readable only when + * corresponding functions are available in “func_list” of /system/getFeatures + * + * @return array + */ + public function getFuncStatus() + { + return $this->call('getFuncStatus'); + } + + /** + * For setting Auto Power Standby status. Actual operations/reactions of enabling Auto Power + * Standby depend on each Device + * + * @param bool $enable Specifies Auto Power Standby status + * @return array + */ + public function setAutoPowerStandby($enable = true) + { + return $this->call('setAutoPowerStandby?enable=' . rawurlencode($enable ? 'true' : 'false')); + } + + /** + * For retrieving Location information + * @return array + */ + public function getLocationInfo() + { + return $this->call('getLocationInfo'); + } + + /** + * For sending specific remote IR code. A Device is operated same as remote IR code reception. But + * continuous IR code cannot be used in this command. Refer to each Device’s IR code list for details + * @param string $code IR code in 8-digit hex + * @return array + */ + public function sendIrCode($code) + { + return $this->call('sendIrCode?code=' . rawurlencode($code)); + } + + /** + * @return array + */ + public function getNameText() + { + return $this->call('getNameText'); + } + + /** + * @param string $type + * @return array + */ + public function isNewFirmwareAvailable($type = 'network') + { + return $this->call('isNewFirmwareAvailable?type=' . rawurlencode($type)); + } + + /** + * @return array + */ + public function getTag() + { + return $this->call('getTag'); + } + + /** + * @return array + */ + public function getDisklavierSettings() + { + return $this->call('getDisklavierSettings'); + } + + + /** + * @return array + */ + public function getMusicCastTreeInfo() + { + return $this->call('getMusicCastTreeInfo'); + } + + /** + * @param $path + * @return array + */ + private function call($path) + { + return $this->get('/system/' . $path); + } +} diff --git a/lib/MusicCast/Api/Tuner.php b/src/Api/Tuner.php old mode 100644 new mode 100755 similarity index 100% rename from lib/MusicCast/Api/Tuner.php rename to src/Api/Tuner.php diff --git a/src/Api/Zone.php b/src/Api/Zone.php new file mode 100755 index 0000000..edb557b --- /dev/null +++ b/src/Api/Zone.php @@ -0,0 +1,158 @@ + + */ +class Zone extends AbstractApi +{ + + + /** + * Returns basic information of each Zone like power, volume, input and so on + * + * @param string $zone target zone. Available for zones with this function + * Values: "main" / "zone2" / "zone3" / "zone4" + * + * @return array + */ + public function getStatus($zone) + { + return $this->call($zone, 'getStatus'); + } + + + /** + * Returns a list of Sound Program available in each Zone. It is possible for the list contents to + be dynamically changed + * + * @param string $zone target zone. Available for zones with this function + * Values: "main" / "zone2" / "zone3" / "zone4" + * + * @return array + */ + public function getSoundProgramList($zone) + { + return $this->call($zone, 'getSoundProgramList'); + } + + /** + * Set the power status of each Zone + * + * @param string $zone target zone. Available for zones with this function + * Values: "main" / "zone2" / "zone3" / "zone4" + * @param string $power power status + * Values: "on" / "standby" / "toggle" + * + * @return array + */ + public function setPower($zone, $power) + { + return $this->call($zone, 'setPower?power='.rawurlencode($power)); + } + + /** + * Set Sleep Timer for each Zone + With Zone B enabled Devices, target Zone is described as Master Power, but Main Zone is used to + set it up via YXC + * + * @param string $zone target zone. Available for zones with this function + * Values: "main" / "zone2" / "zone3" / "zone4" + * @param string $sleep Sleep Time (unit in minutes) + * + * @return array + */ + public function setSleep($zone, $sleep) + { + return $this->call($zone, 'setSleep?sleep='.rawurlencode($sleep)); + } + + /** + * Set Sleep Timer for each Zone + With Zone B enabled Devices, target Zone is described as Master Power, but Main Zone is used to + set it up via YXC + * + * @param string $zone target zone. Available for zones with this function + * Values: "main" / "zone2" / "zone3" / "zone4" + * @param string $volume Specifies volume value + * Value Range: calculated by minimum/maximum/step values gotten + * via /system/getFeatures + * @param string $step Specifies volume step value if the volume is “up” or “down”. If + * nothing specified, minimum step value is used implicitly + * + * @return array + */ + public function setVolume($zone, $volume, $step = null) + { + return $this->call($zone, 'setVolume?volume='.rawurlencode($volume). + (isset($step) ? '&step='.rawurlencode($step) : '')); + } + + /** + * Set mute status in each Zone + * + * @param string $zone target zone. Available for zones with this function + * Values: "main" / "zone2" / "zone3" / "zone4" + * @param string $mute Mute status + * + * @return array + */ + public function setMute($zone, $mute) + { + return $this->call($zone, 'setMute?enable='.rawurlencode($mute?'true':'false')); + } + + /** + * Selecting each Zone input + * + * @param string $zone target zone. Available for zones with this function + * Values: "main" / "zone2" / "zone3" / "zone4" + * @param string $input Specifies Input ID + * Values: Input IDs gotten via /system/getFeatures + * @param string $mode Specifies select mode. If no parameter is specified, actions of input + * change depend on a Device’s specification + * Value: "autoplay_disabled" (Restricts Auto Play of Net/USB related Inputs). + * + * @return array + */ + public function setInput($zone, $input, $mode = null) + { + return $this->call($zone, 'setInput?input='.rawurlencode($input). + (isset($mode) ? '&mode='.rawurlencode($mode) : '')); + } + + /** + * Let a Device do necessary process before changing input in a specific zone. This is valid only + * when “prepare_input_change” exists in “func_list” found in /system/getFuncStatus. + * MusicCast CONTROLLER executes this API when an input icon is selected in a Room, right + * before sending various APIs (of retrieving list information etc.) regarding selecting input + * + * @param string $zone target zone. Available for zones with this function + * Values: "main" / "zone2" / "zone3" / "zone4" + * @param string $input Specifies Input ID + * Values: Input IDs gotten via /system/getFeatures + * + * @return array + */ + public function prepareInputChange($zone, $input) + { + return $this->call($zone, 'prepareInputChange?input='.rawurlencode($input)); + } + + /** + * @param $zone + * @return array|string + */ + public function getSignalInfo($zone) + { + return $this->call($zone, 'getSignalInfo'); + } + + + private function call($zone, $path) + { + return $this->get(rawurlencode($zone).'/'.$path); + } +} diff --git a/src/Cache.php b/src/Cache.php new file mode 100755 index 0000000..321ec0a --- /dev/null +++ b/src/Cache.php @@ -0,0 +1,29 @@ + + */ +class Cache extends DoctrineCachePool +{ + const MINUTE = 60; + const HOUR = self::MINUTE * 60; + const DAY = self::HOUR * 60; + + public function __construct() + { + $cache = new FilesystemCache(sys_get_temp_dir() . DIRECTORY_SEPARATOR . "musiccast"); + parent::__construct($cache); + } +} diff --git a/lib/MusicCast/Client.php b/src/Client.php old mode 100644 new mode 100755 similarity index 94% rename from lib/MusicCast/Client.php rename to src/Client.php index f6e342d..bb94aa7 --- a/lib/MusicCast/Client.php +++ b/src/Client.php @@ -1,23 +1,23 @@ setAllowedTypes('port', ['integer']); $this->options = $resolver->resolve($options); - $this->postResolve($options); + $this->postResolve(); return $this->options; } @@ -303,9 +302,8 @@ private function configureOptions($options = []) /** * Post resolve * - * @param array $options */ - protected function postResolve(array $options = []) + protected function postResolve() { $this->options['base_url'] = sprintf( 'http://%s:%s', diff --git a/src/Controller.php b/src/Controller.php new file mode 100755 index 0000000..b0f7179 --- /dev/null +++ b/src/Controller.php @@ -0,0 +1,437 @@ + + */ +class Controller extends Speaker +{ + /** + * No music playing, but not paused. + * + */ + const STATE_STOPPED = 201; + + /** + * Currently plating music. + */ + const STATE_PLAYING = 202; + + /** + * Music is currently paused. + */ + const STATE_PAUSED = 203; + + /** + * The speaker is currently working on either playing or pausing. + * + * Check it's state again in a second or two + */ + const STATE_TRANSITIONING = 204; + + /** + * The speaker is in an unknown state. + * + * This should only happen if Sonos introduce a new state that this code has not been updated to handle. + */ + const STATE_UNKNOWN = 205; + + + /** + * @var Network $network The network instance this Controller is part of. + */ + protected $network; + + /** + * @var + */ + private $distribution_id; + + /** + * Create a Controller instance from a speaker. + * + * The speaker must be a coordinator. + * + * @param Speaker $speaker + * @param Network $network + * @param int $distribution_id + */ + public function __construct(Speaker $speaker, Network $network, $distribution_id) + { + parent::__construct($speaker->device); + if (!$speaker->isCoordinator()) { + throw new \InvalidArgumentException("You cannot create a Controller instance from a Speaker that is + not the coordinator of it's group"); + } + $this->network = $network; + $this->distribution_id = $distribution_id; + } + + /** + * Get the speakers that are in the group of this controller. + * + * @return Speaker[] + */ + public function getSpeakers() + { + $group = []; + $speakers = $this->network->getSpeakers(); + + foreach ($speakers as $speaker) { + if ($speaker->getUuid() == $this->getUuid()) { + $group[] = $speaker; + continue; + } + if ($speaker->getGroup() === $this->getGroup() && $this->getGroup() != Speaker::NO_GROUP) { + $group[] = $speaker; + } + } + + + return $group; + } + + /** + * Check if this speaker is the coordinator of it's current group. + * + * This method is only here to override the method from the Speaker class. + * A Controller instance is always the coordinator of it's group. + * + * @return bool + */ + public function isCoordinator() + { + return true; + } + + /** + * Get the current state of the group of speakers. + * + * @return int One of the class STATE_ constants + */ + public function getState() + { + $name = $this->getStateName(); + switch ($name) { + case "stop": + return self::STATE_STOPPED; + case "play": + return self::STATE_PLAYING; + case "pause": + return self::STATE_PAUSED; + case "fast_reverse": + return self::STATE_TRANSITIONING; + case "fast_forward": + return self::STATE_TRANSITIONING; + } + return self::STATE_UNKNOWN; + } + + /** + * Get the current state of the group of speakers as the string reported by sonos: PLAYING, PAUSED_PLAYBACK, etc + * + * @return string + */ + public function getStateName() + { + return $this->call('netusb', 'getPlayInfo')['playback']; + } + + /** + * Get attributes about the currently active track in the queue. + * + * @return State Track data containing the following elements + */ + public function getStateDetails() + { + $json = $this->call('netusb', 'getPlayInfo'); + return State::buildState($json, $this->getIp()); + } + + /** + * Set the state of the group. + * + * @param int $state One of the class STATE_ constants + * + * @return static + */ + public function setState($state) + { + switch ($state) { + case self::STATE_PLAYING: + return $this->play(); + case self::STATE_PAUSED: + return $this->pause(); + case self::STATE_STOPPED: + return $this->stop(); + } + throw new \InvalidArgumentException("Unknown state: {$state})"); + } + + /** + * Start playing the active music for this group. + * + * @return static + */ + public function play() + { + return $this->setPlayback('play'); + } + + private function setPlayback($playback) + { + return $this->call('netusb', 'setPlayback', [$playback]); + } + + /** + * Pause the group. + * + * @return static + */ + public function pause() + { + return $this->setPlayback('pause'); + } + + /** + * Pause the group. + * + * @return static + */ + public function stop() + { + return $this->setPlayback('stop'); + } + + /** + * Skip to the next track in the current queue. + * + * @return static + */ + public function next() + { + return $this->setPlayback('next'); + } + + /** + * Skip back to the previous track in the current queue. + * + * @return static + */ + public function previous() + { + return $this->setPlayback('previous'); + } + + + public function setInput($input) + { + if (key_exists('prepareInputChange', $this->call('system', 'getFuncStatus'))) { + $this->call('zone', 'prepareInputChange', ['main', $input]); + } + $this->call('zone', 'setInput', ['main', $input]); + } + + /** + * Adds the specified speaker to the group of this Controller. + * + * @param Speaker $speaker The speaker to add to the group + * + * @return static + */ + public function addSpeaker(Speaker $speaker) + { + if ($speaker->getUuid() === $this->getUuid()) { + return $this; + } + if (!in_array($speaker, $this->getSpeakers())) { + $group = $this->getGroup(); + if ($group == Speaker::NO_GROUP) { + $group = md5($this->device->getIp()); + } + if ($speaker->getGroup() == Speaker::NO_GROUP) { + $speaker->call('dist', 'setClientInfo', [$group, 'main', $this->device->getIp()]); + $this->call( + 'dist', + 'setServerInfo', + [$group, 'add', 'main', array($speaker->device->getIp())] + ); + $this->call('dist', 'startDistribution', [$this->distribution_id]); + } + } + return $this; + } + + + /** + * Removes the specified speaker from the group of this Controller. + * + * @param Speaker $speaker The speaker to remove from the group + * + * @return static + */ + public function removeSpeaker(Speaker $speaker) + { + if ($speaker->getUuid() === $this->getUuid()) { + return $this; + } + if (in_array($speaker, $this->getSpeakers())) { + $speaker->call('dist', 'setClientInfo'); + $this->call( + 'dist', + 'setServerInfo', + [$this->getGroup(), 'remove', 'main', array($speaker->device->getIp())] + ); + $this->call('dist', 'startDistribution', [$this->distribution_id]); + } + return $this; + } + + /** + * Removes all speakers from the group of this Controller. + * + * @return static + */ + public function removeAllSpeakers() + { + foreach ($this->getSpeakers() as $speaker) { + $this->removeSpeaker($speaker); + } + return $this; + } + + + /** + * Adjust the volume of all the speakers controlled by this Controller. + * + * @param int $adjust A relative amount between -100 and 100 + * + * @return static + */ + public function adjustVolume($adjust) + { + $speakers = $this->getSpeakers(); + foreach ($speakers as $speaker) { + $speaker->adjustVolume($adjust); + } + + return $this; + } + + /** + * Check if repeat is currently active. + * + * @return bool + */ + public function getRepeat() + { + return $this->call('netusb', 'getPlayInfo')['repeat'] != 'off'; + } + + + /** + * Turn repeat mode on or off. + * + * @return static + */ + public function toggleRepeat() + { + return $this->call('netusb', 'toggleRepeat'); + } + + + /** + * Check if shuffle is currently active. + * + * @return bool + */ + public function getShuffle() + { + return $this->call('netusb', 'getPlayInfo')['shuffle'] != 'off'; + } + + /** + * Turn shuffle mode on or off. + * + * @return static + */ + public function toggleShuffle() + { + return $this->call('netusb', 'toggleShuffle'); + } + + + /** + * Get the queue for this controller. + * + * @return Queue + */ + public function getQueue() + { + return new Queue($this->call('netusb', 'getPlayQueue')); + } + + + /** + * Get the network instance used by this controller. + * + * @return Network + */ + public function getNetwork() + { + return $this->network; + } + + public function powerOn() + { + $speakers = $this->getSpeakers(); + foreach ($speakers as $speaker) { + $speaker->powerOn(); + } + return $this; + } + + public function standBy() + { + $speakers = $this->getSpeakers(); + foreach ($speakers as $speaker) { + $speaker->standBy(); + } + return $this; + } + + /** + * Mute all speakers controlled. + * + * @param bool $mute Whether the speakers should be muted or not + * + * @return static + */ + public function mute($mute = true) + { + $speakers = $this->getSpeakers(); + foreach ($speakers as $speaker) { + $speaker->mute(); + } + return $this; + } + + /** + * Mute all speakers controlled. + * + * @return static + */ + public function unmute() + { + $speakers = $this->getSpeakers(); + foreach ($speakers as $speaker) { + $speaker->unmute(); + } + return $this; + } +} diff --git a/src/Device.php b/src/Device.php new file mode 100755 index 0000000..cdb9839 --- /dev/null +++ b/src/Device.php @@ -0,0 +1,147 @@ + + */ +class Device implements LoggerAwareInterface +{ + /** + * @var string $ip Device Ip + */ + protected $ip; + /** + * @var Client $client MusicCast Client + */ + private $client; + /** + * @var CacheInterface $cache The cache object to use for the expensive multicast discover + * to find MusicCast devices on the network. + */ + protected $cache; + + /** + * @var LoggerInterface $logger The logging object. + */ + protected $logger; + + + /** + * Device constructor. + * @param $ip + * @param int $port + * @param CacheInterface|null $cache + * @param LoggerInterface|null $logger + */ + public function __construct($ip, $port = 80, CacheInterface $cache = null, LoggerInterface $logger = null) + { + $this->ip = $ip; + $this->client = new Client(['host' => $ip, 'port' => $port]); + if ($cache === null) { + $cache = new Cache(); + } + $this->cache = $cache; + + if ($logger === null) { + $logger = new NullLogger; + } + $this->logger = $logger; + } + + /** + * Set the logger object to use. + * + * @var LoggerInterface $logger The logging object + * + * @return static + */ + public function setLogger(LoggerInterface $logger) + { + $this->logger = $logger; + + return $this; + } + + /** + * @return string + */ + public function getIp() + { + return $this->ip; + } + + + public function call($api, $method, array $args = []) + { + return call_user_func_array(array($this->client->api($api), $method), $args); + } + + + public function getDeviceInfo() + { + $cacheKey = $this->getCacheKey() . __FUNCTION__; + if ($this->cache->has($cacheKey)) { + return $this->cache->get($cacheKey); + } + $info = $this->call('system', 'getDeviceInfo'); + $this->cache->set($cacheKey, $info); + return $info; + } + + private function getCacheKey() + { + return $this->ip; + } + + public function getLocationInfo() + { + $cacheKey = $this->getCacheKey() . __FUNCTION__; + if ($this->cache->has($cacheKey)) { + return $this->cache->get($cacheKey); + } + $info = $this->call('system', 'getLocationInfo'); + $this->cache->set($cacheKey, $info); + return $info; + } + + public function getMusicCastTreeInfo() + { + $cacheKey = $this->getCacheKey() . __FUNCTION__; + if ($this->cache->has($cacheKey)) { + return $this->cache->get($cacheKey); + } + $info = $this->call('system', 'getMusicCastTreeInfo'); + $this->cache->set($cacheKey, $info); + return $info; + } + + public function getNetworkStatus() + { + $cacheKey = $this->getCacheKey() . __FUNCTION__; + if ($this->cache->has($cacheKey)) { + return $this->cache->get($cacheKey); + } + $info = $this->call('system', 'getNetworkStatus'); + $this->cache->set($cacheKey, $info); + return $info; + } + + public function getUuid() + { + return $this->getDeviceInfo()['device_id']; + } +} diff --git a/lib/MusicCast/Enum/ResponseCodes.php b/src/Enum/ResponseCodes.php old mode 100644 new mode 100755 similarity index 100% rename from lib/MusicCast/Enum/ResponseCodes.php rename to src/Enum/ResponseCodes.php diff --git a/lib/MusicCast/Exception/BadMethodCallException.php b/src/Exception/BadMethodCallException.php old mode 100644 new mode 100755 similarity index 100% rename from lib/MusicCast/Exception/BadMethodCallException.php rename to src/Exception/BadMethodCallException.php diff --git a/lib/MusicCast/Exception/ErrorException.php b/src/Exception/ErrorException.php old mode 100644 new mode 100755 similarity index 100% rename from lib/MusicCast/Exception/ErrorException.php rename to src/Exception/ErrorException.php diff --git a/lib/MusicCast/Exception/ExceptionInterface.php b/src/Exception/ExceptionInterface.php old mode 100644 new mode 100755 similarity index 100% rename from lib/MusicCast/Exception/ExceptionInterface.php rename to src/Exception/ExceptionInterface.php diff --git a/lib/MusicCast/Exception/InvalidArgumentException.php b/src/Exception/InvalidArgumentException.php old mode 100644 new mode 100755 similarity index 100% rename from lib/MusicCast/Exception/InvalidArgumentException.php rename to src/Exception/InvalidArgumentException.php diff --git a/src/Exception/NotImplementedException.php b/src/Exception/NotImplementedException.php new file mode 100755 index 0000000..6ee46c9 --- /dev/null +++ b/src/Exception/NotImplementedException.php @@ -0,0 +1,7 @@ + + */ +class Favorite +{ + /** + * @var string|null $name The name of the playlist. + */ + protected $name; + protected $input; + protected $id; + protected $speaker; + + + /** + * Create an instance of the Playlist class. + * + * @param int $index + * @param array $data + * @param Speaker $speaker A controller instance on the playlist's network + */ + public function __construct($index, $data, Speaker $speaker) + { + $this->id = $index; + $this->name = $data['text']; + $this->input = $data['input']; + $this->speaker = $speaker; + } + + + /** + * Get the id of the playlist. + * + * @return string + */ + public function getId() + { + return $this->id; + } + + + /** + * Get the name of the playlist. + * + * @return string + */ + public function getName() + { + return $this->name; + } + + /** + * @return mixed + */ + public function getInput() + { + return $this->input; + } + + + public function play() + { + $this->speaker->call('netusb', 'recallPreset', ['main', $this->id]); + } +} diff --git a/lib/MusicCast/HttpClient/Message/ResponseMediator.php b/src/HttpClient/Message/ResponseMediator.php old mode 100644 new mode 100755 similarity index 100% rename from lib/MusicCast/HttpClient/Message/ResponseMediator.php rename to src/HttpClient/Message/ResponseMediator.php diff --git a/lib/MusicCast/HttpClient/Plugin/AddBasePath.php b/src/HttpClient/Plugin/AddBasePath.php old mode 100644 new mode 100755 similarity index 100% rename from lib/MusicCast/HttpClient/Plugin/AddBasePath.php rename to src/HttpClient/Plugin/AddBasePath.php diff --git a/lib/MusicCast/HttpClient/Plugin/History.php b/src/HttpClient/Plugin/History.php old mode 100644 new mode 100755 similarity index 100% rename from lib/MusicCast/HttpClient/Plugin/History.php rename to src/HttpClient/Plugin/History.php diff --git a/lib/MusicCast/HttpClient/Plugin/MusicCastExceptionThrower.php b/src/HttpClient/Plugin/MusicCastExceptionThrower.php old mode 100644 new mode 100755 similarity index 94% rename from lib/MusicCast/HttpClient/Plugin/MusicCastExceptionThrower.php rename to src/HttpClient/Plugin/MusicCastExceptionThrower.php index 68cfcd9..69328c9 --- a/lib/MusicCast/HttpClient/Plugin/MusicCastExceptionThrower.php +++ b/src/HttpClient/Plugin/MusicCastExceptionThrower.php @@ -26,7 +26,8 @@ public function handleRequest(RequestInterface $request, callable $next, callabl } throw new ErrorException(ResponseCodes::getMessage($code), 400); - } elseif ($response->getStatusCode() === 200) { + } + if ($response->getStatusCode() === 200) { return $response; } diff --git a/src/Network.php b/src/Network.php new file mode 100755 index 0000000..410f87a --- /dev/null +++ b/src/Network.php @@ -0,0 +1,352 @@ + + */ +class Network implements LoggerAwareInterface +{ + + /** + * @var Speaker[]|null $speakers Speakers that are available on the current network. + */ + protected $speakers; + + /** + * @var Playlist[]|null $playlists Playlists that are available on the current network. + */ + protected $playlists; + + /** + * @var CacheInterface $cache The cache object to use for the expensive multicast discover to find + * MusicCast devices on the network. + */ + protected $cache; + + /** + * @var LoggerInterface $logger The logging object. + */ + protected $logger; + + /** + * @var $multicastAddress string The multicast address to use for SSDP discovery. + */ + protected $multicastAddress = "239.255.255.250"; + + /** + * @var $networkInterface string The network interface to use for SSDP discovery. + */ + protected $networkInterface; + /** + * @var Favorite[]|null $favorites Favorites that are available on the current network. + */ + protected $favorites; + + /** + * Create a new instance. + * + * @param CacheInterface $cache The cache object to use for the expensive multicast discover to find + * MusicCast devices on the network + * @param LoggerInterface $logger The logging object + */ + public function __construct(CacheInterface $cache = null, LoggerInterface $logger = null) + { + if ($cache === null) { + $cache = new Cache(); + } + $this->cache = $cache; + + if ($logger === null) { + $logger = new NullLogger; + } + $this->logger = $logger; + } + + /** + * @param LoggerInterface $logger + */ + public function setLogger(LoggerInterface $logger) + { + $this->logger = $logger; + } + + /** + * @return string + */ + public function getMulticastAddress() + { + return $this->multicastAddress; + } + + /** + * @param $multicastAddress + */ + public function setMulticastAddress($multicastAddress) + { + $this->multicastAddress = $multicastAddress; + } + + /** + * @return string + */ + public function getNetworkInterface() + { + return $this->networkInterface; + } + + /** + * @param $networkInterface + */ + public function setNetworkInterface($networkInterface) + { + $this->networkInterface = $networkInterface; + } + + /** + * Get a Controller instance from the network. + * + * Useful for managing playlists/alarms, as these need a controller but it doesn't matter which one. + * + * @return Controller|null + */ + public function getController() + { + $controllers = $this->getControllers(); + if ($controller = reset($controllers)) { + return $controller; + } + return null; + } + + /** + * Get all the coordinators on the network. + * + * @return Controller[] + */ + public function getControllers() + { + $controllers = []; + $speakers = $this->getSpeakers(); + $index = 0; + foreach ($speakers as $speaker) { + if (!$speaker->isCoordinator()) { + continue; + } + $controllers[$speaker->getIp()] = new Controller($speaker, $this, $index++); + } + return $controllers; + } + + /** + * @return Speaker[]|null + */ + public function getSpeakers() + { + if (is_array($this->speakers)) { + return $this->speakers; + } + + $this->logger->debug("creating speaker instances"); + + $cacheKey = $this->getCacheKey(); + + if ($this->cache->has($cacheKey)) { + $this->logger->debug("getting device info from cache"); + $devices = $this->cache->get($cacheKey); + } else { + $devices = $this->getDevices(); + + # Only cache the devices if we actually found some + if (count($devices) > 0) { + $this->cache->set($cacheKey, $devices); + } + } + + if (count($devices) < 1) { + throw new \RuntimeException("No devices found on the current network"); + } + + + # Get the MusicCast devices from 1 speaker + $ip = reset($devices); + $device = new Device($ip, 80); + $this->logger->debug("Getting devices info from: {$ip}"); + $treeInfo = $device->getMusicCastTreeInfo(); + $this->speakers = []; + foreach ($treeInfo['mac_address_list'] as $addr) { + $ip = $addr['ip_address']; + $speaker = new Speaker(new Device($ip, 80)); + $this->speakers[$ip] = $speaker; + } + + return $this->speakers; + } + + public function getSpeakerByName($name) + { + $roughMatch = false; + $speakers = $this->getSpeakers(); + foreach ($speakers as $speaker) { + if ($speaker->getName() === $name) { + return $speaker; + } + if (strtolower($speaker->getName()) === strtolower($name)) { + $roughMatch = $speaker; + } + } + if ($roughMatch) { + return $roughMatch; + } + return null; + } + + protected function getCacheKey() + { + $cacheKey = "devices"; + + $cacheKey .= "_" . gettype($this->networkInterface); + $cacheKey .= "_" . $this->networkInterface; + + $cacheKey .= "_" . $this->multicastAddress; + + return $cacheKey; + } + + /** + * Get all the devices on the current network. + * + * @return string[] An array of ip addresses + */ + protected function getDevices() + { + $this->logger->debug("discovering devices..."); + + $port = 1900; + + $sock = socket_create(AF_INET, SOCK_DGRAM, SOL_UDP); + + $level = getprotobyname("ip"); + + socket_set_option($sock, $level, IP_MULTICAST_TTL, 2); + + if ($this->networkInterface !== null) { + socket_set_option($sock, $level, IP_MULTICAST_IF, $this->networkInterface); + } + + $data = "M-SEARCH * HTTP/1.1\r\n"; + $data .= "HOST: {$this->multicastAddress}:1900\r\n"; + $data .= "MAN: \"ssdp:discover\"\r\n"; + $data .= "MX: 2\r\n"; + $data .= "ST: urn:schemas-upnp-org:device:MediaRenderer:1\r\n"; + + + $this->logger->debug($data); + + socket_sendto($sock, $data, strlen($data), null, $this->multicastAddress, $port); + + $read = [$sock]; + $write = []; + $except = []; + $name = null; + $port = null; + $tmp = ""; + + $response = ""; + while (socket_select($read, $write, $except, 1)) { + socket_recvfrom($sock, $tmp, 2048, null, $name, $port); + $response .= $tmp; + } + + $this->logger->debug($response); + + $devices = []; + foreach (explode("\r\n\r\n", $response) as $reply) { + if (!$reply) { + continue; + } + + $data = []; + foreach (explode("\r\n", $reply) as $line) { + if (!$pos = strpos($line, ":")) { + continue; + } + $key = strtolower(substr($line, 0, $pos)); + $val = trim(substr($line, $pos + 1)); + $data[$key] = $val; + } + $devices[] = $data; + } + + $return = []; + $unique = []; + foreach ($devices as $device) { + if ($device["st"] !== "urn:schemas-upnp-org:device:MediaRenderer:1") { + continue; + } + if (in_array($device["usn"], $unique)) { + continue; + } + $this->logger->debug("found device: {usn}", $device); + + $url = parse_url($device["location"]); + $ip = $url["host"]; + + $return[] = $ip; + $unique[] = $device["usn"]; + } + + return $return; + } + + /** + * Get the coordinator for the specified ip address. + * + * @param string $ip The ip address of the speaker + * + * @return Controller|null + */ + public function getControllerByIp($ip) + { + $speakers = $this->getSpeakers(); + if (!array_key_exists($ip, $speakers)) { + throw new \InvalidArgumentException("No speaker found for the IP address '{$ip}'"); + } + + foreach ($this->getControllers() as $controller) { + if ($controller->getIp() === $ip) { + return $controller; + } + } + return null; + } + + /** + * Get the speaker for the specified ip address. + * + * @param string $ip The ip address of the speaker + * + * @return Speaker + */ + public function getSpeakerByIp($ip) + { + $speakers = $this->getSpeakers(); + if (!array_key_exists($ip, $speakers)) { + throw new \InvalidArgumentException("No speaker found for the IP address '{$ip}'"); + } + return $speakers[$ip]; + } +} diff --git a/src/Playlist.php b/src/Playlist.php new file mode 100755 index 0000000..6960907 --- /dev/null +++ b/src/Playlist.php @@ -0,0 +1,65 @@ + + */ +class Playlist +{ + /** + * @var string|null $name The name of the playlist. + */ + protected $name; + protected $id; + private $speaker; + + + /** + * Create an instance of the Playlist class. + * + * @param int $bank + * @param string $name + * @param Speaker $controller A speaker instance on the playlist's network + */ + public function __construct($bank, $name, Speaker $controller) + { + $this->id = $bank; + $this->name = $name; + $this->speaker = $controller; + } + + + /** + * Get the id of the playlist. + * + * @return string + */ + public function getId() + { + return $this->id; + } + + + /** + * Get the name of the playlist. + * + * @return string + */ + public function getName() + { + return $this->name; + } + + public function play($index = 0) + { + $this->speaker->call('netusb', 'manageMcPlaylist', [$this->id, 'play', $index]); + } +} diff --git a/src/Queue.php b/src/Queue.php new file mode 100755 index 0000000..12dccb7 --- /dev/null +++ b/src/Queue.php @@ -0,0 +1,70 @@ +count = $queueInfo['max_line']; + $this->playing_index = $queueInfo['playing_index']; + foreach ($queueInfo['track_info'] as $track_info) { + $this->tracks[] = new Track($track_info['input'], $track_info['text'], $track_info['thumbnail']); + } + } + + /** + * The number of tracks in the queue. + * + * @return int + */ + public function count() + { + return $this->count; + } + + /** + * @return Track[] + */ + public function getTracks() + { + return $this->tracks; + } + + /** + * @return mixed + */ + public function getPlayingIndex() + { + return $this->playing_index; + } +} diff --git a/src/Speaker.php b/src/Speaker.php new file mode 100755 index 0000000..b821400 --- /dev/null +++ b/src/Speaker.php @@ -0,0 +1,446 @@ + + */ +class Speaker +{ + protected $model; + protected $name; + protected $device; + /** + * @var + */ + protected $playlists; + + /** + * @var + */ + protected $favorites; + + const NO_GROUP = "NoGroup"; + + /** + * Create an instance of the Speaker class. + * + * @param Device $device the ip address that the speaker is listening on + */ + public function __construct($device) + { + $this->device = $device; + $this->model = $device->getDeviceInfo()['model_name']; + $this->name = $device->getNetworkStatus()['network_name']; + } + + public function call($api, $method, array $args = []) + { + return $this->device->call($api, $method, $args); + } + + public function getIp() + { + return $this->device->getIp(); + } + + /** + * @return mixed + */ + public function getModel() + { + return $this->model; + } + + /** + * @return mixed + */ + public function getName() + { + return $this->name; + } + + /** + * Get the uuid of the group this speaker is a member of. + * + * @return string + */ + public function getGroup() + { + $group = $this->call('dist', 'getDistributionInfo')['group_id']; + if (is_numeric($group) && intval($group == 0)) { + $group = self::NO_GROUP; + } + return $group; + } + + /** + * Get the name of the group this speaker is a member of. + * + * @return string + */ + public function getGroupName() + { + $group = $this->call('dist', 'getDistributionInfo')['group_name']; + return $group; + //return str_replace('(Linked) ', '', $group); + } + + /** + * Check if this speaker is the coordinator of it's current group. + * + * @return bool + */ + public function isCoordinator() + { + $role = $this->call('dist', 'getDistributionInfo')['role']; + return $role == 'server' || $this->getGroup() == Speaker::NO_GROUP; + } + + /** + * Get the uuid of this speaker. + * + * @return string The uuid of this speaker + */ + public function getUuid() + { + return $this->device->getUuid(); + } + + /** + * Get the current volume of this speaker. + * + * @return int + */ + public function getVolume() + { + return (int)$this->call('zone', 'getStatus', ['main'])['volume']; + } + + /** + * Adjust the volume of this speaker to a specific value. + * + * @param int $volume The amount to set the volume to between 0 and 100 + * + * @return static + */ + public function setVolume($volume) + { + $this->call('zone', 'setVolume', ['main', $volume]); + return $this; + } + + + /** + * Adjust the volume of this speaker by a relative amount. + * + * @param int $adjust The amount to adjust by between -100 and 100 + * + * @return static + */ + public function adjustVolume($adjust) + { + $this->call('zone', 'setVolume', ['main', $adjust > 0 ? 'up' : 'down', abs($adjust)]); + return $this; + } + + /** + * Check if this speaker is currently muted. + * + * @return bool + */ + public function isMuted() + { + return $this->call('zone', 'getStatus', ['main'])['mute']; + } + + /** + * Unmute this speaker. + * + * @return static + */ + public function unmute() + { + return $this->mute(false); + } + + /** + * Mute this speaker. + * + * @param bool $mute Whether the speaker should be muted or not + * + * @return static + */ + public function mute($mute = true) + { + $this->call('zone', 'setMute', ['main', $mute]); + return $this; + } + + /** + * Power On this speaker. + * + * @return static + */ + public function powerOn() + { + return $this->setPower('on'); + } + + + /** + * Power On this speaker. + * + * @return bool + */ + public function isPowerOn() + { + return $this->call('zone', 'getStatus', ['main'])["power"] == 'on'; + } + + /** + * Stand by this speaker. + * + * @return static + */ + public function standBy() + { + return $this->setPower('standby'); + } + + + /** + * Power toggle this speaker. + * + * @return static + */ + public function powerToggle() + { + return $this->setPower('toggle'); + } + + private function setPower($power) + { + $this->call('zone', 'setPower', ['main', $power]); + return $this; + } + + /** + * Get the currently active media info. + * + * @return array + */ + public function getInput() + { + return $this->call('zone', 'getStatus', ['main'])['input']; + } + + + public function isStreaming() + { + $input = 'input' . $this->call('zone', 'getStatus', ['main'])['input']; + return $input == "tuner" || stripos($input, "hdmi") != false || stripos($input, "av") != false + || stripos($input, "aux") != false || stripos($input, "audio") != false; + } + + /** + * Get List of available input + * + * @return array + */ + public function getInputList() + { + $tags = $this->call('system', 'getTag'); + $inputs = $tags['input_list']; + $return = []; + foreach ($inputs as $input) { + $return[] = $input['id']; + } + return $return; + } + + + /** + * Check if a playlist with the specified name exists on this network. + * + * If no case-sensitive match is found it will return a case-insensitive match. + * + * @param string $name The name of the playlist + * + * @return bool + */ + public function hasPlaylist($name) + { + $playlists = $this->getPlaylists(); + foreach ($playlists as $playlist) { + if ($playlist->getName() === $name) { + return true; + } + if (strtolower($playlist->getName()) === strtolower($name)) { + return true; + } + } + return false; + } + + /** + * Get all the playlists available on the network. + * + * @return Playlist[] + */ + public function getPlaylists() + { + if (is_array($this->playlists)) { + return $this->playlists; + } + $playlist_names = $this->call('netusb', 'getMcPlaylistName')['name_list']; + $playlists = []; + $index = 1; + foreach ($playlist_names as $playlist_name) { + $playlists[$playlist_name] = new Playlist($index++, $playlist_name, $this); + } + return $this->playlists = $playlists; + } + + /** + * Get the playlist with the specified name. + * + * If no case-sensitive match is found it will return a case-insensitive match. + * + * @param string $name The name of the playlist + * + * @return Playlist|null + */ + public function getPlaylistByName($name) + { + $roughMatch = false; + $playlists = $this->getPlaylists(); + foreach ($playlists as $playlist) { + if ($playlist->getName() === $name) { + return $playlist; + } + if (strtolower($playlist->getName()) === strtolower($name)) { + $roughMatch = $playlist; + } + } + if ($roughMatch) { + return $roughMatch; + } + return null; + } + + /** + * Get the playlist with the specified id. + * + * @param int $id The ID of the playlist + * + * @return Playlist + */ + public function getPlaylistById($id) + { + $playlists = $this->getPlaylists(); + foreach ($playlists as $playlist) { + if ($playlist->getId() === $id) { + return $playlist; + } + } + return null; + } + + /** + * Check if a playlist with the specified name exists on this network. + * + * If no case-sensitive match is found it will return a case-insensitive match. + * + * @param string $name The name of the playlist + * + * @return bool + */ + public function hasFavorite($name) + { + $favorites = $this->getFavorites(); + foreach ($favorites as $item) { + if ($item->getName() === $name) { + return true; + } + if (strtolower($item->getName()) === strtolower($name)) { + return true; + } + } + return false; + } + + /** + * Get Favorites on the network. + * + * @return Favorite[] + */ + public function getFavorites() + { + if (is_array($this->favorites)) { + return $this->favorites; + } + $favorites_info = $this->call('netusb', 'getPresetInfo')['preset_info']; + $favorites = []; + $index = 0; + foreach ($favorites_info as $item) { + $index++; + if ($item['text'] != '') { + $favorites[$item['text']] = new Favorite($index, $item, $this); + } + } + return $this->favorites = $favorites; + } + + /** + * Get the playlist with the specified name. + * + * If no case-sensitive match is found it will return a case-insensitive match. + * + * @param string $name The name of the playlist + * + * @return Favorite|null + */ + public function getFavoriteByName($name) + { + $roughMatch = false; + $favorites = $this->getFavorites(); + foreach ($favorites as $item) { + if ($item->getName() === $name) { + return $item; + } + if (strtolower($item->getName()) === strtolower($name)) { + $roughMatch = $item; + } + } + if ($roughMatch) { + return $roughMatch; + } + return null; + } + + /** + * Get the playlist with the specified id. + * + * @param int $id The ID of the playlist + * + * @return Favorite + */ + public function getFavoriteById($id) + { + $favorites = $this->getFavorites(); + foreach ($favorites as $item) { + if ($item->getId() === $id) { + return $item; + } + } + return null; + } +} diff --git a/src/State.php b/src/State.php new file mode 100755 index 0000000..6df2f7c --- /dev/null +++ b/src/State.php @@ -0,0 +1,59 @@ + + */ +class State +{ + /** + * @var string $duration The duration of the currently active track (hh:mm:ss). + */ + public $duration = ""; + + /** + * @var string $position The position of the currently active track (hh:mm:ss). + */ + public $position = ""; + + public $track; + + /** + * Create a Track object. + */ + public function __construct() + { + } + + /** + * Update the track properties using an xml element. + * + * @param $playInfo + * @param $ip + * @return static + */ + public static function buildState($playInfo, $ip) + { + $state = new State(); + if ($art = $playInfo['albumart_url']) { + if (substr($art, 0, 4) !== "http") { + $art = ltrim($art, "/"); + $art = sprintf("http://%s:80/%s", $ip, $art); + } + } + $state->track = new Track( + $playInfo['input'], + $playInfo['track'], + $art, + $playInfo['artist'], + $playInfo['album'] + ); + $state->duration = $playInfo['total_time']; + $state->position = $playInfo['play_time']; + return $state; + } +} diff --git a/src/Tracks/Track.php b/src/Tracks/Track.php new file mode 100755 index 0000000..9f8ccc7 --- /dev/null +++ b/src/Tracks/Track.php @@ -0,0 +1,96 @@ + + */ +class Track +{ + + /** + * @var string $title The name of the track. + */ + protected $title = ""; + + /** + * @var string $artist The name of the artist of the track. + */ + protected $artist = ""; + + /** + * @var string $album The name of the album of the track. + */ + protected $album = ""; + + /** + * @var string $albumArt The full path to the album art for this track. + */ + protected $albumArt = ""; + + protected $input = ""; + + /** + * Track constructor. + * @param $input + * @param $title + * @param $albumArt + * @param null $artist + * @param null $album + */ + public function __construct($input, $title, $albumArt, $artist = null, $album = null) + { + $this->title = $title; + $this->artist = $artist; + $this->album = $album; + $this->albumArt = stripcslashes($albumArt); + $this->input = $input; + } + + /** + * @return string + */ + public function getTitle() + { + return $this->title; + } + + /** + * @return string + */ + public function getArtist() + { + return $this->artist; + } + + /** + * @return string + */ + public function getAlbum() + { + return $this->album; + } + + /** + * @return string + */ + public function getAlbumArt() + { + return $this->albumArt; + } + + /** + * @return string + */ + public function getInput() + { + return $this->input; + } +} diff --git a/test/bootstrap.php b/test/bootstrap.php deleted file mode 100644 index a021287..0000000 --- a/test/bootstrap.php +++ /dev/null @@ -1,14 +0,0 @@ -add('MusicCast\Tests', __DIR__); -return $loader; diff --git a/tests/Api/DistributionLiveTest.php b/tests/Api/DistributionLiveTest.php new file mode 100755 index 0000000..7b1b3d8 --- /dev/null +++ b/tests/Api/DistributionLiveTest.php @@ -0,0 +1,58 @@ + + */ + +namespace MusicCastTests\Api; + +class DistributionLiveTest extends \MusicCastTests\LiveTest +{ + protected function setUp() + { + parent::setUp(); + } + + /** + * @test + */ + public function testGetDistributionInfo() + { + self::assertArrayHasKey('group_id', $this->client->api('dist')->getDistributionInfo()); + } + + /** + * @test + */ + public function testStartDistribution() + { + //self::assertArrayHasKey('???', $this->client->api('dist')->startDistribution()); + $this->markTestSkipped('dist/startDistribution method not implemented'); + } + + /** + * @test + */ + public function testSetClientInfo() + { + self::assertArrayHasKey('response_code', $this->client->api('dist')->setClientInfo()); + } + + /** + * @test + */ + public function testSetGroupName() + { + //self::assertArrayHasKey('???', $this->client->api('dist')->setGroupName()); + $this->markTestSkipped('dist/setGroupName method not implemented'); + } + + + /** + * @test + */ + public function testSetServerInfo() + { + //self::assertArrayHasKey('group_id', $this->client->api('dist')->setServerInfo()); + $this->markTestSkipped('dist/setServerInfo method not implemented'); + } +} diff --git a/tests/Api/NetworkUSBLiveTest.php b/tests/Api/NetworkUSBLiveTest.php new file mode 100755 index 0000000..a918b8b --- /dev/null +++ b/tests/Api/NetworkUSBLiveTest.php @@ -0,0 +1,164 @@ + + */ + +namespace MusicCastTests\Api; + +use Symfony\Component\Yaml\Yaml; + +class NetworkUSBLiveTest extends \MusicCastTests\LiveTest +{ + protected function setUp() + { + parent::setUp(); + $options = Yaml::parse(file_get_contents(__DIR__ . '/../env.yml')); + $controller = $this->network->getControllerByIp($options['host']); + $controller->getPlaylistById(1)->play(); + } + /** + * @test + */ + public function testGetPresetInfo() + { + self::assertArrayHasKey('preset_info', $this->client->api('netusb')->getPresetInfo()); + } + + /** + * @test + */ + public function testGetPlayInfo() + { + self::assertArrayHasKey('input', $this->client->api('netusb')->getPlayInfo()); + } + + /** + * @test + */ + public function testSetPlayback() + { + self::assertArrayHasKey('response_code', $this->client->api('netusb')->setPlayback("play_pause")); + self::assertArrayHasKey('response_code', $this->client->api('netusb')->setPlayback("play_pause")); + } + + /** + * @test + */ + public function testToggleRepeat() + { + self::assertArrayHasKey('response_code', $this->client->api('netusb')->toggleRepeat()); + } + + /** + * @test + */ + public function testToggleShuffle() + { + self::assertArrayHasKey('response_code', $this->client->api('netusb')->toggleShuffle()); + } + + /** + * @test + */ + public function testGetListInfo() + { + self::assertArrayHasKey( + 'list_info', + $this->client->api('netusb')->getListInfo('bluetooth', 5) + ); + } + + /** + * @test + */ + public function testSetListControl() + { + //self::assertArrayHasKey('???', $this->client->api('netusb')->setListControl()); + $this->markTestSkipped('netusb/setListControl method not implemented'); + } + + /** + * @test + */ + public function testSetSearchString() + { + //self::assertArrayHasKey('???', $this->client->api('netusb')->setSearchString()); + $this->markTestSkipped('netusb/setSearchString method not implemented'); + } + + /** + * @test + */ + public function testRecallPreset() + { + self::assertArrayHasKey('response_code', $this->client->api('netusb')->recallPreset('main', 1)); + } + + /** + * @test + */ + public function testStorePreset() + { + //self::assertArrayHasKey('???', $this->client->api('netusb')->storePreset()); + $this->markTestSkipped('netusb/storePreset method not implemented'); + } + + /** + * @test + */ + public function testGetAccountStatus() + { + self::assertArrayHasKey('service_list', $this->client->api('netusb')->getAccountStatus()); + } + + /** + * @test + */ + public function testSwitchAccount() + { + //self::assertArrayHasKey('???', $this->client->api('netusb')->switchAccount()); + $this->markTestSkipped('netusb/switchAccount method not implemented'); + } + + /** + * @test + */ + public function testGetServiceInfo() + { + //self::assertArrayHasKey('???', $this->client->api('netusb')->getServiceInfo()); + $this->markTestSkipped('netusb/getServiceInfo method not implemented'); + } + + /** + * @test + */ + public function testGetMcPlaylistName() + { + self::assertArrayHasKey('name_list', $this->client->api('netusb')->getMcPlaylistName()); + } + + /** + * @test + */ + public function testGetPlayQueue() + { + self::assertArrayHasKey('type', $this->client->api('netusb')->getPlayQueue()); + } + + /** + * @test + */ + public function testGetRecentInfo() + { + self::assertArrayHasKey('recent_info', $this->client->api('netusb')->getRecentInfo()); + } + + /** + * @test + */ + public function testSetYmapUri() + { + //self::assertArrayHasKey('???', $this->client->api('netusb')->setYmapUri()); + $this->markTestSkipped('netusb/setYmapUri method not implemented'); + } +} diff --git a/tests/Api/SystemLiveTest.php b/tests/Api/SystemLiveTest.php new file mode 100755 index 0000000..728e4d6 --- /dev/null +++ b/tests/Api/SystemLiveTest.php @@ -0,0 +1,124 @@ + + */ + +namespace MusicCastTests\Api; + +class SystemLiveTest extends \MusicCastTests\LiveTest +{ + protected function setUp() + { + parent::setUp(); + } + /** + * @test + */ + public function testGetDeviceInfo() + { + self::assertArrayHasKey('model_name', $this->client->api('system')->getDeviceInfo()); + } + + /** + * @test + */ + public function testGetFeatures() + { + self::assertArrayHasKey('system', $this->client->api('system')->getFeatures()); + self::assertArrayHasKey('func_list', $this->client->api('system')->getFeatures()['system']); + } + + /** + * @test + */ + public function testGetNetworkStatus() + { + self::assertArrayHasKey('network_name', $this->client->api('system')->getNetworkStatus()); + } + + /** + * @test + */ + public function testGetFuncStatus() + { + $this->client->api('system')->getFuncStatus(); + } + + /** + * @test + */ + public function testSetAutoPowerStandby() + { + $funcStatus = $this->client->api('system')->getFuncStatus(); + if (array_key_exists('auto_power_standby', $funcStatus)) { + $previous = $funcStatus['auto_power_standby']; + $this->client->api('system')->setAutoPowerStandby(!$previous); + sleep(1); + self::assertTrue($this->client->api('system')->getFuncStatus()['auto_power_standby'] != $previous); + $this->client->api('system')->setAutoPowerStandby($previous); + sleep(1); + self::assertTrue($this->client->api('system')->getFuncStatus()['auto_power_standby'] == $previous); + return; + } + echo 'Can\'t test setAutoPowerStandby on this device'; + } + + /** + * @test + */ + public function testGetLocationInfo() + { + self::assertArrayHasKey('name', $this->client->api('system')->getLocationInfo()); + } + + + /** + * @test + */ + public function testSendIrCode() + { + $this->client->api('system')->sendIrCode('00000000'); + } + + /** + * @test + */ + public function testGetNameText() + { + self::assertArrayHasKey('zone_list', $this->client->api('system')->getNameText()); + } + + /** + * @test + */ + public function testIsNewFirmwareAvailable() + { + self::assertArrayHasKey('available', $this->client->api('system')->isNewFirmwareAvailable()); + } + + /** + * @test + */ + public function testGetTag() + { + self::assertArrayHasKey('zone_list', $this->client->api('system')->getTag()); + } + + /** + * @test + */ + public function testGetDisklavierSettings() + { + //self::assertArrayHasKey('enable', $this->client->api('system')->getDisklavierSettings()); + $this->markTestSkipped('system/getDisklavierSettings method not implemented'); + } + + + /** + * @test + */ + public function testGetMusicCastTreeInfo() + { + self::assertArrayHasKey('mode', $this->client->api('system')->getMusicCastTreeInfo()); + } +} diff --git a/tests/Api/ZoneLiveTest.php b/tests/Api/ZoneLiveTest.php new file mode 100755 index 0000000..2c6e61c --- /dev/null +++ b/tests/Api/ZoneLiveTest.php @@ -0,0 +1,94 @@ + + */ + +namespace MusicCastTests\Api; + +use MusicCast\Exception\ErrorException; + +class ZoneLiveTest extends \MusicCastTests\LiveTest +{ + protected function setUp() + { + parent::setUp(); + } + /** + * @test + */ + public function testGetStatus() + { + self::assertArrayHasKey('power', $this->client->api('zone')->getStatus('main')); + } + + /** + * @test + */ + public function testGetStatusThrowErrorExceptionIfBadZoneName() + { + $this->expectException(ErrorException::class); + $this->client->api('zone')->getStatus('fakeZone'); + } + + public function testGetSoundProgramList() + { + self::assertArrayHasKey('sound_program_list', $this->client->api('zone')->getSoundProgramList('main')); + } + + public function testSetPower() + { + $power = $this->client->api('zone')->getStatus('main')['power']; + $this->client->api('zone')->setPower('main', $power); + } + + public function setSleep() + { + $sleep = $this->client->api('zone')->getStatus('main')['sleep']; + $this->client->api('zone')->setSleep('main', $sleep); + } + + public function testAdjustVolume() + { + $volume = $this->client->api('zone')->getStatus('main')['volume']; + $this->client->api('zone')->setVolume('main', 'up', '1'); + sleep(1); + self::assertEquals($volume + 1, $this->client->api('zone')->getStatus('main')['volume']); + $this->client->api('zone')->setVolume('main', 'down', '1'); + sleep(1); + self::assertEquals($volume, $this->client->api('zone')->getStatus('main')['volume']); + } + + public function testSetVolume() + { + $volume = $this->client->api('zone')->getStatus('main')['volume']; + $this->client->api('zone')->setVolume('main', $volume + 1); + sleep(1); + self::assertEquals($volume + 1, $this->client->api('zone')->getStatus('main')['volume']); + $this->client->api('zone')->setVolume('main', $volume); + sleep(1); + self::assertEquals($volume, $this->client->api('zone')->getStatus('main')['volume']); + } + + public function setMute() + { + $mute = $this->client->api('zone')->getStatus('main')['mute']; + $this->client->api('zone')->setMute('main', !$mute); + sleep(1); + self::assertEquals(!$mute, $this->client->api('zone')->getStatus('main')['mute']); + $this->client->api('zone')->setMute('main', $mute); + sleep(1); + self::assertEquals($mute, $this->client->api('zone')->getStatus('main')['volume']); + } + + public function testPrepareInputChange() + { + $input = $this->client->api('zone')->getStatus('main')['input']; + $this->client->api('zone')->prepareInputChange('main', $input); + } + + public function testSetInput() + { + $input = $this->client->api('zone')->getStatus('main')['input']; + $this->client->api('zone')->setInput('main', $input); + } +} diff --git a/test/MusicCast/Tests/ClientTest.php b/tests/ClientTest.php old mode 100644 new mode 100755 similarity index 60% rename from test/MusicCast/Tests/ClientTest.php rename to tests/ClientTest.php index 1390cd7..22abe1c --- a/test/MusicCast/Tests/ClientTest.php +++ b/tests/ClientTest.php @@ -1,35 +1,30 @@ options = Yaml::parse(file_get_contents(__DIR__ . '/../../env.yml')); - } - - public function test() - { - $client = new Client($this->options); -// var_dump($client->api('system')->functionStatus());die; -// var_dump($client->api('system')->sendIrCode()); - } - /** * @test */ public function shouldNotHaveToPassHttpClientToConstructor() { $client = new Client($this->options); - self::assertInstanceOf(\Http\Client\HttpClient::class, $client->getHttpClient()); } + + protected function setUp() + { + parent::setUp(); + $this->options = Yaml::parse(file_get_contents(__DIR__ . '/env.yml')); + } } diff --git a/tests/ControllerLiveTest.php b/tests/ControllerLiveTest.php new file mode 100755 index 0000000..720b166 --- /dev/null +++ b/tests/ControllerLiveTest.php @@ -0,0 +1,234 @@ +controller = $this->network->getControllerByIp($this->options['host']); + $this->controller->powerOn(); + $this->controller->getPlaylistById(1)->play(); + time_nanosleep(0, 500 * 1000000);//500ms + } + + public function testConstructor1() + { + foreach ($this->network->getSpeakers() as $speaker) { + if ($speaker->isCoordinator()) { + $controller = new Controller($speaker, $this->network, 0); + $this->assertSame($speaker->getIp(), $controller->getIp()); + return; + } + } + + throw new \Exception("No speakers found that are the coordinator of their group"); + } + + + public function testConstructor2() + { + $this->expectException("InvalidArgumentException"); + + foreach ($this->network->getSpeakers() as $speaker) { + if (!$speaker->isCoordinator()) { + new Controller($speaker, $this->network, 0); + return; + } + } + + $this->markTestSkipped("No speakers found that are not the coordinator of their group"); + } + + + public function testIsCoordinator() + { + $this->assertTrue($this->controller->isCoordinator()); + } + + + public function testGetStateName() + { + $states = ["play", "stop", "pause", "play_pause", "previous", "next", + "fast_reverse_start", "fast_reverse_end", "fast_forward_start", + "fast_forward_end"]; + foreach ($this->network->getControllers() as $controller) { + $this->assertContains($controller->getStateName(), $states); + } + } + + + public function testGetState() + { + $states = [Controller::STATE_STOPPED, Controller::STATE_PLAYING, Controller::STATE_PAUSED, Controller::STATE_TRANSITIONING]; + foreach ($this->network->getControllers() as $controller) { + $this->assertContains($controller->getState(), $states); + } + } + + + public function testGetStateDetails() + { + $track_keys = ["input", "title", "artist", "album", "albumArt"]; + $state_keys = ["duration", "position"]; + $state = $this->controller->getStateDetails(); + foreach ($state_keys as $key) { + $this->assertObjectHasAttribute($key, $state); + } + foreach ($track_keys as $key) { + $this->assertObjectHasAttribute($key, $state->track); + } + } + + + public function testNext() + { + $controller = $this->controller; + $number = $controller->getQueue()->getPlayingIndex(); + $controller->next(); + $this->assertSame($controller->getQueue()->getPlayingIndex(), $number + 1); + } + + + public function testPrevious() + { + $controller = $this->controller; + $number = $controller->getQueue()->getPlayingIndex(); + $controller->previous(); + $this->assertSame($controller->getQueue()->getPlayingIndex(), $number); + } + + + public function testGetSpeakers() + { + $speakers = $this->controller->getSpeakers(); + $this->assertContainsOnlyInstancesOf(Speaker::class, $speakers); + } + + + public function testSetVolume() + { + $controller = $this->controller; + $volume = $controller->getVolume(); + $controller->setVolume($volume - 3); + sleep(1); + foreach ($controller->getSpeakers() as $speaker) { + $this->assertSame($volume - 3, $speaker->getVolume()); + } + } + + + public function testAdjustVolume1() + { + $controller = $this->controller; + $volume = $controller->getVolume(); + $controller->setVolume($volume - 3); + sleep(1); + $controller->adjustVolume(3); + sleep(1); + foreach ($controller->getSpeakers() as $speaker) { + $this->assertSame($volume, $speaker->getVolume()); + } + } + + + public function testAdjustVolume2() + { + $controller = $this->controller; + $volume = $controller->getVolume(); + $controller->setVolume($volume + 3); + sleep(1); + $controller->adjustVolume(3 * -1); + sleep(1); + foreach ($controller->getSpeakers() as $speaker) { + $this->assertSame($volume, $speaker->getVolume()); + } + } + + public function testGetPlaylists() + { + $playlists = $this->controller->getPlaylists(); + self::assertNotNull($playlists); + } + + public function testGetPlaylistBy() + { + $playlists = $this->controller->getPlaylists(); + $seek = reset($playlists); + $playlist = $this->controller->getPlaylistById($seek->getId()); + self::assertEquals($seek, $playlist); + $playlist = $this->controller->getPlaylistByName($seek->getName()); + self::assertEquals($seek, $playlist); + } + + public function testGetFavorites() + { + $playlists = $this->controller->getFavorites(); + self::assertNotNull($playlists); + } + + public function testGetFavoriteBy() + { + $favorites = $this->controller->getFavorites(); + $seek = reset($favorites); + $favorite = $this->controller->getFavoriteById($seek->getId()); + self::assertEquals($seek, $favorite); + $favorite = $this->controller->getFavoriteByName($seek->getName()); + self::assertEquals($seek, $favorite); + } + + public function testAddSpeaker() + { + foreach ($this->network->getSpeakers() as $speaker) { + if (!$speaker->isCoordinator() && $speaker->getGroup() == Speaker::NO_GROUP) { + $this->controller->addSpeaker($speaker); + $this->assertSame($speaker->getGroup(), $this->controller->getGroup()); + return; + } + } + } + + public function testRemoveSpeaker() + { + foreach ($this->controller->getSpeakers() as $speaker) { + $this->controller->removeSpeaker($speaker); + $this->assertNotSame($speaker->getGroup(), $this->controller->getGroup()); + return; + } + } + + public function testPowerOn() + { + $this->controller->powerOn(); + } + + + + public function testMute() + { + $this->controller->mute(); + } + + + + public function testUnMute() + { + $this->controller->unmute(); + } +} diff --git a/tests/ControllerTest.php b/tests/ControllerTest.php new file mode 100755 index 0000000..b8aee2f --- /dev/null +++ b/tests/ControllerTest.php @@ -0,0 +1,129 @@ +controller = $this->getController(); + } + + public function getState() + { + self::assertEquals(Controller::STATE_PLAYING, $this->controller->getState()); + } + + public function testGetStateDetails() + { + $state = $this->controller->getStateDetails(); + self::assertEquals( + new Track( + "server", + "Voulez-Vous", + "http://localhost:80/YamahaRemoteControl/AlbumART/AlbumART1115.jpg", + "ABBA", + "Gold: Greatest Hits" + ), + $state->track + ); + self::assertEquals("0", $state->duration); + self::assertEquals("190", $state->position); + } + + + public function testGetPlaylists() + { + $playlists = $this->controller->getPlaylists(); + $keys = ["WakeUp", + "Rock", + "Lounge", + "John", + "Jane"]; + foreach ($keys as $key) { + self::assertArrayHasKey($key, $playlists); + } + self::assertTrue(sizeof($playlists) == sizeof($keys)); + } + + public function getPlaylistByName() + { + $playlist = $this->controller->getPlaylistByName("WakeUp"); + self::assertEquals("WakeUp", $playlist->getName()); + } + + public function getPlaylistByNameIgnoreCase() + { + $playlist = $this->controller->getPlaylistByName("wAkEuP"); + self::assertEquals("WakeUp", $playlist->getName()); + } + + public function testGetPlaylistById() + { + $playlist = $this->controller->getPlaylistById(1); + self::assertEquals("WakeUp", $playlist->getName()); + } + + public function testGetFavorites() + { + $favorites = $this->controller->getFavorites(); + $keys = ["OÜI FM Classic Rock", + "4U Funky Classics"]; + foreach ($keys as $key) { + self::assertArrayHasKey($key, $favorites); + } + self::assertTrue(sizeof($favorites) == sizeof($keys)); + } + + public function getFavoriteByName() + { + $favorite = $this->controller->getFavoriteByName("WakeUp"); + self::assertEquals("OÜI FM Classic Rock", $favorite->getName()); + } + + public function getFavoriteByNameIgnoreCase() + { + $favorite = $this->controller->getFavoriteByName("wAkEuP"); + self::assertEquals("OÜI FM Classic Rock", $favorite->getName()); + } + + public function testGetFavoriteById() + { + $favorite = $this->controller->getFavoriteById(1); + self::assertEquals("OÜI FM Classic Rock", $favorite->getName()); + } + + public function testHasFavorite() + { + self::assertTrue($this->controller->hasFavorite("OÜI FM Classic Rock")); + self::assertTrue($this->controller->hasFavorite("OÜI fm clASSic rOCk")); + self::assertFalse($this->controller->hasFavorite("Non existing favorite")); + } + + public function testGetQueue() + { + $queue = $this->controller->getQueue(); + self::assertEquals(0, $queue->getPlayingIndex()); + self::assertEquals(4, $queue->count()); + self::assertEquals(new Track( + "server", + "Fernando", + "http:\/\/192.168.86.4:50002\/transcoder\/jpegtnscaler.cgi\/ebdart\/73993.jpg" + ), $queue->getTracks()[0]); + } +} diff --git a/tests/DeviceTest.php b/tests/DeviceTest.php new file mode 100755 index 0000000..503c565 --- /dev/null +++ b/tests/DeviceTest.php @@ -0,0 +1,73 @@ +device = $this->getDevice(); + } + + public function testCall() + { + self::assertEquals('ABCDEEFAA063', $this->device->call('system', 'getDeviceInfo')['device_id']); + } + + public function testGetDeviceInfo() + { + $infokeys = ["model_name", "destination", "device_id", "system_id", "system_version", "api_version", + "netmodule_version", "netmodule_checksum", "operation_mode", + "update_error_code"]; + $info = $this->device->getDeviceInfo(); + foreach ($infokeys as $infokey) { + self::assertArrayHasKey($infokey, $info); + } + } + + public function testGetLocationInfo() + { + $infokeys = ["id", "name", "zone_list"]; + $info = $this->device->getLocationInfo(); + foreach ($infokeys as $infokey) { + self::assertArrayHasKey($infokey, $info); + } + } + + public function testGetMusicCastTreeInfo() + { + $infokeys = ["mode", "own_mac_idx", "mac_address_list", "ap_list", "hop_num"]; + $info = $this->device->getMusicCastTreeInfo(); + foreach ($infokeys as $infokey) { + self::assertArrayHasKey($infokey, $info); + } + } + + public function testGetNetworkStatus() + { + $infokeys = ["network_name", "connection", "ip_address", "default_gateway", "wireless_lan"]; + $info = $this->device->getNetworkStatus(); + foreach ($infokeys as $infokey) { + self::assertArrayHasKey($infokey, $info); + } + } + + public function testGetUuid() + { + self::assertEquals("ABCDEEFAA063", $this->device->getUuid()); + } +} diff --git a/tests/LiveTest.php b/tests/LiveTest.php new file mode 100755 index 0000000..b4db291 --- /dev/null +++ b/tests/LiveTest.php @@ -0,0 +1,46 @@ +network = new Network(); + + if (empty($_ENV["MUSICCAST_LIVE_TESTS"])) { + $this->markTestSkipped("Ignoring live tests + (these can be run setting the MUSICCAST_LIVE_TESTS environment variable)"); + return; + } + + try { + $this->network->getSpeakers(); + } catch (\Exception $e) { + $this->markTestSkipped("No speakers found on the current network"); + } + $this->options = Yaml::parse(file_get_contents(__DIR__ . '/env.yml')); + $this->client = new Client($this->options); + } +} diff --git a/tests/MockTest.php b/tests/MockTest.php new file mode 100755 index 0000000..604a76a --- /dev/null +++ b/tests/MockTest.php @@ -0,0 +1,128 @@ +network = Mockery::mock(Network::class); + $this->network->shouldReceive("getSpeakers")->andReturn([]); + $this->client = MockTest::mockCLient(); + } + + private static function mockCLient() + { + $client = Mockery::mock("MusicCast\Client"); + + $systemApi = Mockery::mock("MusicCast\Api\System"); + MockTest::mockMethod($systemApi, 'system', "getDeviceInfo"); + MockTest::mockMethod($systemApi, 'system', "getDisklavierSettings"); + MockTest::mockMethod($systemApi, 'system', "getFeatures"); + MockTest::mockMethod($systemApi, 'system', "getFuncStatus"); + MockTest::mockMethod($systemApi, 'system', "getLocationInfo"); + MockTest::mockMethod($systemApi, 'system', "getMusicCastTreeInfo"); + MockTest::mockMethod($systemApi, 'system', "getNameText"); + MockTest::mockMethod($systemApi, 'system', "getNetworkStatus"); + MockTest::mockMethod($systemApi, 'system', "getTag"); + MockTest::mockMethod($systemApi, 'system', "isNewFirmwareAvailable"); + MockTest::mockMethod($systemApi, 'system', "sendIrCode"); + $client->shouldReceive("api")->with('system')->andReturn($systemApi); + + $distApi = Mockery::mock("MusicCast\Api\Distribution"); + MockTest::mockMethod($distApi, 'dist', "getDistributionInfo"); + MockTest::mockMethod($distApi, 'dist', "setClientInfo"); + MockTest::mockMethod($distApi, 'dist', "setGroupName"); + MockTest::mockMethod($distApi, 'dist', "setServerInfo"); + MockTest::mockMethod($distApi, 'dist', "startDistribution"); + $client->shouldReceive("api")->with('dist')->andReturn($distApi); + + $zoneApi = Mockery::mock("MusicCast\Api\Zone"); + MockTest::mockMethod($zoneApi, 'zone', "getSignalInfo"); + MockTest::mockMethod($zoneApi, 'zone', "getSoundProgramList"); + MockTest::mockMethod($zoneApi, 'zone', "getStatus"); + MockTest::mockMethod($zoneApi, 'zone', "prepareInputChange"); + MockTest::mockMethod($zoneApi, 'zone', "setInput"); + MockTest::mockMethod($zoneApi, 'zone', "setMute"); + MockTest::mockMethod($zoneApi, 'zone', "setPower"); + MockTest::mockMethod($zoneApi, 'zone', "setSleep"); + MockTest::mockMethod($zoneApi, 'zone', "setVolume"); + $client->shouldReceive("api")->with('zone')->andReturn($zoneApi); + + $netusbApi = Mockery::mock("MusicCast\Api\NetworkUSB"); + MockTest::mockMethod($netusbApi, 'netusb', "getAccountStatus"); + MockTest::mockMethod($netusbApi, 'netusb', "getListInfo"); + MockTest::mockMethod($netusbApi, 'netusb', "getMcPlaylist"); + MockTest::mockMethod($netusbApi, 'netusb', "getMcPlaylistName"); + MockTest::mockMethod($netusbApi, 'netusb', "getPlayInfo"); + MockTest::mockMethod($netusbApi, 'netusb', "getPlayQueue"); + MockTest::mockMethod($netusbApi, 'netusb', "getPresetInfo"); + MockTest::mockMethod($netusbApi, 'netusb', "getRecentInfo"); + MockTest::mockMethod($netusbApi, 'netusb', "manageMcPlaylist"); + MockTest::mockMethod($netusbApi, 'netusb', "recallPreset"); + MockTest::mockMethod($netusbApi, 'netusb', "setPlayback"); + MockTest::mockMethod($netusbApi, 'netusb', "storePreset"); + MockTest::mockMethod($netusbApi, 'netusb', "toggleRepeat"); + MockTest::mockMethod($netusbApi, 'netusb', "toggleShuffle"); + $client->shouldReceive("api")->with('netusb')->andReturn($netusbApi); + return $client; + } + + private static function mockMethod($api, $apiName, $method) + { + $api->shouldReceive($method)->andReturn(json_decode( + file_get_contents(__DIR__ . '/assets/' . $apiName . '/' . $method . '.json'), + true + )); + } + + protected function getController() + { + $speaker = $this->getSpeaker(); + return new Controller($speaker, $this->network, 0); + } + + protected function getSpeaker() + { + $speaker = new Speaker($this->getDevice()); + return $speaker; + } + + protected function getDevice() + { + $device = new Device("localhost", 80, new DoctrineCachePool(new VoidCache()), new NullLogger()); + $reflection = new ReflectionClass($device); + $reflection_property = $reflection->getProperty('client'); + $reflection_property->setAccessible(true); + $reflection_property->setValue($device, $this->client); + return $device; + } +} diff --git a/tests/NetworkLiveTest.php b/tests/NetworkLiveTest.php new file mode 100755 index 0000000..148710f --- /dev/null +++ b/tests/NetworkLiveTest.php @@ -0,0 +1,37 @@ +network->getSpeakers(); + self::assertNotNull($speakers); + } + + public function testGetControllers() + { + $controllers = $this->network->getControllers(); + self::assertNotNull($controllers); + } + + public function testGetController() + { + $controller = $this->network->getController(); + self::assertNotNull($controller); + } + + public function testGetControllerByIp() + { + $ip = $this->network->getController()->getIp(); + $controller = $this->network->getControllerByIp($ip); + self::assertEquals($ip, $controller->getIp()); + } +} diff --git a/tests/NetworkTest.php b/tests/NetworkTest.php new file mode 100755 index 0000000..577036d --- /dev/null +++ b/tests/NetworkTest.php @@ -0,0 +1,69 @@ +network = new Network; + } + + public function testDefaultValues() + { + $this->assertSame("devices_NULL__239.255.255.250", $this->getCacheKey()); + } + + protected function getCacheKey() + { + $class = new \ReflectionClass($this->network); + $method = $class->getMethod("getCacheKey"); + $method->setAccessible(true); + return $method->invoke($this->network); + } + + public function testSetMulticastAddress() + { + $this->network->setMulticastAddress("127.0.0.1"); + $this->assertSame("devices_NULL__127.0.0.1", $this->getCacheKey()); + } + + public function testGetNetworkInterface() + { + $this->assertNull($this->network->getNetworkInterface()); + } + + public function testSetNetworkInterfaceString() + { + $this->network->setNetworkInterface("eth0"); + $this->assertSame("eth0", $this->network->getNetworkInterface()); + $this->assertSame("devices_string_eth0_239.255.255.250", $this->getCacheKey()); + } + + public function testSetNetworkInterfaceInteger() + { + $this->network->setNetworkInterface(0); + $this->assertSame(0, $this->network->getNetworkInterface()); + $this->assertSame("devices_integer_0_239.255.255.250", $this->getCacheKey()); + } + + public function testSetNetworkInterfaceEmptyString() + { + $this->network->setNetworkInterface(""); + $this->assertSame("", $this->network->getNetworkInterface()); + $this->assertSame("devices_string__239.255.255.250", $this->getCacheKey()); + } +} diff --git a/tests/SpeakerTest.php b/tests/SpeakerTest.php new file mode 100755 index 0000000..72c7a45 --- /dev/null +++ b/tests/SpeakerTest.php @@ -0,0 +1,81 @@ +speaker = $this->getSpeaker(); + } + + public function tearDown() + { + Mockery::close(); + } + + public function testGetModel() + { + self::assertEquals("RX-V481D", $this->speaker->getModel()); + } + + public function testGetName() + { + self::assertEquals("BedRoom", $this->speaker->getName()); + } + + public function testGetGroup() + { + self::assertEquals(Speaker::NO_GROUP, $this->speaker->getGroup()); + } + + public function testIsCoordinator() + { + self::assertTrue($this->speaker->isCoordinator()); + } + + public function testGetUuid() + { + self::assertEquals("ABCDEEFAA063", $this->speaker->getUuid()); + } + + public function testGetVolume() + { + self::assertEquals("77", $this->speaker->getVolume()); + } + + public function isMuted() + { + self::assertFalse($this->speaker->isMuted()); + } + + public function testInput() + { + self::assertEquals("tuner", $this->speaker->getInput()); + } + + public function testIsPowerOn() + { + self::assertFalse($this->speaker->isPowerOn()); + } + + public function testPowerOn() + { + self::assertNotNull($this->speaker->powerOn()); + } +} diff --git a/tests/assets/dist/getDistributionInfo.json b/tests/assets/dist/getDistributionInfo.json new file mode 100755 index 0000000..a5011c5 --- /dev/null +++ b/tests/assets/dist/getDistributionInfo.json @@ -0,0 +1,8 @@ +{ + "response_code": 0, + "group_id": "00000000000000000000000000000000", + "group_name": "(Linked) BedRoom", + "role": "none", + "server_zone": "main", + "client_list": [] +} \ No newline at end of file diff --git a/tests/assets/dist/setClientInfo.json b/tests/assets/dist/setClientInfo.json new file mode 100755 index 0000000..bf326b8 --- /dev/null +++ b/tests/assets/dist/setClientInfo.json @@ -0,0 +1,3 @@ +{ + "response_code": 0 +} \ No newline at end of file diff --git a/tests/assets/dist/setGroupName.json b/tests/assets/dist/setGroupName.json new file mode 100755 index 0000000..bf326b8 --- /dev/null +++ b/tests/assets/dist/setGroupName.json @@ -0,0 +1,3 @@ +{ + "response_code": 0 +} \ No newline at end of file diff --git a/tests/assets/dist/setServerInfo.json b/tests/assets/dist/setServerInfo.json new file mode 100755 index 0000000..bf326b8 --- /dev/null +++ b/tests/assets/dist/setServerInfo.json @@ -0,0 +1,3 @@ +{ + "response_code": 0 +} \ No newline at end of file diff --git a/tests/assets/dist/startDistribution.json b/tests/assets/dist/startDistribution.json new file mode 100755 index 0000000..bf326b8 --- /dev/null +++ b/tests/assets/dist/startDistribution.json @@ -0,0 +1,3 @@ +{ + "response_code": 0 +} \ No newline at end of file diff --git a/tests/assets/netusb/getAccountStatus.json b/tests/assets/netusb/getAccountStatus.json new file mode 100755 index 0000000..ec80ace --- /dev/null +++ b/tests/assets/netusb/getAccountStatus.json @@ -0,0 +1,45 @@ +{ + "response_code": 0, + "service_list": [ + { + "id": "napster", + "registered": false, + "login_status": "logged_out", + "username": "", + "type": "formal", + "trial_time_left": 0 + }, + { + "id": "juke", + "registered": false, + "login_status": "logged_out", + "username": "", + "type": "formal", + "trial_time_left": 0 + }, + { + "id": "qobuz", + "registered": false, + "login_status": "logged_out", + "username": "", + "type": "formal", + "trial_time_left": 0 + }, + { + "id": "tidal", + "registered": false, + "login_status": "logged_out", + "username": "", + "type": "formal", + "trial_time_left": 0 + }, + { + "id": "deezer", + "registered": true, + "login_status": "logged_in", + "username": "", + "type": "formal", + "trial_time_left": 0 + } + ] +} \ No newline at end of file diff --git a/tests/assets/netusb/getListInfo.json b/tests/assets/netusb/getListInfo.json new file mode 100755 index 0000000..625a7f2 --- /dev/null +++ b/tests/assets/netusb/getListInfo.json @@ -0,0 +1,23 @@ +{ + "response_code": 0, + "input": "server", + "menu_layer": 0, + "max_line": 2, + "index": 0, + "playing_index": -1, + "menu_name": "SERVER", + "list_info": [ + { + "text": "DiskStation", + "subtexts": [], + "thumbnail": "", + "attribute": 2 + }, + { + "text": "Kodi (Kodi)", + "subtexts": [], + "thumbnail": "", + "attribute": 2 + } + ] +} \ No newline at end of file diff --git a/tests/assets/netusb/getMcPlaylist.json b/tests/assets/netusb/getMcPlaylist.json new file mode 100755 index 0000000..44725e4 --- /dev/null +++ b/tests/assets/netusb/getMcPlaylist.json @@ -0,0 +1,32 @@ +{ + "response_code": 0, + "max_line": 4, + "bank": 1, + "index": 0, + "track_info": [ + { + "input": "server", + "text": "Fernando", + "thumbnail": "http:\/\/192.168.86.4:50002\/transcoder\/jpegtnscaler.cgi\/ebdart\/73993.jpg", + "attribute": 63 + }, + { + "input": "server", + "text": "Jailbreak", + "thumbnail": "http:\/\/192.168.86.4:50002\/transcoder\/jpegtnscaler.cgi\/ebdart\/81777.jpg", + "attribute": 63 + }, + { + "input": "server", + "text": "Jailbreak", + "thumbnail": "http:\/\/192.168.86.4:50002\/transcoder\/jpegtnscaler.cgi\/ebdart\/81777.jpg", + "attribute": 63 + }, + { + "input": "server", + "text": "Stairway To Heaven", + "thumbnail": "http:\/\/192.168.86.4:50002\/transcoder\/jpegtnscaler.cgi\/ebdart\/76674.jpg", + "attribute": 63 + } + ] +} \ No newline at end of file diff --git a/tests/assets/netusb/getMcPlaylistName.json b/tests/assets/netusb/getMcPlaylistName.json new file mode 100755 index 0000000..63c2cf3 --- /dev/null +++ b/tests/assets/netusb/getMcPlaylistName.json @@ -0,0 +1,10 @@ +{ + "response_code": 0, + "name_list": [ + "WakeUp", + "Rock", + "Lounge", + "John", + "Jane" + ] +} \ No newline at end of file diff --git a/tests/assets/netusb/getPlayInfo.json b/tests/assets/netusb/getPlayInfo.json new file mode 100755 index 0000000..b3fd14d --- /dev/null +++ b/tests/assets/netusb/getPlayInfo.json @@ -0,0 +1,27 @@ +{ + "response_code": 0, + "input": "server", + "play_queue_type": "user", + "playback": "play", + "repeat": "off", + "shuffle": "off", + "play_time": 190, + "total_time": 0, + "artist": "ABBA", + "album": "Gold: Greatest Hits", + "track": "Voulez-Vous", + "albumart_url": "/YamahaRemoteControl/AlbumART/AlbumART1115.jpg", + "albumart_id": 1115, + "usb_devicetype": "unknown", + "auto_stopped": false, + "attribute": 83886591, + "repeat_available": [ + "off", + "one", + "all" + ], + "shuffle_available": [ + "off", + "on" + ] +} \ No newline at end of file diff --git a/tests/assets/netusb/getPlayQueue.json b/tests/assets/netusb/getPlayQueue.json new file mode 100755 index 0000000..14f1fcc --- /dev/null +++ b/tests/assets/netusb/getPlayQueue.json @@ -0,0 +1,33 @@ +{ + "response_code": 0, + "type": "user", + "max_line": 4, + "playing_index": 0, + "index": 0, + "track_info": [ + { + "input": "server", + "text": "Fernando", + "thumbnail": "http:\/\/192.168.86.4:50002\/transcoder\/jpegtnscaler.cgi\/ebdart\/73993.jpg", + "attribute": 49 + }, + { + "input": "server", + "text": "Jailbreak", + "thumbnail": "http:\/\/192.168.86.4:50002\/transcoder\/jpegtnscaler.cgi\/ebdart\/81777.jpg", + "attribute": 49 + }, + { + "input": "server", + "text": "Jailbreak", + "thumbnail": "http:\/\/192.168.86.4:50002\/transcoder\/jpegtnscaler.cgi\/ebdart\/81777.jpg", + "attribute": 49 + }, + { + "input": "server", + "text": "Stairway To Heaven", + "thumbnail": "http:\/\/192.168.86.4:50002\/transcoder\/jpegtnscaler.cgi\/ebdart\/76674.jpg", + "attribute": 49 + } + ] +} \ No newline at end of file diff --git a/tests/assets/netusb/getPresetInfo.json b/tests/assets/netusb/getPresetInfo.json new file mode 100755 index 0000000..d230a51 --- /dev/null +++ b/tests/assets/netusb/getPresetInfo.json @@ -0,0 +1,171 @@ +{ + "response_code": 0, + "preset_info": [ + { + "input": "net_radio", + "text": "OÜI FM Classic Rock", + "attribute": 0 + }, + { + "input": "net_radio", + "text": "4U Funky Classics", + "attribute": 0 + }, + { + "input": "unknown", + "text": "" + }, + { + "input": "unknown", + "text": "" + }, + { + "input": "unknown", + "text": "" + }, + { + "input": "unknown", + "text": "" + }, + { + "input": "unknown", + "text": "" + }, + { + "input": "unknown", + "text": "" + }, + { + "input": "unknown", + "text": "" + }, + { + "input": "unknown", + "text": "" + }, + { + "input": "unknown", + "text": "" + }, + { + "input": "unknown", + "text": "" + }, + { + "input": "unknown", + "text": "" + }, + { + "input": "unknown", + "text": "" + }, + { + "input": "unknown", + "text": "" + }, + { + "input": "unknown", + "text": "" + }, + { + "input": "unknown", + "text": "" + }, + { + "input": "unknown", + "text": "" + }, + { + "input": "unknown", + "text": "" + }, + { + "input": "unknown", + "text": "" + }, + { + "input": "unknown", + "text": "" + }, + { + "input": "unknown", + "text": "" + }, + { + "input": "unknown", + "text": "" + }, + { + "input": "unknown", + "text": "" + }, + { + "input": "unknown", + "text": "" + }, + { + "input": "unknown", + "text": "" + }, + { + "input": "unknown", + "text": "" + }, + { + "input": "unknown", + "text": "" + }, + { + "input": "unknown", + "text": "" + }, + { + "input": "unknown", + "text": "" + }, + { + "input": "unknown", + "text": "" + }, + { + "input": "unknown", + "text": "" + }, + { + "input": "unknown", + "text": "" + }, + { + "input": "unknown", + "text": "" + }, + { + "input": "unknown", + "text": "" + }, + { + "input": "unknown", + "text": "" + }, + { + "input": "unknown", + "text": "" + }, + { + "input": "unknown", + "text": "" + }, + { + "input": "unknown", + "text": "" + }, + { + "input": "unknown", + "text": "" + } + ], + "func_list": [ + "clear", + "move" + ] +} \ No newline at end of file diff --git a/tests/assets/netusb/getRecentInfo.json b/tests/assets/netusb/getRecentInfo.json new file mode 100755 index 0000000..fda1bf5 --- /dev/null +++ b/tests/assets/netusb/getRecentInfo.json @@ -0,0 +1,285 @@ +{ + "response_code": 0, + "recent_info": [ + { + "input": "server", + "text": "Fernando", + "albumart_url": "http:\/\/192.168.86.4:50002\/transcoder\/jpegtnscaler.cgi\/ebdart\/73993.jpg", + "play_count": 22, + "attribute": 30 + }, + { + "input": "net_radio", + "text": "001A_A Funk", + "albumart_url": "http:\/\/item.radio456.com:80\/007452\/logo\/logo-75542.jpg", + "play_count": 1, + "attribute": 0 + }, + { + "input": "net_radio", + "text": "4U Classic Rock", + "albumart_url": "http:\/\/item.radio456.com:80\/007452\/logo\/logo-19467.jpg", + "play_count": 2, + "attribute": 0 + }, + { + "input": "server", + "text": "Stairway To Heaven", + "albumart_url": "http:\/\/192.168.86.4:50002\/transcoder\/jpegtnscaler.cgi\/ebdart\/76674.jpg", + "play_count": 6, + "attribute": 30 + }, + { + "input": "server", + "text": "Jailbreak", + "albumart_url": "http:\/\/192.168.86.4:50002\/transcoder\/jpegtnscaler.cgi\/ebdart\/81777.jpg", + "play_count": 21, + "attribute": 30 + }, + { + "input": "net_radio", + "text": "4U Funky Classics", + "albumart_url": "http:\/\/item.radio456.com:80\/007452\/logo\/logo-21350.jpg", + "play_count": 2, + "attribute": 0 + }, + { + "input": "server", + "text": "What's Next To The Moon", + "albumart_url": "http:\/\/192.168.86.4:50002\/transcoder\/jpegtnscaler.cgi\/ebdart\/81783.jpg", + "play_count": 1, + "attribute": 30 + }, + { + "input": "server", + "text": "Sin City", + "albumart_url": "http:\/\/192.168.86.4:50002\/transcoder\/jpegtnscaler.cgi\/ebdart\/81788.jpg", + "play_count": 1, + "attribute": 30 + }, + { + "input": "server", + "text": "Riff Raff", + "albumart_url": "http:\/\/192.168.86.4:50002\/transcoder\/jpegtnscaler.cgi\/ebdart\/81789.jpg", + "play_count": 1, + "attribute": 30 + }, + { + "input": "server", + "text": "Gimme A Bullet", + "albumart_url": "http:\/\/192.168.86.4:50002\/transcoder\/jpegtnscaler.cgi\/ebdart\/81785.jpg", + "play_count": 1, + "attribute": 30 + }, + { + "input": "server", + "text": "Down Payment Blues", + "albumart_url": "http:\/\/192.168.86.4:50002\/transcoder\/jpegtnscaler.cgi\/ebdart\/81790.jpg", + "play_count": 1, + "attribute": 30 + }, + { + "input": "server", + "text": "Rock N Roll Damnation", + "albumart_url": "http:\/\/192.168.86.4:50002\/transcoder\/jpegtnscaler.cgi\/ebdart\/81787.jpg", + "play_count": 1, + "attribute": 30 + }, + { + "input": "server", + "text": "Rock n Roll Singer", + "albumart_url": "http:\/\/192.168.86.4:50002\/transcoder\/jpegtnscaler.cgi\/ebdart\/81842.jpg", + "play_count": 1, + "attribute": 30 + }, + { + "input": "server", + "text": "It's A Long Way To The Top (If You Wanna Rock nRoll)", + "albumart_url": "http:\/\/192.168.86.4:50002\/transcoder\/jpegtnscaler.cgi\/ebdart\/81838.jpg", + "play_count": 1, + "attribute": 30 + }, + { + "input": "server", + "text": "Moby Dick", + "albumart_url": "http:\/\/192.168.86.4:50002\/transcoder\/jpegtnscaler.cgi\/ebdart\/76670.jpg", + "play_count": 1, + "attribute": 30 + }, + { + "input": "server", + "text": "Stairway To Heaven", + "albumart_url": "http:\/\/192.168.86.4:50002\/transcoder\/jpegtnscaler.cgi\/ebdart\/76633.jpg", + "play_count": 1, + "attribute": 30 + }, + { + "input": "net_radio", + "text": "Allzic Radio Blues", + "albumart_url": "http:\/\/item.radio456.com:80\/007452\/logo\/logo-74510.jpg", + "play_count": 1, + "attribute": 0 + }, + { + "input": "deezer", + "text": "Brothers In Arms", + "albumart_url": "https:\/\/e-cdns-images.dzcdn.net:443\/images\/cover\/c5478962996d285c9e0ec4860e061350\/500x500-000000-80-0-0.jpg", + "play_count": 1, + "attribute": 0 + }, + { + "input": "deezer", + "text": "Killing In The Name (Remastered)", + "albumart_url": "https:\/\/e-cdns-images.dzcdn.net:443\/images\/cover\/8d836096d689280eef281d1eb0eb4fc5\/500x500-000000-80-0-0.jpg", + "play_count": 1, + "attribute": 0 + }, + { + "input": "deezer", + "text": "Walk on the Wild Side (Remastered)", + "albumart_url": "https:\/\/e-cdns-images.dzcdn.net:443\/images\/cover\/7ba918c690c408d13a6a93a3a9d59867\/500x500-000000-80-0-0.jpg", + "play_count": 1, + "attribute": 0 + }, + { + "input": "deezer", + "text": "Imagine (2010 - Remaster)", + "albumart_url": "https:\/\/e-cdns-images.dzcdn.net:443\/images\/cover\/78c41058a6e7e1546beb7216ec6eddca\/500x500-000000-80-0-0.jpg", + "play_count": 1, + "attribute": 0 + }, + { + "input": "deezer", + "text": "You Really Got Me (Remastered)", + "albumart_url": "https:\/\/e-cdns-images.dzcdn.net:443\/images\/cover\/b1730707727bf955d951f329bbfcb7ae\/500x500-000000-80-0-0.jpg", + "play_count": 1, + "attribute": 0 + }, + { + "input": "deezer", + "text": "Sweet Child O' Mine", + "albumart_url": "https:\/\/e-cdns-images.dzcdn.net:443\/images\/cover\/9087b7676d5f37612ee954d4ffc6f082\/500x500-000000-80-0-0.jpg", + "play_count": 1, + "attribute": 0 + }, + { + "input": "deezer", + "text": "Killing In The Name (Remastered)", + "albumart_url": "https:\/\/e-cdns-images.dzcdn.net:443\/images\/cover\/8d836096d689280eef281d1eb0eb4fc5\/500x500-000000-80-0-0.jpg", + "play_count": 1, + "attribute": 0 + }, + { + "input": "deezer", + "text": "Black Hole Sun", + "albumart_url": "https:\/\/e-cdns-images.dzcdn.net:443\/images\/cover\/94248b664be0890be31cabf62a068293\/500x500-000000-80-0-0.jpg", + "play_count": 1, + "attribute": 0 + }, + { + "input": "deezer", + "text": "Everlong", + "albumart_url": "https:\/\/e-cdns-images.dzcdn.net:443\/images\/cover\/f737d214917c549e651acc4aca11f5b8\/500x500-000000-80-0-0.jpg", + "play_count": 1, + "attribute": 0 + }, + { + "input": "deezer", + "text": "Come As You Are", + "albumart_url": "https:\/\/e-cdns-images.dzcdn.net:443\/images\/cover\/d6feb0afde4aa63160de2d6e8d55160d\/500x500-000000-80-0-0.jpg", + "play_count": 1, + "attribute": 0 + }, + { + "input": "deezer", + "text": "Sunday Bloody Sunday", + "albumart_url": "https:\/\/e-cdns-images.dzcdn.net:443\/images\/cover\/76c4322b0ea4ab5ed1e3a9f7d3208e28\/500x500-000000-80-0-0.jpg", + "play_count": 1, + "attribute": 0 + }, + { + "input": "deezer", + "text": "Killing In The Name (Remastered)", + "albumart_url": "https:\/\/e-cdns-images.dzcdn.net:443\/images\/cover\/8d836096d689280eef281d1eb0eb4fc5\/500x500-000000-80-0-0.jpg", + "play_count": 2, + "attribute": 0 + }, + { + "input": "deezer", + "text": "Sunday Bloody Sunday", + "albumart_url": "https:\/\/e-cdns-images.dzcdn.net:443\/images\/cover\/76c4322b0ea4ab5ed1e3a9f7d3208e28\/500x500-000000-80-0-0.jpg", + "play_count": 2, + "attribute": 0 + }, + { + "input": "deezer", + "text": "Come As You Are", + "albumart_url": "https:\/\/e-cdns-images.dzcdn.net:443\/images\/cover\/d6feb0afde4aa63160de2d6e8d55160d\/500x500-000000-80-0-0.jpg", + "play_count": 3, + "attribute": 0 + }, + { + "input": "deezer", + "text": "Everlong", + "albumart_url": "https:\/\/e-cdns-images.dzcdn.net:443\/images\/cover\/f737d214917c549e651acc4aca11f5b8\/500x500-000000-80-0-0.jpg", + "play_count": 3, + "attribute": 0 + }, + { + "input": "deezer", + "text": "Black Hole Sun", + "albumart_url": "https:\/\/e-cdns-images.dzcdn.net:443\/images\/cover\/94248b664be0890be31cabf62a068293\/500x500-000000-80-0-0.jpg", + "play_count": 4, + "attribute": 0 + }, + { + "input": "deezer", + "text": "Killing In The Name (Remastered)", + "albumart_url": "https:\/\/e-cdns-images.dzcdn.net:443\/images\/cover\/8d836096d689280eef281d1eb0eb4fc5\/500x500-000000-80-0-0.jpg", + "play_count": 5, + "attribute": 0 + }, + { + "input": "deezer", + "text": "Sweet Child O' Mine", + "albumart_url": "https:\/\/e-cdns-images.dzcdn.net:443\/images\/cover\/9087b7676d5f37612ee954d4ffc6f082\/500x500-000000-80-0-0.jpg", + "play_count": 5, + "attribute": 0 + }, + { + "input": "deezer", + "text": "You Really Got Me (Remastered)", + "albumart_url": "https:\/\/e-cdns-images.dzcdn.net:443\/images\/cover\/b1730707727bf955d951f329bbfcb7ae\/500x500-000000-80-0-0.jpg", + "play_count": 6, + "attribute": 0 + }, + { + "input": "deezer", + "text": "Imagine (2010 - Remaster)", + "albumart_url": "https:\/\/e-cdns-images.dzcdn.net:443\/images\/cover\/78c41058a6e7e1546beb7216ec6eddca\/500x500-000000-80-0-0.jpg", + "play_count": 5, + "attribute": 0 + }, + { + "input": "deezer", + "text": "Walk on the Wild Side (Remastered)", + "albumart_url": "https:\/\/e-cdns-images.dzcdn.net:443\/images\/cover\/7ba918c690c408d13a6a93a3a9d59867\/500x500-000000-80-0-0.jpg", + "play_count": 2, + "attribute": 0 + }, + { + "input": "deezer", + "text": "Walk on the Wild Side (Remastered)", + "albumart_url": "https:\/\/e-cdns-images.dzcdn.net:443\/images\/cover\/7ba918c690c408d13a6a93a3a9d59867\/500x500-000000-80-0-0.jpg", + "play_count": 1, + "attribute": 0 + }, + { + "input": "net_radio", + "text": "Allzic Radio Jazz Lounge", + "albumart_url": "http:\/\/item.radio456.com:80\/007452\/logo\/logo-74488.jpg", + "play_count": 19, + "attribute": 0 + } + ] +} \ No newline at end of file diff --git a/tests/assets/netusb/manageMcPlaylist.json b/tests/assets/netusb/manageMcPlaylist.json new file mode 100755 index 0000000..bf326b8 --- /dev/null +++ b/tests/assets/netusb/manageMcPlaylist.json @@ -0,0 +1,3 @@ +{ + "response_code": 0 +} \ No newline at end of file diff --git a/tests/assets/netusb/recallPreset.json b/tests/assets/netusb/recallPreset.json new file mode 100755 index 0000000..bf326b8 --- /dev/null +++ b/tests/assets/netusb/recallPreset.json @@ -0,0 +1,3 @@ +{ + "response_code": 0 +} \ No newline at end of file diff --git a/tests/assets/netusb/setPlayback.json b/tests/assets/netusb/setPlayback.json new file mode 100755 index 0000000..bf326b8 --- /dev/null +++ b/tests/assets/netusb/setPlayback.json @@ -0,0 +1,3 @@ +{ + "response_code": 0 +} \ No newline at end of file diff --git a/tests/assets/netusb/storePreset.json b/tests/assets/netusb/storePreset.json new file mode 100755 index 0000000..bf326b8 --- /dev/null +++ b/tests/assets/netusb/storePreset.json @@ -0,0 +1,3 @@ +{ + "response_code": 0 +} \ No newline at end of file diff --git a/tests/assets/netusb/toggleRepeat.json b/tests/assets/netusb/toggleRepeat.json new file mode 100755 index 0000000..bf326b8 --- /dev/null +++ b/tests/assets/netusb/toggleRepeat.json @@ -0,0 +1,3 @@ +{ + "response_code": 0 +} \ No newline at end of file diff --git a/tests/assets/netusb/toggleShuffle.json b/tests/assets/netusb/toggleShuffle.json new file mode 100755 index 0000000..bf326b8 --- /dev/null +++ b/tests/assets/netusb/toggleShuffle.json @@ -0,0 +1,3 @@ +{ + "response_code": 0 +} \ No newline at end of file diff --git a/tests/assets/system/getDeviceInfo.json b/tests/assets/system/getDeviceInfo.json new file mode 100755 index 0000000..44ac9c1 --- /dev/null +++ b/tests/assets/system/getDeviceInfo.json @@ -0,0 +1,13 @@ +{ + "response_code": 0, + "model_name": "RX-V481D", + "destination": "BG", + "device_id": "ABCDEEFAA063", + "system_id": "ABCC5863", + "system_version": 1.22, + "api_version": 1.17, + "netmodule_version": "1145 ", + "netmodule_checksum": "86FB2237", + "operation_mode": "normal", + "update_error_code": "FFFFFFFF" +} \ No newline at end of file diff --git a/tests/assets/system/getDisklavierSettings.json b/tests/assets/system/getDisklavierSettings.json new file mode 100755 index 0000000..2f4d794 --- /dev/null +++ b/tests/assets/system/getDisklavierSettings.json @@ -0,0 +1,12 @@ +{ + "response_code": 0, + "enable": false, + "mode": "output_primary", + "input": "unknown", + "pair": { + "mac_address": "000000000000" + }, + "disklavier": { + "ip_address": "0.0.0.0" + } +} \ No newline at end of file diff --git a/tests/assets/system/getFeatures.json b/tests/assets/system/getFeatures.json new file mode 100755 index 0000000..e629385 --- /dev/null +++ b/tests/assets/system/getFeatures.json @@ -0,0 +1,347 @@ +{ + "response_code": 0, + "system": { + "func_list": [ + "wired_lan", + "wireless_lan", + "wireless_direct", + "network_standby", + "network_standby_auto", + "bluetooth_standby", + "bluetooth_tx_setting", + "zone_b_volume_sync", + "hdmi_out_1", + "airplay", + "disklavier_settings" + ], + "zone_num": 2, + "input_list": [ + { + "id": "spotify", + "distribution_enable": true, + "rename_enable": false, + "account_enable": false, + "play_info_type": "netusb" + }, + { + "id": "juke", + "distribution_enable": true, + "rename_enable": false, + "account_enable": true, + "play_info_type": "netusb" + }, + { + "id": "qobuz", + "distribution_enable": true, + "rename_enable": false, + "account_enable": true, + "play_info_type": "netusb" + }, + { + "id": "airplay", + "distribution_enable": false, + "rename_enable": false, + "account_enable": false, + "play_info_type": "netusb" + }, + { + "id": "mc_link", + "distribution_enable": false, + "rename_enable": true, + "account_enable": false, + "play_info_type": "netusb" + }, + { + "id": "server", + "distribution_enable": true, + "rename_enable": true, + "account_enable": false, + "play_info_type": "netusb" + }, + { + "id": "net_radio", + "distribution_enable": true, + "rename_enable": true, + "account_enable": false, + "play_info_type": "netusb" + }, + { + "id": "bluetooth", + "distribution_enable": true, + "rename_enable": false, + "account_enable": false, + "play_info_type": "netusb" + }, + { + "id": "usb", + "distribution_enable": true, + "rename_enable": true, + "account_enable": false, + "play_info_type": "netusb" + }, + { + "id": "tuner", + "distribution_enable": true, + "rename_enable": true, + "account_enable": false, + "play_info_type": "tuner" + }, + { + "id": "hdmi1", + "distribution_enable": true, + "rename_enable": true, + "account_enable": false, + "play_info_type": "none" + }, + { + "id": "hdmi2", + "distribution_enable": true, + "rename_enable": true, + "account_enable": false, + "play_info_type": "none" + }, + { + "id": "hdmi3", + "distribution_enable": true, + "rename_enable": true, + "account_enable": false, + "play_info_type": "none" + }, + { + "id": "hdmi4", + "distribution_enable": true, + "rename_enable": true, + "account_enable": false, + "play_info_type": "none" + }, + { + "id": "av1", + "distribution_enable": true, + "rename_enable": true, + "account_enable": false, + "play_info_type": "none" + }, + { + "id": "av2", + "distribution_enable": true, + "rename_enable": true, + "account_enable": false, + "play_info_type": "none" + }, + { + "id": "av3", + "distribution_enable": true, + "rename_enable": true, + "account_enable": false, + "play_info_type": "none" + }, + { + "id": "av4", + "distribution_enable": true, + "rename_enable": true, + "account_enable": false, + "play_info_type": "none" + }, + { + "id": "audio1", + "distribution_enable": true, + "rename_enable": true, + "account_enable": false, + "play_info_type": "none" + }, + { + "id": "audio2", + "distribution_enable": true, + "rename_enable": true, + "account_enable": false, + "play_info_type": "none" + }, + { + "id": "aux", + "distribution_enable": true, + "rename_enable": true, + "account_enable": false, + "play_info_type": "none" + } + ], + "ymap_list": [ + "vtuner" + ] + }, + "zone": [ + { + "id": "main", + "func_list": [ + "power", + "sleep", + "volume", + "mute", + "sound_program", + "direct", + "enhancer", + "tone_control", + "signal_info", + "prepare_input_change", + "link_control", + "link_audio_delay" + ], + "input_list": [ + "spotify", + "juke", + "qobuz", + "airplay", + "mc_link", + "server", + "net_radio", + "bluetooth", + "usb", + "tuner", + "hdmi1", + "hdmi2", + "hdmi3", + "hdmi4", + "av1", + "av2", + "av3", + "av4", + "audio1", + "audio2", + "aux" + ], + "sound_program_list": [ + "munich", + "vienna", + "chamber", + "cellar_club", + "roxy_theatre", + "bottom_line", + "sports", + "action_game", + "roleplaying_game", + "music_video", + "standard", + "spectacle", + "sci-fi", + "adventure", + "drama", + "mono_movie", + "2ch_stereo", + "5ch_stereo", + "surr_decoder", + "straight" + ], + "tone_control_mode_list": [ + "manual" + ], + "link_control_list": [ + "speed", + "standard", + "stability" + ], + "link_audio_delay_list": [ + "audio_sync", + "lip_sync" + ], + "range_step": [ + { + "id": "volume", + "min": 0, + "max": 161, + "step": 1 + }, + { + "id": "tone_control", + "min": -12, + "max": 12, + "step": 1 + } + ] + }, + { + "id": "zone2", + "zone_b": true, + "func_list": [ + "power", + "volume", + "mute", + "prepare_input_change" + ], + "input_list": [ + "spotify", + "juke", + "qobuz", + "airplay", + "mc_link", + "server", + "net_radio", + "bluetooth", + "usb", + "tuner", + "hdmi1", + "hdmi2", + "hdmi3", + "hdmi4", + "av1", + "av2", + "av3", + "av4", + "audio1", + "audio2", + "aux" + ], + "range_step": [ + { + "id": "volume", + "min": 0, + "max": 161, + "step": 1 + } + ] + } + ], + "tuner": { + "func_list": [ + "fm", + "rds", + "dab" + ], + "range_step": [ + { + "id": "fm", + "min": 87500, + "max": 108000, + "step": 50 + } + ], + "preset": { + "type": "separate", + "num": 40 + } + }, + "netusb": { + "func_list": [ + "recent_info", + "play_queue", + "mc_playlist" + ], + "preset": { + "num": 40 + }, + "recent_info": { + "num": 40 + }, + "play_queue": { + "size": 200 + }, + "mc_playlist": { + "size": 200, + "num": 5 + }, + "vtuner_fver": "A" + }, + "distribution": { + "server_zone_list": [ + "main" + ] + } +} \ No newline at end of file diff --git a/tests/assets/system/getFuncStatus.json b/tests/assets/system/getFuncStatus.json new file mode 100755 index 0000000..e9e2be7 --- /dev/null +++ b/tests/assets/system/getFuncStatus.json @@ -0,0 +1,5 @@ +{ + "response_code": 0, + "zone_b_volume_sync": true, + "hdmi_out_1": true +} \ No newline at end of file diff --git a/tests/assets/system/getLocationInfo.json b/tests/assets/system/getLocationInfo.json new file mode 100755 index 0000000..53e95b9 --- /dev/null +++ b/tests/assets/system/getLocationInfo.json @@ -0,0 +1,9 @@ +{ + "response_code": 0, + "id": "a9957ae56c9f48d4a547c49029ae04f9", + "name": "Home", + "zone_list": { + "main": true, + "zone2": false + } +} \ No newline at end of file diff --git a/tests/assets/system/getMusicCastTreeInfo.json b/tests/assets/system/getMusicCastTreeInfo.json new file mode 100755 index 0000000..4cce1b8 --- /dev/null +++ b/tests/assets/system/getMusicCastTreeInfo.json @@ -0,0 +1,50 @@ +{ + "response_code": 0, + "mode": "root_wireless", + "own_mac_idx": 0, + "mac_address_list": [ + { + "hop_num": 0, + "mac_address": "ABCDEFE12F67", + "ap_status": "none", + "child_num": 0, + "ip_address": "192.168.1.9" + }, + { + "hop_num": 0, + "mac_address": "ABCDEA3DFA1D", + "ap_status": "none", + "child_num": 0, + "ip_address": "192.168.1.14" + }, + { + "hop_num": 0, + "mac_address": "ABCDE62642B5", + "ap_status": "none", + "child_num": 0, + "ip_address": "192.168.1.13" + }, + { + "hop_num": 0, + "mac_address": "ABCDEA00A677", + "ap_status": "none", + "child_num": 0, + "ip_address": "192.168.1.10" + } + ], + "ap_list": [ + { + "ssid": "privateWIFI", + "key": "", + "type": "wpa2-psk(aes)", + "bssid": "703ACB751410" + }, + { + "ssid": "privateWIFI", + "key": "", + "type": "wpa2-psk(aes)", + "bssid": "703ACB751922" + } + ], + "hop_num": 0 +} \ No newline at end of file diff --git a/tests/assets/system/getNameText.json b/tests/assets/system/getNameText.json new file mode 100755 index 0000000..12bf340 --- /dev/null +++ b/tests/assets/system/getNameText.json @@ -0,0 +1,181 @@ +{ + "response_code": 0, + "zone_list": [ + { + "id": "main", + "text": "BedRomm" + }, + { + "id": "zone2", + "text": "Room2" + } + ], + "input_list": [ + { + "id": "tuner", + "text": "Tuner" + }, + { + "id": "hdmi1", + "text": "HDMI1" + }, + { + "id": "hdmi2", + "text": "HDMI2" + }, + { + "id": "hdmi3", + "text": "HDMI3" + }, + { + "id": "hdmi4", + "text": "HDMI4" + }, + { + "id": "av1", + "text": "AV1" + }, + { + "id": "av2", + "text": "AV2" + }, + { + "id": "av3", + "text": "AV3" + }, + { + "id": "av4", + "text": "AV4" + }, + { + "id": "aux", + "text": "AUX" + }, + { + "id": "audio1", + "text": "Audio1" + }, + { + "id": "audio2", + "text": "Audio2" + }, + { + "id": "usb", + "text": "USB" + }, + { + "id": "bluetooth", + "text": "Bluetooth" + }, + { + "id": "server", + "text": "Server" + }, + { + "id": "net_radio", + "text": "Net Radio" + }, + { + "id": "spotify", + "text": "Spotify" + }, + { + "id": "juke", + "text": "JUKE" + }, + { + "id": "airplay", + "text": "AirPlay" + }, + { + "id": "qobuz", + "text": "Qobuz" + }, + { + "id": "mc_link", + "text": "MC Link" + } + ], + "sound_program_list": [ + { + "id": "munich", + "text": "Hall in Munich" + }, + { + "id": "vienna", + "text": "Hall in Vienna" + }, + { + "id": "chamber", + "text": "Chamber" + }, + { + "id": "cellar_club", + "text": "Cellar Club" + }, + { + "id": "roxy_theatre", + "text": "The Roxy Theatre" + }, + { + "id": "bottom_line", + "text": "The Bottom Line" + }, + { + "id": "sports", + "text": "Sports" + }, + { + "id": "action_game", + "text": "Action Game" + }, + { + "id": "roleplaying_game", + "text": "Roleplaying Game" + }, + { + "id": "music_video", + "text": "Music Video" + }, + { + "id": "standard", + "text": "Standard" + }, + { + "id": "spectacle", + "text": "Spectacle" + }, + { + "id": "sci-fi", + "text": "Sci-Fi" + }, + { + "id": "adventure", + "text": "Adventure" + }, + { + "id": "drama", + "text": "Drama" + }, + { + "id": "mono_movie", + "text": "Mono Movie" + }, + { + "id": "2ch_stereo", + "text": "2ch Stereo" + }, + { + "id": "5ch_stereo", + "text": "5ch Stereo" + }, + { + "id": "surr_decoder", + "text": "Surround Decoder" + }, + { + "id": "straight", + "text": "Straight" + } + ] +} \ No newline at end of file diff --git a/tests/assets/system/getNetworkStatus.json b/tests/assets/system/getNetworkStatus.json new file mode 100755 index 0000000..37c3250 --- /dev/null +++ b/tests/assets/system/getNetworkStatus.json @@ -0,0 +1,37 @@ +{ + "response_code": 0, + "network_name": "BedRoom", + "connection": "wireless_lan", + "dhcp": true, + "ip_address": "192.168.1.9", + "subnet_mask": "255.255.255.0", + "default_gateway": "192.168.1.1", + "dns_server_1": "192.168.1.1", + "dns_server_2": "0.0.0.0", + "wireless_lan": { + "ssid": "WIFIssid", + "type": "wpa2-psk(aes)", + "key": "", + "ch": 6, + "strength": 100 + }, + "wireless_direct": { + "ssid": "RX-V481D FAA063", + "type": "none", + "key": "" + }, + "musiccast_network": { + "ready": true, + "device_type": "standard", + "child_num": 0, + "ch": 0, + "initial_join_running": false + }, + "mac_address": { + "wired_lan": "ABCDEEFAA063", + "wireless_lan": "ABCDEFE12F66", + "wireless_direct": "ABCDEFE12F67" + }, + "vtuner_id": "ABCDEEFAA063", + "airplay_pin": "" +} \ No newline at end of file diff --git a/tests/assets/system/getTag.json b/tests/assets/system/getTag.json new file mode 100755 index 0000000..a012e2f --- /dev/null +++ b/tests/assets/system/getTag.json @@ -0,0 +1,99 @@ +{ + "response_code": 0, + "zone_list": [ + { + "id": "main", + "tag": 0 + }, + { + "id": "zone2", + "tag": 0 + } + ], + "input_list": [ + { + "id": "tuner", + "tag": 0 + }, + { + "id": "hdmi1", + "tag": 0 + }, + { + "id": "hdmi2", + "tag": 0 + }, + { + "id": "hdmi3", + "tag": 0 + }, + { + "id": "hdmi4", + "tag": 0 + }, + { + "id": "av1", + "tag": 0 + }, + { + "id": "av2", + "tag": 0 + }, + { + "id": "av3", + "tag": 0 + }, + { + "id": "av4", + "tag": 0 + }, + { + "id": "aux", + "tag": 0 + }, + { + "id": "audio1", + "tag": 0 + }, + { + "id": "audio2", + "tag": 0 + }, + { + "id": "usb", + "tag": 0 + }, + { + "id": "bluetooth", + "tag": 0 + }, + { + "id": "server", + "tag": 0 + }, + { + "id": "net_radio", + "tag": 0 + }, + { + "id": "spotify", + "tag": 0 + }, + { + "id": "juke", + "tag": 0 + }, + { + "id": "airplay", + "tag": 0 + }, + { + "id": "qobuz", + "tag": 0 + }, + { + "id": "mc_link", + "tag": 0 + } + ] +} \ No newline at end of file diff --git a/tests/assets/system/isNewFirmwareAvailable.json b/tests/assets/system/isNewFirmwareAvailable.json new file mode 100755 index 0000000..97c5c7d --- /dev/null +++ b/tests/assets/system/isNewFirmwareAvailable.json @@ -0,0 +1,4 @@ +{ + "response_code": 0, + "available": true +} \ No newline at end of file diff --git a/tests/assets/system/sendIrCode.json b/tests/assets/system/sendIrCode.json new file mode 100755 index 0000000..bf326b8 --- /dev/null +++ b/tests/assets/system/sendIrCode.json @@ -0,0 +1,3 @@ +{ + "response_code": 0 +} \ No newline at end of file diff --git a/tests/assets/zone/getSignalInfo.json b/tests/assets/zone/getSignalInfo.json new file mode 100755 index 0000000..4327c9c --- /dev/null +++ b/tests/assets/zone/getSignalInfo.json @@ -0,0 +1,8 @@ +{ + "response_code": 0, + "audio": { + "error": 0, + "format": "MP3", + "fs": "22.05 kHz" + } +} \ No newline at end of file diff --git a/tests/assets/zone/getSoundProgramList.json b/tests/assets/zone/getSoundProgramList.json new file mode 100755 index 0000000..adeba71 --- /dev/null +++ b/tests/assets/zone/getSoundProgramList.json @@ -0,0 +1,25 @@ +{ + "response_code": 0, + "sound_program_list": [ + "munich", + "vienna", + "chamber", + "cellar_club", + "roxy_theatre", + "bottom_line", + "sports", + "action_game", + "roleplaying_game", + "music_video", + "standard", + "spectacle", + "sci-fi", + "adventure", + "drama", + "mono_movie", + "2ch_stereo", + "5ch_stereo", + "surr_decoder", + "straight" + ] +} \ No newline at end of file diff --git a/tests/assets/zone/getStatus.json b/tests/assets/zone/getStatus.json new file mode 100755 index 0000000..b208307 --- /dev/null +++ b/tests/assets/zone/getStatus.json @@ -0,0 +1,21 @@ +{ + "response_code": 0, + "power": "standby", + "sleep": 0, + "volume": 77, + "mute": false, + "max_volume": 161, + "input": "tuner", + "distribution_enable": true, + "sound_program": "5ch_stereo", + "direct": false, + "enhancer": true, + "tone_control": { + "mode": "manual", + "bass": 0, + "treble": 0 + }, + "link_control": "standard", + "link_audio_delay": "lip_sync", + "disable_flags": 0 +} \ No newline at end of file diff --git a/tests/assets/zone/prepareInputChange.json b/tests/assets/zone/prepareInputChange.json new file mode 100755 index 0000000..bf326b8 --- /dev/null +++ b/tests/assets/zone/prepareInputChange.json @@ -0,0 +1,3 @@ +{ + "response_code": 0 +} \ No newline at end of file diff --git a/tests/assets/zone/setInput.json b/tests/assets/zone/setInput.json new file mode 100755 index 0000000..bf326b8 --- /dev/null +++ b/tests/assets/zone/setInput.json @@ -0,0 +1,3 @@ +{ + "response_code": 0 +} \ No newline at end of file diff --git a/tests/assets/zone/setMute.json b/tests/assets/zone/setMute.json new file mode 100755 index 0000000..bf326b8 --- /dev/null +++ b/tests/assets/zone/setMute.json @@ -0,0 +1,3 @@ +{ + "response_code": 0 +} \ No newline at end of file diff --git a/tests/assets/zone/setPower.json b/tests/assets/zone/setPower.json new file mode 100755 index 0000000..bf326b8 --- /dev/null +++ b/tests/assets/zone/setPower.json @@ -0,0 +1,3 @@ +{ + "response_code": 0 +} \ No newline at end of file diff --git a/tests/assets/zone/setSleep.json b/tests/assets/zone/setSleep.json new file mode 100755 index 0000000..bf326b8 --- /dev/null +++ b/tests/assets/zone/setSleep.json @@ -0,0 +1,3 @@ +{ + "response_code": 0 +} \ No newline at end of file diff --git a/tests/assets/zone/setVolume.json b/tests/assets/zone/setVolume.json new file mode 100755 index 0000000..bf326b8 --- /dev/null +++ b/tests/assets/zone/setVolume.json @@ -0,0 +1,3 @@ +{ + "response_code": 0 +} \ No newline at end of file diff --git a/tests/bootstrap.php b/tests/bootstrap.php new file mode 100755 index 0000000..8a40f76 --- /dev/null +++ b/tests/bootstrap.php @@ -0,0 +1,19 @@ + 0 ]]; then + status=255 + fi +done + +exit $status \ No newline at end of file