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