hallon 0.17.0 → 0.18.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/CHANGELOG.md +14 -0
- data/Gemfile +5 -0
- data/README.markdown +1 -1
- data/examples/nordic_ruby_2012.rb +24 -0
- data/hallon.gemspec +1 -1
- data/lib/hallon.rb +3 -3
- data/lib/hallon/audio_queue.rb +51 -37
- data/lib/hallon/observable/album_browse.rb +1 -2
- data/lib/hallon/observable/artist_browse.rb +1 -2
- data/lib/hallon/observable/image.rb +1 -2
- data/lib/hallon/observable/player.rb +4 -4
- data/lib/hallon/observable/playlist.rb +13 -26
- data/lib/hallon/observable/playlist_container.rb +4 -8
- data/lib/hallon/observable/post.rb +1 -2
- data/lib/hallon/observable/search.rb +1 -2
- data/lib/hallon/observable/session.rb +18 -36
- data/lib/hallon/observable/toplist.rb +1 -2
- data/lib/hallon/player.rb +12 -12
- data/lib/hallon/session.rb +3 -0
- data/lib/hallon/version.rb +1 -1
- data/spec/hallon/audio_queue_spec.rb +37 -24
- data/spec/hallon/player_spec.rb +5 -10
- metadata +6 -5
data/CHANGELOG.md
CHANGED
@@ -4,6 +4,19 @@ Hallon’s Changelog
|
|
4
4
|
[HEAD][]
|
5
5
|
------------------
|
6
6
|
|
7
|
+
[v0.18.0][]
|
8
|
+
------------------
|
9
|
+
This issue is to plug a bug of libspotify on Linux (see issue #125). The reason that
|
10
|
+
the minor version is increased is because of the update to libspotify v12.1.51.
|
11
|
+
|
12
|
+
__Changed__
|
13
|
+
- Upgrade to libspotify v12.1.51 [7c72ead7]
|
14
|
+
- All custom Hallon errors now inherit from Hallon::Error [950cf01]
|
15
|
+
|
16
|
+
__Fixed__
|
17
|
+
- Update documentation for observable hooks to not say they yield themselves [8c85c5e]
|
18
|
+
- Fixed race condition in audio format change [a5decb1]
|
19
|
+
|
7
20
|
[v0.17.0][]
|
8
21
|
------------------
|
9
22
|
Updated to libspotify v12.1.45, with all the good parts included!
|
@@ -382,4 +395,5 @@ easier to handle.
|
|
382
395
|
[v0.15.0]: https://github.com/Burgestrand/Hallon/compare/v0.14.0...v0.15.0
|
383
396
|
[v0.16.0]: https://github.com/Burgestrand/Hallon/compare/v0.15.0...v0.16.0
|
384
397
|
[v0.17.0]: https://github.com/Burgestrand/Hallon/compare/v0.16.0...v0.17.0
|
398
|
+
[v0.18.0]: https://github.com/Burgestrand/Hallon/compare/v0.17.0...v0.18.0
|
385
399
|
[HEAD]: https://github.com/Burgestrand/Hallon/compare/v0.17.0...HEAD
|
data/Gemfile
CHANGED
data/README.markdown
CHANGED
@@ -15,7 +15,7 @@ Before you start using Hallon you’ll need to complete the following steps.
|
|
15
15
|
2. [Download your application key from developer.spotify.com](https://developer.spotify.com/en/libspotify/application-key/),
|
16
16
|
and place it in a known location. You’ll have the option of downloading it either in **binary** or c-code. You want the
|
17
17
|
**binary** one. If you do not have an application key already, you will be asked to create one.
|
18
|
-
3. Install libspotify. Hallon always aims to support to most recent version, which is currently **
|
18
|
+
3. Install libspotify. Hallon always aims to support to most recent version, which is currently **v12.1.51**. Older
|
19
19
|
versions are not supported. For help installing libspotify, please see the wiki on [How to install libspotify][].
|
20
20
|
4. Once the above are done, you are ready to try out Hallon.
|
21
21
|
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
|
3
|
+
# Require support code, used by all the examples.
|
4
|
+
require_relative 'example_support'
|
5
|
+
session = Hallon::Session.instance
|
6
|
+
|
7
|
+
require 'pry'
|
8
|
+
|
9
|
+
begin
|
10
|
+
require 'hallon/openal'
|
11
|
+
rescue LoadError => e
|
12
|
+
puts e.message
|
13
|
+
abort "[ERROR] Could not load gem 'hallon-openal', please install with 'gem install hallon-openal'"
|
14
|
+
end
|
15
|
+
|
16
|
+
puts "Creating Player."
|
17
|
+
player = Hallon::Player.new(Hallon::OpenAL)
|
18
|
+
|
19
|
+
puts "Loading track."
|
20
|
+
track = Hallon::Track.new("spotify:track:7Az9kfq4JLD8auD1xoErrP").load
|
21
|
+
player.load(track)
|
22
|
+
player.seek(9.8)
|
23
|
+
|
24
|
+
binding.pry
|
data/hallon.gemspec
CHANGED
@@ -22,7 +22,7 @@ Gem::Specification.new do |gem|
|
|
22
22
|
gem.required_ruby_version = '>= 1.9'
|
23
23
|
|
24
24
|
gem.add_dependency 'ref', '~> 1.0'
|
25
|
-
gem.add_dependency 'spotify', '~> 12.0
|
25
|
+
gem.add_dependency 'spotify', '~> 12.2.0'
|
26
26
|
gem.add_development_dependency 'rake', '~> 0.8'
|
27
27
|
gem.add_development_dependency 'rspec', '~> 2'
|
28
28
|
gem.add_development_dependency 'yard'
|
data/lib/hallon.rb
CHANGED
@@ -73,14 +73,14 @@ module Hallon
|
|
73
73
|
TimeoutError = Class.new(Hallon::Error)
|
74
74
|
|
75
75
|
# Raised by Session.instance
|
76
|
-
NoSessionError = Class.new(
|
76
|
+
NoSessionError = Class.new(Hallon::Error)
|
77
77
|
|
78
78
|
# Raised by Session#login! and Session#relogin!
|
79
|
-
LoginError = Class.new(
|
79
|
+
LoginError = Class.new(Hallon::Error)
|
80
80
|
|
81
81
|
# Raised by PlaylistContainer#num_unseen_tracks_for and PlaylistContainer#unseen_tracks_for.
|
82
82
|
# @note most likely raised because of the playlist not being in the playlist container.
|
83
|
-
OperationFailedError = Class.new(
|
83
|
+
OperationFailedError = Class.new(Hallon::Error)
|
84
84
|
|
85
85
|
class << self
|
86
86
|
# @return [Numeric] default load timeout in seconds, used in {Loadable#load}.
|
data/lib/hallon/audio_queue.rb
CHANGED
@@ -12,56 +12,81 @@ module Hallon
|
|
12
12
|
#
|
13
13
|
# Hallon::AudioQueue is useful for handling {Hallon::Observable::Session#music_delivery_callback}.
|
14
14
|
#
|
15
|
-
# @example
|
15
|
+
# @example filling the buffer
|
16
16
|
# queue = Hallon::AudioQueue.new(4)
|
17
|
-
#
|
18
|
-
# queue.push([
|
19
|
-
# queue.push(
|
20
|
-
# queue.push([5, 6]) # =>
|
21
|
-
# queue.
|
22
|
-
# queue.
|
23
|
-
# queue.
|
17
|
+
# format = { :rate => 44100, :channels => 2 }
|
18
|
+
# queue.push(format, [1, 2]) # => 2
|
19
|
+
# queue.push(format, [3]) # => 1
|
20
|
+
# queue.push(format, [4, 5, 6]) # => 1
|
21
|
+
# queue.push(format, [5, 6]) # => 0
|
22
|
+
# queue.pop(format, 1) # => [1]
|
23
|
+
# queue.push(format, [5, 6]) # => 1
|
24
|
+
# queue.pop(format) # => [2, 3, 4, 5]
|
25
|
+
#
|
26
|
+
# @example changing the format
|
27
|
+
# queue = Hallon::AudioQueue.new(4)
|
28
|
+
# queue.format # => nil
|
29
|
+
# queue.push(:initial_format, [1, 2, 3, 4]) # => 4
|
30
|
+
# queue.size # => 4
|
31
|
+
# queue.format # => :initial_format
|
32
|
+
# queue.push(:new_format, [1, 2]) # => 2
|
33
|
+
# queue.size # => 2
|
34
|
+
# queue.format # => :new_format
|
24
35
|
#
|
25
36
|
# @private
|
26
37
|
class AudioQueue
|
27
38
|
attr_reader :max_size
|
28
39
|
|
29
|
-
# @param [Integer] max_size
|
40
|
+
# @param [Integer] max_size how many frames
|
30
41
|
def initialize(max_size)
|
31
42
|
@max_size = max_size
|
32
|
-
@
|
43
|
+
@frames = []
|
33
44
|
|
34
|
-
@
|
35
|
-
@condvar = @
|
45
|
+
@frames.extend(MonitorMixin)
|
46
|
+
@condvar = @frames.new_cond
|
36
47
|
end
|
37
48
|
|
38
|
-
# @
|
49
|
+
# @note If the format is not the same as the current format, the queue is
|
50
|
+
# emptied before appending the new data. In this case, {#format} will
|
51
|
+
# be assigned to the new format as well.
|
52
|
+
#
|
53
|
+
# @param [Hash] format format of the audio frames given
|
54
|
+
# @param [#take] frames
|
39
55
|
# @return [Integer] how much of the data that was added to the queue
|
40
|
-
def push(
|
56
|
+
def push(format, frames)
|
41
57
|
synchronize do
|
58
|
+
unless format == @format
|
59
|
+
@format = format
|
60
|
+
clear
|
61
|
+
end
|
62
|
+
|
42
63
|
can_accept = max_size - size
|
43
|
-
|
64
|
+
new_frames = frames.take(can_accept)
|
44
65
|
|
45
|
-
@
|
66
|
+
@frames.concat(new_frames)
|
46
67
|
@condvar.signal
|
47
68
|
|
48
|
-
|
69
|
+
new_frames.size
|
49
70
|
end
|
50
71
|
end
|
51
72
|
|
52
73
|
# @note If the queue is empty, this operation will block until data is available.
|
53
|
-
# @
|
54
|
-
#
|
55
|
-
|
74
|
+
# @note When data is available, if it’s not in the same format as the format requested
|
75
|
+
# the return value will be nil. This is to avoid the format changing during wait.
|
76
|
+
#
|
77
|
+
# @param [Hash] format requested format
|
78
|
+
# @param [Integer] num_frames max number of frames to pop off the queue
|
79
|
+
# @return [Array, nil] array of data, but no longer than `num_frames`
|
80
|
+
def pop(format, num_frames = max_size)
|
56
81
|
synchronize do
|
57
82
|
@condvar.wait_while { empty? }
|
58
|
-
@
|
83
|
+
@frames.shift(num_frames) if format == @format
|
59
84
|
end
|
60
85
|
end
|
61
86
|
|
62
|
-
# @return [Integer] number of
|
87
|
+
# @return [Integer] number of frames in buffer.
|
63
88
|
def size
|
64
|
-
synchronize { @
|
89
|
+
synchronize { @frames.size }
|
65
90
|
end
|
66
91
|
|
67
92
|
# @return [Boolean] true if the queue has a {#size} of 0.
|
@@ -71,18 +96,7 @@ module Hallon
|
|
71
96
|
|
72
97
|
# Clear all data from the AudioQueue.
|
73
98
|
def clear
|
74
|
-
synchronize { @
|
75
|
-
end
|
76
|
-
|
77
|
-
# Attach format metadata to the queue.
|
78
|
-
#
|
79
|
-
# @note this will clear the queue of all audio data!
|
80
|
-
# @param format new audio format
|
81
|
-
def format=(format)
|
82
|
-
synchronize do
|
83
|
-
@format = format
|
84
|
-
clear
|
85
|
-
end
|
99
|
+
synchronize { @frames.clear }
|
86
100
|
end
|
87
101
|
|
88
102
|
# Returns the format previously set by #format=.
|
@@ -95,7 +109,7 @@ module Hallon
|
|
95
109
|
# @yield exclusive section around the queue contents
|
96
110
|
# @return whatever the given block returns
|
97
111
|
def synchronize
|
98
|
-
@
|
112
|
+
@frames.synchronize { return yield }
|
99
113
|
end
|
100
114
|
|
101
115
|
# Create a condition variable bound to this AudioQueue.
|
@@ -104,7 +118,7 @@ module Hallon
|
|
104
118
|
# @return [MonitorMixin::ConditionVariable]
|
105
119
|
# @see monitor.rb (ruby stdlib)
|
106
120
|
def new_cond
|
107
|
-
@
|
121
|
+
@frames.new_cond
|
108
122
|
end
|
109
123
|
end
|
110
124
|
end
|
@@ -10,20 +10,20 @@ module Hallon::Observable
|
|
10
10
|
|
11
11
|
protected
|
12
12
|
|
13
|
-
# @return
|
13
|
+
# @return [Array<Method>] array of callback methods
|
14
14
|
def initialize_callbacks
|
15
15
|
%w(end_of_track streaming_error play_token_lost).map { |m| callback_for(m) }
|
16
16
|
end
|
17
17
|
|
18
|
-
# Dummy callback. See Session#end_of_track_callback.
|
18
|
+
# Dummy callback. See {Session#end_of_track_callback}.
|
19
19
|
def end_of_track_callback(session)
|
20
20
|
end
|
21
21
|
|
22
|
-
# Dummy callback. See Session#streaming_error_callback.
|
22
|
+
# Dummy callback. See {Session#streaming_error_callback}.
|
23
23
|
def streaming_error_callback(session, error)
|
24
24
|
end
|
25
25
|
|
26
|
-
# Dummy callback. See Session#play_token_lost_callback.
|
26
|
+
# Dummy callback. See {Session#play_token_lost_callback}.
|
27
27
|
def play_token_lost_callback(session)
|
28
28
|
end
|
29
29
|
end
|
@@ -22,10 +22,9 @@ module Hallon::Observable
|
|
22
22
|
# puts "#{tracks.map(&:name).join(', ')} added at #{position} to #{playlist.name}"
|
23
23
|
# end
|
24
24
|
#
|
25
|
-
# @yield [tracks, position
|
25
|
+
# @yield [tracks, position] tracks_added
|
26
26
|
# @yieldparam [Array<Track>] tracks
|
27
27
|
# @yieldparam [Integer] position
|
28
|
-
# @yieldparam [Playlist] self
|
29
28
|
def tracks_added_callback(pointer, tracks, num_tracks, position, userdata)
|
30
29
|
tracks_ary = tracks.read_array_of_pointer(num_tracks).map do |track|
|
31
30
|
ptr = Spotify::Pointer.new(track, :track, true)
|
@@ -40,9 +39,8 @@ module Hallon::Observable
|
|
40
39
|
# puts "#{tracks.map(&:name).join(', ') removed from #{playlist.name}"
|
41
40
|
# end
|
42
41
|
#
|
43
|
-
# @yield [tracks
|
42
|
+
# @yield [tracks] tracks_removed
|
44
43
|
# @yieldparam [Array<Track>] tracks
|
45
|
-
# @yieldparam [Playlist] self
|
46
44
|
def tracks_removed_callback(pointer, track_indices, num_indices, userdata)
|
47
45
|
trigger(pointer, :tracks_removed, track_indices.read_array_of_int(num_indices))
|
48
46
|
end
|
@@ -52,10 +50,9 @@ module Hallon::Observable
|
|
52
50
|
# puts "#{tracks.map(&:name).join(', ')} moved to #{new_position} to #{playlist.name}"
|
53
51
|
# end
|
54
52
|
#
|
55
|
-
# @yield [tracks, new_position
|
53
|
+
# @yield [tracks, new_position] tracks_moved
|
56
54
|
# @yieldparam [Array<Track>] tracks
|
57
55
|
# @yieldparam [Integer] new_position
|
58
|
-
# @yieldparam [Playlist] self
|
59
56
|
def tracks_moved_callback(pointer, track_indices, num_indices, new_position, userdata)
|
60
57
|
trigger(pointer, :tracks_moved, track_indices.read_array_of_int(num_indices), new_position)
|
61
58
|
end
|
@@ -65,8 +62,7 @@ module Hallon::Observable
|
|
65
62
|
# puts "#{playlist.name} was now previously named something else \o/"
|
66
63
|
# end
|
67
64
|
#
|
68
|
-
# @yield [
|
69
|
-
# @yieldparam [Playlist] self
|
65
|
+
# @yield [] playlist_renamed
|
70
66
|
def playlist_renamed_callback(pointer, userdata)
|
71
67
|
trigger(pointer, :playlist_renamed)
|
72
68
|
end
|
@@ -76,8 +72,7 @@ module Hallon::Observable
|
|
76
72
|
# puts "playlist state changed… to what? from what? D:"
|
77
73
|
# end
|
78
74
|
#
|
79
|
-
# @yield [
|
80
|
-
# @yieldparam [Playlist] self
|
75
|
+
# @yield [] playlist_state_changed
|
81
76
|
def playlist_state_changed_callback(pointer, userdata)
|
82
77
|
trigger(pointer, :playlist_state_changed)
|
83
78
|
end
|
@@ -87,9 +82,8 @@ module Hallon::Observable
|
|
87
82
|
# puts(is_done ? "DONE!" : "not done :(")
|
88
83
|
# end
|
89
84
|
#
|
90
|
-
# @yield [is_done
|
85
|
+
# @yield [is_done] playlist_update_in_progress
|
91
86
|
# @yieldparam [Boolean] is_done
|
92
|
-
# @yieldparam [Playlist] self
|
93
87
|
def playlist_update_in_progress_callback(pointer, done, userdata)
|
94
88
|
trigger(pointer, :playlist_update_in_progress, done)
|
95
89
|
end
|
@@ -99,8 +93,7 @@ module Hallon::Observable
|
|
99
93
|
# puts "#{playlist.name} metadata updated"
|
100
94
|
# end
|
101
95
|
#
|
102
|
-
# @yield [
|
103
|
-
# @yieldparam [Playlist] self
|
96
|
+
# @yield [] playlist_metadata_updated
|
104
97
|
def playlist_metadata_updated_callback(pointer, userdata)
|
105
98
|
trigger(pointer, :playlist_metadata_updated)
|
106
99
|
end
|
@@ -111,11 +104,10 @@ module Hallon::Observable
|
|
111
104
|
# puts "#{track.name} created-info changed"
|
112
105
|
# end
|
113
106
|
#
|
114
|
-
# @yield [position, user, created_at
|
107
|
+
# @yield [position, user, created_at] track_created_changed
|
115
108
|
# @yieldparam [Integer] position
|
116
109
|
# @yieldparam [User] user
|
117
110
|
# @yieldparam [Time] created_at
|
118
|
-
# @yieldparam [Playlist] self
|
119
111
|
def track_created_changed_callback(pointer, position, user, created_at, userdata)
|
120
112
|
user = Spotify::Pointer.new(user, :user, true)
|
121
113
|
trigger(pointer, :track_created_changed, position, Hallon::User.new(user), Time.at(created_at))
|
@@ -127,10 +119,9 @@ module Hallon::Observable
|
|
127
119
|
# puts "#{track.name}#seen? is #{seen}"
|
128
120
|
# end
|
129
121
|
#
|
130
|
-
# @yield [position, is_seen
|
122
|
+
# @yield [position, is_seen] track_seen_changed
|
131
123
|
# @yieldparam [Integer] position
|
132
124
|
# @yieldparam [Boolean] is_seen
|
133
|
-
# @yieldparam [Playlist] self
|
134
125
|
def track_seen_changed_callback(pointer, position, seen, userdata)
|
135
126
|
trigger(pointer, :track_seen_changed, position, seen)
|
136
127
|
end
|
@@ -141,10 +132,9 @@ module Hallon::Observable
|
|
141
132
|
# puts "#{track.name} new message: #{message}"
|
142
133
|
# end
|
143
134
|
#
|
144
|
-
# @yield [position, message
|
135
|
+
# @yield [position, message] track_message_changed
|
145
136
|
# @yieldparam [Integer] position
|
146
137
|
# @yieldparam [String] message
|
147
|
-
# @yieldparam [Playlist] self
|
148
138
|
def track_message_changed_callback(pointer, position, message, userdata)
|
149
139
|
trigger(pointer, :track_message_changed, position, message)
|
150
140
|
end
|
@@ -154,9 +144,8 @@ module Hallon::Observable
|
|
154
144
|
# puts "#{playlist.name} new description: #{description}"
|
155
145
|
# end
|
156
146
|
#
|
157
|
-
# @yield [description
|
147
|
+
# @yield [description] description_changed
|
158
148
|
# @yieldparam [String] description
|
159
|
-
# @yieldparam [Playlist] self
|
160
149
|
def description_changed_callback(pointer, description, userdata)
|
161
150
|
trigger(pointer, :description_changed, description)
|
162
151
|
end
|
@@ -166,9 +155,8 @@ module Hallon::Observable
|
|
166
155
|
# puts "#{playlist.name} has got a new image: #{image.to_link}"
|
167
156
|
# end
|
168
157
|
#
|
169
|
-
# @yield [image
|
158
|
+
# @yield [image] image_changed
|
170
159
|
# @yieldparam [Image, nil] image or nil
|
171
|
-
# @yieldparam [Playlist] self
|
172
160
|
def image_changed_callback(pointer, image, userdata)
|
173
161
|
image = Hallon::Image.from(image)
|
174
162
|
trigger(pointer, :image_changed, image)
|
@@ -179,8 +167,7 @@ module Hallon::Observable
|
|
179
167
|
# puts "#{playlist.name} updated its’ subscribers"
|
180
168
|
# end
|
181
169
|
#
|
182
|
-
# @yield [
|
183
|
-
# @yieldparam [Playlist] self
|
170
|
+
# @yield [] subscribers_changed
|
184
171
|
def subscribers_changed_callback(pointer, userdata)
|
185
172
|
trigger(pointer, :subscribers_changed)
|
186
173
|
end
|
@@ -22,10 +22,9 @@ module Hallon::Observable
|
|
22
22
|
# puts playlist.name + " added at #{position}."
|
23
23
|
# end
|
24
24
|
#
|
25
|
-
# @yield [playlist, position
|
25
|
+
# @yield [playlist, position] playlist_added
|
26
26
|
# @yieldparam [Playlist] playlist
|
27
27
|
# @yieldparam [Integer] position
|
28
|
-
# @yieldparam [PlaylistContainer] self
|
29
28
|
def playlist_added_callback(pointer, playlist, position, userdata)
|
30
29
|
trigger(pointer, :playlist_added, playlist_from(playlist), position)
|
31
30
|
end
|
@@ -35,10 +34,9 @@ module Hallon::Observable
|
|
35
34
|
# puts playlist.name + " removed from #{position}."
|
36
35
|
# end
|
37
36
|
#
|
38
|
-
# @yield [playlist, position
|
37
|
+
# @yield [playlist, position] playlist_removed
|
39
38
|
# @yieldparam [Playlist] playlist
|
40
39
|
# @yieldparam [Integer] position
|
41
|
-
# @yieldparam [PlaylistContainer] self
|
42
40
|
def playlist_removed_callback(pointer, playlist, position, userdata)
|
43
41
|
trigger(pointer, :playlist_removed, playlist_from(playlist), position)
|
44
42
|
end
|
@@ -48,11 +46,10 @@ module Hallon::Observable
|
|
48
46
|
# puts "moved #{playlist.name} from #{position} to #{new_position}"
|
49
47
|
# end
|
50
48
|
#
|
51
|
-
# @yield [playlist, position, new_position
|
49
|
+
# @yield [playlist, position, new_position] playlist_moved
|
52
50
|
# @yieldparam [Playlist] playlist
|
53
51
|
# @yieldparam [Integer] position
|
54
52
|
# @yieldparam [Integer] new_position
|
55
|
-
# @yieldparam [PlaylistContainer] self
|
56
53
|
def playlist_moved_callback(pointer, playlist, position, new_position, userdata)
|
57
54
|
trigger(pointer, :playlist_moved, playlist_from(playlist), position, new_position)
|
58
55
|
end
|
@@ -62,8 +59,7 @@ module Hallon::Observable
|
|
62
59
|
# puts "#{container.owner.name}s container loaded!"
|
63
60
|
# end
|
64
61
|
#
|
65
|
-
# @yield [
|
66
|
-
# @yieldparam [PlaylistContainer] self
|
62
|
+
# @yield [] container_loaded
|
67
63
|
def container_loaded_callback(pointer, userdata)
|
68
64
|
trigger(pointer, :container_loaded)
|
69
65
|
end
|
@@ -23,9 +23,8 @@ module Hallon::Observable
|
|
23
23
|
# puts "Logged in: " + Hallon::Error.explain(error)
|
24
24
|
# end
|
25
25
|
#
|
26
|
-
# @yield [error
|
26
|
+
# @yield [error] logged_in
|
27
27
|
# @yieldparam [Symbol] error
|
28
|
-
# @yieldparam [Session] self
|
29
28
|
# @see Error
|
30
29
|
def logged_in_callback(pointer, error)
|
31
30
|
trigger(pointer, :logged_in, error)
|
@@ -36,8 +35,7 @@ module Hallon::Observable
|
|
36
35
|
# puts "AHHH!"
|
37
36
|
# end
|
38
37
|
#
|
39
|
-
# @yield [
|
40
|
-
# @yieldparam [Session] self
|
38
|
+
# @yield []
|
41
39
|
def logged_out_callback(pointer)
|
42
40
|
trigger(pointer, :logged_out)
|
43
41
|
end
|
@@ -47,8 +45,7 @@ module Hallon::Observable
|
|
47
45
|
# puts "wut wut"
|
48
46
|
# end
|
49
47
|
#
|
50
|
-
# @yield [
|
51
|
-
# @yieldparam [Session] self
|
48
|
+
# @yield []
|
52
49
|
def metadata_updated_callback(pointer)
|
53
50
|
trigger(pointer, :metadata_updated)
|
54
51
|
end
|
@@ -58,9 +55,8 @@ module Hallon::Observable
|
|
58
55
|
# puts "Oh noes: " + Hallon::Error.explain(error)
|
59
56
|
# end
|
60
57
|
#
|
61
|
-
# @yield [error
|
58
|
+
# @yield [error]
|
62
59
|
# @yieldparam [Symbol] error
|
63
|
-
# @yieldparam [Session] self
|
64
60
|
# @see Error
|
65
61
|
def connection_error_callback(pointer, error)
|
66
62
|
trigger(pointer, :connection_error, error)
|
@@ -71,9 +67,8 @@ module Hallon::Observable
|
|
71
67
|
# puts "OH HAI: #{message}"
|
72
68
|
# end
|
73
69
|
#
|
74
|
-
# @yield [message
|
70
|
+
# @yield [message]
|
75
71
|
# @yieldparam [String] message
|
76
|
-
# @yieldparam [Session] self
|
77
72
|
def message_to_user_callback(pointer, message)
|
78
73
|
trigger(pointer, :message_to_user, message)
|
79
74
|
end
|
@@ -83,8 +78,7 @@ module Hallon::Observable
|
|
83
78
|
# puts "main thread turn on"
|
84
79
|
# end
|
85
80
|
#
|
86
|
-
# @yield [
|
87
|
-
# @yieldparam [Session] self
|
81
|
+
# @yield []
|
88
82
|
def notify_main_thread_callback(pointer)
|
89
83
|
trigger(pointer, :notify_main_thread)
|
90
84
|
end
|
@@ -94,10 +88,9 @@ module Hallon::Observable
|
|
94
88
|
# puts ""
|
95
89
|
# end
|
96
90
|
#
|
97
|
-
# @yield [format, frames
|
91
|
+
# @yield [format, frames]
|
98
92
|
# @yieldparam [Hash] format (contains :type, :rate, :channels)
|
99
93
|
# @yieldparam [Enumerator<[Integer...]>] frames (each frame is an array containing format[:channels] integers of format[:type])
|
100
|
-
# @yieldparam [Session] self
|
101
94
|
def music_delivery_callback(pointer, format, frames, num_frames)
|
102
95
|
struct = Spotify::AudioFormat.new(format)
|
103
96
|
|
@@ -125,8 +118,7 @@ module Hallon::Observable
|
|
125
118
|
# puts "another user set us up the bomb!"
|
126
119
|
# end
|
127
120
|
#
|
128
|
-
# @yield [
|
129
|
-
# @yieldparam [Session] self
|
121
|
+
# @yield []
|
130
122
|
def play_token_lost_callback(pointer)
|
131
123
|
trigger(pointer, :play_token_lost)
|
132
124
|
end
|
@@ -136,8 +128,7 @@ module Hallon::Observable
|
|
136
128
|
# puts "all your base are belong to us"
|
137
129
|
# end
|
138
130
|
#
|
139
|
-
# @yield [
|
140
|
-
# @yieldparam [Session] self
|
131
|
+
# @yield []
|
141
132
|
def end_of_track_callback(pointer)
|
142
133
|
trigger(pointer, :end_of_track)
|
143
134
|
end
|
@@ -147,8 +138,7 @@ module Hallon::Observable
|
|
147
138
|
# puts "dum dum tiss"
|
148
139
|
# end
|
149
140
|
#
|
150
|
-
# @yield [
|
151
|
-
# @yieldparam [Session] self
|
141
|
+
# @yield []
|
152
142
|
def start_playback_callback(pointer)
|
153
143
|
trigger(pointer, :start_playback)
|
154
144
|
end
|
@@ -158,8 +148,7 @@ module Hallon::Observable
|
|
158
148
|
# puts "dum dum tiss"
|
159
149
|
# end
|
160
150
|
#
|
161
|
-
# @yield [
|
162
|
-
# @yieldparam [Session] self
|
151
|
+
# @yield []
|
163
152
|
def stop_playback_callback(pointer)
|
164
153
|
trigger(pointer, :stop_playback)
|
165
154
|
end
|
@@ -169,8 +158,7 @@ module Hallon::Observable
|
|
169
158
|
# puts "que?"
|
170
159
|
# end
|
171
160
|
#
|
172
|
-
# @yield [
|
173
|
-
# @yieldparam [Session] self
|
161
|
+
# @yield []
|
174
162
|
# @yieldreturn an integer pair, [samples, dropouts]
|
175
163
|
def get_audio_buffer_stats_callback(pointer, stats)
|
176
164
|
stats = Spotify::AudioBufferStats.new(stats)
|
@@ -184,9 +172,8 @@ module Hallon::Observable
|
|
184
172
|
# puts "boo: " + Hallon::Error.explain(error)
|
185
173
|
# end
|
186
174
|
#
|
187
|
-
# @yield [error
|
175
|
+
# @yield [error]
|
188
176
|
# @yieldparam [Symbol] error
|
189
|
-
# @yieldparam [Session] self
|
190
177
|
def streaming_error_callback(pointer, error)
|
191
178
|
trigger(pointer, :streaming_error, error)
|
192
179
|
end
|
@@ -196,8 +183,7 @@ module Hallon::Observable
|
|
196
183
|
# puts "who am I?!"
|
197
184
|
# end
|
198
185
|
#
|
199
|
-
# @yield [
|
200
|
-
# @yieldparam [Session] self
|
186
|
+
# @yield []
|
201
187
|
def userinfo_updated_callback(pointer)
|
202
188
|
trigger(pointer, :userinfo_updated)
|
203
189
|
end
|
@@ -207,9 +193,8 @@ module Hallon::Observable
|
|
207
193
|
# puts "for great justice: #{message}"
|
208
194
|
# end
|
209
195
|
#
|
210
|
-
# @yield [message
|
196
|
+
# @yield [message]
|
211
197
|
# @yieldparam [String] message
|
212
|
-
# @yieldparam [Session] self
|
213
198
|
def log_message_callback(pointer, message)
|
214
199
|
trigger(pointer, :log_message, message)
|
215
200
|
end
|
@@ -219,8 +204,7 @@ module Hallon::Observable
|
|
219
204
|
# puts "All systems: #{session.status}"
|
220
205
|
# end
|
221
206
|
#
|
222
|
-
# @yield [
|
223
|
-
# @yieldparam [Session] self
|
207
|
+
# @yield []
|
224
208
|
def offline_status_updated_callback(pointer)
|
225
209
|
trigger(pointer, :offline_status_updated)
|
226
210
|
end
|
@@ -230,9 +214,8 @@ module Hallon::Observable
|
|
230
214
|
# puts "FAIL: " + Hallon::Error.explain(error)
|
231
215
|
# end
|
232
216
|
#
|
233
|
-
# @yield [error
|
217
|
+
# @yield [error]
|
234
218
|
# @yieldparam [Symbol] error
|
235
|
-
# @yieldparam [Session] self
|
236
219
|
def offline_error_callback(pointer, error)
|
237
220
|
trigger(pointer, :offline_error, error)
|
238
221
|
end
|
@@ -243,9 +226,8 @@ module Hallon::Observable
|
|
243
226
|
# File.open('.spotify-credentials', 'w') { |io| io.write(credentials) }
|
244
227
|
# end
|
245
228
|
#
|
246
|
-
# @yield [credentials
|
229
|
+
# @yield [credentials]
|
247
230
|
# @yieldparam [String] credentials
|
248
|
-
# @yieldparam [Session] self
|
249
231
|
def credentials_blob_updated_callback(pointer, credentials)
|
250
232
|
trigger(pointer, :credentials_blob_updated, credentials)
|
251
233
|
end
|
data/lib/hallon/player.rb
CHANGED
@@ -41,7 +41,7 @@ module Hallon
|
|
41
41
|
# we keep an audio queue that can store 3s of audio
|
42
42
|
@queue = AudioQueue.new(44100)
|
43
43
|
@driver = driver.new
|
44
|
-
@
|
44
|
+
@driver.format = { rate: 44100, channels: 2, type: :int16 }
|
45
45
|
|
46
46
|
# used for feeder thread to know if it should stream
|
47
47
|
# data to the driver or not (see #status=)
|
@@ -54,14 +54,15 @@ module Hallon
|
|
54
54
|
@thread = Thread.start(@driver, @queue, @status_c) do |output, queue, cond|
|
55
55
|
output.stream do |num_frames|
|
56
56
|
queue.synchronize do
|
57
|
-
|
57
|
+
frames = queue.pop(output.format, *num_frames)
|
58
58
|
|
59
|
-
if
|
59
|
+
if frames.nil?
|
60
|
+
# if we received no frames, it means the audio format changed
|
60
61
|
output.format = queue.format
|
61
|
-
next # format changed, so we return nil
|
62
62
|
end
|
63
63
|
|
64
|
-
|
64
|
+
# frames is either nil, or an array of audio frames
|
65
|
+
frames
|
65
66
|
end
|
66
67
|
end
|
67
68
|
end
|
@@ -99,13 +100,8 @@ module Hallon
|
|
99
100
|
# a hash of (sample) rate, channels and (sample) type.
|
100
101
|
def music_delivery(format, frames)
|
101
102
|
@queue.synchronize do
|
102
|
-
if frames.none?
|
103
|
-
|
104
|
-
elsif @queue.format != format
|
105
|
-
@queue.format = format
|
106
|
-
end
|
107
|
-
|
108
|
-
@queue.push(frames)
|
103
|
+
@queue.clear if frames.none?
|
104
|
+
@queue.push(format, frames)
|
109
105
|
end
|
110
106
|
end
|
111
107
|
|
@@ -134,6 +130,10 @@ module Hallon
|
|
134
130
|
when :paused
|
135
131
|
@driver.pause
|
136
132
|
when :stopped
|
133
|
+
# TODO: even if we clear and stop here, the music_delivery
|
134
|
+
# callback might be waiting for ruby to allow it to callback,
|
135
|
+
# which will result in us getting old audio frames when we
|
136
|
+
# play the next song. I don’t have a good solution yet.
|
137
137
|
@queue.clear
|
138
138
|
@driver.stop
|
139
139
|
else
|
data/lib/hallon/session.rb
CHANGED
@@ -76,6 +76,9 @@ module Hallon
|
|
76
76
|
|
77
77
|
# Create a new Spotify session.
|
78
78
|
#
|
79
|
+
# @example using a https-proxy
|
80
|
+
# session = Hallon::Session.initialize(appkey, proxy: 'https://user@password:hostname')
|
81
|
+
#
|
79
82
|
# @param [#to_s] appkey
|
80
83
|
# @param [Hash] options
|
81
84
|
# @option options [String] :user_agent ("Hallon") User-Agent to use (length < `256`)
|
data/lib/hallon/version.rb
CHANGED
@@ -3,24 +3,37 @@ require 'timeout'
|
|
3
3
|
|
4
4
|
describe Hallon::AudioQueue do
|
5
5
|
let(:queue) { Hallon::AudioQueue.new(4) }
|
6
|
+
let(:format) { { :rate => 44100, :channels => 2, :type => :int16 } }
|
6
7
|
subject { queue }
|
7
8
|
|
8
|
-
it "should conform to the example
|
9
|
-
|
10
|
-
queue.push([
|
11
|
-
queue.push(
|
12
|
-
queue.push([5, 6]).should eq
|
13
|
-
queue.
|
14
|
-
queue.
|
15
|
-
queue.
|
9
|
+
it "should conform to “filling the buffer” example" do
|
10
|
+
format = {:rate => 44100, :channels => 2, :type => :int16}
|
11
|
+
queue.push(format, [1, 2]).should eq 2
|
12
|
+
queue.push(format, [3]).should eq 1
|
13
|
+
queue.push(format, [4, 5, 6]).should eq 1
|
14
|
+
queue.push(format, [5, 6]).should eq 0
|
15
|
+
queue.pop(format, 1).should eq [1]
|
16
|
+
queue.push(format, [5, 6]).should eq 1
|
17
|
+
queue.pop(format).should eq [2, 3, 4, 5]
|
18
|
+
end
|
19
|
+
|
20
|
+
it "should conform to the “changing the format” example" do
|
21
|
+
queue = Hallon::AudioQueue.new(4)
|
22
|
+
queue.format.should eq nil
|
23
|
+
queue.push(:initial_format, [1, 2, 3, 4, 5]).should eq 4
|
24
|
+
queue.size.should eq 4
|
25
|
+
queue.format.should eq :initial_format
|
26
|
+
queue.push(:new_format, [1, 2]).should eq 2
|
27
|
+
queue.size.should eq 2
|
28
|
+
queue.format.should eq :new_format
|
16
29
|
end
|
17
30
|
|
18
31
|
describe "#pop" do
|
19
32
|
it "should not block if the queue is not empty" do
|
20
|
-
queue.push([1, 2])
|
33
|
+
queue.push(format, [1, 2])
|
21
34
|
|
22
35
|
start = Time.now
|
23
|
-
queue.pop.should eq [1, 2]
|
36
|
+
queue.pop(format).should eq [1, 2]
|
24
37
|
(Time.now - start).should be_within(0.001).of(0)
|
25
38
|
end
|
26
39
|
|
@@ -29,15 +42,21 @@ describe Hallon::AudioQueue do
|
|
29
42
|
|
30
43
|
# I could mock out ConditionVariable and Mutex, but where’s the fun in that?
|
31
44
|
start = Time.now
|
32
|
-
Thread.start { sleep 0.2; queue.push([1]) }
|
33
|
-
queue.pop.should eq [1]
|
45
|
+
Thread.start { sleep 0.2; queue.push(format, [1]) }
|
46
|
+
queue.pop(format).should eq [1]
|
34
47
|
(Time.now - start).should be_within(0.08).of(0.2)
|
35
48
|
end
|
49
|
+
|
50
|
+
it "returns does nothing if the format does not match" do
|
51
|
+
queue.push(:one_format, [1, 2, 3, 4])
|
52
|
+
queue.pop(:another_format).should eq nil
|
53
|
+
queue.pop(:one_format).should eq [1, 2, 3, 4]
|
54
|
+
end
|
36
55
|
end
|
37
56
|
|
38
57
|
describe "#clear" do
|
39
58
|
it "should clear the queue" do
|
40
|
-
queue.push([1, 2])
|
59
|
+
queue.push(format, [1, 2])
|
41
60
|
queue.should_not be_empty
|
42
61
|
queue.clear
|
43
62
|
queue.should be_empty
|
@@ -45,17 +64,11 @@ describe Hallon::AudioQueue do
|
|
45
64
|
end
|
46
65
|
|
47
66
|
describe "#format" do
|
48
|
-
it "
|
49
|
-
queue.push([1, 2])
|
50
|
-
queue.
|
51
|
-
queue.
|
52
|
-
queue.should
|
53
|
-
end
|
54
|
-
|
55
|
-
it "should allow setting and retrieving the format" do
|
56
|
-
queue.format.should_not be :new_format
|
57
|
-
queue.format = :new_format
|
58
|
-
queue.format.should be :new_format
|
67
|
+
it "is determined by the format of the audio samples" do
|
68
|
+
queue.push(:one_format, [1, 2, 3])
|
69
|
+
queue.format.should eq :one_format
|
70
|
+
queue.push(:another_format, [4, 5, 6])
|
71
|
+
queue.format.should eq :another_format
|
59
72
|
end
|
60
73
|
end
|
61
74
|
|
data/spec/hallon/player_spec.rb
CHANGED
@@ -131,20 +131,16 @@ describe Hallon::Player do
|
|
131
131
|
player.stop
|
132
132
|
end
|
133
133
|
|
134
|
-
it "should not set the format on music delivery if it’s the same" do
|
135
|
-
queue.should_not_receive(:format=)
|
136
|
-
session.trigger(:music_delivery, queue.format, [1, 2, 3])
|
137
|
-
end
|
138
|
-
|
139
134
|
it "should set the format on music delivery if format changes" do
|
140
|
-
queue.
|
135
|
+
queue.format.should_not eq :new_format
|
141
136
|
session.trigger(:music_delivery, :new_format, [1, 2, 3])
|
137
|
+
queue.format.should eq :new_format
|
142
138
|
end
|
143
139
|
|
144
140
|
# why? it says so in the docs!
|
145
141
|
it "should clear the audio queue when receiving 0 audio frames" do
|
146
142
|
queue.should_receive(:clear)
|
147
|
-
session.trigger(:music_delivery,
|
143
|
+
session.trigger(:music_delivery, queue.format, [])
|
148
144
|
end
|
149
145
|
|
150
146
|
context "the output streaming" do
|
@@ -152,7 +148,7 @@ describe Hallon::Player do
|
|
152
148
|
Thread.stub(:start).and_return{ |*args, &block| block[*args] }
|
153
149
|
|
154
150
|
player # create the Player
|
155
|
-
session.trigger(:music_delivery,
|
151
|
+
session.trigger(:music_delivery, driver.format, [1, 2, 3])
|
156
152
|
|
157
153
|
# it should block while player is stopped
|
158
154
|
begin
|
@@ -183,11 +179,10 @@ describe Hallon::Player do
|
|
183
179
|
driver.stream.call.should eq [1, 2, 3]
|
184
180
|
end
|
185
181
|
|
186
|
-
it "should set the format on initialization" do
|
182
|
+
it "should set the driver format on initialization" do
|
187
183
|
Thread.stub(:start).and_return{ |*args, &block| block[*args] }
|
188
184
|
|
189
185
|
AudioDriverMock.any_instance.should_receive(:format=)
|
190
|
-
Hallon::AudioQueue.any_instance.should_receive(:format=)
|
191
186
|
player
|
192
187
|
end
|
193
188
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: hallon
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.18.0
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2012-
|
12
|
+
date: 2012-07-02 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: ref
|
@@ -34,7 +34,7 @@ dependencies:
|
|
34
34
|
requirements:
|
35
35
|
- - ~>
|
36
36
|
- !ruby/object:Gem::Version
|
37
|
-
version: 12.0
|
37
|
+
version: 12.2.0
|
38
38
|
type: :runtime
|
39
39
|
prerelease: false
|
40
40
|
version_requirements: !ruby/object:Gem::Requirement
|
@@ -42,7 +42,7 @@ dependencies:
|
|
42
42
|
requirements:
|
43
43
|
- - ~>
|
44
44
|
- !ruby/object:Gem::Version
|
45
|
-
version: 12.0
|
45
|
+
version: 12.2.0
|
46
46
|
- !ruby/object:Gem::Dependency
|
47
47
|
name: rake
|
48
48
|
requirement: !ruby/object:Gem::Requirement
|
@@ -145,6 +145,7 @@ files:
|
|
145
145
|
- dev/login.rb
|
146
146
|
- examples/adding_tracks_to_playlist.rb
|
147
147
|
- examples/example_support.rb
|
148
|
+
- examples/nordic_ruby_2012.rb
|
148
149
|
- examples/playing_audio.rb
|
149
150
|
- examples/show_published_playlists_of_user.rb
|
150
151
|
- hallon.gemspec
|
@@ -292,7 +293,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
292
293
|
version: '0'
|
293
294
|
segments:
|
294
295
|
- 0
|
295
|
-
hash:
|
296
|
+
hash: 228682146525853245
|
296
297
|
requirements: []
|
297
298
|
rubyforge_project:
|
298
299
|
rubygems_version: 1.8.24
|