hallon 0.17.0 → 0.18.0

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