sonos 0.3.5 → 0.3.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/Changelog.markdown +40 -1
- data/Readme.markdown +34 -5
- data/lib/sonos.rb +1 -0
- data/lib/sonos/device/speaker.rb +2 -0
- data/lib/sonos/endpoint/a_v_transport.rb +71 -6
- data/lib/sonos/endpoint/device.rb +22 -0
- data/lib/sonos/features.rb +6 -0
- data/lib/sonos/features/voiceover.rb +56 -0
- data/lib/sonos/system.rb +11 -8
- data/lib/sonos/version.rb +1 -1
- data/sonos.gemspec +2 -2
- metadata +6 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 0b7916243ad3a1b97d2b2480654b8a9413fdb36f
|
4
|
+
data.tar.gz: 327c59e209b9bb9193f799c7aded77d7f15fc697
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: afbc45559d030536fd2a4c9e465ce1402a3f6dbae3a77f1893806b9e419e6589fbfe4f3b790b745ee6ecd0e76e150956fe83d414f748fead7f9f9f0eacb0b215
|
7
|
+
data.tar.gz: 7d81b054264e7bd0884b423c2cc2c034e02c0d94a53c055cdcc8a41be317656f0ae8168466173ad3d3ed70574eb79bf6375e3480579649c28be7bc2080beb269
|
data/Changelog.markdown
CHANGED
@@ -1,4 +1,43 @@
|
|
1
|
-
### Version 0.3.
|
1
|
+
### Version 0.3.6 — August 20, 2014
|
2
|
+
|
3
|
+
* Unbreak clearing the queue
|
4
|
+
* Add voiceover
|
5
|
+
* Improve playlist handling
|
6
|
+
* Add support to queue Rdio tracks/albums
|
7
|
+
* Add setting a sleep timer
|
8
|
+
* Allow to queue items at arbitrary positions in the playlist
|
9
|
+
* Make party mode initialization more robust
|
10
|
+
* Add support to create stereo pair
|
11
|
+
|
12
|
+
### Version 0.3.5 — February 4, 2014
|
13
|
+
|
14
|
+
* Allow to queue Spotify tracks/albums/playlists/top lists/starred
|
15
|
+
* Add basic line in support
|
16
|
+
* Allow toggling status light
|
17
|
+
|
18
|
+
### Version 0.3.4 — November 1, 2013
|
19
|
+
|
20
|
+
* Rework UPNP subscription / unsubscriptino process
|
21
|
+
* Add shuffle, repeat and crossfade
|
22
|
+
* Add alarms
|
23
|
+
* Add support for ZP120, Play:1 devices
|
24
|
+
|
25
|
+
### Version 0.3.3 — June 29, 2013
|
26
|
+
|
27
|
+
* Add party mode to CLI
|
28
|
+
* Add support for Sub and Soundbar devices
|
29
|
+
|
30
|
+
### Version 0.3.1 — June 28, 2013
|
31
|
+
|
32
|
+
* Allow to specify a non-nil default IP
|
33
|
+
* Add CLI command to list groups
|
34
|
+
* Add ability to detect if speaker has music
|
35
|
+
* Add more information to `now_playing` output
|
36
|
+
* Support add/remove to queue
|
37
|
+
* Add support for ZP80 devices
|
38
|
+
* Add party mode
|
39
|
+
|
40
|
+
### Version 0.3.0 — February 2, 2013
|
2
41
|
|
3
42
|
* System owns groups that reflect the topology
|
4
43
|
* Group and ungroup speakers
|
data/Readme.markdown
CHANGED
@@ -59,6 +59,7 @@ speaker.add_to_queue 'http://assets.samsoff.es/music/Airports.mp3'
|
|
59
59
|
speaker.remove_from_queue(speaker.queue[:items].last[:queue_id])
|
60
60
|
speaker.save_queue 'Jams'
|
61
61
|
speaker.clear_queue
|
62
|
+
speaker.set_sleep_timer '00:13:00'
|
62
63
|
```
|
63
64
|
|
64
65
|
Or go into what the official control from Sonos, Inc. calls "Party
|
@@ -75,6 +76,38 @@ system.party_over
|
|
75
76
|
|
76
77
|
All of this is based off of the raw `Sonos.system.topology`.
|
77
78
|
|
79
|
+
### Services
|
80
|
+
|
81
|
+
Currently there is support to queue items from the following services, provided
|
82
|
+
the service accounts are set up:
|
83
|
+
|
84
|
+
- Spotify
|
85
|
+
- tracks
|
86
|
+
- albums
|
87
|
+
- playlists
|
88
|
+
- top lists
|
89
|
+
- starred
|
90
|
+
- Rdio
|
91
|
+
- tracks
|
92
|
+
- albums
|
93
|
+
|
94
|
+
The way to add items differs per service at moment:
|
95
|
+
|
96
|
+
For Spotify only the 'Spotify URI' is required:
|
97
|
+
|
98
|
+
``` ruby
|
99
|
+
speaker.add_spotify_to_queue('2CwulIyrmEYwbUWzcEVIhR')
|
100
|
+
```
|
101
|
+
|
102
|
+
Whereas for Rdio more information needs to be provided:
|
103
|
+
|
104
|
+
``` ruby
|
105
|
+
speaker.add_rdio_to_queue({
|
106
|
+
:track => '42083055',
|
107
|
+
:album => '3944937',
|
108
|
+
:username => 'RDIO_USERNAME_HERE' })
|
109
|
+
```
|
110
|
+
|
78
111
|
### CLI
|
79
112
|
|
80
113
|
There is a very limited CLI right now. You can run `sonos devices` to get the IP of all of your devices.
|
@@ -91,6 +124,7 @@ You can also run `sonos pause_all` to pause all your Sonos groups.
|
|
91
124
|
* Detect stereo pair
|
92
125
|
* CLI client for everything
|
93
126
|
* Nonblocking calls with Celluloid::IO
|
127
|
+
* Unified method of adding items from music services
|
94
128
|
|
95
129
|
### Features
|
96
130
|
|
@@ -98,14 +132,10 @@ You can also run `sonos pause_all` to pause all your Sonos groups.
|
|
98
132
|
* Pause all (there is no play all in the controller, we could loop through and do it though)
|
99
133
|
* Party Mode
|
100
134
|
* Line-in
|
101
|
-
* Toggle cross fade
|
102
|
-
* Toggle shuffle
|
103
|
-
* Set repeat mode
|
104
135
|
* Search music library
|
105
136
|
* Browse music library
|
106
137
|
* Skip to song in queue
|
107
138
|
* Alarm clock
|
108
|
-
* Sleep timer
|
109
139
|
* Pandora doesn't use the Queue. I bet things are all jacked up.
|
110
140
|
* CONNECT (and possibly PLAY:5) line in settings
|
111
141
|
* Source name
|
@@ -118,7 +148,6 @@ You can also run `sonos pause_all` to pause all your Sonos groups.
|
|
118
148
|
If we are implementing everything the official Sonos Controller does, here's some more stuff:
|
119
149
|
|
120
150
|
* Set zone name and icon
|
121
|
-
* Create stero pair
|
122
151
|
* Support for SUB
|
123
152
|
* Support for DOCK
|
124
153
|
* Support for CONNECT:AMP (not sure if this is any different from CONNECT)
|
data/lib/sonos.rb
CHANGED
data/lib/sonos/device/speaker.rb
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
require 'savon'
|
2
2
|
require 'sonos/endpoint'
|
3
|
+
require 'sonos/features'
|
3
4
|
|
4
5
|
module Sonos::Device
|
5
6
|
|
@@ -11,6 +12,7 @@ module Sonos::Device
|
|
11
12
|
include Sonos::Endpoint::ContentDirectory
|
12
13
|
include Sonos::Endpoint::Upnp
|
13
14
|
include Sonos::Endpoint::Alarm
|
15
|
+
include Sonos::Features::Voiceover
|
14
16
|
|
15
17
|
MODELS = {
|
16
18
|
:'S1' => 'PLAY:1', # Released Oct 2013
|
@@ -48,6 +48,12 @@ module Sonos::Endpoint::AVTransport
|
|
48
48
|
}
|
49
49
|
end
|
50
50
|
|
51
|
+
# Returns true if the player is not in a paused or stopped state
|
52
|
+
def is_playing?
|
53
|
+
state = get_player_state[:state]
|
54
|
+
!['PAUSED_PLAYBACK', 'STOPPED'].include?(state)
|
55
|
+
end
|
56
|
+
|
51
57
|
# Pause the currently playing track.
|
52
58
|
def pause
|
53
59
|
parse_response send_transport_message('Pause')
|
@@ -77,7 +83,7 @@ module Sonos::Endpoint::AVTransport
|
|
77
83
|
def previous
|
78
84
|
parse_response send_transport_message('Previous')
|
79
85
|
end
|
80
|
-
|
86
|
+
|
81
87
|
def line_in(speaker)
|
82
88
|
set_av_transport_uri('x-rincon-stream:' + speaker.uid.sub('uuid:', ''))
|
83
89
|
end
|
@@ -90,9 +96,14 @@ module Sonos::Endpoint::AVTransport
|
|
90
96
|
parse_response send_transport_message('Seek', "<Unit>REL_TIME</Unit><Target>#{timestamp}</Target>")
|
91
97
|
end
|
92
98
|
|
99
|
+
# Seeks the playlist selection to the provided index
|
100
|
+
def select_track(index)
|
101
|
+
parse_response send_transport_message('Seek', "<Unit>TRACK_NR</Unit><Target>#{index}</Target>")
|
102
|
+
end
|
103
|
+
|
93
104
|
# Clear the queue
|
94
105
|
def clear_queue
|
95
|
-
parse_response
|
106
|
+
parse_response send_transport_message('RemoveAllTracksFromQueue')
|
96
107
|
end
|
97
108
|
|
98
109
|
# Save queue
|
@@ -103,9 +114,10 @@ module Sonos::Endpoint::AVTransport
|
|
103
114
|
# Adds a track to the queue
|
104
115
|
# @param[String] uri Uri of track
|
105
116
|
# @param[String] didl Stanza of DIDL-Lite metadata (generally created by #add_spotify_to_queue)
|
117
|
+
# @param[Integer] position Optional queue insertion position. Leaving this blank inserts at the end.
|
106
118
|
# @return[Integer] Queue position of the added track
|
107
|
-
def add_to_queue(uri, didl = '')
|
108
|
-
response = send_transport_message('AddURIToQueue', "<EnqueuedURI>#{uri}</EnqueuedURI><EnqueuedURIMetaData>#{didl}</EnqueuedURIMetaData><DesiredFirstTrackNumberEnqueued
|
119
|
+
def add_to_queue(uri, didl = '', position = 0)
|
120
|
+
response = send_transport_message('AddURIToQueue', "<EnqueuedURI>#{uri}</EnqueuedURI><EnqueuedURIMetaData>#{didl}</EnqueuedURIMetaData><DesiredFirstTrackNumberEnqueued>#{position}</DesiredFirstTrackNumberEnqueued><EnqueueAsNext>1</EnqueueAsNext>")
|
109
121
|
# TODO yeah, this error handling is a bit soft. For consistency's sake :)
|
110
122
|
pos = response.xpath('.//FirstTrackNumberEnqueued').text
|
111
123
|
if pos.length != 0
|
@@ -115,8 +127,9 @@ module Sonos::Endpoint::AVTransport
|
|
115
127
|
|
116
128
|
# Adds a Spotify track to the queue along with extra data for better metadata retrieval
|
117
129
|
# @param[Hash] opts Various options (id, user, region and type)
|
130
|
+
# @param[Integer] position Optional queue insertion position. Leaving this blank inserts at the end.
|
118
131
|
# @return[Integer] Queue position of the added track(s)
|
119
|
-
def add_spotify_to_queue(opts = {})
|
132
|
+
def add_spotify_to_queue(opts = {}, position = 0)
|
120
133
|
opts = {
|
121
134
|
:id => '',
|
122
135
|
:user => nil,
|
@@ -153,7 +166,46 @@ module Sonos::Endpoint::AVTransport
|
|
153
166
|
return nil
|
154
167
|
end
|
155
168
|
|
156
|
-
add_to_queue(uri, didl_metadata)
|
169
|
+
add_to_queue(uri, didl_metadata, position)
|
170
|
+
end
|
171
|
+
|
172
|
+
# Add an Rdio object to the queue (album or track), anything else can only
|
173
|
+
# be streamed (play now).
|
174
|
+
# @param[Hash] opts Various options (album/track keys, username and type)
|
175
|
+
# @param[Integer] position Optional queue insertion position. Leaving this blank inserts at the end.
|
176
|
+
# @return[Integer] Queue position of the added track(s)
|
177
|
+
def add_rdio_to_queue(opts = {}, position = 0)
|
178
|
+
opts = {
|
179
|
+
:username => nil,
|
180
|
+
:album => nil,
|
181
|
+
:track => nil,
|
182
|
+
:type => 'track',
|
183
|
+
:format => 'mp3' # can be changed, but only 'mp3' is valid.
|
184
|
+
}.merge(opts)
|
185
|
+
|
186
|
+
return nil if opts[:username].nil?
|
187
|
+
|
188
|
+
# Both tracks and albums require the album key. And tracks need a track
|
189
|
+
# key of course.
|
190
|
+
return nil if opts[:album].nil?
|
191
|
+
return nil if opts[:type] == 'track' and opts[:track].nil?
|
192
|
+
|
193
|
+
# In order for valid DIDL we'll pass an empty :track for albums.
|
194
|
+
opts[:track] = '' if opts[:type] == 'album'
|
195
|
+
|
196
|
+
didl_metadata = "<DIDL-Lite xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:upnp="urn:schemas-upnp-org:metadata-1-0/upnp/" xmlns:r="urn:schemas-rinconnetworks-com:metadata-1-0/" xmlns="urn:schemas-upnp-org:metadata-1-0/DIDL-Lite/"><item id="00030020_t%3a%3a#{opts[:track]}%3a%3aa%3a%3a#{opts[:album]}" parentID="0004006c_a%3a%3a#{opts[:album]}" restricted="true"><dc:title></dc:title><upnp:class>object.item.audioItem.musicTrack</upnp:class><desc id="cdudn" nameSpace="urn:schemas-rinconnetworks-com:metadata-1-0/">SA_RINCON2823_#{opts[:username]}</desc></item></DIDL-Lite>"
|
197
|
+
|
198
|
+
case opts[:type]
|
199
|
+
when /track/
|
200
|
+
uri = "x-sonos-http:_t%3a%3a#{opts[:track]}%3a%3aa%3a%3a#{opts[:album]}.#{opts[:format]}?sid=11&flags=32"
|
201
|
+
when /album/
|
202
|
+
type_id = '0004006c_a'
|
203
|
+
uri = "x-rincon-cpcontainer:#{type_id}%3a%3a#{opts[:album]}"
|
204
|
+
else
|
205
|
+
return nil
|
206
|
+
end
|
207
|
+
|
208
|
+
add_to_queue(uri, didl_metadata, position)
|
157
209
|
end
|
158
210
|
|
159
211
|
# Removes a track from the queue
|
@@ -180,6 +232,19 @@ module Sonos::Endpoint::AVTransport
|
|
180
232
|
parse_response send_transport_message('BecomeCoordinatorOfStandaloneGroup')
|
181
233
|
end
|
182
234
|
|
235
|
+
# Set a sleep timer up to 23:59:59
|
236
|
+
# E.g. '00:11:00' for 11 minutes.
|
237
|
+
# @param duration [String] Duration of timer or nil to clear.
|
238
|
+
def set_sleep_timer(duration)
|
239
|
+
if duration.nil?
|
240
|
+
duration = ''
|
241
|
+
elsif duration.gsub(':', '').to_i > 235959
|
242
|
+
duration = '23:59:59'
|
243
|
+
end
|
244
|
+
|
245
|
+
parse_response send_transport_message('ConfigureSleepTimer', "<NewSleepTimerDuration>#{duration}</NewSleepTimerDuration>")
|
246
|
+
end
|
247
|
+
|
183
248
|
private
|
184
249
|
|
185
250
|
# Play a stream.
|
@@ -16,6 +16,22 @@ module Sonos::Endpoint::Device
|
|
16
16
|
parse_response send_device_message('SetLEDState', enabled ? 'On' : 'Off')
|
17
17
|
end
|
18
18
|
|
19
|
+
# Create a stereo pair of two speakers.
|
20
|
+
# This does not take into account which type of players support bonding.
|
21
|
+
# Currently only S1/S3 (play:1/play:3) support this but future players may
|
22
|
+
# gain this abbility too. The speaker on which this method is called is
|
23
|
+
# assumed to be the left speaker of the pair.
|
24
|
+
# @param right [Sonos::Device::Speaker] Right speaker
|
25
|
+
def create_pair_with(right)
|
26
|
+
left = self.uid.sub!('uuid:', '')
|
27
|
+
right = right.uid.sub!('uuid:', '')
|
28
|
+
parse_response = send_bonding_message('AddBondedZones', "#{left}:LF,LF;#{right}:RF,RF")
|
29
|
+
end
|
30
|
+
|
31
|
+
def separate_pair
|
32
|
+
parse_response = send_bonding_message('RemoveBondedZones', '')
|
33
|
+
end
|
34
|
+
|
19
35
|
private
|
20
36
|
|
21
37
|
def device_client
|
@@ -28,4 +44,10 @@ private
|
|
28
44
|
message = %Q{<u:#{name} xmlns:u="#{DEVICE_XMLNS}"><Desired#{attribute}>#{value}</Desired#{attribute}>}
|
29
45
|
device_client.call(name, soap_action: action, message: message)
|
30
46
|
end
|
47
|
+
|
48
|
+
def send_bonding_message(name, value)
|
49
|
+
action = "#{DEVICE_XMLNS}##{name}"
|
50
|
+
message = %Q{<u:#{name} xmlns:u="#{DEVICE_XMLNS}"><ChannelMapSet>#{value}</ChannelMapSet></u:#{name}>}
|
51
|
+
device_client.call(name, soap_action: action, message: message)
|
52
|
+
end
|
31
53
|
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
module Sonos::Features::Voiceover
|
2
|
+
|
3
|
+
# Interrupts the speaker and plays the provided URI. When finished, returns the play head
|
4
|
+
# and state to their original position. Useful for doorbell sounds, announcements, etc.
|
5
|
+
def voiceover!(uri, vol = nil)
|
6
|
+
start_time = Time.now
|
7
|
+
|
8
|
+
result = group_master.with_isolated_state do
|
9
|
+
self.volume = vol if vol
|
10
|
+
group_master.play_blocking(uri)
|
11
|
+
end
|
12
|
+
|
13
|
+
result.merge({duration: (Time.now - start_time )})
|
14
|
+
end
|
15
|
+
|
16
|
+
protected
|
17
|
+
|
18
|
+
def with_isolated_state
|
19
|
+
pause if was_playing = is_playing?
|
20
|
+
unmute if was_muted = muted?
|
21
|
+
previous_volume = volume
|
22
|
+
previous = now_playing
|
23
|
+
|
24
|
+
yield
|
25
|
+
|
26
|
+
# the sonos app does this. I think it tells the player to think of the master queue as active again
|
27
|
+
play uid.gsub('uuid', 'x-rincon-queue') + '#0'
|
28
|
+
|
29
|
+
if previous
|
30
|
+
select_track previous[:queue_position]
|
31
|
+
seek Time.parse("1/1/1970 #{previous[:current_position]} -0000" ).to_i
|
32
|
+
|
33
|
+
self.volume = previous_volume
|
34
|
+
mute if was_muted
|
35
|
+
end
|
36
|
+
|
37
|
+
play if was_playing
|
38
|
+
|
39
|
+
{
|
40
|
+
original_volume: previous_volume,
|
41
|
+
original_state: (was_playing ? 'playing' : 'paused')
|
42
|
+
}
|
43
|
+
end
|
44
|
+
|
45
|
+
def play_blocking(uri)
|
46
|
+
# queue up the track
|
47
|
+
play uri
|
48
|
+
|
49
|
+
# play it
|
50
|
+
play
|
51
|
+
|
52
|
+
# pause the thread until the track is done
|
53
|
+
sleep(0.1) while is_playing?
|
54
|
+
end
|
55
|
+
|
56
|
+
end
|
data/lib/sonos/system.rb
CHANGED
@@ -47,9 +47,12 @@ module Sonos
|
|
47
47
|
|
48
48
|
def find_party_master
|
49
49
|
# 1: If there are any pre-existing groups playing something, use
|
50
|
-
# the lowest-numbered group's master
|
50
|
+
# the lowest-numbered group's master. But ensure to only check a
|
51
|
+
# master_speaker that is actually a speaker, and not an Accessory.
|
51
52
|
groups.each do |group|
|
52
|
-
|
53
|
+
if group.master_speaker.speaker? and group.master_speaker.has_music?
|
54
|
+
return group.master_speaker
|
55
|
+
end
|
53
56
|
end
|
54
57
|
|
55
58
|
# 2: Lowest-number speaker that's playing something
|
@@ -60,18 +63,18 @@ module Sonos
|
|
60
63
|
# 3: lowest-numbered speaker
|
61
64
|
speakers[0]
|
62
65
|
end
|
63
|
-
|
66
|
+
|
64
67
|
# Party's over :(
|
65
68
|
def party_over
|
66
69
|
groups.each { |g| g.disband }
|
67
70
|
rescan @topology
|
68
71
|
end
|
69
|
-
|
72
|
+
|
70
73
|
def rescan(topology = Discovery.new.topology)
|
71
74
|
@topology = topology
|
72
75
|
@groups = []
|
73
76
|
@devices = @topology.collect(&:device)
|
74
|
-
|
77
|
+
|
75
78
|
construct_groups
|
76
79
|
|
77
80
|
speakers.each do |speaker|
|
@@ -102,6 +105,9 @@ module Sonos
|
|
102
105
|
master = node if node.coordinator == "true"
|
103
106
|
end
|
104
107
|
|
108
|
+
# Skip this group if there is no master
|
109
|
+
next if master.nil?
|
110
|
+
|
105
111
|
# register other nodes in groups as slave nodes
|
106
112
|
nodes = []
|
107
113
|
@topology.each do |node|
|
@@ -110,9 +116,6 @@ module Sonos
|
|
110
116
|
nodes << node unless node.uuid == master.uuid
|
111
117
|
end
|
112
118
|
|
113
|
-
# Skip this group if there is no master
|
114
|
-
next if master.nil?
|
115
|
-
|
116
119
|
# Add the group
|
117
120
|
@groups << Group.new(master.device, nodes.collect(&:device))
|
118
121
|
end
|
data/lib/sonos/version.rb
CHANGED
data/sonos.gemspec
CHANGED
@@ -6,8 +6,8 @@ require 'sonos/version'
|
|
6
6
|
Gem::Specification.new do |gem|
|
7
7
|
gem.name = 'sonos'
|
8
8
|
gem.version = Sonos::VERSION
|
9
|
-
gem.authors = ['Sam Soffes', 'Aaron Gotwalt']
|
10
|
-
gem.email = ['sam@soff.es', 'gotwalt@gmail.com']
|
9
|
+
gem.authors = ['Sam Soffes', 'Aaron Gotwalt', 'Jasper Lievisse Adriaanse']
|
10
|
+
gem.email = ['sam@soff.es', 'gotwalt@gmail.com', 'jasper@humppa.nl']
|
11
11
|
gem.description = 'Control Sonos speakers with Ruby'
|
12
12
|
gem.summary = gem.description
|
13
13
|
gem.homepage = 'https://github.com/soffes/sonos'
|
metadata
CHANGED
@@ -1,15 +1,16 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: sonos
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.3.
|
4
|
+
version: 0.3.6
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Sam Soffes
|
8
8
|
- Aaron Gotwalt
|
9
|
+
- Jasper Lievisse Adriaanse
|
9
10
|
autorequire:
|
10
11
|
bindir: bin
|
11
12
|
cert_chain: []
|
12
|
-
date: 2014-
|
13
|
+
date: 2014-08-20 00:00:00.000000000 Z
|
13
14
|
dependencies:
|
14
15
|
- !ruby/object:Gem::Dependency
|
15
16
|
name: savon
|
@@ -71,6 +72,7 @@ description: Control Sonos speakers with Ruby
|
|
71
72
|
email:
|
72
73
|
- sam@soff.es
|
73
74
|
- gotwalt@gmail.com
|
75
|
+
- jasper@humppa.nl
|
74
76
|
executables:
|
75
77
|
- sonos
|
76
78
|
extensions: []
|
@@ -98,6 +100,8 @@ files:
|
|
98
100
|
- lib/sonos/endpoint/device.rb
|
99
101
|
- lib/sonos/endpoint/rendering.rb
|
100
102
|
- lib/sonos/endpoint/upnp.rb
|
103
|
+
- lib/sonos/features.rb
|
104
|
+
- lib/sonos/features/voiceover.rb
|
101
105
|
- lib/sonos/group.rb
|
102
106
|
- lib/sonos/system.rb
|
103
107
|
- lib/sonos/topology_node.rb
|