From 0ae69c641c595cf3400ae2c973994001e6a06c07 Mon Sep 17 00:00:00 2001 From: Damien Surot Date: Thu, 17 Aug 2017 12:04:09 +0200 Subject: [PATCH 01/25] Zone and system call implementation --- lib/MusicCast/Api/Network.php | 11 ----- lib/MusicCast/Api/NetworkUSB.php | 18 ++++++++ lib/MusicCast/Api/Usb.php | 11 ----- test/MusicCast/Tests/Api/SystemTest.php | 15 +++++++ test/MusicCast/Tests/Api/TestCase.php | 7 +++ test/MusicCast/Tests/Api/ZoneTest.php | 60 +++++++++++++++++++++++++ 6 files changed, 100 insertions(+), 22 deletions(-) delete mode 100644 lib/MusicCast/Api/Network.php create mode 100644 lib/MusicCast/Api/NetworkUSB.php delete mode 100644 lib/MusicCast/Api/Usb.php create mode 100644 test/MusicCast/Tests/Api/SystemTest.php create mode 100644 test/MusicCast/Tests/Api/TestCase.php create mode 100644 test/MusicCast/Tests/Api/ZoneTest.php 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/NetworkUSB.php b/lib/MusicCast/Api/NetworkUSB.php new file mode 100644 index 0000000..3cf65ca --- /dev/null +++ b/lib/MusicCast/Api/NetworkUSB.php @@ -0,0 +1,18 @@ + + */ +class Network extends AbstractApi +{ + private function call($path) + { + return $this->get('/system' . $path); + } +} 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/test/MusicCast/Tests/Api/SystemTest.php b/test/MusicCast/Tests/Api/SystemTest.php new file mode 100644 index 0000000..3b8143e --- /dev/null +++ b/test/MusicCast/Tests/Api/SystemTest.php @@ -0,0 +1,15 @@ + + */ + +namespace MusicCast\Tests\Api; + + +class SystemTest extends TestCase +{ + + /** + * @test + */ + public function testGetDeviceInfo() + { + assert(($this->client->api('system')->getDeviceInfo())['response_code'] === 0); + } + + public function testGetFeatures() + { + assert(($this->client->api('system')->getFeatures())['response_code'] === 0); + } + + public function testGetNetworkStatus() + { + assert(($this->client->api('system')->getNetworkStatus())['response_code'] === 0); + } + + public function testGetFuncStatus() + { + assert(($this->client->api('system')->getFuncStatus())['response_code'] === 0); + } + + 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); + assert($this->client->api('system')->getFuncStatus()['auto_power_standby'] != $previous); + $this->client->api('system')->setAutoPowerStandby($previous); + assert($this->client->api('system')->getFuncStatus()['auto_power_standby'] === $previous); + } + else { + echo 'Can\'t test setAutoPowerStandby on this device'; + } + + } + + public function testGetLocationInfo() + { + assert(($this->client->api('system')->getLocationInfo())['response_code'] === 0); + } + + public function testSendIrCode() + { + assert(($this->client->api('system')->sendIrCode('00000000'))['response_code'] === 0); + } +} \ No newline at end of file From a68493b55c22a3b8e1548e99951d5893e2eac7ca Mon Sep 17 00:00:00 2001 From: Damien Surot Date: Thu, 17 Aug 2017 12:21:08 +0200 Subject: [PATCH 02/25] Zone and system call implementation --- .gitignore | 1 + lib/MusicCast/Api/AbstractApi.php | 3 +- lib/MusicCast/Api/NetworkUSB.php | 17 +++- lib/MusicCast/Api/System.php | 70 +++++++++---- lib/MusicCast/Api/Zone.php | 130 +++++++++++++++++++++++- test/MusicCast/Tests/Api/SystemTest.php | 57 +++++++++-- test/MusicCast/Tests/Api/TestCase.php | 29 +++++- test/MusicCast/Tests/Api/ZoneTest.php | 77 +++++++++----- test/MusicCast/Tests/ClientTest.php | 7 +- 9 files changed, 322 insertions(+), 69 deletions(-) diff --git a/.gitignore b/.gitignore index 3b77c7c..a6189c4 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,4 @@ /phpunit.xml /composer.lock /.php_cs.cache +/.idea/ \ No newline at end of file diff --git a/lib/MusicCast/Api/AbstractApi.php b/lib/MusicCast/Api/AbstractApi.php index 31715e5..a52697b 100644 --- a/lib/MusicCast/Api/AbstractApi.php +++ b/lib/MusicCast/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/NetworkUSB.php b/lib/MusicCast/Api/NetworkUSB.php index 3cf65ca..a58fd76 100644 --- a/lib/MusicCast/Api/NetworkUSB.php +++ b/lib/MusicCast/Api/NetworkUSB.php @@ -6,13 +6,24 @@ /** * APIs in regard to Network/USB related setting and getting information * Target Inputs: USB / Network related ones (Server / Net Radio / Pandora / Spotify / AirPlay etc.) - * + * * @author Sam Van der Borght + * @author Damien Surot */ -class Network extends AbstractApi +class NetworkUSB extends AbstractApi { + + /** + * + * @return array + */ + public function getPresetInfo() + { + return $this->call('getPresetInfo'); + } + private function call($path) { - return $this->get('/system' . $path); + return $this->get('/netusb/' . $path); } } diff --git a/lib/MusicCast/Api/System.php b/lib/MusicCast/Api/System.php index 52a540b..e29973f 100644 --- a/lib/MusicCast/Api/System.php +++ b/lib/MusicCast/Api/System.php @@ -3,6 +3,10 @@ namespace MusicCast\Api; +/** + * @author Sam Van der Borght + * @author Damien Surot + */ /** * @author Sam Van der Borght */ @@ -13,48 +17,80 @@ class System extends AbstractApi * * @return array */ - public function deviceInfo() + public function getDeviceInfo() { - return $this->get('/getDeviceInfo'); + return $this->call('getDeviceInfo'); } /** * For retrieving feature information equipped with a Device + * + * @return array */ - public function features() + public function getFeatures() { - return $this->get('/getFeatures'); + return $this->call('getFeatures'); } - public function networkStatus() + /** + * For retrieving network related setup / information + * + * @return array + */ + public function getNetworkStatus() { - return $this->get('/getNetworkStatus'); + return $this->call('getNetworkStatus'); } - public function functionStatus() + /** + * 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->get('/getFuncStatus'); + return $this->call('getFuncStatus'); } - public function autoPowerStandby($enable = true) + /** + * 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) { - throw new \Exception('Not implemented'); + return $this->call('setAutoPowerStandby?enable=' . rawurlencode($enable ? 'true' : 'false')); } - public function locationInfo() + /** + * For retrieving Location information + * @return array + */ + public function getLocationInfo() { - return $this->get('/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 $code IR code in 8-digit hex + * @return array + */ public function sendIrCode($code) { - throw new \Exception('Not implemented'); - - return $this->get('/sendIrCode?code=1'); + return $this->call('sendIrCode?code=' . rawurlencode($code)); } - public function get($path, array $parameters = array(), array $requestHeaders = array()) + /** + * @param $path + * @return array + */ + private function call($path) { - return parent::get('/system' . $path, $parameters, $requestHeaders); + return $this->get('/system/' . $path); } } diff --git a/lib/MusicCast/Api/Zone.php b/lib/MusicCast/Api/Zone.php index e2a4760..65aef2e 100644 --- a/lib/MusicCast/Api/Zone.php +++ b/lib/MusicCast/Api/Zone.php @@ -1,23 +1,147 @@ + */ class Zone extends AbstractApi { /** - * Returns the status of the given zone + * Returns basic information of each Zone like power, volume, input and so on * - * @param string $zone + * @param string $zone target zone. Available for zones with this function + * Values: "main" / "zone2" / "zone3" / "zone4" * * @return array */ - public function status($zone) + 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?mute='.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)); + } + + private function call($zone, $path) { return $this->get(rawurlencode($zone).'/'.$path); diff --git a/test/MusicCast/Tests/Api/SystemTest.php b/test/MusicCast/Tests/Api/SystemTest.php index 3b8143e..a1ee32b 100644 --- a/test/MusicCast/Tests/Api/SystemTest.php +++ b/test/MusicCast/Tests/Api/SystemTest.php @@ -1,15 +1,58 @@ */ namespace MusicCast\Tests\Api; - -class SystemTest +class SystemTest extends TestCase { -} \ No newline at end of file + /** + * @test + */ + public function testGetDeviceInfo() + { + self::assertArrayHasKey('model_name', $this->client->api('system')->getDeviceInfo()); + } + + public function testGetFeatures() + { + self::assertArrayHasKey('system', $this->client->api('system')->getFeatures()); + self::assertArrayHasKey('func_list', ($this->client->api('system')->getFeatures())['system']); + } + + public function testGetNetworkStatus() + { + self::assertArrayHasKey('network_name', $this->client->api('system')->getNetworkStatus()); + } + + public function testGetFuncStatus() + { + $this->client->api('system')->getFuncStatus(); + } + + 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); + self::assertTrue($this->client->api('system')->getFuncStatus()['auto_power_standby'] != $previous); + $this->client->api('system')->setAutoPowerStandby($previous); + self::assertTrue($this->client->api('system')->getFuncStatus()['auto_power_standby'] == $previous); + return; + } + echo 'Can\'t test setAutoPowerStandby on this device'; + } + + public function testGetLocationInfo() + { + self::assertArrayHasKey('name', $this->client->api('system')->getLocationInfo()); + } + + public function testSendIrCode() + { + $this->client->api('system')->sendIrCode('00000000'); + } +} diff --git a/test/MusicCast/Tests/Api/TestCase.php b/test/MusicCast/Tests/Api/TestCase.php index 8b77950..5a4eb91 100644 --- a/test/MusicCast/Tests/Api/TestCase.php +++ b/test/MusicCast/Tests/Api/TestCase.php @@ -1,7 +1,26 @@ + */ + +namespace MusicCast\Tests\Api; + +use MusicCast\Client; +use Symfony\Component\Yaml\Yaml; + +abstract class TestCase extends \PHPUnit_Framework_TestCase +{ + /** + * @var [] + */ + protected $options; + + protected $client; + + + protected function setUp() + { + $this->options = Yaml::parse(file_get_contents(__DIR__ . '/../../../env.yml')); + $this->client = new Client($this->options); + } +} diff --git a/test/MusicCast/Tests/Api/ZoneTest.php b/test/MusicCast/Tests/Api/ZoneTest.php index 539d30f..2c6603b 100644 --- a/test/MusicCast/Tests/Api/ZoneTest.php +++ b/test/MusicCast/Tests/Api/ZoneTest.php @@ -5,56 +5,81 @@ namespace MusicCast\Tests\Api; +use MusicCast\Exception\ErrorException; -class SystemTest extends TestCase +class ZoneTest extends TestCase { /** * @test */ - public function testGetDeviceInfo() + public function testGetStatus() { - assert(($this->client->api('system')->getDeviceInfo())['response_code'] === 0); + self::assertArrayHasKey('power', $this->client->api('zone')->getStatus('main')); } - public function testGetFeatures() + /** + * @test + */ + public function testGetStatusThrowErrorExceptionIfBadZoneName() { - assert(($this->client->api('system')->getFeatures())['response_code'] === 0); + $this->expectException(ErrorException::class); + $this->client->api('zone')->getStatus('fakeZone'); } - public function testGetNetworkStatus() + public function testGetSoundProgramList() { - assert(($this->client->api('system')->getNetworkStatus())['response_code'] === 0); + self::assertArrayHasKey('sound_program_list', $this->client->api('zone')->getSoundProgramList('main')); } - public function testGetFuncStatus() + public function testSetPower() { - assert(($this->client->api('system')->getFuncStatus())['response_code'] === 0); + $power = ($this->client->api('zone')->getStatus('main'))['power']; + $this->client->api('zone')->setPower('main', $power); } - public function testSetAutoPowerStandby() + public function setSleep() { - $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); - assert($this->client->api('system')->getFuncStatus()['auto_power_standby'] != $previous); - $this->client->api('system')->setAutoPowerStandby($previous); - assert($this->client->api('system')->getFuncStatus()['auto_power_standby'] === $previous); - } - else { - echo 'Can\'t test setAutoPowerStandby on this device'; - } + $sleep = ($this->client->api('zone')->getStatus('main'))['sleep']; + $this->client->api('zone')->setSleep('main', $sleep); + } + public function testSetVolumeByStep() + { + $volume = ($this->client->api('zone')->getStatus('main'))['volume']; + $this->client->api('zone')->setVolume('main', 'up', '1'); + self::assertTrue(($this->client->api('zone')->getStatus('main'))['volume'] == ($volume + 1)); + $this->client->api('zone')->setVolume('main', 'down', '1'); + self::assertTrue(($this->client->api('zone')->getStatus('main'))['volume'] == $volume); + } + + public function testSetVolumeByLevel() + { + $volume = ($this->client->api('zone')->getStatus('main'))['volume']; + $this->client->api('zone')->setVolume('main', $volume + 1); + self::assertTrue(($this->client->api('zone')->getStatus('main'))['volume'] == ($volume + 1)); + $this->client->api('zone')->setVolume('main', $volume); + self::assertTrue(($this->client->api('zone')->getStatus('main'))['volume'] == $volume); + } + + public function setMute() + { + $mute = ($this->client->api('zone')->getStatus('main'))['mute']; + $this->client->api('zone')->setMute('main', !$mute); + self::assertTrue(($this->client->api('zone')->getStatus('main'))['mute'] == !$mute); + $this->client->api('zone')->setMute('main', $mute); + self::assertTrue(($this->client->api('zone')->getStatus('main'))['volume'] == $mute); } - public function testGetLocationInfo() + public function testPrepareInputChange() { - assert(($this->client->api('system')->getLocationInfo())['response_code'] === 0); + $input = ($this->client->api('zone')->getStatus('main'))['input']; + $this->client->api('zone')->prepareInputChange('main', $input); } - public function testSendIrCode() + public function testSetInput() { - assert(($this->client->api('system')->sendIrCode('00000000'))['response_code'] === 0); + $input = ($this->client->api('zone')->getStatus('main'))['input']; + $this->client->api('zone')->setInput('main', $input); } -} \ No newline at end of file +} diff --git a/test/MusicCast/Tests/ClientTest.php b/test/MusicCast/Tests/ClientTest.php index 1390cd7..82623c7 100644 --- a/test/MusicCast/Tests/ClientTest.php +++ b/test/MusicCast/Tests/ClientTest.php @@ -1,4 +1,5 @@ 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 From a719d2e8bf783063abfde0248ccbe5a271c9ce31 Mon Sep 17 00:00:00 2001 From: Damien Surot Date: Sun, 27 Aug 2017 17:51:11 +0200 Subject: [PATCH 03/25] Netusb call implementation --- lib/MusicCast/Api/Distribution.php | 81 ++++++ lib/MusicCast/Api/NetworkUSB.php | 262 +++++++++++++++++- lib/MusicCast/Api/System.php | 43 ++- lib/MusicCast/Api/Zone.php | 8 + lib/MusicCast/Client.php | 14 +- test/MusicCast/Tests/Api/DistributionTest.php | 54 ++++ test/MusicCast/Tests/Api/NetworkUSBTest.php | 156 +++++++++++ test/MusicCast/Tests/Api/SystemTest.php | 61 ++++ 8 files changed, 669 insertions(+), 10 deletions(-) create mode 100644 lib/MusicCast/Api/Distribution.php create mode 100644 test/MusicCast/Tests/Api/DistributionTest.php create mode 100644 test/MusicCast/Tests/Api/NetworkUSBTest.php diff --git a/lib/MusicCast/Api/Distribution.php b/lib/MusicCast/Api/Distribution.php new file mode 100644 index 0000000..ea633e5 --- /dev/null +++ b/lib/MusicCast/Api/Distribution.php @@ -0,0 +1,81 @@ + + */ + +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 int $num + * @param string $groupId + * @param array $zone + * @param string $server_ip_addr + * @return array + */ + public function setCientInfo($groupId, array $zone, $server_ip_addr) + { + return $this->callPost( + 'setCientInfo', + array('group_id' => $groupId, 'zone' => $zone, 'server_ip_address' => $server_ip_addr) + ); + } + + private function callPost($path, array $parameters = array()) + { + return $this->post('/dist/' . $path, $parameters); + } + + /** + * @param $name + * @return array + */ + public function setGroupName($name) + { + return $this->callPost( + 'setGroupName', + array('name' => $name) + ); + } + + /** + * @param $groupId + * @param $type + * @param $zone + * @param array $client_list Clients IP + * @return array + */ + public function setServerInfo($groupId, $type, $zone, array $client_list = array()) + { + return $this->callPost( + 'setServerInfo', + array('group_id' => $groupId, 'type' => $type, 'zone' => $zone, 'client_list' => $client_list) + ); + } +} diff --git a/lib/MusicCast/Api/NetworkUSB.php b/lib/MusicCast/Api/NetworkUSB.php index a58fd76..27fb1c0 100644 --- a/lib/MusicCast/Api/NetworkUSB.php +++ b/lib/MusicCast/Api/NetworkUSB.php @@ -14,16 +14,274 @@ 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->call('getPresetInfo'); + return $this->callGet('getPresetInfo'); } - private function call($path) + /** + *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 $itype + * @param $timeout + * @return array + */ + 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 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/lib/MusicCast/Api/System.php b/lib/MusicCast/Api/System.php index e29973f..aaaf0ad 100644 --- a/lib/MusicCast/Api/System.php +++ b/lib/MusicCast/Api/System.php @@ -77,7 +77,7 @@ public function 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 $code IR code in 8-digit hex + * @param string $code IR code in 8-digit hex * @return array */ public function sendIrCode($code) @@ -85,6 +85,47 @@ public function sendIrCode($code) return $this->call('sendIrCode?code=' . rawurlencode($code)); } + /** + * @return array + */ + public function getNameText() + { + return $this->call('getNameText'); + } + + /** + * @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 diff --git a/lib/MusicCast/Api/Zone.php b/lib/MusicCast/Api/Zone.php index 65aef2e..8256764 100644 --- a/lib/MusicCast/Api/Zone.php +++ b/lib/MusicCast/Api/Zone.php @@ -141,6 +141,14 @@ public function prepareInputChange($zone, $input) return $this->call($zone, 'prepareInputChange?input='.rawurlencode($input)); } + /** + * @return array|string + */ + public function getSignalInfo($zone) + { + return $this->call($zone, 'getSignalInfo'); + } + private function call($zone, $path) { diff --git a/lib/MusicCast/Client.php b/lib/MusicCast/Client.php index f6e342d..4722c04 100644 --- a/lib/MusicCast/Client.php +++ b/lib/MusicCast/Client.php @@ -2,22 +2,22 @@ namespace MusicCast; use Http\Client\Common\HttpMethodsClient; +use Http\Client\Common\Plugin; use Http\Client\Common\PluginClient; use Http\Client\HttpClient; use Http\Discovery\HttpClientDiscovery; use Http\Discovery\MessageFactoryDiscovery; use Http\Discovery\StreamFactoryDiscovery; -use Http\Client\Common\Plugin; use Http\Discovery\UriFactoryDiscovery; use Http\Message\MessageFactory; use Http\Message\StreamFactory; use MusicCast\Api\ApiInterface; use MusicCast\Exception\BadMethodCallException; use MusicCast\Exception\InvalidArgumentException; +use MusicCast\HttpClient\Plugin\AddBasePath; use MusicCast\HttpClient\Plugin\History; use MusicCast\HttpClient\Plugin\MusicCastExceptionThrower; use Symfony\Component\OptionsResolver\OptionsResolver; -use MusicCast\HttpClient\Plugin\AddBasePath; class Client { @@ -122,17 +122,17 @@ public function api($name) case 'zone': $api = new Api\Zone($this); break; + case 'dist': + $api = new Api\Distribution($this); + break; case 'system': $api = new Api\System($this); break; case 'tuner': $api = new Api\Tuner($this); break; - case 'network': - $api = new Api\Network($this); - break; - case 'usb': - $api = new Api\Usb($this); + case 'netusb': + $api = new Api\NetworkUSB($this); break; case 'cd': $api = new Api\CD($this); diff --git a/test/MusicCast/Tests/Api/DistributionTest.php b/test/MusicCast/Tests/Api/DistributionTest.php new file mode 100644 index 0000000..669af6e --- /dev/null +++ b/test/MusicCast/Tests/Api/DistributionTest.php @@ -0,0 +1,54 @@ + + */ + +namespace MusicCast\Tests\Api; + +class DistributionTest extends TestCase +{ + /** + * @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 testSetCientInfo() + { + //self::assertArrayHasKey('???', $this->client->api('dist')->setCientInfo()); + $this->markTestSkipped('dist/setCientInfo method not implemented'); + } + + /** + * @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/test/MusicCast/Tests/Api/NetworkUSBTest.php b/test/MusicCast/Tests/Api/NetworkUSBTest.php new file mode 100644 index 0000000..b5c0a31 --- /dev/null +++ b/test/MusicCast/Tests/Api/NetworkUSBTest.php @@ -0,0 +1,156 @@ + + */ + +namespace MusicCast\Tests\Api; + +class NetworkUSBTest extends TestCase +{ + /** + * @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('???', $this->client->api('netusb')->setPlayback()); + $this->markTestSkipped('netusb/setPlayback method not implemented'); + } + + /** + * @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('???', $this->client->api('netusb')->recallPreset()); + $this->markTestSkipped('netusb/recallPreset method not implemented'); + } + + /** + * @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/test/MusicCast/Tests/Api/SystemTest.php b/test/MusicCast/Tests/Api/SystemTest.php index a1ee32b..d124511 100644 --- a/test/MusicCast/Tests/Api/SystemTest.php +++ b/test/MusicCast/Tests/Api/SystemTest.php @@ -16,22 +16,34 @@ 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(); @@ -46,13 +58,62 @@ public function testSetAutoPowerStandby() 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()); + } } From c15e6318e242cfc2dbaff98a31217676d37320b8 Mon Sep 17 00:00:00 2001 From: Damien Surot Date: Sat, 14 Oct 2017 01:17:45 +0200 Subject: [PATCH 04/25] Api, Services and Tests --- .coveralls.yml | 3 + .editorconfig | 0 .gitignore | 2 +- .styleci.yml | 0 .travis.yml | 26 +- README.md | 0 composer.json | 99 +-- grumphp.yml | 3 +- phpunit.xml.dist | 35 +- {lib/MusicCast => src}/Api/AbstractApi.php | 0 {lib/MusicCast => src}/Api/ApiInterface.php | 0 {lib/MusicCast => src}/Api/CD.php | 0 {lib/MusicCast => src}/Api/Distribution.php | 24 +- {lib/MusicCast => src}/Api/Event.php | 7 +- {lib/MusicCast => src}/Api/NetworkUSB.php | 17 + {lib/MusicCast => src}/Api/System.php | 0 {lib/MusicCast => src}/Api/Tuner.php | 0 {lib/MusicCast => src}/Api/Zone.php | 0 src/Cache.php | 28 + {lib/MusicCast => src}/Client.php | 0 src/Controller.php | 591 ++++++++++++++++++ src/Device.php | 139 ++++ {lib/MusicCast => src}/Enum/ResponseCodes.php | 0 .../Exception/BadMethodCallException.php | 0 .../Exception/ErrorException.php | 0 .../Exception/ExceptionInterface.php | 0 .../Exception/InvalidArgumentException.php | 0 src/Exception/NotImplementedException.php | 7 + .../Exception/RuntimeException.php | 0 src/Favorite.php | 72 +++ .../HttpClient/Message/ResponseMediator.php | 0 .../HttpClient/Plugin/AddBasePath.php | 0 .../HttpClient/Plugin/History.php | 0 .../Plugin/MusicCastExceptionThrower.php | 3 +- src/Network.php | 312 +++++++++ src/Playlist.php | 64 ++ src/Speaker.php | 163 +++++ src/State.php | 43 ++ src/Tracks/Track.php | 69 ++ test/MusicCast/Tests/Api/TestCase.php | 26 - test/bootstrap.php | 14 - .../Api/DistributionLiveTest.php | 9 +- .../Api/NetworkUSBLiveTest.php | 20 +- .../Api/SystemLiveTest.php | 11 +- .../Api/ZoneLiveTest.php | 31 +- .../MusicCast/Tests => tests}/ClientTest.php | 15 +- tests/ControllerLiveTest.php | 69 ++ tests/LiveTest.php | 41 ++ tests/MockTest.php | 111 ++++ tests/NetworkLiveTest.php | 37 ++ tests/NetworkTest.php | 69 ++ tests/SpeakerTest.php | 68 ++ tests/assets/dist/getDistributionInfo.json | 8 + tests/assets/dist/setClientInfo.json | 3 + tests/assets/dist/setGroupName.json | 3 + tests/assets/dist/setServerInfo.json | 3 + tests/assets/dist/startDistribution.json | 3 + tests/assets/system/getDeviceInfo.json | 13 + .../assets/system/getDisklavierSettings.json | 12 + tests/assets/system/getFeatures.json | 347 ++++++++++ tests/assets/system/getFuncStatus.json | 5 + tests/assets/system/getLocationInfo.json | 9 + tests/assets/system/getMusicCastTreeInfo.json | 50 ++ tests/assets/system/getNameText.json | 181 ++++++ tests/assets/system/getNetworkStatus.json | 37 ++ tests/assets/system/getTag.json | 99 +++ .../assets/system/isNewFirmwareAvailable.json | 4 + tests/assets/system/sendIrCode.json | 3 + tests/assets/zone/getSignalInfo.json | 8 + tests/assets/zone/getSoundProgramList.json | 25 + tests/assets/zone/getStatus.json | 21 + tests/assets/zone/prepareInputChange.json | 3 + tests/assets/zone/setInput.json | 3 + tests/assets/zone/setMute.json | 3 + tests/assets/zone/setPower.json | 3 + tests/assets/zone/setSleep.json | 3 + tests/assets/zone/setVolume.json | 3 + tests/bootstrap.php | 19 + tests/env.yml.dist | 2 + tests/lint.sh | 14 + 80 files changed, 2955 insertions(+), 160 deletions(-) create mode 100644 .coveralls.yml mode change 100644 => 100755 .editorconfig mode change 100644 => 100755 .gitignore mode change 100644 => 100755 .styleci.yml mode change 100644 => 100755 .travis.yml mode change 100644 => 100755 README.md mode change 100644 => 100755 composer.json mode change 100644 => 100755 grumphp.yml rename {lib/MusicCast => src}/Api/AbstractApi.php (100%) mode change 100644 => 100755 rename {lib/MusicCast => src}/Api/ApiInterface.php (100%) mode change 100644 => 100755 rename {lib/MusicCast => src}/Api/CD.php (100%) mode change 100644 => 100755 rename {lib/MusicCast => src}/Api/Distribution.php (82%) mode change 100644 => 100755 rename {lib/MusicCast => src}/Api/Event.php (82%) mode change 100644 => 100755 rename {lib/MusicCast => src}/Api/NetworkUSB.php (94%) mode change 100644 => 100755 rename {lib/MusicCast => src}/Api/System.php (100%) mode change 100644 => 100755 rename {lib/MusicCast => src}/Api/Tuner.php (100%) mode change 100644 => 100755 rename {lib/MusicCast => src}/Api/Zone.php (100%) mode change 100644 => 100755 create mode 100755 src/Cache.php rename {lib/MusicCast => src}/Client.php (100%) mode change 100644 => 100755 create mode 100755 src/Controller.php create mode 100755 src/Device.php rename {lib/MusicCast => src}/Enum/ResponseCodes.php (100%) mode change 100644 => 100755 rename {lib/MusicCast => src}/Exception/BadMethodCallException.php (100%) mode change 100644 => 100755 rename {lib/MusicCast => src}/Exception/ErrorException.php (100%) mode change 100644 => 100755 rename {lib/MusicCast => src}/Exception/ExceptionInterface.php (100%) mode change 100644 => 100755 rename {lib/MusicCast => src}/Exception/InvalidArgumentException.php (100%) mode change 100644 => 100755 create mode 100755 src/Exception/NotImplementedException.php rename {lib/MusicCast => src}/Exception/RuntimeException.php (100%) mode change 100644 => 100755 create mode 100644 src/Favorite.php rename {lib/MusicCast => src}/HttpClient/Message/ResponseMediator.php (100%) mode change 100644 => 100755 rename {lib/MusicCast => src}/HttpClient/Plugin/AddBasePath.php (100%) mode change 100644 => 100755 rename {lib/MusicCast => src}/HttpClient/Plugin/History.php (100%) mode change 100644 => 100755 rename {lib/MusicCast => src}/HttpClient/Plugin/MusicCastExceptionThrower.php (94%) mode change 100644 => 100755 create mode 100755 src/Network.php create mode 100644 src/Playlist.php create mode 100755 src/Speaker.php create mode 100755 src/State.php create mode 100644 src/Tracks/Track.php delete mode 100644 test/MusicCast/Tests/Api/TestCase.php delete mode 100644 test/bootstrap.php rename test/MusicCast/Tests/Api/DistributionTest.php => tests/Api/DistributionLiveTest.php (88%) mode change 100644 => 100755 rename test/MusicCast/Tests/Api/NetworkUSBTest.php => tests/Api/NetworkUSBLiveTest.php (82%) mode change 100644 => 100755 rename test/MusicCast/Tests/Api/SystemTest.php => tests/Api/SystemLiveTest.php (93%) mode change 100644 => 100755 rename test/MusicCast/Tests/Api/ZoneTest.php => tests/Api/ZoneLiveTest.php (69%) mode change 100644 => 100755 rename {test/MusicCast/Tests => tests}/ClientTest.php (91%) mode change 100644 => 100755 create mode 100644 tests/ControllerLiveTest.php create mode 100644 tests/LiveTest.php create mode 100644 tests/MockTest.php create mode 100755 tests/NetworkLiveTest.php create mode 100644 tests/NetworkTest.php create mode 100644 tests/SpeakerTest.php create mode 100644 tests/assets/dist/getDistributionInfo.json create mode 100644 tests/assets/dist/setClientInfo.json create mode 100644 tests/assets/dist/setGroupName.json create mode 100644 tests/assets/dist/setServerInfo.json create mode 100644 tests/assets/dist/startDistribution.json create mode 100644 tests/assets/system/getDeviceInfo.json create mode 100644 tests/assets/system/getDisklavierSettings.json create mode 100644 tests/assets/system/getFeatures.json create mode 100644 tests/assets/system/getFuncStatus.json create mode 100644 tests/assets/system/getLocationInfo.json create mode 100644 tests/assets/system/getMusicCastTreeInfo.json create mode 100644 tests/assets/system/getNameText.json create mode 100644 tests/assets/system/getNetworkStatus.json create mode 100644 tests/assets/system/getTag.json create mode 100644 tests/assets/system/isNewFirmwareAvailable.json create mode 100644 tests/assets/system/sendIrCode.json create mode 100644 tests/assets/zone/getSignalInfo.json create mode 100644 tests/assets/zone/getSoundProgramList.json create mode 100644 tests/assets/zone/getStatus.json create mode 100644 tests/assets/zone/prepareInputChange.json create mode 100644 tests/assets/zone/setInput.json create mode 100644 tests/assets/zone/setMute.json create mode 100644 tests/assets/zone/setPower.json create mode 100644 tests/assets/zone/setSleep.json create mode 100644 tests/assets/zone/setVolume.json create mode 100755 tests/bootstrap.php create mode 100644 tests/env.yml.dist create mode 100644 tests/lint.sh diff --git a/.coveralls.yml b/.coveralls.yml new file mode 100644 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 a6189c4..053d9bb --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,6 @@ # Created by .ignore support plugin (hsz.mobi) /vendor/ -/test/env.yml +/tests/env.yml /phpunit.xml /composer.lock /.php_cs.cache 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/README.md b/README.md old mode 100644 new mode 100755 diff --git a/composer.json b/composer.json old mode 100644 new mode 100755 index 558f8ff..8de1d0d --- a/composer.json +++ b/composer.json @@ -1,46 +1,61 @@ { - "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": "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-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" + }, + "require-dev": { + "squizlabs/php_codesniffer": "^2.7", + "mockery/mockery": "^0.9.4", + "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", + "doctrine/cache": "^1.4", + "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" } + } } 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/phpunit.xml.dist b/phpunit.xml.dist index 461db94..1a25369 100644 --- 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 100% rename from lib/MusicCast/Api/AbstractApi.php rename to src/Api/AbstractApi.php 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/lib/MusicCast/Api/Distribution.php b/src/Api/Distribution.php old mode 100644 new mode 100755 similarity index 82% rename from lib/MusicCast/Api/Distribution.php rename to src/Api/Distribution.php index ea633e5..c482df7 --- a/lib/MusicCast/Api/Distribution.php +++ b/src/Api/Distribution.php @@ -33,25 +33,26 @@ public function startDistribution($num = 0) } /** - * @param int $num * @param string $groupId * @param array $zone * @param string $server_ip_addr * @return array */ - public function setCientInfo($groupId, array $zone, $server_ip_addr) + public function setClientInfo($groupId = '', array $zone = null, $server_ip_addr = null) { + $info = array('group_id' => $groupId); + if ($zone != null) { + $info['zone'] = $zone; + } + if ($server_ip_addr != null) { + $info['server_ip_address'] = $server_ip_addr; + } return $this->callPost( 'setCientInfo', - array('group_id' => $groupId, 'zone' => $zone, 'server_ip_address' => $server_ip_addr) + $info ); } - private function callPost($path, array $parameters = array()) - { - return $this->post('/dist/' . $path, $parameters); - } - /** * @param $name * @return array @@ -64,6 +65,13 @@ public function setGroupName($name) ); } + private function callPost($path, array $parameters = array()) + { + return $this->post('/dist/' . $path, $parameters); + } + + + /** * @param $groupId * @param $type diff --git a/lib/MusicCast/Api/Event.php b/src/Api/Event.php old mode 100644 new mode 100755 similarity index 82% rename from lib/MusicCast/Api/Event.php rename to src/Api/Event.php index e9ce4c9..85b1501 --- 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); + // $plugin = $this->client->getPlugin(AddBasePath::class); + // $this->client->removePlugin(AddBasePath::class); return $this->get('', [], $headers); -// $this->client->addPlugin($plugin); + // $this->client->addPlugin($plugin); } } diff --git a/lib/MusicCast/Api/NetworkUSB.php b/src/Api/NetworkUSB.php old mode 100644 new mode 100755 similarity index 94% rename from lib/MusicCast/Api/NetworkUSB.php rename to src/Api/NetworkUSB.php index 27fb1c0..a4a989c --- a/lib/MusicCast/Api/NetworkUSB.php +++ b/src/Api/NetworkUSB.php @@ -247,6 +247,23 @@ public function getMcPlaylistName() return $this->callGet('getMcPlaylistName'); } + /** + * @return array + */ + 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)); + } + + /** + * @return array + */ + public function getMcPlaylist($bank, $index = 0) + { + return $this->callGet('getMcPlaylist?bank=' . rawurlencode($bank) . '&index=' . rawurlencode($index)); + } + /** * @param integer $index * @return array diff --git a/lib/MusicCast/Api/System.php b/src/Api/System.php old mode 100644 new mode 100755 similarity index 100% rename from lib/MusicCast/Api/System.php rename to src/Api/System.php 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/lib/MusicCast/Api/Zone.php b/src/Api/Zone.php old mode 100644 new mode 100755 similarity index 100% rename from lib/MusicCast/Api/Zone.php rename to src/Api/Zone.php diff --git a/src/Cache.php b/src/Cache.php new file mode 100755 index 0000000..63db75f --- /dev/null +++ b/src/Cache.php @@ -0,0 +1,28 @@ +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->group = $this->getGroup(); + $this->group_name = $this->getName(); + $this->uuid = $this->getUuid(); + $this->speakers = $this->getSpeakers(); + } + + /** + * Get the speakers that are in the group of this controller. + * + * @return Speaker[] + */ + public function getSpeakers() + { + if (is_array($this->speakers)) { + return $this->speakers; + } + $group = []; + $speakers = $this->network->getSpeakers(); + foreach ($speakers as $speaker) { + if ($speaker->getGroup() === $this->getGroup()) { + $group[] = $speaker; + } + } + return $this->speakers = $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 + */ + private function getStateName() + { + return $this->device->getClient()->api('netusb')->getPlayInfo()['playback']; + } + + /** + * Get attributes about the currently active track in the queue. + * + * @return State Track data containing the following elements + */ + public function getStateDetails() + { + $data = $this->device->getClient()->api('netusb')->getPlayInfo(); + + + $state = State::createFromJson($data, $this); + + $state->duration = $data["total_time"]; + $state->position = $data["play_time"]; + + + return $state; + } + + /** + * 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->device->getClient()->api('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'); + } + + /** + * Get the currently active media info. + * + * @return array + */ + public function getMediaInfo() + { + return $this->device->getClient()->api('netusb')->getPlayInfo()['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; + } + $this->speakers[$speaker->getDevice()->getIp()] = $speaker; + $nbSpeaker = sizeof($this->speakers); + $speaker->device->getClient()->api('dist')->setClientInfo($speaker->getGroup(), 'main', $this->ip); + $this->device->getClient()->api('dist')->setServerInfo($speaker->getGroup(), 'add', 'main', [$speaker->ip]); + $this->device->getClient()->api('dist')->startDistribution($nbSpeaker); + + if ($nbSpeaker == 1) { + $this->device->getClient()->api('dist')->setGroupName($this->name . '+' . $speaker->getName()); + return $this; + } + $this->device->getClient()->api('dist')->setGroupName($this->name . '+' . $nbSpeaker . ' rooms'); + 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; + } + unset($this->speakers[$speaker->getDevice()->getIp()]); + $nbSpeaker = sizeof($this->speakers); + $this->device->getClient()->api('dist')->setServerInfo($speaker->getGroup(), 'remove', 'main', [$speaker->ip]); + $speaker->device->getClient()->api('dist')->setClientInfo(); + $this->device->getClient()->api('dist')->startDistribution($nbSpeaker); + + + if ($nbSpeaker == 1) { + $this->device->getClient()->api('dist')->setGroupName($this->name . '+' + . array_values($this->speakers)[0]->getName()); + return $this; + } + $this->device->getClient()->api('dist')->setGroupName($this->name . '+' . $nbSpeaker . ' rooms'); + return $this; + } + + + /** + * Set the current volume of all the speakers controlled by this Controller. + * + * @param int $volume An amount between 0 and 100 + * + * @return static + */ + public function setVolume($volume) + { + $speakers = $this->getSpeakers(); + foreach ($speakers as $speaker) { + $speaker->setVolume($volume); + } + + 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->device->getClient()->api('netusb')->getPlayInfo()['repeat'] != 'off'; + } + + + /** + * Turn repeat mode on or off. + * + * @param bool $repeat Whether repeat should be on or not + * + * @return static + */ + public function toggleRepeat() + { + return $this->device->getClient()->api('netusb')->toggleRepeat(); + } + + + /** + * Check if shuffle is currently active. + * + * @return bool + */ + public function getShuffle() + { + return $this->device->getClient()->api('netusb')->getPlayInfo()['shuffle'] != 'off'; + } + + + /** + * Turn shuffle mode on or off. + * + * @param bool $shuffle Whether shuffle should be on or not + * + * @return static + */ + public function toggleShuffle() + { + return $this->device->getClient()->api('netusb')->toggleShuffle(); + } + + + /** + * Get the queue for this controller. + * + * @return Queue + */ + public function getQueue() + { + throw new NotImplementedException(); + } + + + /** + * Grab the current state of the Controller (including it's queue and playing attributes). + * + * @param bool $pause Whether to pause the controller or not + * + * @return ControllerState + */ + public function exportState($pause = true) + { + throw new NotImplementedException(); + } + + /** + * 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 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->device->getClient()->api('netusb')->getMcPlaylistName()['name_list']; + $playlists = []; + $index = 1; + foreach ($playlist_names as $playlist_name) { + $playlists[] = 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 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; + } + } + + /** + * Get the playlist with the specified id. + * + * @param int The ID of the playlist + * + * @return Playlist + */ + public function getPlaylistById($id) + { + $playlists = $this->getPlaylists(); + foreach ($playlists as $playlist) { + if ($playlist->getId() === $id) { + return $playlist; + } + } + } + + /** + * 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 The name of the playlist + * + * @return bool + */ + public function hasFavorites($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->device->getClient()->api('netusb')->getPresetInfo()['preset_info']; + $favorites = []; + $index = 0; + foreach ($favorites_info as $item) { + $index++; + if ($item['text'] != '') { + $favorites[] = 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 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; + } + } + + /** + * Get the playlist with the specified id. + * + * @param int The ID of the playlist + * + * @return Favorite + */ + public function getFavoriteById($id) + { + $favorites = $this->getFavorites(); + foreach ($favorites as $item) { + if ($item->getId() === $id) { + return $item; + } + } + } + + /** + * Get the network instance used by this controller. + * + * @return Network + */ + public function getNetwork() + { + return $this->network; + } +} diff --git a/src/Device.php b/src/Device.php new file mode 100755 index 0000000..548a6df --- /dev/null +++ b/src/Device.php @@ -0,0 +1,139 @@ +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(): string + { + return $this->ip; + } + + /** + * @return Client + */ + public function getClient(): Client + { + return $this->client; + } + + + public function getDeviceInfo() + { + $cacheKey = $this->getCacheKey() . __FUNCTION__; + if ($this->cache->has($cacheKey)) { + return $this->cache->get($cacheKey); + } + $info = $this->client->api('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->client->api('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->client->api('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->client->api('system')->getNetworkStatus(); + $this->cache->set($cacheKey, $info); + return $info; + } +} 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 @@ +id = $index; + $this->name = $data['text']; + $this->input = $data['input']; + $this->controller = $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; + } + + /** + * @return mixed + */ + public function getInput() + { + return $this->input; + } + + + public function play() + { + $this->controller->getDevice()->getClient()->api('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..5fc6739 --- /dev/null +++ b/src/Network.php @@ -0,0 +1,312 @@ +cache = $cache; + + if ($logger === null) { + $logger = new NullLogger; + } + $this->logger = $logger; + } + + /** + * @param LoggerInterface $logger + */ + public function setLogger(LoggerInterface $logger) + { + $this->logger = $logger; + } + + /** + * @return + */ + public function getMulticastAddress() + { + return $this->multicastAddress; + } + + /** + * @param $multicastAddress + */ + public function setMulticastAddress($multicastAddress) + { + $this->multicastAddress = $multicastAddress; + } + + /** + * @return + */ + 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; + } + } + + /** + * Get all the coordinators on the network. + * + * @return Controller[] + */ + public function getControllers() + { + $controllers = []; + $speakers = $this->getSpeakers(); + foreach ($speakers as $speaker) { + if (!$speaker->isCoordinator()) { + continue; + } + $controllers[$speaker->getDevice()->getIp()] = new Controller($speaker, $this); + } + return $controllers; + } + + /** + * Get all the speakers on the network. + * + * @return + */ + public function getSpeakers() + { + if (is_array($this->speakers)) { + return $this->speakers; + } + + $this->logger->info("creating speaker instances"); + + $cacheKey = $this->getCacheKey(); + + if ($this->cache->has($cacheKey)) { + $this->logger->info("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->notice("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; + } + + 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->info("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->info("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}'"); + } + $group = $speakers[$ip]->getGroup(); + foreach ($this->getControllers() as $controller) { + if ($controller->getGroup() === $group) { + return $controller; + } + } + } +} diff --git a/src/Playlist.php b/src/Playlist.php new file mode 100644 index 0000000..d497faf --- /dev/null +++ b/src/Playlist.php @@ -0,0 +1,64 @@ +id = $bank; + $this->name = $name; + $this->controller = $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->controller->getDevice()->getClient()->api('netusb')->manageMcPlaylist($this->id, 'play', $index); + } +} diff --git a/src/Speaker.php b/src/Speaker.php new file mode 100755 index 0000000..437b346 --- /dev/null +++ b/src/Speaker.php @@ -0,0 +1,163 @@ +device = $device; + $this->model = $device->getDeviceInfo()['model_name']; + $this->name = $device->getNetworkStatus()['network_name']; + } + + /** + * @return Device + */ + public function getDevice(): Device + { + return $this->device; + } + + + /** + * @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->device->getClient()->api('dist')->getDistributionInfo()['group_id']; + if (is_numeric($group)) { + $group = $this->device->getIp(); + } + return $group; + } + + /** + * Check if this speaker is the coordinator of it's current group. + * + * @return bool + */ + public function isCoordinator() + { + $role = $this->device->getClient()->api('dist')->getDistributionInfo()['role']; + return $role == 'server' || $role == 'none'; + } + + /** + * Get the uuid of this speaker. + * + * @return string The uuid of this speaker + */ + public function getUuid() + { + return $this->device->getDeviceInfo()['device_id']; + } + + /** + * Get the current volume of this speaker. + * + * @param int The current volume between 0 and 100 + * + * @return int + */ + public function getVolume() + { + return (int)$this->device->getClient()->api('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->device->getClient()->api('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->device->getClient()->api('zone')->setVolume('main', $adjust > 0 ? 'up' : 'down', abs($adjust)); + return $this; + } + + /** + * Check if this speaker is currently muted. + * + * @return bool + */ + public function isMuted() + { + return (bool)$this->device->getClient()->api('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->device->getClient()->api('zone')->setMute('main', $mute); + return $this; + } +} diff --git a/src/State.php b/src/State.php new file mode 100755 index 0000000..0ea069d --- /dev/null +++ b/src/State.php @@ -0,0 +1,43 @@ +duration = "02:30"; + $track->position = ''; + return $track; + } +} diff --git a/src/Tracks/Track.php b/src/Tracks/Track.php new file mode 100644 index 0000000..6b486e6 --- /dev/null +++ b/src/Tracks/Track.php @@ -0,0 +1,69 @@ +title = $json['track']; + $track->input = $json['input']; + + $track->artist = $json['artist']; + $track->album = $json['album']; + + + if ($art = $json['albumart_url']) { + if (substr($art, 0, 4) !== "http") { + $art = ltrim($art, "/"); + $art = sprintf("http://%s:80/%s", $controller->getIp(), $art); + } + $track->albumArt = $art; + } + + + return $track; + } +} diff --git a/test/MusicCast/Tests/Api/TestCase.php b/test/MusicCast/Tests/Api/TestCase.php deleted file mode 100644 index 5a4eb91..0000000 --- a/test/MusicCast/Tests/Api/TestCase.php +++ /dev/null @@ -1,26 +0,0 @@ - - */ - -namespace MusicCast\Tests\Api; - -use MusicCast\Client; -use Symfony\Component\Yaml\Yaml; - -abstract class TestCase extends \PHPUnit_Framework_TestCase -{ - /** - * @var [] - */ - protected $options; - - protected $client; - - - protected function setUp() - { - $this->options = Yaml::parse(file_get_contents(__DIR__ . '/../../../env.yml')); - $this->client = new Client($this->options); - } -} 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/test/MusicCast/Tests/Api/DistributionTest.php b/tests/Api/DistributionLiveTest.php old mode 100644 new mode 100755 similarity index 88% rename from test/MusicCast/Tests/Api/DistributionTest.php rename to tests/Api/DistributionLiveTest.php index 669af6e..1c37562 --- a/test/MusicCast/Tests/Api/DistributionTest.php +++ b/tests/Api/DistributionLiveTest.php @@ -3,10 +3,15 @@ * @author Damien SUROT */ -namespace MusicCast\Tests\Api; +namespace MusicCastTests\Api; -class DistributionTest extends TestCase +class DistributionLiveTest extends \MusicCastTests\LiveTest { + protected function setUp() + { + parent::setUp(); + } + /** * @test */ diff --git a/test/MusicCast/Tests/Api/NetworkUSBTest.php b/tests/Api/NetworkUSBLiveTest.php old mode 100644 new mode 100755 similarity index 82% rename from test/MusicCast/Tests/Api/NetworkUSBTest.php rename to tests/Api/NetworkUSBLiveTest.php index b5c0a31..a918b8b --- a/test/MusicCast/Tests/Api/NetworkUSBTest.php +++ b/tests/Api/NetworkUSBLiveTest.php @@ -3,10 +3,19 @@ * @author Damien SUROT */ -namespace MusicCast\Tests\Api; +namespace MusicCastTests\Api; -class NetworkUSBTest extends TestCase +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 */ @@ -28,8 +37,8 @@ public function testGetPlayInfo() */ public function testSetPlayback() { - //self::assertArrayHasKey('???', $this->client->api('netusb')->setPlayback()); - $this->markTestSkipped('netusb/setPlayback method not implemented'); + self::assertArrayHasKey('response_code', $this->client->api('netusb')->setPlayback("play_pause")); + self::assertArrayHasKey('response_code', $this->client->api('netusb')->setPlayback("play_pause")); } /** @@ -82,8 +91,7 @@ public function testSetSearchString() */ public function testRecallPreset() { - //self::assertArrayHasKey('???', $this->client->api('netusb')->recallPreset()); - $this->markTestSkipped('netusb/recallPreset method not implemented'); + self::assertArrayHasKey('response_code', $this->client->api('netusb')->recallPreset('main', 1)); } /** diff --git a/test/MusicCast/Tests/Api/SystemTest.php b/tests/Api/SystemLiveTest.php old mode 100644 new mode 100755 similarity index 93% rename from test/MusicCast/Tests/Api/SystemTest.php rename to tests/Api/SystemLiveTest.php index d124511..af02c75 --- a/test/MusicCast/Tests/Api/SystemTest.php +++ b/tests/Api/SystemLiveTest.php @@ -3,11 +3,14 @@ * @author Damien SUROT */ -namespace MusicCast\Tests\Api; +namespace MusicCastTests\Api; -class SystemTest extends TestCase +class SystemLiveTest extends \MusicCastTests\LiveTest { - + protected function setUp() + { + parent::setUp(); + } /** * @test */ @@ -50,8 +53,10 @@ public function testSetAutoPowerStandby() 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; } diff --git a/test/MusicCast/Tests/Api/ZoneTest.php b/tests/Api/ZoneLiveTest.php old mode 100644 new mode 100755 similarity index 69% rename from test/MusicCast/Tests/Api/ZoneTest.php rename to tests/Api/ZoneLiveTest.php index 2c6603b..6e1301d --- a/test/MusicCast/Tests/Api/ZoneTest.php +++ b/tests/Api/ZoneLiveTest.php @@ -3,13 +3,16 @@ * @author Damien SUROT */ -namespace MusicCast\Tests\Api; +namespace MusicCastTests\Api; use MusicCast\Exception\ErrorException; -class ZoneTest extends TestCase +class ZoneLiveTest extends \MusicCastTests\LiveTest { - + protected function setUp() + { + parent::setUp(); + } /** * @test */ @@ -44,31 +47,37 @@ public function setSleep() $this->client->api('zone')->setSleep('main', $sleep); } - public function testSetVolumeByStep() + public function testAdjustVolume() { $volume = ($this->client->api('zone')->getStatus('main'))['volume']; $this->client->api('zone')->setVolume('main', 'up', '1'); - self::assertTrue(($this->client->api('zone')->getStatus('main'))['volume'] == ($volume + 1)); + sleep(1); + self::assertEquals(($volume + 1), ($this->client->api('zone')->getStatus('main'))['volume']); $this->client->api('zone')->setVolume('main', 'down', '1'); - self::assertTrue(($this->client->api('zone')->getStatus('main'))['volume'] == $volume); + sleep(1); + self::assertEquals($volume, ($this->client->api('zone')->getStatus('main'))['volume']); } - public function testSetVolumeByLevel() + public function testSetVolume() { $volume = ($this->client->api('zone')->getStatus('main'))['volume']; $this->client->api('zone')->setVolume('main', $volume + 1); - self::assertTrue(($this->client->api('zone')->getStatus('main'))['volume'] == ($volume + 1)); + sleep(1); + self::assertEquals(($volume + 1), ($this->client->api('zone')->getStatus('main'))['volume']); $this->client->api('zone')->setVolume('main', $volume); - self::assertTrue(($this->client->api('zone')->getStatus('main'))['volume'] == $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); - self::assertTrue(($this->client->api('zone')->getStatus('main'))['mute'] == !$mute); + sleep(1); + self::assertEquals(!$mute, ($this->client->api('zone')->getStatus('main'))['mute']); $this->client->api('zone')->setMute('main', $mute); - self::assertTrue(($this->client->api('zone')->getStatus('main'))['volume'] == $mute); + sleep(1); + self::assertEquals($mute, ($this->client->api('zone')->getStatus('main'))['volume']); } public function testPrepareInputChange() diff --git a/test/MusicCast/Tests/ClientTest.php b/tests/ClientTest.php old mode 100644 new mode 100755 similarity index 91% rename from test/MusicCast/Tests/ClientTest.php rename to tests/ClientTest.php index 82623c7..be36417 --- a/test/MusicCast/Tests/ClientTest.php +++ b/tests/ClientTest.php @@ -1,30 +1,29 @@ options = Yaml::parse(file_get_contents(__DIR__ . '/../../env.yml')); - } - - /** * @test */ public function shouldNotHaveToPassHttpClientToConstructor() { $client = new Client($this->options); - self::assertInstanceOf(\Http\Client\HttpClient::class, $client->getHttpClient()); } + + protected function setUp() + { + $this->options = Yaml::parse(file_get_contents(__DIR__ . '/env.yml')); + } } diff --git a/tests/ControllerLiveTest.php b/tests/ControllerLiveTest.php new file mode 100644 index 0000000..d575a43 --- /dev/null +++ b/tests/ControllerLiveTest.php @@ -0,0 +1,69 @@ +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 testGetState() + { + $state = $this->controller->getState(); + self::assertNotEquals(Controller::STATE_UNKNOWN, $state); + } + + public function testGetStateDetails() + { + $state = $this->controller->getStateDetails(); + self::assertNotNull($state); + } + + protected function setUp() + { + parent::setUp(); + $this->controller = $this->network->getController(); + } +} diff --git a/tests/LiveTest.php b/tests/LiveTest.php new file mode 100644 index 0000000..6d6d2d7 --- /dev/null +++ b/tests/LiveTest.php @@ -0,0 +1,41 @@ +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->client = new Client(Yaml::parse(file_get_contents(__DIR__ . '/env.yml'))); + } +} diff --git a/tests/MockTest.php b/tests/MockTest.php new file mode 100644 index 0000000..42e63c8 --- /dev/null +++ b/tests/MockTest.php @@ -0,0 +1,111 @@ +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); + 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); + } + + 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..1921b9d --- /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()->getDevice()->getIp(); + $controller = $this->network->getControllerByIp($ip); + self::assertEquals($ip, $controller->getDevice()->getIp()); + } +} diff --git a/tests/NetworkTest.php b/tests/NetworkTest.php new file mode 100644 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 100644 index 0000000..c4b23d4 --- /dev/null +++ b/tests/SpeakerTest.php @@ -0,0 +1,68 @@ +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("localhost", $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()); + } +} diff --git a/tests/assets/dist/getDistributionInfo.json b/tests/assets/dist/getDistributionInfo.json new file mode 100644 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 100644 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 100644 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 100644 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 100644 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/system/getDeviceInfo.json b/tests/assets/system/getDeviceInfo.json new file mode 100644 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 100644 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 100644 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 100644 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 100644 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 100644 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 100644 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 100644 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 100644 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 100644 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 100644 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 100644 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 100644 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 100644 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 100644 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 100644 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 100644 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 100644 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 100644 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 100644 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 From 7256f6be28952ac28f2f743865a6ce839396019f Mon Sep 17 00:00:00 2001 From: Damien Surot Date: Sat, 14 Oct 2017 01:21:18 +0200 Subject: [PATCH 05/25] Api, Services and Tests --- test/env.yml.dist | 2 -- 1 file changed, 2 deletions(-) delete mode 100644 test/env.yml.dist diff --git a/test/env.yml.dist b/test/env.yml.dist deleted file mode 100644 index 021bcce..0000000 --- a/test/env.yml.dist +++ /dev/null @@ -1,2 +0,0 @@ -host: localhost -port: 80 From f9f74a45c9ea258baedd656d68666cf836e50e18 Mon Sep 17 00:00:00 2001 From: Damien Surot Date: Sat, 14 Oct 2017 01:46:21 +0200 Subject: [PATCH 06/25] Fix missing local test file --- tests/ClientTest.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/ClientTest.php b/tests/ClientTest.php index be36417..22abe1c 100755 --- a/tests/ClientTest.php +++ b/tests/ClientTest.php @@ -5,7 +5,7 @@ use MusicCast\Client; use Symfony\Component\Yaml\Yaml; -class ClientTest extends \PHPUnit_Framework_TestCase +class ClientTest extends LiveTest { /** @@ -24,6 +24,7 @@ public function shouldNotHaveToPassHttpClientToConstructor() protected function setUp() { + parent::setUp(); $this->options = Yaml::parse(file_get_contents(__DIR__ . '/env.yml')); } } From 59e3d98b92a4c11737ebe5f0d9dd26466ca4274a Mon Sep 17 00:00:00 2001 From: Damien Surot Date: Sat, 14 Oct 2017 01:52:18 +0200 Subject: [PATCH 07/25] Make lint.sh executable --- tests/lint.sh | 0 1 file changed, 0 insertions(+), 0 deletions(-) mode change 100644 => 100755 tests/lint.sh diff --git a/tests/lint.sh b/tests/lint.sh old mode 100644 new mode 100755 From 64c79db08189e61eef3af1fc517bb66d64023aa8 Mon Sep 17 00:00:00 2001 From: Damien Surot Date: Sat, 14 Oct 2017 18:16:03 +0200 Subject: [PATCH 08/25] Fix 5.6 compatibility --- src/Device.php | 4 ++-- src/Speaker.php | 2 +- tests/Api/SystemLiveTest.php | 2 +- tests/lint.sh | 0 4 files changed, 4 insertions(+), 4 deletions(-) mode change 100755 => 100644 tests/lint.sh diff --git a/src/Device.php b/src/Device.php index 548a6df..42a920f 100755 --- a/src/Device.php +++ b/src/Device.php @@ -74,7 +74,7 @@ public function setLogger(LoggerInterface $logger) /** * @return string */ - public function getIp(): string + public function getIp() { return $this->ip; } @@ -82,7 +82,7 @@ public function getIp(): string /** * @return Client */ - public function getClient(): Client + public function getClient() { return $this->client; } diff --git a/src/Speaker.php b/src/Speaker.php index 437b346..0be664e 100755 --- a/src/Speaker.php +++ b/src/Speaker.php @@ -32,7 +32,7 @@ public function __construct($device) /** * @return Device */ - public function getDevice(): Device + public function getDevice() { return $this->device; } diff --git a/tests/Api/SystemLiveTest.php b/tests/Api/SystemLiveTest.php index af02c75..728e4d6 100755 --- a/tests/Api/SystemLiveTest.php +++ b/tests/Api/SystemLiveTest.php @@ -25,7 +25,7 @@ public function testGetDeviceInfo() public function testGetFeatures() { self::assertArrayHasKey('system', $this->client->api('system')->getFeatures()); - self::assertArrayHasKey('func_list', ($this->client->api('system')->getFeatures())['system']); + self::assertArrayHasKey('func_list', $this->client->api('system')->getFeatures()['system']); } /** diff --git a/tests/lint.sh b/tests/lint.sh old mode 100755 new mode 100644 From 33a598687f0f8b57e30ffc464a3d6c5a60f6dc4f Mon Sep 17 00:00:00 2001 From: Damien Surot Date: Sat, 14 Oct 2017 18:18:15 +0200 Subject: [PATCH 09/25] Make lint.sh executable --- tests/lint.sh | 0 1 file changed, 0 insertions(+), 0 deletions(-) mode change 100644 => 100755 tests/lint.sh diff --git a/tests/lint.sh b/tests/lint.sh old mode 100644 new mode 100755 From f3427ff642e0c29367592119c183064d4b5c0712 Mon Sep 17 00:00:00 2001 From: Damien Surot Date: Sat, 14 Oct 2017 18:22:04 +0200 Subject: [PATCH 10/25] Fix 5.6 compatibility --- tests/Api/ZoneLiveTest.php | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/tests/Api/ZoneLiveTest.php b/tests/Api/ZoneLiveTest.php index 6e1301d..57bd708 100755 --- a/tests/Api/ZoneLiveTest.php +++ b/tests/Api/ZoneLiveTest.php @@ -37,58 +37,58 @@ public function testGetSoundProgramList() public function testSetPower() { - $power = ($this->client->api('zone')->getStatus('main'))['power']; + $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']; + $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']; + $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']); + self::assertEquals($volume, $this->client->api('zone')->getStatus('main')['volume']); } public function testSetVolume() { - $volume = ($this->client->api('zone')->getStatus('main'))['volume']; + $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']); + 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']); + self::assertEquals($volume, $this->client->api('zone')->getStatus('main')['volume']); } public function setMute() { - $mute = ($this->client->api('zone')->getStatus('main'))['mute']; + $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']); + 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']); + self::assertEquals($mute, $this->client->api('zone')->getStatus('main')['volume']); } public function testPrepareInputChange() { - $input = ($this->client->api('zone')->getStatus('main'))['input']; + $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']; + $input = $this->client->api('zone')->getStatus('main')['input']; $this->client->api('zone')->setInput('main', $input); } } From 80a1843cdf191e94b631acdbbc208fe0fc2a77e3 Mon Sep 17 00:00:00 2001 From: Damien Surot Date: Sat, 14 Oct 2017 18:29:53 +0200 Subject: [PATCH 11/25] Fix force lowest issue --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 8de1d0d..f458b67 100755 --- a/composer.json +++ b/composer.json @@ -25,7 +25,7 @@ "php-http/httplug": "^1.1", "php-http/discovery": "^1.0", "php-http/client-implementation": "^1.0", - "php-http/client-common": "^1.3", + "php-http/client-common": "^1.5", "php-http/cache-plugin": "^1.2", "symfony/yaml": "^3.2", "myclabs/php-enum": "^1.5", From 41190fc107a191829e5ba1107d77f2021509f99c Mon Sep 17 00:00:00 2001 From: Damien Surot Date: Sat, 14 Oct 2017 18:31:17 +0200 Subject: [PATCH 12/25] Fix 5.6 compatibility --- tests/Api/ZoneLiveTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Api/ZoneLiveTest.php b/tests/Api/ZoneLiveTest.php index 57bd708..2c6e61c 100755 --- a/tests/Api/ZoneLiveTest.php +++ b/tests/Api/ZoneLiveTest.php @@ -52,7 +52,7 @@ 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']); + 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']); From b2a72261b8a3a143136ce5465417e44ed4817684 Mon Sep 17 00:00:00 2001 From: Damien Surot Date: Sat, 14 Oct 2017 18:43:01 +0200 Subject: [PATCH 13/25] Fix force lowest issue --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index f458b67..8de1d0d 100755 --- a/composer.json +++ b/composer.json @@ -25,7 +25,7 @@ "php-http/httplug": "^1.1", "php-http/discovery": "^1.0", "php-http/client-implementation": "^1.0", - "php-http/client-common": "^1.5", + "php-http/client-common": "^1.3", "php-http/cache-plugin": "^1.2", "symfony/yaml": "^3.2", "myclabs/php-enum": "^1.5", From 11f4c87dd424afafaf213389beeeea5d036c2293 Mon Sep 17 00:00:00 2001 From: Damien Surot Date: Sat, 14 Oct 2017 18:48:58 +0200 Subject: [PATCH 14/25] Fix force lowest issue --- composer.json | 3 +++ 1 file changed, 3 insertions(+) diff --git a/composer.json b/composer.json index 8de1d0d..2ee124d 100755 --- a/composer.json +++ b/composer.json @@ -57,5 +57,8 @@ "branch-alias": { "dev-master": "1.0-dev" } + }, + "conflict": { + "php-http/message-factory": "< 1.0.2" } } From f655716662789a3fa81494b89c1d4f9cb97a40d2 Mon Sep 17 00:00:00 2001 From: Damien Surot Date: Sat, 14 Oct 2017 18:55:42 +0200 Subject: [PATCH 15/25] Fix force lowest issue in php 7.2 --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 2ee124d..8772d34 100755 --- a/composer.json +++ b/composer.json @@ -34,7 +34,7 @@ "require-dev": { "squizlabs/php_codesniffer": "^2.7", "mockery/mockery": "^0.9.4", - "phpunit/phpunit": "^4.0 || ^5.5", + "phpunit/phpunit": "^5.7.14", "phpunit/php-code-coverage": "^4", "php-http/guzzle6-adapter": "^1.0", "guzzlehttp/psr7": "^1.2", From 2fcb45e0f53ad1787e40798cd46414317c9eb891 Mon Sep 17 00:00:00 2001 From: Damien Surot Date: Sun, 15 Oct 2017 19:42:29 +0200 Subject: [PATCH 16/25] Add/Remove speaker feature --- src/Api/Distribution.php | 34 +++--- src/Controller.php | 87 +++++++-------- src/Network.php | 7 +- src/Speaker.php | 45 +++++++- src/State.php | 18 ++- src/Tracks/Track.php | 5 +- tests/Api/DistributionLiveTest.php | 3 +- tests/ControllerLiveTest.php | 169 +++++++++++++++++++++++++++-- tests/LiveTest.php | 5 +- tests/MockTest.php | 2 +- tests/SpeakerTest.php | 2 +- 11 files changed, 280 insertions(+), 97 deletions(-) diff --git a/src/Api/Distribution.php b/src/Api/Distribution.php index c482df7..9bb2e64 100755 --- a/src/Api/Distribution.php +++ b/src/Api/Distribution.php @@ -38,19 +38,14 @@ public function startDistribution($num = 0) * @param string $server_ip_addr * @return array */ - public function setClientInfo($groupId = '', array $zone = null, $server_ip_addr = null) + public function setClientInfo($groupId = '', $zone = 'main', $server_ip_addr = null) { $info = array('group_id' => $groupId); - if ($zone != null) { - $info['zone'] = $zone; - } + $info['zone'] = array($zone); if ($server_ip_addr != null) { $info['server_ip_address'] = $server_ip_addr; } - return $this->callPost( - 'setCientInfo', - $info - ); + return $this->callPost('setClientInfo', $info); } /** @@ -59,10 +54,7 @@ public function setClientInfo($groupId = '', array $zone = null, $server_ip_addr */ public function setGroupName($name) { - return $this->callPost( - 'setGroupName', - array('name' => $name) - ); + return $this->callPost('setGroupName', array('name' => $name)); } private function callPost($path, array $parameters = array()) @@ -71,7 +63,6 @@ private function callPost($path, array $parameters = array()) } - /** * @param $groupId * @param $type @@ -79,11 +70,18 @@ private function callPost($path, array $parameters = array()) * @param array $client_list Clients IP * @return array */ - public function setServerInfo($groupId, $type, $zone, array $client_list = array()) + public function setServerInfo($groupId, $type = null, $zone = null, array $client_list = null) { - return $this->callPost( - 'setServerInfo', - array('group_id' => $groupId, 'type' => $type, 'zone' => $zone, 'client_list' => $client_list) - ); + $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/src/Controller.php b/src/Controller.php index c2ffb82..0d48b2a 100755 --- a/src/Controller.php +++ b/src/Controller.php @@ -2,8 +2,6 @@ namespace MusicCast; -use MusicCast\Exception\NotImplementedException; - /** * Allows interaction with the groups of speakers. * @@ -52,6 +50,7 @@ class Controller extends Speaker private $speakers; private $playlists; private $favorites; + private $distribution_id; /** * Create a Controller instance from a speaker. @@ -60,7 +59,7 @@ class Controller extends Speaker * * @param Speaker $speaker */ - public function __construct(Speaker $speaker, Network $network) + public function __construct(Speaker $speaker, Network $network, $distribution_id) { parent::__construct($speaker->device); if (!$speaker->isCoordinator()) { @@ -72,6 +71,7 @@ public function __construct(Speaker $speaker, Network $network) $this->group_name = $this->getName(); $this->uuid = $this->getUuid(); $this->speakers = $this->getSpeakers(); + $this->distribution_id = $distribution_id; } /** @@ -87,7 +87,8 @@ public function getSpeakers() $group = []; $speakers = $this->network->getSpeakers(); foreach ($speakers as $speaker) { - if ($speaker->getGroup() === $this->getGroup()) { + if ($speaker->getGroup() === $this->getGroup() && + !(is_numeric($speaker->getGroup()) || intval($speaker->getGroup()) == 0)) { $group[] = $speaker; } } @@ -135,7 +136,7 @@ public function getState() * * @return string */ - private function getStateName() + public function getStateName() { return $this->device->getClient()->api('netusb')->getPlayInfo()['playback']; } @@ -147,16 +148,7 @@ private function getStateName() */ public function getStateDetails() { - $data = $this->device->getClient()->api('netusb')->getPlayInfo(); - - - $state = State::createFromJson($data, $this); - - $state->duration = $data["total_time"]; - $state->position = $data["play_time"]; - - - return $state; + return State::createFromJson($this); } /** @@ -256,17 +248,19 @@ public function addSpeaker(Speaker $speaker) if ($speaker->getUuid() === $this->getUuid()) { return $this; } - $this->speakers[$speaker->getDevice()->getIp()] = $speaker; - $nbSpeaker = sizeof($this->speakers); - $speaker->device->getClient()->api('dist')->setClientInfo($speaker->getGroup(), 'main', $this->ip); - $this->device->getClient()->api('dist')->setServerInfo($speaker->getGroup(), 'add', 'main', [$speaker->ip]); - $this->device->getClient()->api('dist')->startDistribution($nbSpeaker); - - if ($nbSpeaker == 1) { - $this->device->getClient()->api('dist')->setGroupName($this->name . '+' . $speaker->getName()); - return $this; + if (!in_array($speaker, $this->speakers)) { + $group = $this->getGroup(); + if ($group == Speaker::NO_GROUP) { + $group = md5($this->device->getIp()); + } + if ($speaker->getGroup() == Speaker::NO_GROUP) { + $speaker->device->getClient()->api('dist')->setClientInfo($group, 'main', $this->device->getIp()); + $this->device->getClient()->api('dist')-> + setServerInfo($group, 'add', 'main', array($speaker->getDevice()->getIp())); + $this->device->getClient()->api('dist')->startDistribution($this->distribution_id); + $this->speakers[] = $speaker; + } } - $this->device->getClient()->api('dist')->setGroupName($this->name . '+' . $nbSpeaker . ' rooms'); return $this; } @@ -283,19 +277,26 @@ public function removeSpeaker(Speaker $speaker) if ($speaker->getUuid() === $this->getUuid()) { return $this; } - unset($this->speakers[$speaker->getDevice()->getIp()]); - $nbSpeaker = sizeof($this->speakers); - $this->device->getClient()->api('dist')->setServerInfo($speaker->getGroup(), 'remove', 'main', [$speaker->ip]); - $speaker->device->getClient()->api('dist')->setClientInfo(); - $this->device->getClient()->api('dist')->startDistribution($nbSpeaker); - + if (in_array($speaker, $this->speakers)) { + unset($this->speakers[array_search($speaker, $this->speakers)]); + $speaker->device->getClient()->api('dist')->setClientInfo(); + $this->device->getClient()->api('dist')-> + setServerInfo($this->getGroup(), 'remove', 'main', array($speaker->getDevice()->getIp())); + $this->device->getClient()->api('dist')->startDistribution($this->distribution_id); + } + return $this; + } - if ($nbSpeaker == 1) { - $this->device->getClient()->api('dist')->setGroupName($this->name . '+' - . array_values($this->speakers)[0]->getName()); - return $this; + /** + * Removes all speakers from the group of this Controller. + * + * @return static + */ + public function removeAllSpeakers() + { + foreach ($this->getSpeakers() as $speaker) { + $this->removeSpeaker($speaker); } - $this->device->getClient()->api('dist')->setGroupName($this->name . '+' . $nbSpeaker . ' rooms'); return $this; } @@ -390,22 +391,10 @@ public function toggleShuffle() */ public function getQueue() { - throw new NotImplementedException(); + return $this->device->getClient()->api('netusb')->getPlayQueue(); } - /** - * Grab the current state of the Controller (including it's queue and playing attributes). - * - * @param bool $pause Whether to pause the controller or not - * - * @return ControllerState - */ - public function exportState($pause = true) - { - throw new NotImplementedException(); - } - /** * Check if a playlist with the specified name exists on this network. * diff --git a/src/Network.php b/src/Network.php index 5fc6739..d4f9855 100755 --- a/src/Network.php +++ b/src/Network.php @@ -135,11 +135,12 @@ public function getControllers() { $controllers = []; $speakers = $this->getSpeakers(); + $index = 0; foreach ($speakers as $speaker) { if (!$speaker->isCoordinator()) { continue; } - $controllers[$speaker->getDevice()->getIp()] = new Controller($speaker, $this); + $controllers[$speaker->getDevice()->getIp()] = new Controller($speaker, $this, $index++); } return $controllers; } @@ -302,9 +303,9 @@ public function getControllerByIp($ip) if (!array_key_exists($ip, $speakers)) { throw new \InvalidArgumentException("No speaker found for the IP address '{$ip}'"); } - $group = $speakers[$ip]->getGroup(); + foreach ($this->getControllers() as $controller) { - if ($controller->getGroup() === $group) { + if ($controller->getDevice()->getIp() === $ip) { return $controller; } } diff --git a/src/Speaker.php b/src/Speaker.php index 0be664e..59aaae2 100755 --- a/src/Speaker.php +++ b/src/Speaker.php @@ -16,6 +16,7 @@ class Speaker protected $model; protected $name; protected $device; + const NO_GROUP = "NoGroup"; /** * Create an instance of the Speaker class. @@ -62,8 +63,8 @@ public function getName() public function getGroup() { $group = $this->device->getClient()->api('dist')->getDistributionInfo()['group_id']; - if (is_numeric($group)) { - $group = $this->device->getIp(); + if (is_numeric($group) && intval($group == 0)) { + $group = self::NO_GROUP; } return $group; } @@ -76,7 +77,7 @@ public function getGroup() public function isCoordinator() { $role = $this->device->getClient()->api('dist')->getDistributionInfo()['role']; - return $role == 'server' || $role == 'none'; + return $role == 'server' || $this->getGroup() == Speaker::NO_GROUP; } /** @@ -160,4 +161,42 @@ public function mute($mute = true) $this->device->getClient()->api('zone')->setMute('main', $mute); return $this; } + + + /** + * Power On this speaker. + * + * @return static + */ + public function powerOn() + { + return $this->setPower('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->device->getClient()->api('zone')->setPower('main', $power); + return $this; + } } diff --git a/src/State.php b/src/State.php index 0ea069d..565d105 100755 --- a/src/State.php +++ b/src/State.php @@ -17,6 +17,11 @@ class State extends \MusicCast\Tracks\Track */ public $position = ""; + /** + * @var int $queueNumber The zero-based number of the track in the queue. + */ + public $queueNumber = 0; + /** * Create a Track object. */ @@ -28,16 +33,19 @@ public function __construct() /** * Update the track properties using an xml element. * - * @param array $json The json element representing the track meta data. + * @param Device $device The device. * @param Controller $controller A controller instance on the playlist's network * * @return static */ - public static function createFromJson($json, Controller $controller) + public static function createFromJson(Controller $controller) { - $track = parent::createFromJson($json, $controller); - $track->duration = "02:30"; - $track->position = ''; + $data = $controller->getDevice()->getClient()->api('netusb')->getPlayInfo(); + $track = parent::createFromJson($controller); + $track->duration = $data['total_time']; + $track->position = $data['play_time']; + $data = $controller->getDevice()->getClient()->api('netusb')->getPlayQueue(); + $track->queueNumber = $data['playing_index']; return $track; } } diff --git a/src/Tracks/Track.php b/src/Tracks/Track.php index 6b486e6..191d1eb 100644 --- a/src/Tracks/Track.php +++ b/src/Tracks/Track.php @@ -45,8 +45,9 @@ public function __construct() { } - public static function createFromJson($json, Controller $controller) + public static function createFromJson(Controller $controller) { + $json = $controller->getDevice()->getClient()->api('netusb')->getPlayInfo(); $track = new Track(); $track->title = $json['track']; $track->input = $json['input']; @@ -58,7 +59,7 @@ public static function createFromJson($json, Controller $controller) if ($art = $json['albumart_url']) { if (substr($art, 0, 4) !== "http") { $art = ltrim($art, "/"); - $art = sprintf("http://%s:80/%s", $controller->getIp(), $art); + $art = sprintf("http://%s:80/%s", $controller->getDevice()->getIp(), $art); } $track->albumArt = $art; } diff --git a/tests/Api/DistributionLiveTest.php b/tests/Api/DistributionLiveTest.php index 1c37562..8072d14 100755 --- a/tests/Api/DistributionLiveTest.php +++ b/tests/Api/DistributionLiveTest.php @@ -34,8 +34,7 @@ public function testStartDistribution() */ public function testSetCientInfo() { - //self::assertArrayHasKey('???', $this->client->api('dist')->setCientInfo()); - $this->markTestSkipped('dist/setCientInfo method not implemented'); + self::assertArrayHasKey('response_code', $this->client->api('dist')->setClientInfo()); } /** diff --git a/tests/ControllerLiveTest.php b/tests/ControllerLiveTest.php index d575a43..009f9e1 100644 --- a/tests/ControllerLiveTest.php +++ b/tests/ControllerLiveTest.php @@ -9,6 +9,7 @@ namespace MusicCastTests; use MusicCast\Controller; +use MusicCast\Speaker; class ControllerLiveTest extends \MusicCastTests\LiveTest { @@ -17,6 +18,148 @@ class ControllerLiveTest extends \MusicCastTests\LiveTest */ protected $controller; + + protected function setUp() + { + parent::setUp(); + $this->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->getDevice()->getIp(), $controller->getDevice()->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()) { + $controller = 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() + { + $keys = ["title", "artist", "album", "queueNumber", "duration", "position", "input"]; + $state = $this->controller->getStateDetails(); + foreach ($keys as $key) { + $this->assertObjectHasAttribute($key, $state); + if (in_array($key, ["queueNumber"])) { + $this->assertInternalType("integer", $state->$key); + } + } + } + + + public function testNext() + { + $controller = $this->controller; + $number = $controller->getStateDetails()->queueNumber; + $controller->next(); + $this->assertSame($controller->getStateDetails()->queueNumber, $number + 1); + } + + + public function testPrevious() + { + $controller = $this->controller; + $number = $controller->getStateDetails()->queueNumber; + $controller->previous(); + $this->assertSame($controller->getStateDetails()->queueNumber, $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); + time_nanosleep(0, 500 * 1000000);//500ms + foreach ($controller->getSpeakers() as $speaker) { + $this->assertSame($volume - 3, $speaker->getVolume()); + } + } + + + public function testAdjustVolume1() + { + $controller = $this->controller; + $volume = $controller->getVolume(); + $controller->setVolume($volume - 3); + time_nanosleep(0, 500 * 1000000);//500ms + $controller->adjustVolume(3); + time_nanosleep(0, 500 * 1000000);//500ms + foreach ($controller->getSpeakers() as $speaker) { + $this->assertSame($volume, $speaker->getVolume()); + } + } + + + public function testAdjustVolume2() + { + $controller = $this->controller; + $volume = $controller->getVolume(); + $controller->setVolume($volume + 3); + time_nanosleep(0, 500 * 1000000);//500ms + $controller->adjustVolume(3 * -1); + time_nanosleep(0, 500 * 1000000);//500ms + foreach ($controller->getSpeakers() as $speaker) { + $this->assertSame($volume, $speaker->getVolume()); + } + } + public function testGetPlaylists() { $playlists = $this->controller->getPlaylists(); @@ -49,21 +192,23 @@ public function testGetFavoriteBy() self::assertEquals($seek, $favorite); } - public function testGetState() - { - $state = $this->controller->getState(); - self::assertNotEquals(Controller::STATE_UNKNOWN, $state); - } - - public function testGetStateDetails() + public function testAddSpeaker() { - $state = $this->controller->getStateDetails(); - self::assertNotNull($state); + 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; + } + } } - protected function setUp() + public function testRemoveSpeaker() { - parent::setUp(); - $this->controller = $this->network->getController(); + foreach ($this->controller->getSpeakers() as $speaker) { + $this->controller->removeSpeaker($speaker); + $this->assertNotSame($speaker->getGroup(), $this->controller->getGroup()); + return; + } } } diff --git a/tests/LiveTest.php b/tests/LiveTest.php index 6d6d2d7..f1e65cb 100644 --- a/tests/LiveTest.php +++ b/tests/LiveTest.php @@ -21,6 +21,8 @@ abstract class LiveTest extends \PHPUnit_Framework_TestCase protected $client; + protected $options; + protected function setUp() { $this->network = new Network(); @@ -36,6 +38,7 @@ protected function setUp() } catch (\Exception $e) { $this->markTestSkipped("No speakers found on the current network"); } - $this->client = new Client(Yaml::parse(file_get_contents(__DIR__ . '/env.yml'))); + $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 index 42e63c8..871550a 100644 --- a/tests/MockTest.php +++ b/tests/MockTest.php @@ -90,7 +90,7 @@ private static function mockMethod($api, $apiName, $method) protected function getController() { $speaker = $this->getSpeaker(); - return new Controller($speaker, $this->network); + return new Controller($speaker, $this->network, 0); } protected function getSpeaker() diff --git a/tests/SpeakerTest.php b/tests/SpeakerTest.php index c4b23d4..ac42ea6 100644 --- a/tests/SpeakerTest.php +++ b/tests/SpeakerTest.php @@ -43,7 +43,7 @@ public function testGetName() public function testGetGroup() { - self::assertEquals("localhost", $this->speaker->getGroup()); + self::assertEquals(Speaker::NO_GROUP, $this->speaker->getGroup()); } public function testIsCoordinator() From 7d14bacf5c9bd23e7143c2b5cc9820b03c0fe3e3 Mon Sep 17 00:00:00 2001 From: Damien Surot Date: Sat, 21 Oct 2017 11:29:14 +0200 Subject: [PATCH 17/25] API Call refacto + more tests --- .coveralls.yml | 0 CREDITS.md | 5 + LICENSE | 21 ++ README.md | 33 +- composer.json | 11 +- phpunit.xml.dist | 0 src/Api/Distribution.php | 2 +- src/Api/Event.php | 7 +- src/Api/NetworkUSB.php | 14 +- src/Api/System.php | 1 + src/Api/Zone.php | 1 + src/Cache.php | 1 + src/Client.php | 20 +- src/Controller.php | 104 ++++--- src/Device.php | 38 ++- src/Favorite.php | 9 +- src/Network.php | 37 ++- src/Playlist.php | 3 +- src/Queue.php | 70 +++++ src/Speaker.php | 32 +- src/State.php | 44 +-- src/Tracks/Track.php | 76 +++-- tests/Api/DistributionLiveTest.php | 2 +- tests/ControllerLiveTest.php | 33 +- tests/ControllerTest.php | 133 ++++++++ tests/DeviceTest.php | 73 +++++ tests/LiveTest.php | 4 +- tests/MockTest.php | 17 ++ tests/NetworkLiveTest.php | 4 +- tests/NetworkTest.php | 0 tests/SpeakerTest.php | 4 +- tests/assets/dist/getDistributionInfo.json | 0 tests/assets/dist/setClientInfo.json | 0 tests/assets/dist/setGroupName.json | 0 tests/assets/dist/setServerInfo.json | 0 tests/assets/dist/startDistribution.json | 0 tests/assets/netusb/getAccountStatus.json | 45 +++ tests/assets/netusb/getListInfo.json | 23 ++ tests/assets/netusb/getMcPlaylist.json | 32 ++ tests/assets/netusb/getMcPlaylistName.json | 10 + tests/assets/netusb/getPlayInfo.json | 27 ++ tests/assets/netusb/getPlayQueue.json | 33 ++ tests/assets/netusb/getPresetInfo.json | 171 +++++++++++ tests/assets/netusb/getRecentInfo.json | 285 ++++++++++++++++++ tests/assets/netusb/manageMcPlaylist.json | 3 + tests/assets/netusb/recallPreset.json | 3 + tests/assets/netusb/setPlayback.json | 3 + tests/assets/netusb/storePreset.json | 3 + tests/assets/netusb/toggleRepeat.json | 3 + tests/assets/netusb/toggleShuffle.json | 3 + tests/assets/system/getDeviceInfo.json | 0 .../assets/system/getDisklavierSettings.json | 0 tests/assets/system/getFeatures.json | 0 tests/assets/system/getFuncStatus.json | 0 tests/assets/system/getLocationInfo.json | 0 tests/assets/system/getMusicCastTreeInfo.json | 0 tests/assets/system/getNameText.json | 0 tests/assets/system/getNetworkStatus.json | 0 tests/assets/system/getTag.json | 0 .../assets/system/isNewFirmwareAvailable.json | 0 tests/assets/system/sendIrCode.json | 0 tests/assets/zone/getSignalInfo.json | 0 tests/assets/zone/getSoundProgramList.json | 0 tests/assets/zone/getStatus.json | 0 tests/assets/zone/prepareInputChange.json | 0 tests/assets/zone/setInput.json | 0 tests/assets/zone/setMute.json | 0 tests/assets/zone/setPower.json | 0 tests/assets/zone/setSleep.json | 0 tests/assets/zone/setVolume.json | 0 tests/env.yml.dist | 0 71 files changed, 1256 insertions(+), 187 deletions(-) mode change 100644 => 100755 .coveralls.yml create mode 100755 CREDITS.md create mode 100755 LICENSE mode change 100644 => 100755 phpunit.xml.dist mode change 100644 => 100755 src/Favorite.php mode change 100644 => 100755 src/Playlist.php create mode 100755 src/Queue.php mode change 100644 => 100755 src/Tracks/Track.php mode change 100644 => 100755 tests/ControllerLiveTest.php create mode 100755 tests/ControllerTest.php create mode 100755 tests/DeviceTest.php mode change 100644 => 100755 tests/LiveTest.php mode change 100644 => 100755 tests/MockTest.php mode change 100644 => 100755 tests/NetworkTest.php mode change 100644 => 100755 tests/SpeakerTest.php mode change 100644 => 100755 tests/assets/dist/getDistributionInfo.json mode change 100644 => 100755 tests/assets/dist/setClientInfo.json mode change 100644 => 100755 tests/assets/dist/setGroupName.json mode change 100644 => 100755 tests/assets/dist/setServerInfo.json mode change 100644 => 100755 tests/assets/dist/startDistribution.json create mode 100755 tests/assets/netusb/getAccountStatus.json create mode 100755 tests/assets/netusb/getListInfo.json create mode 100755 tests/assets/netusb/getMcPlaylist.json create mode 100755 tests/assets/netusb/getMcPlaylistName.json create mode 100755 tests/assets/netusb/getPlayInfo.json create mode 100755 tests/assets/netusb/getPlayQueue.json create mode 100755 tests/assets/netusb/getPresetInfo.json create mode 100755 tests/assets/netusb/getRecentInfo.json create mode 100755 tests/assets/netusb/manageMcPlaylist.json create mode 100755 tests/assets/netusb/recallPreset.json create mode 100755 tests/assets/netusb/setPlayback.json create mode 100755 tests/assets/netusb/storePreset.json create mode 100755 tests/assets/netusb/toggleRepeat.json create mode 100755 tests/assets/netusb/toggleShuffle.json mode change 100644 => 100755 tests/assets/system/getDeviceInfo.json mode change 100644 => 100755 tests/assets/system/getDisklavierSettings.json mode change 100644 => 100755 tests/assets/system/getFeatures.json mode change 100644 => 100755 tests/assets/system/getFuncStatus.json mode change 100644 => 100755 tests/assets/system/getLocationInfo.json mode change 100644 => 100755 tests/assets/system/getMusicCastTreeInfo.json mode change 100644 => 100755 tests/assets/system/getNameText.json mode change 100644 => 100755 tests/assets/system/getNetworkStatus.json mode change 100644 => 100755 tests/assets/system/getTag.json mode change 100644 => 100755 tests/assets/system/isNewFirmwareAvailable.json mode change 100644 => 100755 tests/assets/system/sendIrCode.json mode change 100644 => 100755 tests/assets/zone/getSignalInfo.json mode change 100644 => 100755 tests/assets/zone/getSoundProgramList.json mode change 100644 => 100755 tests/assets/zone/getStatus.json mode change 100644 => 100755 tests/assets/zone/prepareInputChange.json mode change 100644 => 100755 tests/assets/zone/setInput.json mode change 100644 => 100755 tests/assets/zone/setMute.json mode change 100644 => 100755 tests/assets/zone/setPower.json mode change 100644 => 100755 tests/assets/zone/setSleep.json mode change 100644 => 100755 tests/assets/zone/setVolume.json mode change 100644 => 100755 tests/env.yml.dist diff --git a/.coveralls.yml b/.coveralls.yml old mode 100644 new mode 100755 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 index f6b5ebd..b7891e0 100755 --- 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) @@ -30,22 +29,17 @@ $ composer require samvdb/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); - +$musicCast = new \duncan3dc\Sonos\Network; +$controllers = $musicCast->getControllers(); +foreach ($controllers as $controller) { + echo $controller->getGroup()\n"; + echo "\tState: " . $controller->getStateName() . "\n"; + $controller->play(); +} ``` ## Enabling events @@ -89,11 +83,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 index 8772d34..f341a64 100755 --- a/composer.json +++ b/composer.json @@ -1,11 +1,12 @@ { - "name": "samvdb/php-musiccast-api", - "description": "PHP Wrapper for Yamaha MusicCast API", + "name": "granddam/php-musiccast-api", + "description": "A PHP library for interacting with Yamaha MusicCast speakers", "type": "library", "keywords": [ "yamaha", "musiccast", - "api" + "api", + "client" ], "license": "MIT", "authors": [ @@ -29,7 +30,8 @@ "php-http/cache-plugin": "^1.2", "symfony/yaml": "^3.2", "myclabs/php-enum": "^1.5", - "cache/doctrine-adapter": "^1.0" + "cache/doctrine-adapter": "^1.0", + "doctrine/cache": "^1.4" }, "require-dev": { "squizlabs/php_codesniffer": "^2.7", @@ -41,7 +43,6 @@ "phpro/grumphp": "^0.11.1", "nikic/php-parser": "^3.0", "friendsofphp/php-cs-fixer": "^2.0", - "doctrine/cache": "^1.4", "satooshi/php-coveralls": "^1.0" }, "autoload": { diff --git a/phpunit.xml.dist b/phpunit.xml.dist old mode 100644 new mode 100755 diff --git a/src/Api/Distribution.php b/src/Api/Distribution.php index 9bb2e64..fdd41ee 100755 --- a/src/Api/Distribution.php +++ b/src/Api/Distribution.php @@ -34,7 +34,7 @@ public function startDistribution($num = 0) /** * @param string $groupId - * @param array $zone + * @param string $zone * @param string $server_ip_addr * @return array */ diff --git a/src/Api/Event.php b/src/Api/Event.php index 85b1501..f1d36fd 100755 --- a/src/Api/Event.php +++ b/src/Api/Event.php @@ -10,6 +10,7 @@ class Event extends AbstractApi { /** + * * Event notification timeouts in 10 minutes if no further event request is sent from an IP address * set as event receiving device. If another request is made within 10 minutes of previous request, * the timeout duration is reset and extended. @@ -17,6 +18,10 @@ class Event extends AbstractApi * registered device using X-AppPort. * * The default port is 41100 + * @param int $port + * @param string $appName + * @param string $version + * @return array|string */ public function subscribe($port = 41100, $appName = 'MusicCast', $version = '1') { @@ -27,7 +32,7 @@ public function subscribe($port = 41100, $appName = 'MusicCast', $version = '1') // $plugin = $this->client->getPlugin(AddBasePath::class); // $this->client->removePlugin(AddBasePath::class); - return $this->get('', [], $headers); + return $this->get('', $headers); // $this->client->addPlugin($plugin); } } diff --git a/src/Api/NetworkUSB.php b/src/Api/NetworkUSB.php index a4a989c..7bec23b 100755 --- a/src/Api/NetworkUSB.php +++ b/src/Api/NetworkUSB.php @@ -229,9 +229,9 @@ public function switchAccount($input, $index, $timeout) /** * @param $input - * @param $itype + * @param $type * @param $timeout - * @return array + * @return array|string */ public function getServiceInfo($input, $type, $timeout) { @@ -248,7 +248,11 @@ public function getMcPlaylistName() } /** - * @return array + * @param $bank + * @param $type + * @param int $index + * @param string $zone + * @return array|string */ public function manageMcPlaylist($bank, $type, $index = 0, $zone = 'main') { @@ -257,7 +261,9 @@ public function manageMcPlaylist($bank, $type, $index = 0, $zone = 'main') } /** - * @return array + * @param $bank + * @param int $index + * @return array|string */ public function getMcPlaylist($bank, $index = 0) { diff --git a/src/Api/System.php b/src/Api/System.php index aaaf0ad..4254460 100755 --- a/src/Api/System.php +++ b/src/Api/System.php @@ -94,6 +94,7 @@ public function getNameText() } /** + * @param string $type * @return array */ public function isNewFirmwareAvailable($type = 'network') diff --git a/src/Api/Zone.php b/src/Api/Zone.php index 8256764..69f637c 100755 --- a/src/Api/Zone.php +++ b/src/Api/Zone.php @@ -142,6 +142,7 @@ public function prepareInputChange($zone, $input) } /** + * @param $zone * @return array|string */ public function getSignalInfo($zone) diff --git a/src/Cache.php b/src/Cache.php index 63db75f..327694e 100755 --- a/src/Cache.php +++ b/src/Cache.php @@ -13,6 +13,7 @@ /** * A cache provider. + * @author Damien Surot */ class Cache extends DoctrineCachePool { diff --git a/src/Client.php b/src/Client.php index 4722c04..bb94aa7 100755 --- a/src/Client.php +++ b/src/Client.php @@ -1,4 +1,5 @@ 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 index 0d48b2a..a7e98b8 100755 --- a/src/Controller.php +++ b/src/Controller.php @@ -7,6 +7,7 @@ * * Although sometimes a Controller is synonymous with a Speaker, when speakers are grouped together * only the coordinator can receive events (play/pause/etc) + * @author Damien Surot */ class Controller extends Speaker { @@ -45,11 +46,28 @@ class Controller extends Speaker * @var Network $network The network instance this Controller is part of. */ protected $network; + + /** + * @var string + */ private $group; - private $uuid; + /** + * @var Speaker[] + */ private $speakers; + /** + * @var + */ private $playlists; + + /** + * @var + */ private $favorites; + + /** + * @var + */ private $distribution_id; /** @@ -58,6 +76,8 @@ class Controller extends 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) { @@ -68,8 +88,6 @@ public function __construct(Speaker $speaker, Network $network, $distribution_id } $this->network = $network; $this->group = $this->getGroup(); - $this->group_name = $this->getName(); - $this->uuid = $this->getUuid(); $this->speakers = $this->getSpeakers(); $this->distribution_id = $distribution_id; } @@ -138,7 +156,7 @@ public function getState() */ public function getStateName() { - return $this->device->getClient()->api('netusb')->getPlayInfo()['playback']; + return $this->call('netusb', 'getPlayInfo')['playback']; } /** @@ -148,7 +166,8 @@ public function getStateName() */ public function getStateDetails() { - return State::createFromJson($this); + $json = $this->call('netusb', 'getPlayInfo'); + return State::buildState($json, $this->getIp()); } /** @@ -183,7 +202,7 @@ public function play() private function setPlayback($playback) { - return $this->device->getClient()->api('netusb')->setPlayback($playback); + return $this->call('netusb', 'setPlayback', [$playback]); } /** @@ -233,7 +252,7 @@ public function previous() */ public function getMediaInfo() { - return $this->device->getClient()->api('netusb')->getPlayInfo()['input']; + return $this->call('netusb', 'getPlayInfo')['input']; } /** @@ -254,10 +273,13 @@ public function addSpeaker(Speaker $speaker) $group = md5($this->device->getIp()); } if ($speaker->getGroup() == Speaker::NO_GROUP) { - $speaker->device->getClient()->api('dist')->setClientInfo($group, 'main', $this->device->getIp()); - $this->device->getClient()->api('dist')-> - setServerInfo($group, 'add', 'main', array($speaker->getDevice()->getIp())); - $this->device->getClient()->api('dist')->startDistribution($this->distribution_id); + $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]); $this->speakers[] = $speaker; } } @@ -279,10 +301,13 @@ public function removeSpeaker(Speaker $speaker) } if (in_array($speaker, $this->speakers)) { unset($this->speakers[array_search($speaker, $this->speakers)]); - $speaker->device->getClient()->api('dist')->setClientInfo(); - $this->device->getClient()->api('dist')-> - setServerInfo($this->getGroup(), 'remove', 'main', array($speaker->getDevice()->getIp())); - $this->device->getClient()->api('dist')->startDistribution($this->distribution_id); + $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; } @@ -336,6 +361,14 @@ public function adjustVolume($adjust) return $this; } + public function isStreaming() + { + $input = $this->call('zone', 'getStatus', ['main'])['input']; + return $input == "tuner" || strpos("hdmi", $input) != false || strpos("av", $input) != false + || strpos("aux", $input) != false || strpos("audio", $input) != false + || strpos("bluetooth", $input) != false; + } + /** * Check if repeat is currently active. * @@ -343,20 +376,18 @@ public function adjustVolume($adjust) */ public function getRepeat() { - return $this->device->getClient()->api('netusb')->getPlayInfo()['repeat'] != 'off'; + return $this->call('netusb', 'getPlayInfo')['repeat'] != 'off'; } /** * Turn repeat mode on or off. * - * @param bool $repeat Whether repeat should be on or not - * * @return static */ public function toggleRepeat() { - return $this->device->getClient()->api('netusb')->toggleRepeat(); + return $this->call('netusb', 'toggleRepeat'); } @@ -367,20 +398,17 @@ public function toggleRepeat() */ public function getShuffle() { - return $this->device->getClient()->api('netusb')->getPlayInfo()['shuffle'] != 'off'; + return $this->call('netusb', 'getPlayInfo')['shuffle'] != 'off'; } - /** * Turn shuffle mode on or off. * - * @param bool $shuffle Whether shuffle should be on or not - * * @return static */ public function toggleShuffle() { - return $this->device->getClient()->api('netusb')->toggleShuffle(); + return $this->call('netusb', 'toggleShuffle'); } @@ -391,7 +419,7 @@ public function toggleShuffle() */ public function getQueue() { - return $this->device->getClient()->api('netusb')->getPlayQueue(); + return new Queue($this->call('netusb', 'getPlayQueue')); } @@ -400,7 +428,7 @@ public function getQueue() * * If no case-sensitive match is found it will return a case-insensitive match. * - * @param string The name of the playlist + * @param string $name The name of the playlist * * @return bool */ @@ -428,11 +456,11 @@ public function getPlaylists() if (is_array($this->playlists)) { return $this->playlists; } - $playlist_names = $this->device->getClient()->api('netusb')->getMcPlaylistName()['name_list']; + $playlist_names = $this->call('netusb', 'getMcPlaylistName')['name_list']; $playlists = []; $index = 1; foreach ($playlist_names as $playlist_name) { - $playlists[] = new Playlist($index++, $playlist_name, $this); + $playlists[$playlist_name] = new Playlist($index++, $playlist_name, $this); } return $this->playlists = $playlists; } @@ -442,7 +470,7 @@ public function getPlaylists() * * If no case-sensitive match is found it will return a case-insensitive match. * - * @param string The name of the playlist + * @param string $name The name of the playlist * * @return Playlist|null */ @@ -461,12 +489,13 @@ public function getPlaylistByName($name) if ($roughMatch) { return $roughMatch; } + return null; } /** * Get the playlist with the specified id. * - * @param int The ID of the playlist + * @param int $id The ID of the playlist * * @return Playlist */ @@ -478,6 +507,7 @@ public function getPlaylistById($id) return $playlist; } } + return null; } /** @@ -485,11 +515,11 @@ public function getPlaylistById($id) * * If no case-sensitive match is found it will return a case-insensitive match. * - * @param string The name of the playlist + * @param string $name The name of the playlist * * @return bool */ - public function hasFavorites($name) + public function hasFavorite($name) { $favorites = $this->getFavorites(); foreach ($favorites as $item) { @@ -513,13 +543,13 @@ public function getFavorites() if (is_array($this->favorites)) { return $this->favorites; } - $favorites_info = $this->device->getClient()->api('netusb')->getPresetInfo()['preset_info']; + $favorites_info = $this->call('netusb', 'getPresetInfo')['preset_info']; $favorites = []; $index = 0; foreach ($favorites_info as $item) { $index++; if ($item['text'] != '') { - $favorites[] = new Favorite($index, $item, $this); + $favorites[$item['text']] = new Favorite($index, $item, $this); } } return $this->favorites = $favorites; @@ -530,7 +560,7 @@ public function getFavorites() * * If no case-sensitive match is found it will return a case-insensitive match. * - * @param string The name of the playlist + * @param string $name The name of the playlist * * @return Favorite|null */ @@ -549,12 +579,13 @@ public function getFavoriteByName($name) if ($roughMatch) { return $roughMatch; } + return null; } /** * Get the playlist with the specified id. * - * @param int The ID of the playlist + * @param int $id The ID of the playlist * * @return Favorite */ @@ -566,6 +597,7 @@ public function getFavoriteById($id) return $item; } } + return null; } /** diff --git a/src/Device.php b/src/Device.php index 42a920f..cdb9839 100755 --- a/src/Device.php +++ b/src/Device.php @@ -13,6 +13,11 @@ use Psr\Log\NullLogger; use Psr\SimpleCache\CacheInterface; +/** + * Class Device + * @package MusicCast + * @author Damien Surot + */ class Device implements LoggerAwareInterface { /** @@ -22,7 +27,7 @@ class Device implements LoggerAwareInterface /** * @var Client $client MusicCast Client */ - protected $client; + private $client; /** * @var CacheInterface $cache The cache object to use for the expensive multicast discover * to find MusicCast devices on the network. @@ -36,11 +41,11 @@ class Device implements LoggerAwareInterface /** - * 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 + * 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) { @@ -79,12 +84,10 @@ public function getIp() return $this->ip; } - /** - * @return Client - */ - public function getClient() + + public function call($api, $method, array $args = []) { - return $this->client; + return call_user_func_array(array($this->client->api($api), $method), $args); } @@ -94,7 +97,7 @@ public function getDeviceInfo() if ($this->cache->has($cacheKey)) { return $this->cache->get($cacheKey); } - $info = $this->client->api('system')->getDeviceInfo(); + $info = $this->call('system', 'getDeviceInfo'); $this->cache->set($cacheKey, $info); return $info; } @@ -110,7 +113,7 @@ public function getLocationInfo() if ($this->cache->has($cacheKey)) { return $this->cache->get($cacheKey); } - $info = $this->client->api('system')->getLocationInfo(); + $info = $this->call('system', 'getLocationInfo'); $this->cache->set($cacheKey, $info); return $info; } @@ -121,7 +124,7 @@ public function getMusicCastTreeInfo() if ($this->cache->has($cacheKey)) { return $this->cache->get($cacheKey); } - $info = $this->client->api('system')->getMusicCastTreeInfo(); + $info = $this->call('system', 'getMusicCastTreeInfo'); $this->cache->set($cacheKey, $info); return $info; } @@ -132,8 +135,13 @@ public function getNetworkStatus() if ($this->cache->has($cacheKey)) { return $this->cache->get($cacheKey); } - $info = $this->client->api('system')->getNetworkStatus(); + $info = $this->call('system', 'getNetworkStatus'); $this->cache->set($cacheKey, $info); return $info; } + + public function getUuid() + { + return $this->getDeviceInfo()['device_id']; + } } diff --git a/src/Favorite.php b/src/Favorite.php old mode 100644 new mode 100755 index cb3bd55..24735ac --- a/src/Favorite.php +++ b/src/Favorite.php @@ -8,6 +8,11 @@ namespace MusicCast; +/** + * Class Favorite + * @package MusicCast + * @author Damien Surot + */ class Favorite { /** @@ -22,7 +27,7 @@ class Favorite /** * Create an instance of the Playlist class. * - * @param int $bank + * @param int $index * @param array $data * @param Controller $controller A controller instance on the playlist's network */ @@ -67,6 +72,6 @@ public function getInput() public function play() { - $this->controller->getDevice()->getClient()->api('netusb')->recallPreset('main', $this->id); + $this->controller->call('netusb', 'main', [$this->id]); } } diff --git a/src/Network.php b/src/Network.php index d4f9855..b1c6651 100755 --- a/src/Network.php +++ b/src/Network.php @@ -13,6 +13,11 @@ use Psr\Log\NullLogger; use Psr\SimpleCache\CacheInterface; +/** + * Class Network + * @package MusicCast + * @author Damien Surot + */ class Network implements LoggerAwareInterface { @@ -80,7 +85,7 @@ public function setLogger(LoggerInterface $logger) } /** - * @return + * @return string */ public function getMulticastAddress() { @@ -96,7 +101,7 @@ public function setMulticastAddress($multicastAddress) } /** - * @return + * @return string */ public function getNetworkInterface() { @@ -124,6 +129,7 @@ public function getController() if ($controller = reset($controllers)) { return $controller; } + return null; } /** @@ -140,15 +146,13 @@ public function getControllers() if (!$speaker->isCoordinator()) { continue; } - $controllers[$speaker->getDevice()->getIp()] = new Controller($speaker, $this, $index++); + $controllers[$speaker->getIp()] = new Controller($speaker, $this, $index++); } return $controllers; } /** - * Get all the speakers on the network. - * - * @return + * @return Speaker[]|null */ public function getSpeakers() { @@ -192,6 +196,24 @@ public function getSpeakers() 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"; @@ -305,9 +327,10 @@ public function getControllerByIp($ip) } foreach ($this->getControllers() as $controller) { - if ($controller->getDevice()->getIp() === $ip) { + if ($controller->getIp() === $ip) { return $controller; } } + return null; } } diff --git a/src/Playlist.php b/src/Playlist.php old mode 100644 new mode 100755 index d497faf..e45eb0b --- a/src/Playlist.php +++ b/src/Playlist.php @@ -10,6 +10,7 @@ /** * Provides an interface for managing playlists on the current network. + * @author Damien Surot */ class Playlist { @@ -59,6 +60,6 @@ public function getName() public function play($index = 0) { - $this->controller->getDevice()->getClient()->api('netusb')->manageMcPlaylist($this->id, 'play', $index); + $this->controller->call('netusb', 'manageMcPlaylist', [$this->id, 'play', $index]); } } diff --git a/src/Queue.php b/src/Queue.php new file mode 100755 index 0000000..554bdd8 --- /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(): array + { + return $this->tracks; + } + + /** + * @return mixed + */ + public function getPlayingIndex() + { + return $this->playing_index; + } +} diff --git a/src/Speaker.php b/src/Speaker.php index 59aaae2..400d8be 100755 --- a/src/Speaker.php +++ b/src/Speaker.php @@ -10,6 +10,7 @@ /** * Represents an individual MusicCast speaker, to allow volume, equalisation, and other settings to be managed. + * @author Damien Surot */ class Speaker { @@ -30,14 +31,15 @@ public function __construct($device) $this->name = $device->getNetworkStatus()['network_name']; } - /** - * @return Device - */ - public function getDevice() + public function call($api, $method, array $args = []) { - return $this->device; + return $this->device->call($api, $method, $args); } + public function getIp() + { + return $this->device->getIp(); + } /** * @return mixed @@ -62,7 +64,7 @@ public function getName() */ public function getGroup() { - $group = $this->device->getClient()->api('dist')->getDistributionInfo()['group_id']; + $group = $this->call('dist', 'getDistributionInfo')['group_id']; if (is_numeric($group) && intval($group == 0)) { $group = self::NO_GROUP; } @@ -76,7 +78,7 @@ public function getGroup() */ public function isCoordinator() { - $role = $this->device->getClient()->api('dist')->getDistributionInfo()['role']; + $role = $this->call('dist', 'getDistributionInfo')['role']; return $role == 'server' || $this->getGroup() == Speaker::NO_GROUP; } @@ -87,19 +89,17 @@ public function isCoordinator() */ public function getUuid() { - return $this->device->getDeviceInfo()['device_id']; + return $this->device->getUuid(); } /** * Get the current volume of this speaker. * - * @param int The current volume between 0 and 100 - * * @return int */ public function getVolume() { - return (int)$this->device->getClient()->api('zone')->getStatus('main')['volume']; + return (int)$this->call('zone', 'getStatus', ['main'])['volume']; } /** @@ -111,7 +111,7 @@ public function getVolume() */ public function setVolume($volume) { - $this->device->getClient()->api('zone')->setVolume('main', $volume); + $this->call('zone', 'setVolume', ['main', $volume]); return $this; } @@ -125,7 +125,7 @@ public function setVolume($volume) */ public function adjustVolume($adjust) { - $this->device->getClient()->api('zone')->setVolume('main', $adjust > 0 ? 'up' : 'down', abs($adjust)); + $this->call('zone', 'setVolume', ['main', $adjust > 0 ? 'up' : 'down', abs($adjust)]); return $this; } @@ -136,7 +136,7 @@ public function adjustVolume($adjust) */ public function isMuted() { - return (bool)$this->device->getClient()->api('zone')->getStatus('main')['mute']; + return (bool)$this->call('zone', 'getStatus', ['main'])['mute']; } /** @@ -158,7 +158,7 @@ public function unmute() */ public function mute($mute = true) { - $this->device->getClient()->api('zone')->setMute('main', $mute); + $this->call('zone', 'setMute', ['main', $mute]); return $this; } @@ -196,7 +196,7 @@ public function powerToggle() private function setPower($power) { - $this->device->getClient()->api('zone')->setPower('main', $power); + $this->call('zone', 'setPower', ['main', $power]); return $this; } } diff --git a/src/State.php b/src/State.php index 565d105..6df2f7c 100755 --- a/src/State.php +++ b/src/State.php @@ -2,10 +2,13 @@ namespace MusicCast; +use MusicCast\Tracks\Track; + /** * Representation of the current state of a controller. + * @author Damien Surot */ -class State extends \MusicCast\Tracks\Track +class State { /** * @var string $duration The duration of the currently active track (hh:mm:ss). @@ -17,35 +20,40 @@ class State extends \MusicCast\Tracks\Track */ public $position = ""; - /** - * @var int $queueNumber The zero-based number of the track in the queue. - */ - public $queueNumber = 0; + public $track; /** * Create a Track object. */ public function __construct() { - parent::__construct(); } /** * Update the track properties using an xml element. * - * @param Device $device The device. - * @param Controller $controller A controller instance on the playlist's network - * - * @return static + * @param $playInfo + * @param $ip + * @return static */ - public static function createFromJson(Controller $controller) + public static function buildState($playInfo, $ip) { - $data = $controller->getDevice()->getClient()->api('netusb')->getPlayInfo(); - $track = parent::createFromJson($controller); - $track->duration = $data['total_time']; - $track->position = $data['play_time']; - $data = $controller->getDevice()->getClient()->api('netusb')->getPlayQueue(); - $track->queueNumber = $data['playing_index']; - return $track; + $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 old mode 100644 new mode 100755 index 191d1eb..aeacab9 --- a/src/Tracks/Track.php +++ b/src/Tracks/Track.php @@ -8,10 +8,9 @@ namespace MusicCast\Tracks; -use MusicCast\Controller; - /** * Representation of a track. + * @author Damien Surot */ class Track { @@ -19,52 +18,79 @@ class Track /** * @var string $title The name of the track. */ - public $title = ""; + protected $title = ""; /** * @var string $artist The name of the artist of the track. */ - public $artist = ""; + protected $artist = ""; /** * @var string $album The name of the album of the track. */ - public $album = ""; + protected $album = ""; /** * @var string $albumArt The full path to the album art for this track. */ - public $albumArt = ""; + protected $albumArt = ""; - public $input = ""; + protected $input = ""; /** - * Create a Track object. + * Track constructor. + * @param $input + * @param $title + * @param $albumArt + * @param null $artist + * @param null $album */ - public function __construct() + 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; } - public static function createFromJson(Controller $controller) + /** + * @return string + */ + public function getTitle(): string { - $json = $controller->getDevice()->getClient()->api('netusb')->getPlayInfo(); - $track = new Track(); - $track->title = $json['track']; - $track->input = $json['input']; - - $track->artist = $json['artist']; - $track->album = $json['album']; + return $this->title; + } + /** + * @return string + */ + public function getArtist(): string + { + return $this->artist; + } - if ($art = $json['albumart_url']) { - if (substr($art, 0, 4) !== "http") { - $art = ltrim($art, "/"); - $art = sprintf("http://%s:80/%s", $controller->getDevice()->getIp(), $art); - } - $track->albumArt = $art; - } + /** + * @return string + */ + public function getAlbum(): string + { + return $this->album; + } + /** + * @return string + */ + public function getAlbumArt(): string + { + return $this->albumArt; + } - return $track; + /** + * @return string + */ + public function getInput(): string + { + return $this->input; } } diff --git a/tests/Api/DistributionLiveTest.php b/tests/Api/DistributionLiveTest.php index 8072d14..7b1b3d8 100755 --- a/tests/Api/DistributionLiveTest.php +++ b/tests/Api/DistributionLiveTest.php @@ -32,7 +32,7 @@ public function testStartDistribution() /** * @test */ - public function testSetCientInfo() + public function testSetClientInfo() { self::assertArrayHasKey('response_code', $this->client->api('dist')->setClientInfo()); } diff --git a/tests/ControllerLiveTest.php b/tests/ControllerLiveTest.php old mode 100644 new mode 100755 index 009f9e1..45678cd --- a/tests/ControllerLiveTest.php +++ b/tests/ControllerLiveTest.php @@ -33,7 +33,7 @@ public function testConstructor1() foreach ($this->network->getSpeakers() as $speaker) { if ($speaker->isCoordinator()) { $controller = new Controller($speaker, $this->network, 0); - $this->assertSame($speaker->getDevice()->getIp(), $controller->getDevice()->getIp()); + $this->assertSame($speaker->getIp(), $controller->getIp()); return; } } @@ -48,7 +48,7 @@ public function testConstructor2() foreach ($this->network->getSpeakers() as $speaker) { if (!$speaker->isCoordinator()) { - $controller = new Controller($speaker, $this->network, 0); + new Controller($speaker, $this->network, 0); return; } } @@ -85,13 +85,14 @@ public function testGetState() public function testGetStateDetails() { - $keys = ["title", "artist", "album", "queueNumber", "duration", "position", "input"]; + $track_keys = ["input", "title", "artist", "album", "albumArt"]; + $state_keys = ["duration", "position"]; $state = $this->controller->getStateDetails(); - foreach ($keys as $key) { + foreach ($state_keys as $key) { $this->assertObjectHasAttribute($key, $state); - if (in_array($key, ["queueNumber"])) { - $this->assertInternalType("integer", $state->$key); - } + } + foreach ($track_keys as $key) { + $this->assertObjectHasAttribute($key, $state->track); } } @@ -99,18 +100,18 @@ public function testGetStateDetails() public function testNext() { $controller = $this->controller; - $number = $controller->getStateDetails()->queueNumber; + $number = $controller->getQueue()->getPlayingIndex(); $controller->next(); - $this->assertSame($controller->getStateDetails()->queueNumber, $number + 1); + $this->assertSame($controller->getQueue()->getPlayingIndex(), $number + 1); } public function testPrevious() { $controller = $this->controller; - $number = $controller->getStateDetails()->queueNumber; + $number = $controller->getQueue()->getPlayingIndex(); $controller->previous(); - $this->assertSame($controller->getStateDetails()->queueNumber, $number); + $this->assertSame($controller->getQueue()->getPlayingIndex(), $number); } @@ -126,7 +127,7 @@ public function testSetVolume() $controller = $this->controller; $volume = $controller->getVolume(); $controller->setVolume($volume - 3); - time_nanosleep(0, 500 * 1000000);//500ms + sleep(1); foreach ($controller->getSpeakers() as $speaker) { $this->assertSame($volume - 3, $speaker->getVolume()); } @@ -138,9 +139,9 @@ public function testAdjustVolume1() $controller = $this->controller; $volume = $controller->getVolume(); $controller->setVolume($volume - 3); - time_nanosleep(0, 500 * 1000000);//500ms + sleep(1); $controller->adjustVolume(3); - time_nanosleep(0, 500 * 1000000);//500ms + sleep(1); foreach ($controller->getSpeakers() as $speaker) { $this->assertSame($volume, $speaker->getVolume()); } @@ -152,9 +153,9 @@ public function testAdjustVolume2() $controller = $this->controller; $volume = $controller->getVolume(); $controller->setVolume($volume + 3); - time_nanosleep(0, 500 * 1000000);//500ms + sleep(1); $controller->adjustVolume(3 * -1); - time_nanosleep(0, 500 * 1000000);//500ms + sleep(1); foreach ($controller->getSpeakers() as $speaker) { $this->assertSame($volume, $speaker->getVolume()); } diff --git a/tests/ControllerTest.php b/tests/ControllerTest.php new file mode 100755 index 0000000..9c9aad0 --- /dev/null +++ b/tests/ControllerTest.php @@ -0,0 +1,133 @@ +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 testGetMediaInfo() + { + self::assertEquals("server", $this->controller->getMediaInfo()); + } + + 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 old mode 100644 new mode 100755 index f1e65cb..b4db291 --- a/tests/LiveTest.php +++ b/tests/LiveTest.php @@ -18,7 +18,9 @@ abstract class LiveTest extends \PHPUnit_Framework_TestCase * @var Network */ protected $network; - + /** + * @var Client + */ protected $client; protected $options; diff --git a/tests/MockTest.php b/tests/MockTest.php old mode 100644 new mode 100755 index 871550a..604a76a --- a/tests/MockTest.php +++ b/tests/MockTest.php @@ -76,6 +76,23 @@ private static function mockCLient() 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; } diff --git a/tests/NetworkLiveTest.php b/tests/NetworkLiveTest.php index 1921b9d..148710f 100755 --- a/tests/NetworkLiveTest.php +++ b/tests/NetworkLiveTest.php @@ -30,8 +30,8 @@ public function testGetController() public function testGetControllerByIp() { - $ip = $this->network->getController()->getDevice()->getIp(); + $ip = $this->network->getController()->getIp(); $controller = $this->network->getControllerByIp($ip); - self::assertEquals($ip, $controller->getDevice()->getIp()); + self::assertEquals($ip, $controller->getIp()); } } diff --git a/tests/NetworkTest.php b/tests/NetworkTest.php old mode 100644 new mode 100755 diff --git a/tests/SpeakerTest.php b/tests/SpeakerTest.php old mode 100644 new mode 100755 index ac42ea6..13b4824 --- a/tests/SpeakerTest.php +++ b/tests/SpeakerTest.php @@ -11,10 +11,8 @@ use Mockery; use MusicCast\Speaker; -class SpeakerTest extends MockTest +class SpeakerTest extends \MusicCastTests\MockTest { - protected $device; - /** * @var Speaker */ diff --git a/tests/assets/dist/getDistributionInfo.json b/tests/assets/dist/getDistributionInfo.json old mode 100644 new mode 100755 diff --git a/tests/assets/dist/setClientInfo.json b/tests/assets/dist/setClientInfo.json old mode 100644 new mode 100755 diff --git a/tests/assets/dist/setGroupName.json b/tests/assets/dist/setGroupName.json old mode 100644 new mode 100755 diff --git a/tests/assets/dist/setServerInfo.json b/tests/assets/dist/setServerInfo.json old mode 100644 new mode 100755 diff --git a/tests/assets/dist/startDistribution.json b/tests/assets/dist/startDistribution.json old mode 100644 new mode 100755 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 old mode 100644 new mode 100755 diff --git a/tests/assets/system/getDisklavierSettings.json b/tests/assets/system/getDisklavierSettings.json old mode 100644 new mode 100755 diff --git a/tests/assets/system/getFeatures.json b/tests/assets/system/getFeatures.json old mode 100644 new mode 100755 diff --git a/tests/assets/system/getFuncStatus.json b/tests/assets/system/getFuncStatus.json old mode 100644 new mode 100755 diff --git a/tests/assets/system/getLocationInfo.json b/tests/assets/system/getLocationInfo.json old mode 100644 new mode 100755 diff --git a/tests/assets/system/getMusicCastTreeInfo.json b/tests/assets/system/getMusicCastTreeInfo.json old mode 100644 new mode 100755 diff --git a/tests/assets/system/getNameText.json b/tests/assets/system/getNameText.json old mode 100644 new mode 100755 diff --git a/tests/assets/system/getNetworkStatus.json b/tests/assets/system/getNetworkStatus.json old mode 100644 new mode 100755 diff --git a/tests/assets/system/getTag.json b/tests/assets/system/getTag.json old mode 100644 new mode 100755 diff --git a/tests/assets/system/isNewFirmwareAvailable.json b/tests/assets/system/isNewFirmwareAvailable.json old mode 100644 new mode 100755 diff --git a/tests/assets/system/sendIrCode.json b/tests/assets/system/sendIrCode.json old mode 100644 new mode 100755 diff --git a/tests/assets/zone/getSignalInfo.json b/tests/assets/zone/getSignalInfo.json old mode 100644 new mode 100755 diff --git a/tests/assets/zone/getSoundProgramList.json b/tests/assets/zone/getSoundProgramList.json old mode 100644 new mode 100755 diff --git a/tests/assets/zone/getStatus.json b/tests/assets/zone/getStatus.json old mode 100644 new mode 100755 diff --git a/tests/assets/zone/prepareInputChange.json b/tests/assets/zone/prepareInputChange.json old mode 100644 new mode 100755 diff --git a/tests/assets/zone/setInput.json b/tests/assets/zone/setInput.json old mode 100644 new mode 100755 diff --git a/tests/assets/zone/setMute.json b/tests/assets/zone/setMute.json old mode 100644 new mode 100755 diff --git a/tests/assets/zone/setPower.json b/tests/assets/zone/setPower.json old mode 100644 new mode 100755 diff --git a/tests/assets/zone/setSleep.json b/tests/assets/zone/setSleep.json old mode 100644 new mode 100755 diff --git a/tests/assets/zone/setVolume.json b/tests/assets/zone/setVolume.json old mode 100644 new mode 100755 diff --git a/tests/env.yml.dist b/tests/env.yml.dist old mode 100644 new mode 100755 From 3aa7053469d29d6264043c57a32fe1916145be40 Mon Sep 17 00:00:00 2001 From: Damien Surot Date: Sat, 21 Oct 2017 11:32:10 +0200 Subject: [PATCH 18/25] Fix 5.6 compatibility --- src/Queue.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Queue.php b/src/Queue.php index 554bdd8..12dccb7 100755 --- a/src/Queue.php +++ b/src/Queue.php @@ -55,7 +55,7 @@ public function count() /** * @return Track[] */ - public function getTracks(): array + public function getTracks() { return $this->tracks; } From c47220e02a63067a9b39348ed3f02a75375b6362 Mon Sep 17 00:00:00 2001 From: Damien Surot Date: Sat, 21 Oct 2017 11:34:27 +0200 Subject: [PATCH 19/25] Fix 5.6 compatibility --- src/Tracks/Track.php | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Tracks/Track.php b/src/Tracks/Track.php index aeacab9..9f8ccc7 100755 --- a/src/Tracks/Track.php +++ b/src/Tracks/Track.php @@ -57,7 +57,7 @@ public function __construct($input, $title, $albumArt, $artist = null, $album = /** * @return string */ - public function getTitle(): string + public function getTitle() { return $this->title; } @@ -65,7 +65,7 @@ public function getTitle(): string /** * @return string */ - public function getArtist(): string + public function getArtist() { return $this->artist; } @@ -73,7 +73,7 @@ public function getArtist(): string /** * @return string */ - public function getAlbum(): string + public function getAlbum() { return $this->album; } @@ -81,7 +81,7 @@ public function getAlbum(): string /** * @return string */ - public function getAlbumArt(): string + public function getAlbumArt() { return $this->albumArt; } @@ -89,7 +89,7 @@ public function getAlbumArt(): string /** * @return string */ - public function getInput(): string + public function getInput() { return $this->input; } From b55a289f61e352defd48261857a4361591323e3a Mon Sep 17 00:00:00 2001 From: Damien Surot Date: Tue, 31 Oct 2017 22:28:53 +0100 Subject: [PATCH 20/25] Move methods from controller to speaker --- src/Controller.php | 65 +++++++++++++++--------------------- src/Favorite.php | 3 +- src/Speaker.php | 59 +++++++++++++++++++++++++++++++- tests/ControllerLiveTest.php | 5 +++ tests/ControllerTest.php | 4 --- tests/SpeakerTest.php | 15 +++++++++ 6 files changed, 107 insertions(+), 44 deletions(-) diff --git a/src/Controller.php b/src/Controller.php index a7e98b8..a2a9535 100755 --- a/src/Controller.php +++ b/src/Controller.php @@ -47,10 +47,6 @@ class Controller extends Speaker */ protected $network; - /** - * @var string - */ - private $group; /** * @var Speaker[] */ @@ -87,7 +83,6 @@ public function __construct(Speaker $speaker, Network $network, $distribution_id not the coordinator of it's group"); } $this->network = $network; - $this->group = $this->getGroup(); $this->speakers = $this->getSpeakers(); $this->distribution_id = $distribution_id; } @@ -105,6 +100,9 @@ public function getSpeakers() $group = []; $speakers = $this->network->getSpeakers(); foreach ($speakers as $speaker) { + if ($speaker->getUuid() == $this->getUuid()) { + $group[] = $speaker; + } if ($speaker->getGroup() === $this->getGroup() && !(is_numeric($speaker->getGroup()) || intval($speaker->getGroup()) == 0)) { $group[] = $speaker; @@ -245,14 +243,13 @@ public function previous() return $this->setPlayback('previous'); } - /** - * Get the currently active media info. - * - * @return array - */ - public function getMediaInfo() + + public function setInput($input) { - return $this->call('netusb', 'getPlayInfo')['input']; + if (key_exists('prepareInputChange', $this->call('system', 'getFuncStatus'))) { + $this->call('zone', 'prepareInputChange', ['main', $input]); + } + $this->call('zone', 'setInput', ['main', $input]); } /** @@ -326,24 +323,6 @@ public function removeAllSpeakers() } - /** - * Set the current volume of all the speakers controlled by this Controller. - * - * @param int $volume An amount between 0 and 100 - * - * @return static - */ - public function setVolume($volume) - { - $speakers = $this->getSpeakers(); - foreach ($speakers as $speaker) { - $speaker->setVolume($volume); - } - - return $this; - } - - /** * Adjust the volume of all the speakers controlled by this Controller. * @@ -361,14 +340,6 @@ public function adjustVolume($adjust) return $this; } - public function isStreaming() - { - $input = $this->call('zone', 'getStatus', ['main'])['input']; - return $input == "tuner" || strpos("hdmi", $input) != false || strpos("av", $input) != false - || strpos("aux", $input) != false || strpos("audio", $input) != false - || strpos("bluetooth", $input) != false; - } - /** * Check if repeat is currently active. * @@ -609,4 +580,22 @@ 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; + } } diff --git a/src/Favorite.php b/src/Favorite.php index 24735ac..0f6c040 100755 --- a/src/Favorite.php +++ b/src/Favorite.php @@ -70,8 +70,9 @@ public function getInput() } + public function play() { - $this->controller->call('netusb', 'main', [$this->id]); + $this->controller->call('netusb', 'recallPreset', ['main', $this->id]); } } diff --git a/src/Speaker.php b/src/Speaker.php index 400d8be..4760c2b 100755 --- a/src/Speaker.php +++ b/src/Speaker.php @@ -71,6 +71,18 @@ public function getGroup() 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. * @@ -162,7 +174,6 @@ public function mute($mute = true) return $this; } - /** * Power On this speaker. * @@ -173,6 +184,17 @@ 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. * @@ -199,4 +221,39 @@ 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->getInput(); + return $input == "tuner" || stripos($input, "hdmi") != false || stripos($input, "av") != false + || stripos($input, "aux") != false || stripos($input, "audio") != false + || stripos($input, "bluetooth") != 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; + } } diff --git a/tests/ControllerLiveTest.php b/tests/ControllerLiveTest.php index 45678cd..1d85f7f 100755 --- a/tests/ControllerLiveTest.php +++ b/tests/ControllerLiveTest.php @@ -212,4 +212,9 @@ public function testRemoveSpeaker() return; } } + + public function testPowerOn() + { + $this->controller->powerOn(); + } } diff --git a/tests/ControllerTest.php b/tests/ControllerTest.php index 9c9aad0..b8aee2f 100755 --- a/tests/ControllerTest.php +++ b/tests/ControllerTest.php @@ -46,10 +46,6 @@ public function testGetStateDetails() self::assertEquals("190", $state->position); } - public function testGetMediaInfo() - { - self::assertEquals("server", $this->controller->getMediaInfo()); - } public function testGetPlaylists() { diff --git a/tests/SpeakerTest.php b/tests/SpeakerTest.php index 13b4824..72c7a45 100755 --- a/tests/SpeakerTest.php +++ b/tests/SpeakerTest.php @@ -63,4 +63,19 @@ 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()); + } } From c5d67eef75c650fd09ec547bee180329f2fc7d38 Mon Sep 17 00:00:00 2001 From: Damien Surot Date: Wed, 1 Nov 2017 18:32:07 +0100 Subject: [PATCH 21/25] PLaylist and favorites managed by speaker --- src/Controller.php | 186 ------------------------------------------- src/Favorite.php | 11 ++- src/Network.php | 26 ++++-- src/Playlist.php | 10 +-- src/Speaker.php | 193 ++++++++++++++++++++++++++++++++++++++++++++- 5 files changed, 221 insertions(+), 205 deletions(-) diff --git a/src/Controller.php b/src/Controller.php index a2a9535..5ffab47 100755 --- a/src/Controller.php +++ b/src/Controller.php @@ -51,15 +51,6 @@ class Controller extends Speaker * @var Speaker[] */ private $speakers; - /** - * @var - */ - private $playlists; - - /** - * @var - */ - private $favorites; /** * @var @@ -394,183 +385,6 @@ public function getQueue() } - /** - * 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; - } - /** * Get the network instance used by this controller. * diff --git a/src/Favorite.php b/src/Favorite.php index 0f6c040..ba02a9e 100755 --- a/src/Favorite.php +++ b/src/Favorite.php @@ -21,7 +21,7 @@ class Favorite protected $name; protected $input; protected $id; - protected $controller; + protected $speaker; /** @@ -29,14 +29,14 @@ class Favorite * * @param int $index * @param array $data - * @param Controller $controller A controller instance on the playlist's network + * @param Speaker $speaker A controller instance on the playlist's network */ - public function __construct($index, $data, Controller $controller) + public function __construct($index, $data, Speaker $speaker) { $this->id = $index; $this->name = $data['text']; $this->input = $data['input']; - $this->controller = $controller; + $this->speaker = $speaker; } @@ -70,9 +70,8 @@ public function getInput() } - public function play() { - $this->controller->call('netusb', 'recallPreset', ['main', $this->id]); + $this->speaker->call('netusb', 'recallPreset', ['main', $this->id]); } } diff --git a/src/Network.php b/src/Network.php index b1c6651..410f87a 100755 --- a/src/Network.php +++ b/src/Network.php @@ -160,12 +160,12 @@ public function getSpeakers() return $this->speakers; } - $this->logger->info("creating speaker instances"); + $this->logger->debug("creating speaker instances"); $cacheKey = $this->getCacheKey(); if ($this->cache->has($cacheKey)) { - $this->logger->info("getting device info from cache"); + $this->logger->debug("getting device info from cache"); $devices = $this->cache->get($cacheKey); } else { $devices = $this->getDevices(); @@ -184,7 +184,7 @@ public function getSpeakers() # Get the MusicCast devices from 1 speaker $ip = reset($devices); $device = new Device($ip, 80); - $this->logger->notice("Getting devices info from: {$ip}"); + $this->logger->debug("Getting devices info from: {$ip}"); $treeInfo = $device->getMusicCastTreeInfo(); $this->speakers = []; foreach ($treeInfo['mac_address_list'] as $addr) { @@ -233,7 +233,7 @@ protected function getCacheKey() */ protected function getDevices() { - $this->logger->info("discovering devices..."); + $this->logger->debug("discovering devices..."); $port = 1900; @@ -300,7 +300,7 @@ protected function getDevices() if (in_array($device["usn"], $unique)) { continue; } - $this->logger->info("found device: {usn}", $device); + $this->logger->debug("found device: {usn}", $device); $url = parse_url($device["location"]); $ip = $url["host"]; @@ -333,4 +333,20 @@ public function getControllerByIp($ip) } 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 index e45eb0b..6960907 100755 --- a/src/Playlist.php +++ b/src/Playlist.php @@ -19,7 +19,7 @@ class Playlist */ protected $name; protected $id; - private $controller; + private $speaker; /** @@ -27,13 +27,13 @@ class Playlist * * @param int $bank * @param string $name - * @param Controller $controller A controller instance on the playlist's network + * @param Speaker $controller A speaker instance on the playlist's network */ - public function __construct($bank, $name, Controller $controller) + public function __construct($bank, $name, Speaker $controller) { $this->id = $bank; $this->name = $name; - $this->controller = $controller; + $this->speaker = $controller; } @@ -60,6 +60,6 @@ public function getName() public function play($index = 0) { - $this->controller->call('netusb', 'manageMcPlaylist', [$this->id, 'play', $index]); + $this->speaker->call('netusb', 'manageMcPlaylist', [$this->id, 'play', $index]); } } diff --git a/src/Speaker.php b/src/Speaker.php index 4760c2b..1f875de 100755 --- a/src/Speaker.php +++ b/src/Speaker.php @@ -17,6 +17,16 @@ class Speaker protected $model; protected $name; protected $device; + /** + * @var + */ + protected $playlists; + + /** + * @var + */ + protected $favorites; + const NO_GROUP = "NoGroup"; /** @@ -235,10 +245,9 @@ public function getInput() public function isStreaming() { - $input = 'input' . $this->getInput(); + $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 - || stripos($input, "bluetooth") != false; + || stripos($input, "aux") != false || stripos($input, "audio") != false; } /** @@ -256,4 +265,182 @@ public function getInputList() } 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; + } } From 6606b2765122b5defbfe73b857e1f250ab727f21 Mon Sep 17 00:00:00 2001 From: Damien Surot Date: Fri, 3 Nov 2017 03:46:19 +0100 Subject: [PATCH 22/25] Remove cached speakers to handle multi app modification --- src/Cache.php | 2 +- src/Controller.php | 22 ++++++---------------- 2 files changed, 7 insertions(+), 17 deletions(-) diff --git a/src/Cache.php b/src/Cache.php index 327694e..321ec0a 100755 --- a/src/Cache.php +++ b/src/Cache.php @@ -23,7 +23,7 @@ class Cache extends DoctrineCachePool public function __construct() { - $cache = new FilesystemCache(sys_get_temp_dir() . DIRECTORY_SEPARATOR . "musicast"); + $cache = new FilesystemCache(sys_get_temp_dir() . DIRECTORY_SEPARATOR . "musiccast"); parent::__construct($cache); } } diff --git a/src/Controller.php b/src/Controller.php index 5ffab47..ce6f894 100755 --- a/src/Controller.php +++ b/src/Controller.php @@ -47,11 +47,6 @@ class Controller extends Speaker */ protected $network; - /** - * @var Speaker[] - */ - private $speakers; - /** * @var */ @@ -74,7 +69,6 @@ public function __construct(Speaker $speaker, Network $network, $distribution_id not the coordinator of it's group"); } $this->network = $network; - $this->speakers = $this->getSpeakers(); $this->distribution_id = $distribution_id; } @@ -85,21 +79,19 @@ public function __construct(Speaker $speaker, Network $network, $distribution_id */ public function getSpeakers() { - if (is_array($this->speakers)) { - return $this->speakers; - } $group = []; $speakers = $this->network->getSpeakers(); + foreach ($speakers as $speaker) { if ($speaker->getUuid() == $this->getUuid()) { $group[] = $speaker; + continue; } - if ($speaker->getGroup() === $this->getGroup() && - !(is_numeric($speaker->getGroup()) || intval($speaker->getGroup()) == 0)) { + if ($speaker->getGroup() === $this->getGroup() && $this->getGroup() != Speaker::NO_GROUP) { $group[] = $speaker; } } - return $this->speakers = $group; + return $group; } /** @@ -255,7 +247,7 @@ public function addSpeaker(Speaker $speaker) if ($speaker->getUuid() === $this->getUuid()) { return $this; } - if (!in_array($speaker, $this->speakers)) { + if (!in_array($speaker, $this->getSpeakers())) { $group = $this->getGroup(); if ($group == Speaker::NO_GROUP) { $group = md5($this->device->getIp()); @@ -268,7 +260,6 @@ public function addSpeaker(Speaker $speaker) [$group, 'add', 'main', array($speaker->device->getIp())] ); $this->call('dist', 'startDistribution', [$this->distribution_id]); - $this->speakers[] = $speaker; } } return $this; @@ -287,8 +278,7 @@ public function removeSpeaker(Speaker $speaker) if ($speaker->getUuid() === $this->getUuid()) { return $this; } - if (in_array($speaker, $this->speakers)) { - unset($this->speakers[array_search($speaker, $this->speakers)]); + if (in_array($speaker, $this->getSpeakers())) { $speaker->call('dist', 'setClientInfo'); $this->call( 'dist', From 04c815013a7bccbd79a1655a139f091238430ed2 Mon Sep 17 00:00:00 2001 From: Damien Surot Date: Sat, 4 Nov 2017 09:20:30 +0100 Subject: [PATCH 23/25] Fix muting/unmuting --- src/Api/Zone.php | 2 +- src/Controller.php | 32 ++++++++++++++++++++++++++++++++ src/Speaker.php | 2 +- tests/ControllerLiveTest.php | 14 ++++++++++++++ 4 files changed, 48 insertions(+), 2 deletions(-) diff --git a/src/Api/Zone.php b/src/Api/Zone.php index 69f637c..edb557b 100755 --- a/src/Api/Zone.php +++ b/src/Api/Zone.php @@ -101,7 +101,7 @@ public function setVolume($zone, $volume, $step = null) */ public function setMute($zone, $mute) { - return $this->call($zone, 'setMute?mute='.rawurlencode($mute?'true':'false')); + return $this->call($zone, 'setMute?enable='.rawurlencode($mute?'true':'false')); } /** diff --git a/src/Controller.php b/src/Controller.php index ce6f894..b0f7179 100755 --- a/src/Controller.php +++ b/src/Controller.php @@ -91,6 +91,8 @@ public function getSpeakers() $group[] = $speaker; } } + + return $group; } @@ -402,4 +404,34 @@ public function 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/Speaker.php b/src/Speaker.php index 1f875de..b821400 100755 --- a/src/Speaker.php +++ b/src/Speaker.php @@ -158,7 +158,7 @@ public function adjustVolume($adjust) */ public function isMuted() { - return (bool)$this->call('zone', 'getStatus', ['main'])['mute']; + return $this->call('zone', 'getStatus', ['main'])['mute']; } /** diff --git a/tests/ControllerLiveTest.php b/tests/ControllerLiveTest.php index 1d85f7f..720b166 100755 --- a/tests/ControllerLiveTest.php +++ b/tests/ControllerLiveTest.php @@ -217,4 +217,18 @@ public function testPowerOn() { $this->controller->powerOn(); } + + + + public function testMute() + { + $this->controller->mute(); + } + + + + public function testUnMute() + { + $this->controller->unmute(); + } } From f1aff79c51d2f7dba5a249e00f2bb2c7cc686623 Mon Sep 17 00:00:00 2001 From: felixbublitz Date: Thu, 31 Jan 2019 15:25:09 +0100 Subject: [PATCH 24/25] Update README.md fix some mistakes in the readme. --- README.md | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index b7891e0..6bb0a5e 100755 --- a/README.md +++ b/README.md @@ -24,7 +24,7 @@ $ curl -s http://getcomposer.org/installer | php Via composer ```bash -$ composer require samvdb/php-musiccast-api php-http/guzzle6-adapter +$ composer require granDam/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. @@ -33,10 +33,12 @@ You can install any adapter you want but guzzle is probably fine for what you wa Start all groups playing music ```php -$musicCast = new \duncan3dc\Sonos\Network; +require 'vendor/autoload.php'; + +$musicCast = new MusicCast\Network(); $controllers = $musicCast->getControllers(); foreach ($controllers as $controller) { - echo $controller->getGroup()\n"; + echo $controller->getGroup() + "\n"; echo "\tState: " . $controller->getStateName() . "\n"; $controller->play(); } From 570c41d40eafbf881dcfd647b1674438b1ada74b Mon Sep 17 00:00:00 2001 From: Luc Guinchard Date: Mon, 11 Feb 2019 09:39:06 +0100 Subject: [PATCH 25/25] name in error MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit granDam →granddam --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 6bb0a5e..ef1eec0 100755 --- a/README.md +++ b/README.md @@ -24,7 +24,7 @@ $ curl -s http://getcomposer.org/installer | php Via composer ```bash -$ composer require granDam/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.