shoutout 0.0.1 → 0.0.2

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.
data/README.md CHANGED
@@ -1,4 +1,4 @@
1
- # shoutout
1
+ # shoutout [![Build Status](https://travis-ci.org/DouweM/shoutout.png?branch=master)](https://travis-ci.org/DouweM/shoutout)
2
2
 
3
3
  A Ruby library for easily getting metadata from Shoutcast-compatible audio streaming servers.
4
4
 
@@ -19,60 +19,68 @@ gem "shoutout"
19
19
  ```ruby
20
20
  require "shoutout"
21
21
 
22
- shoutout = Shoutout.new("http://82.201.100.5:8000/radio538")
22
+ # You can open a connection to the stream that will live for the duration of the block using `.open`:
23
+ Shoutout::Stream.open("http://82.201.100.5:8000/radio538") do |stream|
24
+ # Use `stream` as described below.
25
+ end
26
+
27
+ # If you want a little more control over opening and closing the connection, just use `.new`:
28
+ stream = Shoutout::Stream.new("http://82.201.100.5:8000/radio538")
23
29
 
24
- # Explicitly open a connection with the server. You're responsible for closing this connection using `#disconnect`.
25
- shoutout.connect
30
+ # You can then explicitly open a connection to the stream.
31
+ # You're responsible for closing it using `#disconnect` when you're done.
32
+ stream.connect
26
33
 
27
- # If you call any of the reader methods below without having explicitly opened a connection,
28
- # one will be opened and closed around reading the information implicitly.
34
+ # If you call any of the reader methods below without having opened a connection using `#connect`
35
+ # or `.open`, one will be opened and closed around reading the information implicitly.
29
36
  # This is convenient if you're only looking for one piece of information, but it is of course
30
37
  # very inefficient if you're going to do multiple reads.
31
38
 
32
39
  # Stream info
33
- shoutout.name # => "RADIO538"
34
- shoutout.description # => "ARE YOU IN"
35
- shoutout.genre # => "Various"
36
- shoutout.notice # => nil in this case, but this could very well have a value for your stream
37
- shoutout.content_type # => "audio/mpeg"
38
- shoutout.bitrate # => 128
39
- shoutout.public? # => true
40
- shoutout.audio_info # => { :samplerate => 44100, :bitrate => 128, :channels => 2 }
40
+ stream.name # => "RADIO538"
41
+ stream.description # => "ARE YOU IN"
42
+ stream.genre # => "Various"
43
+ stream.notice # => nil in this case, but this could very well have a value for your stream
44
+ stream.content_type # => "audio/mpeg"
45
+ stream.bitrate # => 128
46
+ stream.public? # => true
47
+ stream.audio_info # => { :samplerate => 44100, :bitrate => 128, :channels => 2 }
41
48
 
42
49
  # Current metadata
43
- shoutout.metadata # => { "StreamTitle" => "ARMIN VAN BUUREN - THIS IS WHAT IT FEELS LIKE", "StreamUrl" => "http://www.radio538.nl" }
50
+ stream.metadata # => { "StreamTitle" => "Armin van Buuren - This Is What It Feels Like", "StreamUrl" => "http://www.radio538.nl" }
44
51
 
45
52
  # The Metadata object is a Hash that has been extended with the following features:
46
- shoutout.metadata[:stream_title] # => "ARMIN VAN BUUREN - THIS IS WHAT IT FEELS LIKE"
47
- shoutout.metadata[:stream_url] # => "http://www.radio538.nl"
48
- shoutout.metadata.now_playing # => "ARMIN VAN BUUREN - THIS IS WHAT IT FEELS LIKE"
49
- shoutout.metadata.website # => "http://www.radio538.nl"
50
- shoutout.metadata.artist # => "ARMIN VAN BUUREN"
51
- shoutout.metadata.song # => "THIS IS WHAT IT FEELS LIKE"
53
+ stream.metadata[:stream_title] # => "Armin van Buuren - This Is What It Feels Like"
54
+ stream.metadata[:stream_url] # => "http://www.radio538.nl"
55
+ stream.metadata.now_playing # => "Armin van Buuren - This Is What It Feels Like"
56
+ stream.metadata.website # => "http://www.radio538.nl"
57
+ stream.metadata.artist # => "Armin van Buuren"
58
+ stream.metadata.song # => "This Is What It Feels Like"
52
59
 
53
- # Conveniently, `#now_playing` and `#website` are also available on the Shoutout instance:
54
- shoutout.now_playing # => "ARMIN VAN BUUREN - THIS IS WHAT IT FEELS LIKE"
55
- shoutout.website # => "http://www.radio538.nl"
60
+ # Conveniently, `#now_playing` and `#website` are also available on the stream object:
61
+ stream.now_playing # => "Armin van Buuren - This Is What It Feels Like"
62
+ stream.website # => "http://www.radio538.nl"
56
63
 
57
64
  # For convenience, all of the reader methods above are also available as class methods:
58
- Shoutout.now_playing("http://82.201.100.5:8000/radio538") # => "ARMIN VAN BUUREN - THIS IS WHAT IT FEELS LIKE"
59
- # Just like the equivalent `Shoutout.new("http://82.201.100.5:8000/radio538").now_playing`,
65
+ Shoutout::Stream.now_playing("http://82.201.100.5:8000/radio538") # => "Armin van Buuren - This Is What It Feels Like"
66
+ # Just like the equivalent `Shoutout::Stream.new("http://82.201.100.5:8000/radio538").now_playing`,
60
67
  # this will automatically open and close a connection around reading the information.
61
68
 
62
- # You can have a block called every time the metadata changes:
63
- shoutout.metadata_change do |metadata|
69
+ # You can be notified every time the metadata changes:
70
+ stream.metadata_change do |metadata|
64
71
  puts "Now playing: #{metadata.song} by #{metadata.artist}"
72
+ # => Now playing: This Is What It Feels Like by Armin van Buuren
65
73
  end
66
74
  # Of course, this only works with an explicitly opened connection.
67
75
 
68
76
  # If you're done setting up but want the program to keep listening for metadata, say so:
69
- shoutout.listen
77
+ stream.listen
70
78
  # Note that listening will only end when the connection is lost or the end of file is reached,
71
79
  # so anything that comes after this call will only then be executed.
72
80
  # This will generally be the last call in your program.
73
81
 
74
82
  # If we don't want to wait around and listen, just let the program exit or disconnect explicitly:
75
- shoutout.disconnect
83
+ stream.disconnect
76
84
  ```
77
85
 
78
86
  ## Examples
@@ -6,173 +6,4 @@ require "shoutout/util"
6
6
  require "shoutout/metadata"
7
7
  require "shoutout/headers"
8
8
  require "shoutout/quick_access"
9
-
10
- class Shoutout
11
- include QuickAccess
12
-
13
- attr_reader :url
14
-
15
- class << self
16
- def open(url)
17
- shoutcast = new(url)
18
-
19
- begin
20
- shoutcast.connect
21
- yield shoutcast
22
- ensure
23
- shoutcast.disconnect
24
- end
25
- end
26
-
27
- def metadata(url)
28
- new(url).metadata
29
- end
30
- end
31
-
32
- def initialize(url)
33
- @url = url
34
- end
35
-
36
- def connected?
37
- @connected
38
- end
39
-
40
- def connect
41
- return false if @connected
42
-
43
- uri = URI.parse(@url)
44
- @socket = TCPSocket.new(uri.host, uri.port)
45
- @socket.puts "GET #{uri.path} HTTP/1.0"
46
- @socket.puts "Icy-MetaData: 1"
47
- @socket.puts
48
-
49
- # Read status line
50
- status_line = @socket.gets
51
- status_code = status_line.match(/\AHTTP\/([0-9]\.[0-9]) ([0-9]{3})/)[2].to_i
52
-
53
- @connected = true
54
-
55
- read_headers
56
-
57
- if status_code >= 300 && status_code < 400 && headers[:location]
58
- disconnect
59
-
60
- @url = URI.join(uri, headers[:location]).to_s
61
-
62
- return connect
63
- end
64
-
65
- unless status_code >= 200 && status_code < 300
66
- disconnect
67
-
68
- return false
69
- end
70
-
71
- unless metadata_interval
72
- disconnect
73
-
74
- return false
75
- end
76
-
77
- @read_metadata_thread = Thread.new(&method(:read_metadata))
78
-
79
- true
80
- end
81
-
82
- def disconnect
83
- return false unless @connected
84
-
85
- @connected = false
86
-
87
- @socket.close if @socket && !@socket.closed?
88
- @socket = nil
89
-
90
- true
91
- end
92
-
93
- def listen
94
- return unless @connected
95
-
96
- @read_metadata_thread.join
97
- @last_metadata_change_thread.join if @last_metadata_change_thread
98
- end
99
-
100
- def metadata
101
- return @metadata if defined?(@metadata)
102
-
103
- original_metadata_change_block = @metadata_change_block
104
-
105
- received = false
106
- metadata_change do |new_metadata|
107
- received = true
108
- end
109
-
110
- already_connected = @connected
111
- connect unless already_connected
112
-
113
- sleep 0.015 until received
114
-
115
- disconnect unless already_connected
116
-
117
- metadata_change(&original_metadata_change_block) if original_metadata_change_block
118
-
119
- @metadata
120
- end
121
-
122
- def metadata_change(&block)
123
- @metadata_change_block = block
124
-
125
- report_metadata_change(@metadata) if defined?(@metadata)
126
-
127
- true
128
- end
129
-
130
- private
131
- def read_headers
132
- raw_headers = ""
133
- while line = @socket.gets
134
- break if line.chomp == ""
135
- raw_headers << line
136
- end
137
- @headers = Headers.parse(raw_headers)
138
- end
139
-
140
- def read_metadata
141
- while @connected
142
- # Skip audio data
143
- data = @socket.read(metadata_interval) || raise(EOFError)
144
-
145
- data = @socket.read(1) || raise(EOFError)
146
- metadata_length = data.unpack("c")[0] * 16
147
- next if metadata_length == 0
148
-
149
- data = @socket.read(metadata_length) || raise(EOFError)
150
- raw_metadata = data.unpack("A*")[0]
151
- @metadata = Metadata.parse(raw_metadata)
152
-
153
- report_metadata_change(@metadata)
154
- end
155
- rescue Errno::EBADF, IOError => e
156
- # Connection lost
157
- disconnect
158
- end
159
-
160
- def report_metadata_change(metadata)
161
- @last_metadata_change_thread = Thread.new(metadata, @last_metadata_change_thread) do |metadata, last_metadata_change_thread|
162
- last_metadata_change_thread.join if last_metadata_change_thread
163
-
164
- @metadata_change_block.call(metadata) if @metadata_change_block
165
- end
166
- end
167
-
168
- def headers
169
- return @headers if defined?(@headers)
170
-
171
- # Connected but no headers? I give up.
172
- return [] if @connected
173
-
174
- connect && disconnect
175
-
176
- @headers
177
- end
178
- end
9
+ require "shoutout/stream"
@@ -1,4 +1,4 @@
1
- class Shoutout
1
+ module Shoutout
2
2
  class Headers < Hash
3
3
  def self.parse(raw_headers)
4
4
  headers = {}
@@ -1,4 +1,4 @@
1
- class Shoutout
1
+ module Shoutout
2
2
  class Metadata < Hash
3
3
  def self.parse(raw_metadata)
4
4
  metadata = {}
@@ -51,12 +51,17 @@ class Shoutout
51
51
  end
52
52
 
53
53
  def artist
54
- now_playing.split(" - ")[0]
54
+ artist_and_song[0]
55
55
  end
56
56
 
57
57
  def song
58
- now_playing.split(" - ")[1]
58
+ artist_and_song[1]
59
59
  end
60
+
61
+ private
62
+ def artist_and_song
63
+ @artist_and_song ||= now_playing.split(" - ", 2)
64
+ end
60
65
  end
61
66
 
62
67
  include QuickAccess
@@ -1,4 +1,4 @@
1
- class Shoutout
1
+ module Shoutout
2
2
  module QuickAccess
3
3
  def self.included(base)
4
4
  base.extend(ClassMethods)
@@ -0,0 +1,171 @@
1
+ module Shoutout
2
+ class Stream
3
+ include QuickAccess
4
+
5
+ attr_reader :url
6
+
7
+ class << self
8
+ def open(url)
9
+ stream = new(url)
10
+
11
+ begin
12
+ stream.connect
13
+ yield stream
14
+ ensure
15
+ stream.disconnect
16
+ end
17
+ end
18
+
19
+ def metadata(url)
20
+ new(url).metadata
21
+ end
22
+ end
23
+
24
+ def initialize(url)
25
+ @url = url
26
+ end
27
+
28
+ def connected?
29
+ @connected
30
+ end
31
+
32
+ def connect
33
+ return false if @connected
34
+
35
+ uri = URI.parse(@url)
36
+ @socket = TCPSocket.new(uri.host, uri.port)
37
+ @socket.puts "GET #{uri.path} HTTP/1.0"
38
+ @socket.puts "Icy-MetaData: 1"
39
+ @socket.puts
40
+
41
+ # Read status line
42
+ status_line = @socket.gets
43
+ status_code = status_line.match(/\AHTTP\/([0-9]\.[0-9]) ([0-9]{3})/)[2].to_i
44
+
45
+ @connected = true
46
+
47
+ read_headers
48
+
49
+ if status_code >= 300 && status_code < 400 && headers[:location]
50
+ disconnect
51
+
52
+ @url = URI.join(uri, headers[:location]).to_s
53
+
54
+ return connect
55
+ end
56
+
57
+ unless status_code >= 200 && status_code < 300
58
+ disconnect
59
+
60
+ return false
61
+ end
62
+
63
+ unless metadata_interval
64
+ disconnect
65
+
66
+ return false
67
+ end
68
+
69
+ @read_metadata_thread = Thread.new(&method(:read_metadata))
70
+
71
+ true
72
+ end
73
+
74
+ def disconnect
75
+ return false unless @connected
76
+
77
+ @connected = false
78
+
79
+ @socket.close if @socket && !@socket.closed?
80
+ @socket = nil
81
+
82
+ true
83
+ end
84
+
85
+ def listen
86
+ return unless @connected
87
+
88
+ @read_metadata_thread.join
89
+ @last_metadata_change_thread.join if @last_metadata_change_thread
90
+ end
91
+
92
+ def metadata
93
+ return @metadata if defined?(@metadata)
94
+
95
+ original_metadata_change_block = @metadata_change_block
96
+
97
+ received = false
98
+ metadata_change do |new_metadata|
99
+ received = true
100
+ end
101
+
102
+ already_connected = @connected
103
+ connect unless already_connected
104
+
105
+ sleep 0.015 until received
106
+
107
+ disconnect unless already_connected
108
+
109
+ metadata_change(&original_metadata_change_block) if original_metadata_change_block
110
+
111
+ @metadata
112
+ end
113
+
114
+ def metadata_change(&block)
115
+ @metadata_change_block = block
116
+
117
+ report_metadata_change(@metadata) if defined?(@metadata)
118
+
119
+ true
120
+ end
121
+
122
+ private
123
+ def read_headers
124
+ raw_headers = ""
125
+ while line = @socket.gets
126
+ break if line.chomp == ""
127
+ raw_headers << line
128
+ end
129
+ @headers = Headers.parse(raw_headers)
130
+ end
131
+
132
+ def read_metadata
133
+ while @connected
134
+ # Skip audio data
135
+ data = @socket.read(metadata_interval) || raise(EOFError)
136
+
137
+ data = @socket.read(1) || raise(EOFError)
138
+ metadata_length = data.unpack("c")[0] * 16
139
+ next if metadata_length == 0
140
+
141
+ data = @socket.read(metadata_length) || raise(EOFError)
142
+ raw_metadata = data.unpack("A*")[0]
143
+ @metadata = Metadata.parse(raw_metadata)
144
+
145
+ report_metadata_change(@metadata)
146
+ end
147
+ rescue Errno::EBADF, IOError => e
148
+ # Connection lost
149
+ disconnect
150
+ end
151
+
152
+ def report_metadata_change(metadata)
153
+ @last_metadata_change_thread = Thread.new(metadata, @last_metadata_change_thread) do |metadata, last_metadata_change_thread|
154
+ last_metadata_change_thread.join if last_metadata_change_thread
155
+
156
+ @metadata_change_block.call(metadata) if @metadata_change_block
157
+ end
158
+ end
159
+
160
+ def headers
161
+ return @headers if defined?(@headers)
162
+
163
+ # Connected but no headers? I give up.
164
+ return [] if @connected
165
+
166
+ connect && disconnect
167
+
168
+ @headers
169
+ end
170
+ end
171
+ end
@@ -1,4 +1,4 @@
1
- class Shoutout
1
+ module Shoutout
2
2
  module Util
3
3
  def self.camelize(term)
4
4
  term = term.sub(/^[a-z\d]*/) { $&.capitalize }
@@ -1,3 +1,3 @@
1
- class Shoutout
2
- VERSION = "0.0.1"
1
+ module Shoutout
2
+ VERSION = "0.0.2"
3
3
  end
@@ -3,7 +3,7 @@ require "spec_helper"
3
3
  describe Shoutout::QuickAccess do
4
4
 
5
5
  let(:url) { "http://82.201.100.5:8000/radio538" }
6
- subject { Shoutout.new(url) }
6
+ subject { Shoutout::Stream.new(url) }
7
7
 
8
8
  describe "#audio_info" do
9
9
 
@@ -28,15 +28,15 @@ describe Shoutout::QuickAccess do
28
28
  describe ".name" do
29
29
 
30
30
  it "creates a new instance" do
31
- Shoutout.should_receive(:new).with(url).and_return(double("shoutout").as_null_object)
31
+ Shoutout::Stream.should_receive(:new).with(url).and_return(double("stream").as_null_object)
32
32
 
33
- Shoutout.name(url)
33
+ Shoutout::Stream.name(url)
34
34
  end
35
35
 
36
36
  it "calls #name on the opened connection" do
37
- Shoutout.any_instance.should_receive(:name)
37
+ Shoutout::Stream.any_instance.should_receive(:name)
38
38
 
39
- Shoutout.name(url)
39
+ Shoutout::Stream.name(url)
40
40
  end
41
41
  end
42
42
  end
@@ -1,11 +1,10 @@
1
1
  require "spec_helper"
2
2
 
3
- describe Shoutout do
4
-
3
+ describe Shoutout::Stream do
5
4
  let(:url) { "http://82.201.100.5:8000/radio538" }
6
5
  let(:uri) { URI.parse(url) }
7
6
  subject! { described_class.new(url) }
8
- let(:response_data) { File.read(File.expand_path("../fixtures/ok_response", __FILE__)) }
7
+ let(:response_data) { File.read(File.expand_path("../../fixtures/ok_response", __FILE__)) }
9
8
  let(:socket) { FakeTCPSocket.new(response_data) }
10
9
 
11
10
  before(:each) do
@@ -105,7 +104,7 @@ describe Shoutout do
105
104
 
106
105
  context "when the response indicates a redirect" do
107
106
 
108
- let(:redirect_response_data) { File.read(File.expand_path("../fixtures/redirect_response", __FILE__)) }
107
+ let(:redirect_response_data) { File.read(File.expand_path("../../fixtures/redirect_response", __FILE__)) }
109
108
  let(:redirect_socket) { FakeTCPSocket.new(redirect_response_data) }
110
109
 
111
110
  before(:each) do
@@ -138,7 +137,7 @@ describe Shoutout do
138
137
 
139
138
  context "when the response indicates an error" do
140
139
 
141
- let(:response_data) { File.read(File.expand_path("../fixtures/error_response", __FILE__)) }
140
+ let(:response_data) { File.read(File.expand_path("../../fixtures/error_response", __FILE__)) }
142
141
 
143
142
  it "closes the connection" do
144
143
  subject.should_receive(:disconnect).twice.and_call_original
@@ -155,7 +154,7 @@ describe Shoutout do
155
154
 
156
155
  context "when the response is unsupported" do
157
156
 
158
- let(:response_data) { File.read(File.expand_path("../fixtures/unsupported_response", __FILE__)) }
157
+ let(:response_data) { File.read(File.expand_path("../../fixtures/unsupported_response", __FILE__)) }
159
158
 
160
159
  it "closes the connection" do
161
160
  subject.should_receive(:disconnect).twice.and_call_original
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: shoutout
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1
4
+ version: 0.0.2
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -13,7 +13,7 @@ date: 2013-09-08 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: rake
16
- requirement: &70299652575840 !ruby/object:Gem::Requirement
16
+ requirement: &70322661963000 !ruby/object:Gem::Requirement
17
17
  none: false
18
18
  requirements:
19
19
  - - ! '>='
@@ -21,10 +21,10 @@ dependencies:
21
21
  version: '0'
22
22
  type: :development
23
23
  prerelease: false
24
- version_requirements: *70299652575840
24
+ version_requirements: *70322661963000
25
25
  - !ruby/object:Gem::Dependency
26
26
  name: rspec
27
- requirement: &70299652591920 !ruby/object:Gem::Requirement
27
+ requirement: &70322661962580 !ruby/object:Gem::Requirement
28
28
  none: false
29
29
  requirements:
30
30
  - - ! '>='
@@ -32,7 +32,7 @@ dependencies:
32
32
  version: '0'
33
33
  type: :development
34
34
  prerelease: false
35
- version_requirements: *70299652591920
35
+ version_requirements: *70322661962580
36
36
  description: A Ruby library for easily getting metadata from Shoutcast-compatible
37
37
  audio streaming servers
38
38
  email: douwe@selenight.nl
@@ -43,6 +43,7 @@ files:
43
43
  - lib/shoutout/headers.rb
44
44
  - lib/shoutout/metadata.rb
45
45
  - lib/shoutout/quick_access.rb
46
+ - lib/shoutout/stream.rb
46
47
  - lib/shoutout/util.rb
47
48
  - lib/shoutout/version.rb
48
49
  - lib/shoutout.rb
@@ -57,8 +58,8 @@ files:
57
58
  - spec/shoutout/headers_spec.rb
58
59
  - spec/shoutout/metadata_spec.rb
59
60
  - spec/shoutout/quick_access_spec.rb
61
+ - spec/shoutout/stream_spec.rb
60
62
  - spec/shoutout/util_spec.rb
61
- - spec/shoutout_spec.rb
62
63
  - spec/spec_helper.rb
63
64
  - spec/support/fake_tcp_socket.rb
64
65
  homepage: https://github.com/DouweM/shoutout
@@ -76,7 +77,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
76
77
  version: '0'
77
78
  segments:
78
79
  - 0
79
- hash: 848854510650494207
80
+ hash: 401984402802077394
80
81
  required_rubygems_version: !ruby/object:Gem::Requirement
81
82
  none: false
82
83
  requirements:
@@ -85,7 +86,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
85
86
  version: '0'
86
87
  segments:
87
88
  - 0
88
- hash: 848854510650494207
89
+ hash: 401984402802077394
89
90
  requirements: []
90
91
  rubyforge_project:
91
92
  rubygems_version: 1.8.6
@@ -100,7 +101,7 @@ test_files:
100
101
  - spec/shoutout/headers_spec.rb
101
102
  - spec/shoutout/metadata_spec.rb
102
103
  - spec/shoutout/quick_access_spec.rb
104
+ - spec/shoutout/stream_spec.rb
103
105
  - spec/shoutout/util_spec.rb
104
- - spec/shoutout_spec.rb
105
106
  - spec/spec_helper.rb
106
107
  - spec/support/fake_tcp_socket.rb