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.
@@ -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
@@ -1,6 +1,11 @@
1
1
  source :rubygems
2
2
  gemspec
3
3
 
4
+ # Useful for testing out audio.
5
+ unless ENV["TRAVIS"] # does not build on travis
6
+ gem 'hallon-openal'
7
+ end
8
+
4
9
  gem 'ruby_parser', '>= 3.0.0.a1', '< 4.0'
5
10
  gem 'pry'
6
11
  gem 'simplecov'
@@ -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 **v10.1.16**. Older
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
@@ -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.2'
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'
@@ -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(StandardError)
76
+ NoSessionError = Class.new(Hallon::Error)
77
77
 
78
78
  # Raised by Session#login! and Session#relogin!
79
- LoginError = Class.new(StandardError)
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(StandardError)
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}.
@@ -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
- # queue.push([1, 2]) # => 2
18
- # queue.push([3]) # => 1
19
- # queue.push([4, 5, 6]) # => 1
20
- # queue.push([5, 6]) # => 0
21
- # queue.pop(1) # => [1]
22
- # queue.push([5, 6]) # => 1
23
- # queue.pop # => [2, 3, 4, 5]
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
- @samples = []
43
+ @frames = []
33
44
 
34
- @samples.extend(MonitorMixin)
35
- @condvar = @samples.new_cond
45
+ @frames.extend(MonitorMixin)
46
+ @condvar = @frames.new_cond
36
47
  end
37
48
 
38
- # @param [#take] samples
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(samples)
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
- new_samples = samples.take(can_accept)
64
+ new_frames = frames.take(can_accept)
44
65
 
45
- @samples.concat(new_samples)
66
+ @frames.concat(new_frames)
46
67
  @condvar.signal
47
68
 
48
- new_samples.size
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
- # @param [Integer] num_samples max number of samples to pop off the queue
54
- # @return [Array] data, where data.size might be less than num_samples but never more
55
- def pop(num_samples = max_size)
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
- @samples.shift(num_samples)
83
+ @frames.shift(num_frames) if format == @format
59
84
  end
60
85
  end
61
86
 
62
- # @return [Integer] number of samples in buffer.
87
+ # @return [Integer] number of frames in buffer.
63
88
  def size
64
- synchronize { @samples.size }
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 { @samples.clear }
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
- @samples.synchronize { return yield }
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
- @samples.new_cond
121
+ @frames.new_cond
108
122
  end
109
123
  end
110
124
  end
@@ -21,8 +21,7 @@ module Hallon::Observable
21
21
  # puts "Album browser has loaded!"
22
22
  # end
23
23
  #
24
- # @yield [self]
25
- # @yieldparam [AlbumBrowse] self
24
+ # @yield []
26
25
  def load_callback(pointer, userdata)
27
26
  trigger(pointer, :load)
28
27
  end
@@ -21,8 +21,7 @@ module Hallon::Observable
21
21
  # puts "Artist browser has loaded!"
22
22
  # end
23
23
  #
24
- # @yield [self]
25
- # @yieldparam [ArtistBrowse] self
24
+ # @yield []
26
25
  def load_callback(pointer, userdata)
27
26
  trigger(pointer, :load)
28
27
  end
@@ -21,8 +21,7 @@ module Hallon::Observable
21
21
  # puts "Image has loaded"
22
22
  # end
23
23
  #
24
- # @yield [self]
25
- # @yieldparam [Image] self
24
+ # @yield []
26
25
  def load_callback(pointer, userdata)
27
26
  trigger(pointer, :load)
28
27
  end
@@ -10,20 +10,20 @@ module Hallon::Observable
10
10
 
11
11
  protected
12
12
 
13
- # @return nil
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, self] tracks_added
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, self] tracks_removed
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, self] tracks_moved
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 [self] playlist_renamed
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 [self] playlist_state_changed
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, self] playlist_update_in_progress
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 [self] playlist_metadata_updated
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, self] track_created_changed
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, self] track_seen_changed
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, self] track_message_changed
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, self] description_changed
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, self] image_changed
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 [self] subscribers_changed
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, self] playlist_added
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, self] playlist_removed
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, self] playlist_moved
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 [self] container_loaded
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
@@ -21,8 +21,7 @@ module Hallon::Observable
21
21
  # puts "ze user be havin’ sum posts"
22
22
  # end
23
23
  #
24
- # @yield [self]
25
- # @yieldparam [User::Post] self
24
+ # @yield []
26
25
  def complete_callback(pointer, userdata)
27
26
  trigger(pointer, :complete)
28
27
  end
@@ -20,8 +20,7 @@ module Hallon::Observable
20
20
  # puts "search for #{search.query} is complete!"
21
21
  # end
22
22
  #
23
- # @yield [self]
24
- # @yieldparam [Search] self
23
+ # @yield []
25
24
  def load_callback(pointer, userdata)
26
25
  trigger(pointer, :load)
27
26
  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, self] logged_in
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 [self]
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 [self]
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, self]
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, self]
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 [self]
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, self]
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 [self]
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 [self]
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 [self]
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 [self]
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 [self]
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, self]
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 [self]
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, self]
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 [self]
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, self]
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, self]
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
@@ -20,8 +20,7 @@ module Hallon::Observable
20
20
  # puts "the toplist has loaded!"
21
21
  # end
22
22
  #
23
- # @yield [self]
24
- # @yieldparam [Toplist] self
23
+ # @yield []
25
24
  def load_callback(pointer, userdata)
26
25
  trigger(pointer, :load)
27
26
  end
@@ -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
- @queue.format = @driver.format = { rate: 44100, channels: 2, type: :int16 }
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
- cond.wait_until { status == :playing }
57
+ frames = queue.pop(output.format, *num_frames)
58
58
 
59
- if output.format != queue.format
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
- queue.pop(*num_frames)
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
- @queue.clear
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
@@ -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`)
@@ -3,5 +3,5 @@ module Hallon
3
3
  # Current release version of Hallon
4
4
  #
5
5
  # @see http://semver.org/
6
- VERSION = [0, 17, 0].join('.')
6
+ VERSION = [0, 18, 0].join('.')
7
7
  end
@@ -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 specification of its’ documentation" do
9
- queue.push([1, 2]).should eq 2
10
- queue.push([3]).should eq 1
11
- queue.push([4, 5, 6]).should eq 1
12
- queue.push([5, 6]).should eq 0
13
- queue.pop(1).should eq [1]
14
- queue.push([5, 6]).should eq 1
15
- queue.pop.should eq [2, 3, 4, 5]
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 "should clear the queue when setting the format" do
49
- queue.push([1, 2])
50
- queue.should_not be_empty
51
- queue.format = :new_format
52
- queue.should be_empty
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
 
@@ -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.should_receive(:format=).with(:new_format)
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, driver.format, [])
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, queue.format, [1, 2, 3])
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.17.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-06-15 00:00:00.000000000 Z
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.2
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.2
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: 995209467900648626
296
+ hash: 228682146525853245
296
297
  requirements: []
297
298
  rubyforge_project:
298
299
  rubygems_version: 1.8.24