class Shoutout::Stream

Attributes

url[R]

Public Class Methods

metadata(url) click to toggle source
# File lib/shoutout/stream.rb, line 19
def metadata(url)
  new(url).metadata
end
new(url) click to toggle source
# File lib/shoutout/stream.rb, line 24
def initialize(url)
  @url = url
end
open(url) { |stream| ... } click to toggle source
# File lib/shoutout/stream.rb, line 8
def open(url)
  stream = new(url)

  begin
    stream.connect
    yield stream
  ensure
    stream.disconnect
  end
end

Public Instance Methods

connect() click to toggle source
# File lib/shoutout/stream.rb, line 32
def connect
  return false if @connected

  uri = URI.parse(@url)
  @socket = TCPSocket.new(uri.host, uri.port)
  @socket.puts "GET #{uri.path} HTTP/1.0"
  @socket.puts "Icy-MetaData: 1"
  @socket.puts

  # Read status line
  status_line = @socket.gets
  status_code = status_line.match(/\AHTTP\/([0-9]\.[0-9]) ([0-9]{3})/)[2].to_i

  @connected = true

  read_headers

  if status_code >= 300 && status_code < 400 && headers[:location]
    disconnect

    @url = URI.join(uri, headers[:location]).to_s

    return connect
  end

  unless status_code >= 200 && status_code < 300
    disconnect

    return false
  end

  unless metadata_interval
    disconnect

    return false
  end

  @read_metadata_thread = Thread.new(&method(:read_metadata))

  true
end
connected?() click to toggle source
# File lib/shoutout/stream.rb, line 28
def connected?
  @connected
end
disconnect() click to toggle source
# File lib/shoutout/stream.rb, line 74
def disconnect
  return false unless @connected

  @connected = false

  @socket.close if @socket && !@socket.closed?
  @socket = nil

  true
end
listen() click to toggle source
# File lib/shoutout/stream.rb, line 85
def listen
  return unless @connected

  @read_metadata_thread.join
  @last_metadata_change_thread.join if @last_metadata_change_thread
end
metadata() click to toggle source
# File lib/shoutout/stream.rb, line 92
def metadata
  return @metadata if defined?(@metadata)

  original_metadata_change_block = @metadata_change_block

  received = false
  metadata_change do |new_metadata|
    received = true
  end

  already_connected = @connected
  connect unless already_connected

  sleep 0.015 until received

  disconnect unless already_connected

  metadata_change(&original_metadata_change_block) if original_metadata_change_block

  @metadata
end
metadata_change(&block) click to toggle source
# File lib/shoutout/stream.rb, line 114
def metadata_change(&block)
  @metadata_change_block = block

  report_metadata_change(@metadata) if defined?(@metadata)

  true
end

Private Instance Methods

headers() click to toggle source
# File lib/shoutout/stream.rb, line 160
def headers
  return @headers if defined?(@headers)

  # Connected but no headers? I give up.
  return [] if @connected

  connect && disconnect

  @headers
end
read_headers() click to toggle source
# File lib/shoutout/stream.rb, line 123
def read_headers
  raw_headers = ""
  while line = @socket.gets
    break if line.chomp == ""
    raw_headers << line
  end
  @headers = Headers.parse(raw_headers)
end
read_metadata() click to toggle source
# File lib/shoutout/stream.rb, line 132
def read_metadata
  while @connected
    # Skip audio data
    data = @socket.read(metadata_interval) || raise(EOFError)

    data = @socket.read(1) || raise(EOFError)
    metadata_length = data.unpack("c")[0] * 16
    next if metadata_length == 0

    data = @socket.read(metadata_length) || raise(EOFError)
    raw_metadata = data.unpack("A*")[0]
    @metadata = Metadata.parse(raw_metadata)

    report_metadata_change(@metadata)
  end
rescue Errno::EBADF, IOError => e
  # Connection lost
  disconnect
end
report_metadata_change(metadata) click to toggle source
# File lib/shoutout/stream.rb, line 152
def report_metadata_change(metadata)
  @last_metadata_change_thread = Thread.new(metadata, @last_metadata_change_thread) do |metadata, last_metadata_change_thread|
    last_metadata_change_thread.join if last_metadata_change_thread

    @metadata_change_block.call(metadata) if @metadata_change_block
  end
end