Class: Hallon::Player

Inherits:
Base
  • Object
show all
Extended by:
Observable::Player
Defined in:
lib/hallon/player.rb

Overview

A wrapper around Session for playing, stopping and otherwise controlling the playback features of libspotify.

See Also:

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Methods included from Observable::Player

end_of_track_callback, extended, initialize_callbacks, play_token_lost_callback, streaming_error_callback

Methods inherited from Base

#==, from, from_link, #is_linkable?, #session, to_link, #to_pointer

Constructor Details

#initialize(driver) { ... } ⇒ Player

Note:

for instructions on how to write your own audio driver, see Hallons’ README

Constructs a Player, given an audio driver.

Examples:

player = Hallon::Player.new(Hallon::OpenAL)
player.play(track)

Yields:

  • instance_evals itself, allowing you to define callbacks using on


36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
# File 'lib/hallon/player.rb', line 36

def initialize(driver, &block)
  @session = Hallon::Session.instance
  @pointer = @session.pointer

  # sample rate is often (if not always) 44.1KHz, so
  # we keep an audio queue that can store 3s of audio
  @queue  = AudioQueue.new(44100)
  @driver = driver.new
  @driver.format = { rate: 44100, channels: 2, type: :int16 }

  # used for feeder thread to know if it should stream
  # data to the driver or not (see #status=)
  @status_c = @queue.new_cond
  # set initial status (we assume stopped)
  self.status = :stopped

  # this thread feeds the audio driver with data, but
  # if we are not playing it’ll wait until we are
  @thread = Thread.start(@driver, @queue, @status_c) do |output, queue, cond|
    output.stream do |num_frames|
      queue.synchronize do
        frames = queue.pop(output.format, *num_frames)

        if frames.nil?
          # if we received no frames, it means the audio format changed
          output.format = queue.format
        end

        # frames is either nil, or an array of audio frames
        frames
      end
    end
  end

  @session.on(:start_playback, &method(:start_playback))
  @session.on(:stop_playback,  &method(:stop_playback))
  @session.on(:music_delivery, &method(:music_delivery))
  @session.on(:get_audio_buffer_stats, &method(:get_audio_buffer_stats))

  @session.on(:end_of_track)    { |*args| trigger(:end_of_track, *args) }
  @session.on(:streaming_error) { |*args| trigger(:streaming_error, *args) }
  @session.on(:play_token_lost) { |*args| trigger(:play_token_lost, *args) }

  instance_eval(&block) if block_given?
end

Instance Attribute Details

#pointerSpotify::Session (readonly)


14
15
16
# File 'lib/hallon/player.rb', line 14

def pointer
  @pointer
end

#statusSymbol


17
18
19
# File 'lib/hallon/player.rb', line 17

def status
  @status
end

Class Method Details

.bitratesArray<Symbol>


20
21
22
23
24
25
# File 'lib/hallon/player.rb', line 20

def self.bitrates
  Spotify.enum_type(:bitrate).symbols.sort_by do |sym|
    # sort by bitrate quality
    sym.to_s.to_i
  end
end

Instance Method Details

#bitrate=(bitrate) ⇒ Object

Note:

the double possible errors is a result of the same thing as for Session#offline_bitrate=, see its documentation for further information.

Set preferred playback bitrate.

Raises:

  • (ArgumentError)

    if given invalid bitrate

  • (Spotify::Error)

    if libspotify does not accept the given bitrate


210
211
212
# File 'lib/hallon/player.rb', line 210

def bitrate=(bitrate)
  Spotify.try(:session_preferred_bitrate, pointer, bitrate)
end

#get_audio_buffer_statsObject (protected)

Called by libspotify to request information about our audio buffer. Required if we want libspotify to tell us when we should start and stop playback.


111
112
113
114
# File 'lib/hallon/player.rb', line 111

def get_audio_buffer_stats
  drops = @driver.drops if @driver.respond_to?(:drops)
  [@queue.size, drops.to_i]
end

#load(track) ⇒ Player

Loads a Track for playing.

Raises:

  • (Error)

    if the track could not be loaded


219
220
221
222
223
# File 'lib/hallon/player.rb', line 219

def load(track)
  track = Track.new(track) unless track.is_a?(Track)
  error = Spotify.session_player_load(pointer, track.pointer)
  tap { Error.maybe_raise(error) }
end

#music_delivery(format, frames) ⇒ Object (protected)

Called by libspotify on music delivery; format is a hash of (sample) rate, channels and (sample) type.


101
102
103
104
105
106
# File 'lib/hallon/player.rb', line 101

def music_delivery(format, frames)
  @queue.synchronize do
    @queue.clear if frames.none?
    @queue.push(format, frames)
  end
end

#pauseObject

Pause playback of a Track.


176
177
178
179
# File 'lib/hallon/player.rb', line 176

def pause
  self.status = :paused
  Spotify.session_player_play(pointer, false)
end

#play(track = nil) ⇒ Player

Note:

If no track is given, will try to play currently #loaded track.

Start playing the currently loaded, or given, Track.

Examples:

player.play("spotify:track:44FHDONpdYeDpmqyS3BLRP")

168
169
170
171
# File 'lib/hallon/player.rb', line 168

def play(track = nil)
  load(track) unless track.nil?
  tap { Spotify.session_player_play(pointer, true) }
end

#play!(track = nil) ⇒ Player

Like #play, but blocks until the track has finished playing.


193
194
195
196
197
198
199
200
# File 'lib/hallon/player.rb', line 193

def play!(track = nil)
  end_of_track = false
  old_callback = on(:end_of_track) { end_of_track = true }
  play(track)
  wait_for(:end_of_track) { end_of_track }
ensure
  on(:end_of_track, &old_callback)
end

#prefetch(track) ⇒ Player

Note:

You can only prefetch if caching is on.

Prepares a Track for playing, without #loading it.


230
231
232
233
# File 'lib/hallon/player.rb', line 230

def prefetch(track)
  error = Spotify.session_player_prefetch(pointer, track.pointer)
  tap { Error.maybe_raise(error) }
end

#seek(seconds) ⇒ Player

Seek to the desired position of the currently loaded Track.


239
240
241
# File 'lib/hallon/player.rb', line 239

def seek(seconds)
  tap { Spotify.session_player_seek(pointer, seconds * 1000) }
end

#start_playbackObject (protected)

Called by libspotify when the driver should start audio playback.

Will be called after calling our buffers are full enough to support continous playback.


88
89
90
# File 'lib/hallon/player.rb', line 88

def start_playback
  self.status = :playing
end

#stopObject

Stop playing current track and unload it.


184
185
186
187
# File 'lib/hallon/player.rb', line 184

def stop
  self.status = :stopped
  Spotify.session_player_unload(pointer)
end

#stop_playbackObject (protected)

Called by libspotify when the driver should pause audio playback.

Might happen if we’re playing audio faster than we can stream it.


95
96
97
# File 'lib/hallon/player.rb', line 95

def stop_playback
  self.status = :paused
end

#to_sString

Note:

default output also shows all our instance variables, that is kind of unnecessary and might take some time to display, for example if the audio queue is full


154
155
156
157
158
# File 'lib/hallon/player.rb', line 154

def to_s
  name    = self.class.name
  address = pointer.address.to_s(16)
  "<#{name} session=0x#{address} driver=#{@driver.class} status=#{status}>"
end

#volume_normalization=(normalize_volume) ⇒ Object


249
250
251
# File 'lib/hallon/player.rb', line 249

def volume_normalization=(normalize_volume)
  Spotify.session_set_volume_normalization(pointer, !! normalize_volume)
end

#volume_normalization?Boolean


244
245
246
# File 'lib/hallon/player.rb', line 244

def volume_normalization?
  Spotify.session_get_volume_normalization(pointer)
end