hallon 0.12.0 → 0.13.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (47) hide show
  1. data/CHANGELOG.md +43 -0
  2. data/Gemfile +3 -1
  3. data/README.markdown +41 -11
  4. data/Rakefile +12 -0
  5. data/examples/audio_driver.rb +55 -0
  6. data/examples/playing_audio.rb +10 -50
  7. data/hallon.gemspec +1 -1
  8. data/lib/hallon.rb +1 -1
  9. data/lib/hallon/album_browse.rb +22 -11
  10. data/lib/hallon/artist_browse.rb +64 -33
  11. data/lib/hallon/audio_driver.rb +138 -0
  12. data/lib/hallon/audio_queue.rb +110 -0
  13. data/lib/hallon/enumerator.rb +55 -16
  14. data/lib/hallon/error.rb +9 -2
  15. data/lib/hallon/image.rb +6 -5
  16. data/lib/hallon/link.rb +7 -4
  17. data/lib/hallon/linkable.rb +27 -0
  18. data/lib/hallon/observable/player.rb +18 -1
  19. data/lib/hallon/observable/session.rb +5 -1
  20. data/lib/hallon/player.rb +180 -54
  21. data/lib/hallon/playlist.rb +33 -20
  22. data/lib/hallon/playlist_container.rb +78 -64
  23. data/lib/hallon/search.rb +51 -33
  24. data/lib/hallon/session.rb +1 -1
  25. data/lib/hallon/toplist.rb +36 -18
  26. data/lib/hallon/track.rb +12 -6
  27. data/lib/hallon/version.rb +1 -1
  28. data/spec/hallon/artist_browse_spec.rb +3 -4
  29. data/spec/hallon/audio_queue_spec.rb +89 -0
  30. data/spec/hallon/enumerator_spec.rb +50 -25
  31. data/spec/hallon/error_spec.rb +2 -2
  32. data/spec/hallon/image_spec.rb +12 -5
  33. data/spec/hallon/link_spec.rb +8 -9
  34. data/spec/hallon/linkable_spec.rb +11 -0
  35. data/spec/hallon/observable/session_spec.rb +4 -0
  36. data/spec/hallon/player_spec.rb +118 -5
  37. data/spec/hallon/playlist_container_spec.rb +2 -2
  38. data/spec/hallon/playlist_spec.rb +32 -37
  39. data/spec/hallon/search_spec.rb +3 -3
  40. data/spec/hallon/user_spec.rb +0 -6
  41. data/spec/spec_helper.rb +10 -0
  42. data/spec/support/audio_driver_mock.rb +23 -0
  43. data/spec/support/context_stub_session.rb +5 -0
  44. data/spec/support/shared_for_linkable_objects.rb +22 -2
  45. metadata +26 -20
  46. data/lib/hallon/queue.rb +0 -71
  47. data/spec/hallon/queue_spec.rb +0 -35
data/CHANGELOG.md CHANGED
@@ -1,6 +1,47 @@
1
1
  Hallon’s Changelog
2
2
  ==================
3
3
 
4
+ [HEAD][]
5
+ ------------------
6
+
7
+ __Added__
8
+
9
+ __Changed__
10
+
11
+ __Fixed__
12
+
13
+ [v0.13.0][]
14
+ ------------------
15
+
16
+ Hallon v0.13.0 brings support for using external audio drivers with
17
+ the Hallon::Player API. The specification on how to write your own
18
+ driver is in the README.
19
+
20
+ An audio driver was also written as a separate gem:
21
+
22
+ https://rubygems.org/gems/hallon-openal
23
+
24
+ __Added__
25
+
26
+ - Linkable#to_str, all linkable objects can now easily be converted to a spotify URI [132981a9]
27
+ - AudioQueue#clear/#synchronize/#new_cond (formerly Queue) [62bf4622, c2b14481, bb65cf28]
28
+ - AudioQueue#format/format= [27084a3a]
29
+
30
+ __Changed__
31
+
32
+ - Rewritten Player API (now deals in audio drivers) [53cfac21]
33
+ - Rewritten Enumerator system (Playlist#tracks, Search#albums et al) [676f7d1e]
34
+ - Search#{tracks,albums,artists}_total removed in favor of Search#{tracks,albums,artists}.total [d5c2e7aa]
35
+ - Image#== and Link#== [8a1e4a33]
36
+ - Player#load now accepts a spotify uri [710baf34]
37
+ - Renamed Queue to AudioQueue [c2b14481]
38
+ - Error.mabe_raise no longer ignores :is_loading, and now takes an :ignores option [53ad65c8]
39
+
40
+ __Fixed__
41
+
42
+ - Enumerators now check size before each iteration [4ec24969]
43
+ - Playlist#update_subscribers now returns the Playlist [86120836]
44
+
4
45
  [v0.12.0][]
5
46
  ------------------
6
47
 
@@ -217,3 +258,5 @@ easier to handle.
217
258
  [v0.10.1]: https://github.com/Burgestrand/Hallon/compare/v0.9.1...v0.10.1
218
259
  [v0.11.0]: https://github.com/Burgestrand/Hallon/compare/v0.10.1...v0.11.0
219
260
  [v0.12.0]: https://github.com/Burgestrand/Hallon/compare/v0.11.0...v0.12.0
261
+ [v0.13.0]: https://github.com/Burgestrand/Hallon/compare/v0.12.0...v0.13.0
262
+ [HEAD]: https://github.com/Burgestrand/Hallon/compare/v0.12.0...HEAD
data/Gemfile CHANGED
@@ -4,4 +4,6 @@ gemspec
4
4
  gem 'ruby_parser'
5
5
  gem 'pry'
6
6
  gem 'cover_me', :platform => :ruby_19
7
- gem 'coreaudio' if RUBY_PLATFORM =~ /darwin/i
7
+ gem 'coreaudio', :platform => :ruby_19 if RUBY_PLATFORM =~ /darwin/i
8
+ gem 'hallon-openal'
9
+ gem 'rb-readline'
data/README.markdown CHANGED
@@ -1,9 +1,7 @@
1
1
  [What is Hallon?][] [![Build Status][]](http://travis-ci.org/Burgestrand/Hallon)
2
- ===============
2
+ ===================
3
3
 
4
- We rubyists have this awesome [spotify gem][] allowing us to use [libspotify][] from within Ruby, but it has a significant drawback: the `libspotify` API is very hard to use. Now, we can’t have that, so what do we do? We make Hallon!
5
-
6
- Hallon is Swedish for “[Raspberry][]”, and has been written to satisfy my needs for API simplicity. Hallon is written on top of [Spotify for Ruby][], but with the goal of making the experience of using `libspotify` from Ruby much more enjoyable.
4
+ Hallon which is Swedish for “[Raspberry][] is a ruby gem for interacting with the official Spotify C API. It is written on top of [Spotify for Ruby][], with the goal of making the experience of using `libspotify` as enjoyable as it can be.
7
5
 
8
6
  Hallon would not have been possible if not for these people:
9
7
 
@@ -16,13 +14,42 @@ Also, these people are worthy of mention simply for their contribution:
16
14
  - Jesper Särnesjö, unknowingly providing me a starting point with [Greenstripes][]
17
15
  - Emil “@mrevilme” Palm, for his patience in helping me debug Hallon deadlock issues
18
16
 
19
- Code samples can be found under `examples/` directory.
17
+ Code samples can be found under the `examples/` directory. An explanation on how to run them can be found on the [Hallon wiki on GitHub](https://github.com/Burgestrand/Hallon/wiki).
18
+
19
+ Installation
20
+ ------------
21
+
22
+ gem install hallon
23
+
24
+ If you want to play audio you’ll need to install an audio driver. As of current writing there is only one driver in existence. You can install it with:
25
+
26
+ gem install hallon-openal
27
+
28
+ For more information about audio support in Hallon, see the section "Audio support" below.
29
+
30
+ Audio support
31
+ -------------
32
+ Hallon supports streaming audio from Spotify via [Hallon::Player][]. When you create the player you give it your current session and an audio driver, which the player will then use for audio playback.
33
+
34
+ ```ruby
35
+ player = Hallon::Player.new(session, Hallon::OpenAL)
36
+ player.play(loaded_track)
37
+ ```
38
+
39
+ Available drivers are:
40
+
41
+ - [Hallon::OpenAL](https://rubygems.org/gems/hallon-openal)
42
+
43
+ gem install hallon-openal
44
+
45
+ For information on how to write your own audio driver, see [Hallon::ExampleAudioDriver].
20
46
 
21
47
  You have any questions?
22
48
  -----------------------
23
- If you need to discuss issues or feature requests you can use [Hallons issue tracker](http://github.com/Burgestrand/Hallon/issues). For *anything* else you have to say or ask I can also be reached via [email (found on GitHub profile)](http://github.com/Burgestrand) or [@burgestrand on twitter](http://twitter.com/Burgestrand).
49
+ I can be reached at my [email (found on GitHub profile)](http://github.com/Burgestrand) or [@burgestrand on twitter](http://twitter.com/Burgestrand). I’d be extremely happy to discuss Hallon with
50
+ you if you have any feedback or thoughts.
24
51
 
25
- In fact, you can contact me via email or twitter even if it’s about features or issues. I’ll probably put them in the issue tracker myself after the discussion ;)
52
+ For issues and feature requests, please use use [Hallons issue tracker](http://github.com/Burgestrand/Hallon/issues).
26
53
 
27
54
  This is awesome! I want to help!
28
55
  --------------------------------
@@ -34,20 +61,20 @@ Sweet! You contribute in more than one way!
34
61
  ### [Send me feedback and requests](http://github.com/Burgestrand/Hallon/issues)
35
62
  Really, I ❤ feedback! Suggestions on how to improve the API, tell me what is delicious about Hallon, tell me what is yucky about Hallon… anything! All feedback is useful in one way or another.
36
63
 
37
- Finally, here’s some important notes
38
- ------------------------------------
64
+ Finally, here are some important notes
65
+ --------------------------------------
39
66
 
40
67
  ### Hallon is unstable
41
68
  The API is unstable, my code is likely unstable. Everything should be considered unstable!
42
69
 
43
70
  ### Hallon only supports one session per process
44
- You can only keep one session with Spotify alive at a time in the same process, due to a limitation of `libspotify`.
71
+ You can only keep one session with Spotify alive at a time within the same process, due to a limitation of `libspotify`.
45
72
 
46
73
  ### You still have to worry about threads
47
74
  I have been doing my best at hiding the complexity in `libspotify`, but it’s still a work in progress. Despite my efforts, you’ll need to be familiar with concurrent programming to use Hallon properly.
48
75
 
49
76
  ### When forking, you need to be extra careful
50
- If you fork, you need to instantiate the session in the process you plan to use Hallon in. You want to use Hallon in the parent? Create the session in the parent. You want to use it in the child? Create the session in the child! This is a limitation of libspotify itself.
77
+ If you fork, you need to instantiate the session within the process you plan to use Hallon in. You want to use Hallon in the parent? Create the session in the parent. You want to use it in the child? Create the session in the child! This is a limitation of libspotify itself.
51
78
 
52
79
  Versioning policy
53
80
  -----------------
@@ -66,3 +93,6 @@ Hallon is licensed under a 2-clause (Simplified) BSD license. More information c
66
93
  [Greenstripes]: http://github.com/sarnesjo/greenstripes
67
94
  [What is Hallon?]: http://burgestrand.se/articles/hallon-delicious-ruby-bindings-to-libspotify.html
68
95
  [Build Status]: https://secure.travis-ci.org/Burgestrand/Hallon.png
96
+
97
+ [Hallon::Player]: http://rubydoc.info/github/Burgestrand/Hallon/Hallon/Player
98
+ [Hallon::ExampleAudioDriver]: http://rubydoc.info/github/Burgestrand/Hallon/Hallon/ExampleAudioDriver
data/Rakefile CHANGED
@@ -110,6 +110,18 @@ task 'spotify:coverage' do
110
110
  [method, "#{method}!"]
111
111
  end
112
112
 
113
+ # Hallon::Enumerator
114
+ no_receiver[:size] = proc do |recv, meth, (_, name)|
115
+ name.value if name.respond_to?(:value)
116
+ end
117
+
118
+ # Hallon::Enumerator
119
+ no_receiver[:item] = proc do |recv, meth, (_, name)|
120
+ next unless name.respond_to?(:value)
121
+ method = name.value.to_s
122
+ [method.delete("!"), method]
123
+ end
124
+
113
125
  fails = {}
114
126
  FileList['lib/**/*.rb'].each do |file|
115
127
  begin
@@ -0,0 +1,55 @@
1
+ # coding: utf-8
2
+ require 'monitor'
3
+
4
+ begin
5
+ require 'coreaudio'
6
+ rescue LoadError
7
+ abort <<-ERROR
8
+ This example requires the ruby-coreaudio gem.
9
+
10
+ See: http://rubygems.org/gems/coreaudio
11
+ ERROR
12
+ end
13
+
14
+ puts <<-INFO
15
+ **************************************************************
16
+
17
+ Keep in mind, you’re now using the CoreAudio driver, part
18
+ of Hallon examples. This driver does not buffer data, so
19
+ even the slightest hickup in Ruby will make the playback
20
+ stutter. The reason is that this CoreAudio driver does not
21
+ buffer data internally.
22
+
23
+ **************************************************************
24
+ INFO
25
+
26
+ module Hallon
27
+ class CoreAudio
28
+ attr_reader :output
29
+ protected :output
30
+
31
+ def initialize(format)
32
+ @device = ::CoreAudio.default_output_device
33
+ @output = @device.output_buffer(format[:rate] * 3)
34
+ @format = format
35
+ end
36
+
37
+ attr_accessor :format
38
+
39
+ def stream
40
+ loop { output << yield }
41
+ end
42
+
43
+ def play
44
+ output.start
45
+ end
46
+
47
+ def stop
48
+ output.stop
49
+ end
50
+
51
+ def pause
52
+ output.stop
53
+ end
54
+ end
55
+ end
@@ -2,22 +2,20 @@
2
2
 
3
3
  $LOAD_PATH.unshift(File.expand_path('../lib', File.dirname(__FILE__)))
4
4
 
5
+ require 'bundler/setup'
5
6
  require 'hallon'
6
- require_relative '../spec/support/config'
7
7
 
8
8
  begin
9
- require 'coreaudio'
9
+ require 'hallon/openal'
10
10
  rescue LoadError
11
- abort <<-ERROR
12
- This example requires the ruby-coreaudio gem.
13
-
14
- See: http://rubygems.org/gems/coreaudio
15
- ERROR
11
+ require_relative 'audio_driver'
16
12
  end
17
13
 
14
+ require_relative '../spec/support/config'
15
+
18
16
  # Utility
19
17
  def say(string)
20
- system('say', string)
18
+ # system('say', string)
21
19
  end
22
20
 
23
21
  def tell(string)
@@ -34,16 +32,7 @@ end
34
32
 
35
33
  # Hallon set-up.
36
34
 
37
- sample_rate = 44100 # 44100 samples / second
38
- device = CoreAudio.default_output_device
39
- output = device.output_buffer(sample_rate * 3)
40
- audio_queue = Hallon::Queue.new(sample_rate)
41
-
42
35
  session = Hallon::Session.initialize IO.read(ENV['HALLON_APPKEY']) do
43
- on(:log_message) do |message|
44
- puts "[LOG] #{message}"
45
- end
46
-
47
36
  on(:connection_error) do |error|
48
37
  Hallon::Error.maybe_raise(error)
49
38
  end
@@ -53,31 +42,8 @@ session = Hallon::Session.initialize IO.read(ENV['HALLON_APPKEY']) do
53
42
  end
54
43
  end
55
44
 
56
- end_of_track = false
57
- player = Hallon::Player.new(session) do
58
- on(:music_delivery) do |format, frames|
59
- audio_queue.push(frames)
60
- end
61
-
62
- on(:start_playback) do
63
- puts "(start playback)"
64
- output.start
65
- end
66
-
67
- on(:stop_playback) do
68
- puts "(stop playback)"
69
- output.stop
70
- end
71
-
72
- on(:get_audio_buffer_stats) do
73
- [audio_queue.size, 0]
74
- end
75
-
76
- on(:end_of_track) do
77
- puts "End of track!"
78
- end_of_track = true
79
- end
80
- end
45
+ driver = defined?(Hallon::OpenAL) ? Hallon::OpenAL : Hallon::CoreAudio
46
+ player = Hallon::Player.new(session, driver)
81
47
 
82
48
  # Program flow.
83
49
 
@@ -89,7 +55,7 @@ search = loop do
89
55
 
90
56
  tell "Searching for “#{search.query}”…"
91
57
  session.wait_for do
92
- search.loaded? or Hallon::Error.maybe_raise(search.status)
58
+ search.loaded? or Hallon::Error.maybe_raise(search.status, ignore: :is_loading)
93
59
  end
94
60
 
95
61
  if search.tracks.size.zero?
@@ -119,12 +85,6 @@ track = loop do
119
85
  end
120
86
  end
121
87
 
122
- player.play(track)
123
-
124
88
  tell "Alright! Playing “#{track.name}” by “#{track.artist.name}”."
125
-
126
- until end_of_track
127
- output << audio_queue.pop(sample_rate)
128
- end
129
-
89
+ player.play!(track)
130
90
  tell "Done! This was “#{track.name}” by “#{track.artist.name}”. Bye bye!"
data/hallon.gemspec CHANGED
@@ -19,7 +19,7 @@ Gem::Specification.new do |gem|
19
19
 
20
20
  gem.version = Hallon::VERSION
21
21
  gem.platform = Gem::Platform::RUBY
22
- gem.required_ruby_version = '~> 1.8'
22
+ gem.required_ruby_version = '>= 1.9'
23
23
 
24
24
  gem.add_dependency 'ref', '~> 1.0'
25
25
  gem.add_dependency 'spotify', '~> 10.3.0'
data/lib/hallon.rb CHANGED
@@ -9,8 +9,8 @@ require 'hallon/linkable'
9
9
  require 'hallon/version'
10
10
  require 'hallon/error'
11
11
  require 'hallon/base'
12
- require 'hallon/queue'
13
12
  require 'hallon/enumerator'
13
+ require 'hallon/audio_queue'
14
14
 
15
15
  require 'hallon/observable/album_browse'
16
16
  require 'hallon/observable/artist_browse'
@@ -7,6 +7,24 @@ module Hallon
7
7
  # @see Album
8
8
  # @see http://developer.spotify.com/en/libspotify/docs/group__albumbrowse.html
9
9
  class AlbumBrowse < Base
10
+ # Enumerates through all copyright notices of an album browsing object.
11
+ class Copyrights < Enumerator
12
+ size :albumbrowse_num_copyrights
13
+
14
+ # @return [String, nil]
15
+ item :albumbrowse_copyright
16
+ end
17
+
18
+ # Enumerates through all tracks of an album browsing object.
19
+ class Tracks < Enumerator
20
+ size :albumbrowse_num_tracks
21
+
22
+ # @return [Track, nil]
23
+ item :albumbrowse_track! do |track|
24
+ Track.from(track)
25
+ end
26
+ end
27
+
10
28
  extend Observable::AlbumBrowse
11
29
 
12
30
  # Creates an AlbumBrowse instance from an Album or an Album pointer.
@@ -65,21 +83,14 @@ module Hallon
65
83
  Rational(duration, 1000) if duration > 0
66
84
  end
67
85
 
68
- # @return [Enumerator<String>] list of copyright notices.
86
+ # @return [Copyrights] enumerator of copyright notices.
69
87
  def copyrights
70
- size = Spotify.albumbrowse_num_copyrights(pointer)
71
- Enumerator.new(size) do |i|
72
- Spotify.albumbrowse_copyright(pointer, i)
73
- end
88
+ Copyrights.new(self)
74
89
  end
75
90
 
76
- # @return [Enumerator<Track>] list of tracks.
91
+ # @return [Tracks] enumerator of tracks.
77
92
  def tracks
78
- size = Spotify.albumbrowse_num_tracks(pointer)
79
- Enumerator.new(size) do |i|
80
- track = Spotify.albumbrowse_track!(pointer, i)
81
- Track.new(track)
82
- end
93
+ Tracks.new(self)
83
94
  end
84
95
  end
85
96
  end
@@ -5,6 +5,56 @@ module Hallon
5
5
  # @see Artist
6
6
  # @see http://developer.spotify.com/en/libspotify/docs/group__artistbrowse.html
7
7
  class ArtistBrowse < Base
8
+ # Enumerates through all portrait images of an artist browsing object.
9
+ class Portraits < Enumerator
10
+ size :artistbrowse_num_portraits
11
+
12
+ # @return [Image, nil]
13
+ item :artistbrowse_portrait do |portrait|
14
+ Image.new(portrait)
15
+ end
16
+ end
17
+
18
+ # Enumerates through all portrait links of an artist browsing object.
19
+ class PortraitLinks < Enumerator
20
+ size :artistbrowse_num_portraits
21
+
22
+ # @return [Link, nil]
23
+ item :link_create_from_artistbrowse_portrait! do |portrait|
24
+ Link.from(portrait)
25
+ end
26
+ end
27
+
28
+ # Enumerates through all tracks of an artist browsing object.
29
+ class Tracks < Enumerator
30
+ size :artistbrowse_num_tracks
31
+
32
+ # @return [Track, nil]
33
+ item :artistbrowse_track! do |track|
34
+ Track.from(track)
35
+ end
36
+ end
37
+
38
+ # Enumerates through all albums of an artist browsing object.
39
+ class Albums < Enumerator
40
+ size :artistbrowse_num_albums
41
+
42
+ # @return [Album, nil]
43
+ item :artistbrowse_album! do |album|
44
+ Album.from(album)
45
+ end
46
+ end
47
+
48
+ # Enumerates through all similar artists of an artist browsing object.
49
+ class SimilarArtists < Enumerator
50
+ size :artistbrowse_num_similar_artists
51
+
52
+ # @return [Artist, nil]
53
+ item :artistbrowse_similar_artist! do |artist|
54
+ Artist.from(artist)
55
+ end
56
+ end
57
+
8
58
  extend Observable::ArtistBrowse
9
59
 
10
60
  # @return [Array<Symbol>] artist browsing types for use in {#initialize}
@@ -63,48 +113,29 @@ module Hallon
63
113
  Rational(duration, 1000) if duration > 0
64
114
  end
65
115
 
66
- # Retrieve artist portraits as an {Image} or a {Link}.
67
- #
68
- # @param [Boolean] as_image true if you want an enumerator of Images (false for Links)
69
- # @return [Enumerator<Image>, Enumerator<Link>] artist portraits.
70
- def portraits(as_image = true)
71
- size = Spotify.artistbrowse_num_portraits(pointer)
72
- Enumerator.new(size) do |i|
73
- if as_image
74
- id = Spotify.artistbrowse_portrait(pointer, i)
75
- Image.new(id)
76
- else
77
- link = Spotify.link_create_from_artistbrowse_portrait!(pointer, i)
78
- Link.new(link)
79
- end
80
- end
116
+ # @return [Portraits] artist portraits as {Image}s.
117
+ def portraits
118
+ Portraits.new(self)
119
+ end
120
+
121
+ # @return [PortraitImages] artist portraits as {Link}s.
122
+ def portrait_links
123
+ PortraitLinks.new(self)
81
124
  end
82
125
 
83
- # @return [Enumerator<Track>] artist authored tracks.
126
+ # @return [Tracks] artist authored tracks.
84
127
  def tracks
85
- size = Spotify.artistbrowse_num_tracks(pointer)
86
- Enumerator.new(size) do |i|
87
- track = Spotify.artistbrowse_track!(pointer, i)
88
- Track.new(track)
89
- end
128
+ Tracks.new(self)
90
129
  end
91
130
 
92
- # @return [Enumerator<Album>] artist authored albums.
131
+ # @return [Albums] artist authored albums.
93
132
  def albums
94
- size = Spotify.artistbrowse_num_albums(pointer)
95
- Enumerator.new(size) do |i|
96
- album = Spotify.artistbrowse_album!(pointer, i)
97
- Album.new(album)
98
- end
133
+ Albums.new(self)
99
134
  end
100
135
 
101
- # @return [Enumartor<Artist>] similar artists to this artist.
136
+ # @return [SimilarArtists] similar artists to this artist.
102
137
  def similar_artists
103
- size = Spotify.artistbrowse_num_similar_artists(pointer)
104
- Enumerator.new(size) do |i|
105
- artist = Spotify.artistbrowse_similar_artist!(pointer, i)
106
- Artist.new(artist)
107
- end
138
+ SimilarArtists.new(self)
108
139
  end
109
140
  end
110
141
  end