module Sonos::Endpoint::AVTransport
Constants
- TRANSPORT_ENDPOINT
- TRANSPORT_XMLNS
Public Instance Methods
Add an Rdio object to the queue (album or track), anything else can only be streamed (play now). @param opts Various options (album/track keys, username and type) @param position Optional queue insertion position. Leaving this blank inserts at the end. @return Queue position of the added track(s)
# File lib/sonos/endpoint/a_v_transport.rb, line 177 def add_rdio_to_queue(opts = {}, position = 0) opts = { :username => nil, :album => nil, :track => nil, :type => 'track', :format => 'mp3' # can be changed, but only 'mp3' is valid. }.merge(opts) return nil if opts[:username].nil? # Both tracks and albums require the album key. And tracks need a track # key of course. return nil if opts[:album].nil? return nil if opts[:type] == 'track' and opts[:track].nil? # In order for valid DIDL we'll pass an empty :track for albums. opts[:track] = '' if opts[:type] == 'album' 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>" case opts[:type] when /track/ uri = "x-sonos-http:_t%3a%3a#{opts[:track]}%3a%3aa%3a%3a#{opts[:album]}.#{opts[:format]}?sid=11&flags=32" when /album/ type_id = '0004006c_a' uri = "x-rincon-cpcontainer:#{type_id}%3a%3a#{opts[:album]}" else return nil end add_to_queue(uri, didl_metadata, position) end
Adds a Spotify track to the queue along with extra data for better metadata retrieval @param opts Various options (id, user, region and type) @param position Optional queue insertion position. Leaving this blank inserts at the end. @return Queue position of the added track(s)
# File lib/sonos/endpoint/a_v_transport.rb, line 132 def add_spotify_to_queue(opts = {}, position = 0) opts = { :id => '', :user => nil, :region => nil, :type => 'track' }.merge(opts) # Basic validation of the accepted types; playlists need an associated user # and the toplist (for tracks and albums) need to specify a region. return nil if opts[:type] == 'playlist' and opts[:user].nil? return nil if opts[:type] =~ /toplist_tracks/ and opts[:region].nil? # In order for the player to retrieve track duration, artist, album etc # we need to pass it some metadata ourselves. 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="#{rand(10000000..99999999)}spotify%3a#{opts[:type]}%3a#{opts[:id]}" 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_RINCON2311_X_#Svc2311-0-Token</desc></item></DIDL-Lite>" r_id = rand(10000000..99999999) case opts[:type] when /playlist/ uri = "x-rincon-cpcontainer:#{r_id}spotify%3auser%3a#{opts[:user]}%3aplaylist%3a#{opts[:id]}" when /toplist_(tracks)/ subtype = opts[:type].sub('toplist_', '') # only 'tracks' are supported right now by Sonos. uri = "x-rincon-cpcontainer:#{r_id}toplist%2f#{subtype}%2fregion%2f#{opts[:region]}" when /album/ uri = "x-rincon-cpcontainer:#{r_id}spotify%3aalbum%3a#{opts[:id]}" when /artist/ uri = "x-rincon-cpcontainer:#{r_id}tophits%3aspotify%3aartist%3a#{opts[:id]}" when /starred/ uri = "x-rincon-cpcontainer:#{r_id}starred" when /track/ uri = "x-sonos-spotify:spotify%3a#{opts[:type]}%3a#{opts[:id]}" else return nil end add_to_queue(uri, didl_metadata, position) end
Adds a track to the queue @param uri Uri of track @param didl Stanza of DIDL-Lite metadata (generally created by add_spotify_to_queue
) @param position Optional queue insertion position. Leaving this blank inserts at the end. @return Queue position of the added track
# File lib/sonos/endpoint/a_v_transport.rb, line 119 def add_to_queue(uri, didl = '', position = 0) response = send_transport_message('AddURIToQueue', "<EnqueuedURI>#{uri}</EnqueuedURI><EnqueuedURIMetaData>#{didl}</EnqueuedURIMetaData><DesiredFirstTrackNumberEnqueued>#{position}</DesiredFirstTrackNumberEnqueued><EnqueueAsNext>1</EnqueueAsNext>") # TODO yeah, this error handling is a bit soft. For consistency's sake :) pos = response.xpath('.//FirstTrackNumberEnqueued').text if pos.length != 0 pos.to_i end end
Clear the queue
# File lib/sonos/endpoint/a_v_transport.rb, line 105 def clear_queue parse_response send_transport_message('RemoveAllTracksFromQueue') end
Get information about the state the player is in.
# File lib/sonos/endpoint/a_v_transport.rb, line 40 def get_player_state response = send_transport_message('GetTransportInfo') body = response.body[:get_transport_info_response] { status: body[:current_transport_status], state: body[:current_transport_state], speed: body[:current_speed], } end
Add another speaker to this group. Trying to call this on a stereo pair slave will fail.
# File lib/sonos/endpoint/a_v_transport.rb, line 225 def group(slave) slave.join(self) end
# File lib/sonos/endpoint/a_v_transport.rb, line 35 def has_music? !now_playing.nil? end
Returns true if the player is not in a paused or stopped state
# File lib/sonos/endpoint/a_v_transport.rb, line 52 def is_playing? state = get_player_state[:state] !['PAUSED_PLAYBACK', 'STOPPED'].include?(state) end
Join another speaker’s group. Trying to call this on a stereo pair slave will fail.
# File lib/sonos/endpoint/a_v_transport.rb, line 219 def join(master) parse_response set_av_transport_uri('x-rincon:' + master.uid.sub('uuid:', '')) end
# File lib/sonos/endpoint/a_v_transport.rb, line 87 def line_in(speaker) set_av_transport_uri('x-rincon-stream:' + speaker.uid.sub('uuid:', '')) end
Play the next track.
# File lib/sonos/endpoint/a_v_transport.rb, line 78 def next parse_response send_transport_message('Next') end
Get information about the currently playing track. @return [Hash] information about the current track.
# File lib/sonos/endpoint/a_v_transport.rb, line 9 def now_playing response = send_transport_message('GetPositionInfo') body = response.body[:get_position_info_response] doc = Nokogiri::XML(body[:track_meta_data]) # No music return nil if doc.children.length == 0 art_path = doc.xpath('//upnp:albumArtURI').inner_text # TODO: No idea why this is necessary. Maybe its a Nokogiri thing art_path.sub!('/getaa?s=1=x-sonos-http', '/getaa?s=1&u=x-sonos-http') { title: doc.xpath('//dc:title').inner_text, artist: doc.xpath('//dc:creator').inner_text, album: doc.xpath('//upnp:album').inner_text, info: doc.xpath('//r:streamContent').inner_text, queue_position: body[:track], track_duration: body[:track_duration], current_position: body[:rel_time], uri: body[:track_uri], album_art: "http://#{self.ip}:#{Sonos::PORT}#{art_path}" } end
Pause the currently playing track.
# File lib/sonos/endpoint/a_v_transport.rb, line 58 def pause parse_response send_transport_message('Pause') end
Play the currently selected track or play a stream. @param [String] uri Optional uri of the track to play. Leaving this blank, plays the current track.
# File lib/sonos/endpoint/a_v_transport.rb, line 64 def play(uri = nil) # Play a song from the uri set_av_transport_uri(uri) and return if uri # Play the currently selected track parse_response send_transport_message('Play') end
Play the previous track.
# File lib/sonos/endpoint/a_v_transport.rb, line 83 def previous parse_response send_transport_message('Previous') end
Removes a track from the queue @param object_id Track’s queue ID
# File lib/sonos/endpoint/a_v_transport.rb, line 213 def remove_from_queue(object_id) parse_response send_transport_message('RemoveTrackFromQueue', "<ObjectID>#{object_id}</ObjectID><UpdateID>0</UpdateID></u:RemoveTrackFromQueue>") end
Save queue
# File lib/sonos/endpoint/a_v_transport.rb, line 110 def save_queue(title) parse_response send_transport_message('SaveQueue', "<Title>#{title}</Title><ObjectID></ObjectID>") end
Seeks to a given timestamp in the current track @param [Fixnum] seconds
# File lib/sonos/endpoint/a_v_transport.rb, line 93 def seek(seconds = 0) # Must be sent in the format of HH:MM:SS timestamp = Time.at(seconds).utc.strftime('%H:%M:%S') parse_response send_transport_message('Seek', "<Unit>REL_TIME</Unit><Target>#{timestamp}</Target>") end
Seeks the playlist selection to the provided index
# File lib/sonos/endpoint/a_v_transport.rb, line 100 def select_track(index) parse_response send_transport_message('Seek', "<Unit>TRACK_NR</Unit><Target>#{index}</Target>") end
Set a sleep timer up to 23:59:59 E.g. ‘00:11:00’ for 11 minutes. @param duration [String] Duration of timer or nil to clear.
# File lib/sonos/endpoint/a_v_transport.rb, line 238 def set_sleep_timer(duration) if duration.nil? duration = '' elsif duration.gsub(':', '').to_i > 235959 duration = '23:59:59' end parse_response send_transport_message('ConfigureSleepTimer', "<NewSleepTimerDuration>#{duration}</NewSleepTimerDuration>") end
Stop playing.
# File lib/sonos/endpoint/a_v_transport.rb, line 73 def stop parse_response send_transport_message('Stop') end
Ungroup from its current group. Trying to call this on a stereo pair slave will fail.
# File lib/sonos/endpoint/a_v_transport.rb, line 231 def ungroup parse_response send_transport_message('BecomeCoordinatorOfStandaloneGroup') end
Private Instance Methods
# File lib/sonos/endpoint/a_v_transport.rb, line 259 def send_transport_message(name, part = '<Speed>1</Speed>') action = "#{TRANSPORT_XMLNS}##{name}" message = %Q{<u:#{name} xmlns:u="#{TRANSPORT_XMLNS}"><InstanceID>0</InstanceID>#{part}</u:#{name}>} transport_client.call(name, soap_action: action, message: message) end
Play a stream.
# File lib/sonos/endpoint/a_v_transport.rb, line 251 def set_av_transport_uri(uri) send_transport_message('SetAVTransportURI', "<CurrentURI>#{uri}</CurrentURI><CurrentURIMetaData></CurrentURIMetaData>") end
# File lib/sonos/endpoint/a_v_transport.rb, line 255 def transport_client @transport_client ||= Savon.client endpoint: "http://#{self.group_master.ip}:#{Sonos::PORT}#{TRANSPORT_ENDPOINT}", namespace: Sonos::NAMESPACE, log: Sonos.logging_enabled end