sonos 0.3.5 → 0.3.6
Sign up to get free protection for your applications and to get access to all the features.
- 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
|