hallon 0.4.0 → 0.8.0
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.
- data/.gitmodules +3 -0
- data/.travis.yml +2 -0
- data/CHANGELOG +30 -6
- data/README.markdown +7 -7
- data/Rakefile +70 -16
- data/examples/logging_in.rb +3 -3
- data/examples/printing_link_information.rb +1 -1
- data/examples/show_published_playlists_of_user.rb +92 -0
- data/hallon.gemspec +7 -4
- data/lib/hallon.rb +16 -4
- data/lib/hallon/album.rb +16 -6
- data/lib/hallon/album_browse.rb +78 -0
- data/lib/hallon/artist.rb +59 -0
- data/lib/hallon/artist_browse.rb +89 -0
- data/lib/hallon/base.rb +7 -0
- data/lib/hallon/enumerator.rb +64 -0
- data/lib/hallon/error.rb +8 -6
- data/lib/hallon/ext/spotify.rb +3 -3
- data/lib/hallon/image.rb +25 -12
- data/lib/hallon/link.rb +4 -4
- data/lib/hallon/linkable.rb +4 -2
- data/lib/hallon/observable.rb +1 -4
- data/lib/hallon/player.rb +130 -0
- data/lib/hallon/search.rb +128 -0
- data/lib/hallon/session.rb +226 -25
- data/lib/hallon/toplist.rb +83 -0
- data/lib/hallon/track.rb +62 -7
- data/lib/hallon/user.rb +6 -6
- data/lib/hallon/version.rb +1 -1
- data/spec/hallon/album_browse_spec.rb +20 -0
- data/spec/hallon/album_spec.rb +12 -7
- data/spec/hallon/artist_browse_spec.rb +29 -0
- data/spec/hallon/artist_spec.rb +32 -0
- data/spec/hallon/enumerator_spec.rb +106 -0
- data/spec/hallon/error_spec.rb +10 -0
- data/spec/hallon/hallon_spec.rb +5 -1
- data/spec/hallon/image_spec.rb +39 -25
- data/spec/hallon/linkable_spec.rb +12 -4
- data/spec/hallon/observable_spec.rb +5 -0
- data/spec/hallon/player_spec.rb +73 -0
- data/spec/hallon/search_spec.rb +80 -0
- data/spec/hallon/session_spec.rb +187 -6
- data/spec/hallon/toplist_spec.rb +40 -0
- data/spec/hallon/track_spec.rb +43 -8
- data/spec/mockspotify.rb +47 -0
- data/spec/mockspotify/.gitignore +5 -0
- data/spec/mockspotify/extconf.rb +5 -0
- data/spec/mockspotify/mockspotify_spec.rb +41 -0
- data/spec/spec_helper.rb +20 -0
- data/spec/support/common_objects.rb +84 -7
- metadata +72 -20
- data/lib/hallon/ext/object.rb +0 -16
@@ -0,0 +1,130 @@
|
|
1
|
+
module Hallon
|
2
|
+
# A wrapper around Session for playing, stopping and otherwise
|
3
|
+
# controlling the playback features of libspotify.
|
4
|
+
#
|
5
|
+
# @note This is very much a work in progress. Given Session still
|
6
|
+
# takes care of all callbacks, and the callbacks themselves
|
7
|
+
# must still be handled by means of Ruby FFI.
|
8
|
+
# @see Session
|
9
|
+
class Player
|
10
|
+
include Observable
|
11
|
+
|
12
|
+
# @return [Array<Symbol>] a list of available playback bitrates.
|
13
|
+
def self.bitrates
|
14
|
+
Spotify.enum_type(:bitrate).symbols.sort_by do |sym|
|
15
|
+
# sort by bitrate quality
|
16
|
+
sym.to_s.to_i
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
# Constructs a Player, given a Session.
|
21
|
+
#
|
22
|
+
# @example
|
23
|
+
# Hallon::Player.new(session) do
|
24
|
+
# on(:music_delivery) do |*frames|
|
25
|
+
# end
|
26
|
+
#
|
27
|
+
# on(:start_playback) do
|
28
|
+
# end
|
29
|
+
#
|
30
|
+
# on(:stop_playback) do
|
31
|
+
# end
|
32
|
+
#
|
33
|
+
# on(:play_token_lost) do
|
34
|
+
# end
|
35
|
+
#
|
36
|
+
# on(:end_of_track) do
|
37
|
+
# end
|
38
|
+
#
|
39
|
+
# on(:streaming_error) do |error|
|
40
|
+
# end
|
41
|
+
#
|
42
|
+
# on(:buffer_size?) do
|
43
|
+
# # return the pair of [samples, dropouts] of your audiobuffer
|
44
|
+
# end
|
45
|
+
# end
|
46
|
+
#
|
47
|
+
# @param [Session] session
|
48
|
+
# @yield instance_evals itself, allowing you to define callbacks using `on`
|
49
|
+
def initialize(session, &block)
|
50
|
+
instance_eval(&block) if block_given?
|
51
|
+
|
52
|
+
@session = session
|
53
|
+
@pointer = @session.pointer
|
54
|
+
|
55
|
+
%w[start_playback stop_playback play_token_lost end_of_track streaming_error].each do |cb|
|
56
|
+
@session.on(cb) { |*args| trigger(cb, *args) }
|
57
|
+
end
|
58
|
+
|
59
|
+
@session.on(:audio_buffer_stats) do |stats_ptr|
|
60
|
+
stats = Spotify::AudioBufferStats.new(stats_ptr)
|
61
|
+
samples, dropouts = trigger(:buffer_size?)
|
62
|
+
stats[:samples] = samples || 0
|
63
|
+
stats[:dropouts] = dropouts || 0
|
64
|
+
end
|
65
|
+
|
66
|
+
@session.on(:music_delivery) do |format, frames, num_frames|
|
67
|
+
trigger(:music_delivery, format, frames, num_frames)
|
68
|
+
num_frames # assume we consume all data
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
# Set preferred playback bitrate.
|
73
|
+
#
|
74
|
+
# @param [Symbol] bitrate one of :96k, :160k, :320k
|
75
|
+
# @return [Symbol]
|
76
|
+
def bitrate=(bitrate)
|
77
|
+
Spotify.session_preferred_bitrate(@pointer, bitrate)
|
78
|
+
end
|
79
|
+
|
80
|
+
# Loads a Track for playing.
|
81
|
+
#
|
82
|
+
# @param [Track] track
|
83
|
+
# @return [Player]
|
84
|
+
# @raise [Error] if the track could not be loaded
|
85
|
+
def load(track)
|
86
|
+
error = Spotify.session_player_load(@pointer, track.pointer)
|
87
|
+
tap { Error.maybe_raise(error) }
|
88
|
+
end
|
89
|
+
|
90
|
+
# Prepares a Track for playing, without loading it.
|
91
|
+
#
|
92
|
+
# @note You can only prefetch if caching is on.
|
93
|
+
# @param [Track] track
|
94
|
+
# @return [Player]
|
95
|
+
def prefetch(track)
|
96
|
+
error = Spotify.session_player_prefetch(@pointer, track.pointer)
|
97
|
+
tap { Error.maybe_raise(error) }
|
98
|
+
end
|
99
|
+
|
100
|
+
# Starts playing a Track by feeding data to your application.
|
101
|
+
#
|
102
|
+
# @return [Player]
|
103
|
+
def play(track = nil)
|
104
|
+
load(track) unless track.nil?
|
105
|
+
tap { Spotify.session_player_play(@pointer, true) }
|
106
|
+
end
|
107
|
+
|
108
|
+
# Pause playback of a Track.
|
109
|
+
#
|
110
|
+
# @return [Player]
|
111
|
+
def pause
|
112
|
+
tap { Spotify.session_player_play(@pointer, false) }
|
113
|
+
end
|
114
|
+
|
115
|
+
# Stop playing current track and unload it.
|
116
|
+
#
|
117
|
+
# @return [Player]
|
118
|
+
def stop
|
119
|
+
tap { Spotify.session_player_unload(@pointer) }
|
120
|
+
end
|
121
|
+
|
122
|
+
# Seek to the desired position of the currently loaded Track.
|
123
|
+
#
|
124
|
+
# @param [Numeric] seconds offset position in seconds
|
125
|
+
# @return [Player]
|
126
|
+
def seek(seconds)
|
127
|
+
tap { Spotify.session_player_seek(@pointer, seconds * 1000) }
|
128
|
+
end
|
129
|
+
end
|
130
|
+
end
|
@@ -0,0 +1,128 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
module Hallon
|
3
|
+
# Search allows you to search Spotify for tracks, albums
|
4
|
+
# and artists, just like in the client.
|
5
|
+
#
|
6
|
+
# @see http://developer.spotify.com/en/libspotify/docs/group__search.html
|
7
|
+
class Search < Base
|
8
|
+
include Observable
|
9
|
+
|
10
|
+
# @return [Array<Symbol>] a list of radio genres available for search
|
11
|
+
def self.genres
|
12
|
+
Spotify.enum_type(:radio_genre).symbols
|
13
|
+
end
|
14
|
+
|
15
|
+
# @param [Range<Integer>] range (from_year..to_year)
|
16
|
+
# @param [Symbol, …] genres
|
17
|
+
# @return [Search] radio search in given period and genres
|
18
|
+
def self.radio(range, *genres)
|
19
|
+
from_year, to_year = range.begin, range.end
|
20
|
+
genres = genres.reduce(0) do |mask, genre|
|
21
|
+
mask | (Spotify.enum_value(genre) || 0)
|
22
|
+
end
|
23
|
+
|
24
|
+
search = allocate
|
25
|
+
search.instance_eval do
|
26
|
+
@callback = proc { search.trigger(:load) }
|
27
|
+
pointer = Spotify.radio_search_create(session.pointer, from_year, to_year, genres, @callback, nil)
|
28
|
+
@pointer = Spotify::Pointer.new(pointer, :search, false)
|
29
|
+
|
30
|
+
self
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
# Construct a new search with given query.
|
35
|
+
#
|
36
|
+
# @param [String] query search query
|
37
|
+
# @param [Hash] options additional search options
|
38
|
+
# @option options [#to_i] :tracks (25) max number of tracks you want in result
|
39
|
+
# @option options [#to_i] :albums (25) max number of albums you want in result
|
40
|
+
# @option options [#to_i] :artists (25) max number of artists you want in result
|
41
|
+
# @option options [#to_i] :tracks_offset (0) offset of tracks in search result
|
42
|
+
# @option options [#to_i] :albums_offset (0) offset of albums in search result
|
43
|
+
# @option options [#to_i] :artists_offset (0) offset of artists in search result
|
44
|
+
# @see http://developer.spotify.com/en/libspotify/docs/group__search.html#gacf0b5e902e27d46ef8b1f40e332766df
|
45
|
+
def initialize(query, options = {})
|
46
|
+
o = {
|
47
|
+
:tracks => 25,
|
48
|
+
:albums => 25,
|
49
|
+
:artists => 25,
|
50
|
+
:tracks_offset => 0,
|
51
|
+
:albums_offset => 0,
|
52
|
+
:artists_offset => 0
|
53
|
+
}.merge(options)
|
54
|
+
|
55
|
+
@callback = proc { trigger(:load) }
|
56
|
+
pointer = Spotify.search_create(session.pointer, query, o[:tracks_offset].to_i, o[:tracks].to_i, o[:albums_offset].to_i, o[:albums].to_i, o[:artists_offset].to_i, o[:artists].to_i, @callback, nil)
|
57
|
+
@pointer = Spotify::Pointer.new(pointer, :search, false)
|
58
|
+
end
|
59
|
+
|
60
|
+
# @return [Boolean] true if the search has been fully loaded
|
61
|
+
def loaded?
|
62
|
+
Spotify.search_is_loaded(@pointer)
|
63
|
+
end
|
64
|
+
|
65
|
+
# @return [Symbol] error status
|
66
|
+
def error
|
67
|
+
Spotify.search_error(@pointer)
|
68
|
+
end
|
69
|
+
|
70
|
+
# @return [String] search query this search was created with
|
71
|
+
def query
|
72
|
+
Spotify.search_query(@pointer)
|
73
|
+
end
|
74
|
+
|
75
|
+
# @return [String] “did you mean?” suggestion for current search
|
76
|
+
def did_you_mean
|
77
|
+
Spotify.search_did_you_mean(@pointer)
|
78
|
+
end
|
79
|
+
|
80
|
+
# @return [Enumerator<Track>] enumerate over all tracks in the search result
|
81
|
+
def tracks
|
82
|
+
size = Spotify.search_num_tracks(@pointer)
|
83
|
+
Enumerator.new(size) do |i|
|
84
|
+
track = Spotify.search_track(@pointer, i)
|
85
|
+
Track.new(track) unless track.null?
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
# @return [Integer] total tracks available for this search query
|
90
|
+
def total_tracks
|
91
|
+
Spotify.search_total_tracks(@pointer)
|
92
|
+
end
|
93
|
+
|
94
|
+
# @return [Enumerator<Album>] enumerate over all albums in the search result
|
95
|
+
def albums
|
96
|
+
size = Spotify.search_num_albums(@pointer)
|
97
|
+
Enumerator.new(size) do |i|
|
98
|
+
album = Spotify.search_album(@pointer, i)
|
99
|
+
Album.new(album) unless album.null?
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
# @return [Integer] total tracks available for this search query
|
104
|
+
def total_albums
|
105
|
+
Spotify.search_total_albums(@pointer)
|
106
|
+
end
|
107
|
+
|
108
|
+
# @return [Enumerator<Artist>] enumerate over all artists in the search result
|
109
|
+
def artists
|
110
|
+
size = Spotify.search_num_artists(@pointer)
|
111
|
+
Enumerator.new(size) do |i|
|
112
|
+
artist = Spotify.search_artist(@pointer, i)
|
113
|
+
Artist.new(artist) unless artist.null?
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
# @return [Integer] total tracks available for this search query
|
118
|
+
def total_artists
|
119
|
+
Spotify.search_total_artists(@pointer)
|
120
|
+
end
|
121
|
+
|
122
|
+
# @return [Link] link for this search query
|
123
|
+
def to_link
|
124
|
+
pointer = Spotify.link_create_from_search(@pointer)
|
125
|
+
Link.new(pointer) unless pointer.null?
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
data/lib/hallon/session.rb
CHANGED
@@ -15,6 +15,15 @@ module Hallon
|
|
15
15
|
# @return [Hash]
|
16
16
|
attr_reader :options
|
17
17
|
|
18
|
+
# The current session cache size (in megabytes).
|
19
|
+
#
|
20
|
+
# @note This is not provided by libspotify, and the value is only valid
|
21
|
+
# as long as the cache size is only adjusted through {#cache_size=}
|
22
|
+
# and not the Spotify FFI interface.
|
23
|
+
#
|
24
|
+
# @return [Integer]
|
25
|
+
attr_reader :cache_size
|
26
|
+
|
18
27
|
# libspotify only allows one session per process.
|
19
28
|
include Singleton
|
20
29
|
class << self
|
@@ -22,16 +31,38 @@ module Hallon
|
|
22
31
|
end
|
23
32
|
|
24
33
|
# Session allows you to define your own callbacks.
|
25
|
-
include
|
34
|
+
include Observable
|
26
35
|
|
27
|
-
#
|
28
|
-
#
|
36
|
+
# Initializes the Spotify session. If you need to access the
|
37
|
+
# instance at a later time, you can use {instance}.
|
29
38
|
#
|
30
|
-
# @
|
39
|
+
# @see Session.instance
|
31
40
|
# @see Session#initialize
|
41
|
+
#
|
42
|
+
# @param (see Session#initialize)
|
43
|
+
# @return [Session]
|
44
|
+
def Session.initialize(*args, &block)
|
45
|
+
raise "Session has already been initialized" if @__instance__
|
46
|
+
@__instance__ = new(*args, &block)
|
47
|
+
end
|
48
|
+
|
49
|
+
# Returns the previously initialized Session.
|
50
|
+
#
|
51
|
+
# @see Session.instance
|
52
|
+
#
|
32
53
|
# @return [Session]
|
33
|
-
def Session.instance
|
34
|
-
@__instance__
|
54
|
+
def Session.instance
|
55
|
+
@__instance__ or raise "Session has not been initialized"
|
56
|
+
end
|
57
|
+
|
58
|
+
# @return [Array<Symbol>] list of available connection types.
|
59
|
+
def self.connection_types
|
60
|
+
Spotify.enum_type(:connection_type).symbols
|
61
|
+
end
|
62
|
+
|
63
|
+
# @return [Array<Symbol>] list of available connection rules
|
64
|
+
def self.connection_rules
|
65
|
+
Spotify.enum_type(:connection_rules).symbols
|
35
66
|
end
|
36
67
|
|
37
68
|
# Create a new Spotify session.
|
@@ -49,7 +80,6 @@ module Hallon
|
|
49
80
|
# @raise [Hallon::Error] if `sp_session_create` fails
|
50
81
|
# @see http://developer.spotify.com/en/libspotify/docs/structsp__session__config.html
|
51
82
|
def initialize(appkey, options = {}, &block)
|
52
|
-
@appkey = appkey.to_s
|
53
83
|
@options = {
|
54
84
|
:user_agent => "Hallon",
|
55
85
|
:settings_path => "tmp",
|
@@ -66,15 +96,18 @@ module Hallon
|
|
66
96
|
# Set configuration, as well as callbacks
|
67
97
|
config = Spotify::SessionConfig.new
|
68
98
|
config[:api_version] = Hallon::API_VERSION
|
69
|
-
config.application_key =
|
99
|
+
config.application_key = appkey
|
70
100
|
@options.each { |(key, value)| config.send(:"#{key}=", value) }
|
71
101
|
config[:callbacks] = Spotify::SessionCallbacks.new(self, @sp_callbacks = {})
|
72
102
|
|
103
|
+
# Default cache size is 0 (automatic)
|
104
|
+
@cache_size = 0
|
105
|
+
|
73
106
|
instance_eval(&block) if block_given?
|
74
107
|
|
75
108
|
# You pass a pointer to the session pointer to libspotify >:)
|
76
109
|
FFI::MemoryPointer.new(:pointer) do |p|
|
77
|
-
|
110
|
+
Error::maybe_raise Spotify.session_create(config, p)
|
78
111
|
@pointer = p.read_pointer
|
79
112
|
end
|
80
113
|
end
|
@@ -84,20 +117,22 @@ module Hallon
|
|
84
117
|
# @return [Fixnum] minimum time until it should be called again
|
85
118
|
def process_events
|
86
119
|
FFI::MemoryPointer.new(:int) do |p|
|
87
|
-
Spotify
|
120
|
+
Spotify.session_process_events(@pointer, p)
|
88
121
|
return p.read_int
|
89
122
|
end
|
90
123
|
end
|
91
124
|
|
92
125
|
# Wait for the given callbacks to fire until the block returns true
|
93
126
|
#
|
127
|
+
# @note Given block will be called once instantly without parameters.
|
128
|
+
# @note If no events happen for 0.25 seconds, the given block will be called
|
129
|
+
# with `:timeout` as parameter.
|
94
130
|
# @param [Symbol, ...] *events list of events to wait for
|
95
131
|
# @yield [Symbol, *args] name of the callback that fired, and its’ arguments
|
96
132
|
# @return [Hash<Event, Arguments>]
|
97
|
-
def process_events_on(*events
|
98
|
-
|
99
|
-
|
100
|
-
protecting_handlers do
|
133
|
+
def process_events_on(*events)
|
134
|
+
yield or protecting_handlers do
|
135
|
+
channel = SizedQueue.new(1)
|
101
136
|
on(*events) { |*args| channel << args }
|
102
137
|
on(:notify_main_thread) { channel << :notify }
|
103
138
|
|
@@ -107,10 +142,10 @@ module Hallon
|
|
107
142
|
params = Timeout::timeout(0.25) { channel.pop }
|
108
143
|
redo if params == :notify
|
109
144
|
rescue Timeout::Error
|
110
|
-
params =
|
145
|
+
params = :timeout
|
111
146
|
end
|
112
147
|
|
113
|
-
if result =
|
148
|
+
if result = yield(*params)
|
114
149
|
return result
|
115
150
|
end
|
116
151
|
end
|
@@ -122,39 +157,187 @@ module Hallon
|
|
122
157
|
#
|
123
158
|
# @param [String] username
|
124
159
|
# @param [String] password
|
160
|
+
# @param [Boolean] remember_me have libspotify remember credentials for {#relogin}
|
161
|
+
# @return [self]
|
162
|
+
def login(username, password, remember_me = false)
|
163
|
+
tap { Spotify.session_login(@pointer, username, password, @remembered = remember_me) }
|
164
|
+
end
|
165
|
+
|
166
|
+
# Login the remembered user (see {#login}).
|
167
|
+
#
|
168
|
+
# @raise [Hallon::Error] if no credentials are stored in libspotify
|
169
|
+
def relogin
|
170
|
+
Error.maybe_raise Spotify.session_relogin(@pointer)
|
171
|
+
end
|
172
|
+
|
173
|
+
# Username of the user stored in libspotify-remembered credentials.
|
174
|
+
#
|
175
|
+
# @return [String]
|
176
|
+
def remembered_user
|
177
|
+
bufflen = Spotify.session_remembered_user(@pointer, nil, 0)
|
178
|
+
FFI::Buffer.alloc_out(bufflen + 1) do |b|
|
179
|
+
Spotify.session_remembered_user(@pointer, b, b.size)
|
180
|
+
return b.get_string(0)
|
181
|
+
end if bufflen > 0
|
182
|
+
end
|
183
|
+
|
184
|
+
# Remove stored login credentials in libspotify.
|
185
|
+
#
|
186
|
+
# @note If no credentials are stored nothing’ll happen.
|
125
187
|
# @return [self]
|
126
|
-
def
|
127
|
-
Spotify
|
128
|
-
self
|
188
|
+
def forget_me!
|
189
|
+
tap { Spotify.session_forget_me(@pointer) }
|
129
190
|
end
|
130
191
|
|
131
192
|
# Logs out of Spotify. Does nothing if not logged in.
|
132
193
|
#
|
133
194
|
# @return [self]
|
134
195
|
def logout
|
135
|
-
Spotify
|
136
|
-
self
|
196
|
+
tap { Spotify.session_logout(@pointer) if logged_in? }
|
137
197
|
end
|
138
198
|
|
139
199
|
# Retrieve the currently logged in {User}.
|
140
200
|
#
|
141
201
|
# @return [User]
|
142
202
|
def user
|
143
|
-
User.new Spotify
|
203
|
+
User.new Spotify.session_user(@pointer)
|
144
204
|
end
|
145
205
|
|
146
206
|
# Retrieve the relation type between logged in {User} and `user`.
|
147
207
|
#
|
148
208
|
# @return [Symbol] :unknown, :none, :unidirectional or :bidirectional
|
149
209
|
def relation_type?(user)
|
150
|
-
Spotify
|
210
|
+
Spotify.user_relation_type(@pointer, user.pointer)
|
151
211
|
end
|
152
212
|
|
153
213
|
# Retrieve current connection status.
|
154
214
|
#
|
155
215
|
# @return [Symbol]
|
156
216
|
def status
|
157
|
-
Spotify
|
217
|
+
Spotify.session_connectionstate(@pointer)
|
218
|
+
end
|
219
|
+
|
220
|
+
# Set session cache size in megabytes.
|
221
|
+
#
|
222
|
+
# @param [Integer]
|
223
|
+
# @return [Integer]
|
224
|
+
def cache_size=(size)
|
225
|
+
Spotify.session_set_cache_size(@pointer, @cache_size = size)
|
226
|
+
end
|
227
|
+
|
228
|
+
# @return [String] Currently logged in users’ country.
|
229
|
+
def country
|
230
|
+
coded = Spotify.session_user_country(@pointer)
|
231
|
+
country = ((coded >> 8) & 0xFF).chr
|
232
|
+
country << (coded & 0xFF).chr
|
233
|
+
end
|
234
|
+
|
235
|
+
# Star the given tracks.
|
236
|
+
#
|
237
|
+
# @example
|
238
|
+
# track = Hallon::Track.new("spotify:track:2LFQV2u6wXZmmySCWBkYGu")
|
239
|
+
# session.star(track)
|
240
|
+
#
|
241
|
+
# @param [Track…]
|
242
|
+
# @return [Session]
|
243
|
+
def star(*tracks)
|
244
|
+
tap { tracks_starred(tracks, true) }
|
245
|
+
end
|
246
|
+
|
247
|
+
# Unstar the given tracks.
|
248
|
+
#
|
249
|
+
# @example
|
250
|
+
# track = Hallon::Track.new("spotify:track:2LFQV2u6wXZmmySCWBkYGu")
|
251
|
+
# session.unstar(track)
|
252
|
+
#
|
253
|
+
# @param [Track…]
|
254
|
+
# @return [Session]
|
255
|
+
def unstar(*tracks)
|
256
|
+
tap { tracks_starred(tracks, false) }
|
257
|
+
end
|
258
|
+
|
259
|
+
# @note This will be 0 if not logged in.
|
260
|
+
# @note As of current writing, I am unsure if there’s a good way to find out
|
261
|
+
# when this enumerator will be populated. No callbacks or other status
|
262
|
+
# field can tell you when the current sessions’ friends are available.
|
263
|
+
# @return [Enumerator<User>] friends of currently logged in user
|
264
|
+
def friends
|
265
|
+
size = if logged_in?
|
266
|
+
# segfaults unless logged in
|
267
|
+
Spotify.session_num_friends(@pointer)
|
268
|
+
else
|
269
|
+
0
|
270
|
+
end
|
271
|
+
|
272
|
+
Enumerator.new(size) do |i|
|
273
|
+
friend = Spotify.session_friend(@pointer, i)
|
274
|
+
User.new(friend)
|
275
|
+
end
|
276
|
+
end
|
277
|
+
|
278
|
+
# Set the connection rules for this session.
|
279
|
+
#
|
280
|
+
# @param [Symbol, …] connection_rules
|
281
|
+
# @see Session.connection_rules
|
282
|
+
def connection_rules=(connection_rules)
|
283
|
+
rules = Array(connection_rules).reduce(0) do |mask, rule|
|
284
|
+
mask | (Spotify.enum_value(rule) || 0)
|
285
|
+
end
|
286
|
+
|
287
|
+
Spotify.session_set_connection_rules(@pointer, rules)
|
288
|
+
end
|
289
|
+
|
290
|
+
# Set the connection type for this session.
|
291
|
+
#
|
292
|
+
# @param [Symbol] connection_type
|
293
|
+
# @see Session.connection_types
|
294
|
+
def connection_type=(connection_type)
|
295
|
+
Spotify.session_set_connection_type(@pointer, connection_type)
|
296
|
+
end
|
297
|
+
|
298
|
+
# Remaining time left you can stay offline before needing to relogin.
|
299
|
+
#
|
300
|
+
# @return [Integer] offline time left in seconds
|
301
|
+
def offline_time_left
|
302
|
+
Spotify.offline_time_left(@pointer)
|
303
|
+
end
|
304
|
+
|
305
|
+
# Offline synchronization status.
|
306
|
+
#
|
307
|
+
# @return [Hash, nil] sync status, or nil if not applicable
|
308
|
+
# @see http://developer.spotify.com/en/libspotify/docs/structsp__offline__sync__status.html
|
309
|
+
def offline_sync_status
|
310
|
+
struct = Spotify::OfflineSyncStatus.new
|
311
|
+
if Spotify.offline_sync_get_status(@pointer, struct.pointer)
|
312
|
+
Hash[struct.members.zip(struct.values)]
|
313
|
+
end
|
314
|
+
end
|
315
|
+
|
316
|
+
# Number of playlists marked for offline sync.
|
317
|
+
#
|
318
|
+
# @return [Integer]
|
319
|
+
def offline_playlists_count
|
320
|
+
Spotify.offline_num_playlists(@pointer)
|
321
|
+
end
|
322
|
+
|
323
|
+
# Number of offline tracks left to sync for offline mode.
|
324
|
+
#
|
325
|
+
# @return [Integer]
|
326
|
+
def offline_tracks_to_sync
|
327
|
+
Spotify.offline_tracks_to_sync(@pointer)
|
328
|
+
end
|
329
|
+
|
330
|
+
# Set preferred offline bitrate.
|
331
|
+
#
|
332
|
+
# @example
|
333
|
+
# session.offline_bitrate = :'96k', true
|
334
|
+
#
|
335
|
+
# @param [Symbol] bitrate
|
336
|
+
# @param [Boolean] resync (default: false)
|
337
|
+
# @see Player.bitrates
|
338
|
+
def offline_bitrate=(bitrate)
|
339
|
+
bitrate, resync = Array(bitrate)
|
340
|
+
Spotify.session_preferred_offline_bitrate(@pointer, bitrate, !! resync)
|
158
341
|
end
|
159
342
|
|
160
343
|
# True if currently logged in.
|
@@ -175,11 +358,29 @@ module Hallon
|
|
175
358
|
status == :disconnected
|
176
359
|
end
|
177
360
|
|
361
|
+
# True if offline.
|
362
|
+
# @see #status
|
363
|
+
def offline?
|
364
|
+
status == :offline
|
365
|
+
end
|
366
|
+
|
178
367
|
# String representation of the Session.
|
179
368
|
#
|
180
369
|
# @return [String]
|
181
370
|
def to_s
|
182
|
-
"<#{self.class.name}>"
|
371
|
+
"<#{self.class.name}:0x#{object_id.to_s(16)} status=#{status} @options=#{options.inspect}>"
|
183
372
|
end
|
373
|
+
|
374
|
+
private
|
375
|
+
# Set starred status of given tracks.
|
376
|
+
#
|
377
|
+
# @param [Array<Track>] tracks
|
378
|
+
# @param [Boolean] starred
|
379
|
+
def tracks_starred(tracks, starred)
|
380
|
+
FFI::MemoryPointer.new(:pointer, tracks.size) do |ptr|
|
381
|
+
ptr.write_array_of_pointer tracks.map(&:pointer)
|
382
|
+
Spotify.track_set_starred(pointer, ptr, tracks.size, starred)
|
383
|
+
end
|
384
|
+
end
|
184
385
|
end
|
185
386
|
end
|