radiodan 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/.gitignore +1 -4
- data/Gemfile.lock +47 -1
- data/Guardfile +9 -0
- data/Rakefile +6 -0
- data/TODO +15 -3
- data/doc/state.markdown +44 -0
- data/lib/radiodan.rb +5 -5
- data/lib/radiodan/adapter/mpd.rb +85 -0
- data/lib/radiodan/adapter/mpd/ack.rb +11 -0
- data/lib/radiodan/adapter/mpd/connection.rb +42 -0
- data/lib/radiodan/adapter/mpd/playlist_parser.rb +36 -0
- data/lib/radiodan/adapter/mpd/response.rb +99 -0
- data/lib/radiodan/builder.rb +12 -13
- data/lib/{em_additions.rb → radiodan/em_additions.rb} +0 -0
- data/lib/radiodan/logging.rb +3 -3
- data/lib/radiodan/middleware/panic.rb +4 -4
- data/lib/radiodan/player.rb +35 -21
- data/lib/radiodan/playlist.rb +112 -0
- data/lib/radiodan/playlist_sync.rb +47 -0
- data/lib/radiodan/track.rb +38 -0
- data/lib/radiodan/version.rb +1 -1
- data/radiodan.gemspec +10 -5
- data/spec/lib/builder_spec.rb +57 -0
- data/spec/lib/event_binding_spec.rb +8 -0
- data/spec/lib/logging_spec.rb +8 -0
- data/spec/lib/player_spec.rb +78 -0
- data/spec/lib/playlist_parser_spec.rb +23 -0
- data/spec/lib/playlist_spec.rb +147 -0
- data/spec/lib/playlist_sync_spec.rb +97 -0
- data/spec/lib/track_spec.rb +41 -0
- data/spec/spec_helper.rb +15 -0
- metadata +118 -8
- data/lib/radiodan/content.rb +0 -18
- data/lib/radiodan/middleware/mpd.rb +0 -145
- data/lib/radiodan/state.rb +0 -24
data/lib/radiodan/content.rb
DELETED
@@ -1,18 +0,0 @@
|
|
1
|
-
=begin
|
2
|
-
The Content object defines the source of audio
|
3
|
-
for the player.
|
4
|
-
|
5
|
-
We cant return the name of a playlist from mpd: we might as well build it up
|
6
|
-
in memory.
|
7
|
-
|
8
|
-
Attributes:
|
9
|
-
type: playlist, a URL, or a single file
|
10
|
-
location: URI for the content
|
11
|
-
mode: sequential, random, resume
|
12
|
-
song_number: song to resume play
|
13
|
-
play_from: position to resume from (seconds)
|
14
|
-
=end
|
15
|
-
|
16
|
-
class Radiodan
|
17
|
-
class Content < Struct.new(:type, :name, :files, :mode, :song_number, :play_from); end
|
18
|
-
end
|
@@ -1,145 +0,0 @@
|
|
1
|
-
require 'em-simple_telnet'
|
2
|
-
require 'ostruct'
|
3
|
-
|
4
|
-
class Radiodan
|
5
|
-
class MPD
|
6
|
-
include Logging
|
7
|
-
COMMANDS = %w{stop pause clear play}
|
8
|
-
Ack = Struct.new(:error_id, :position, :command, :description)
|
9
|
-
class AckError < Exception; end
|
10
|
-
|
11
|
-
attr_reader :player
|
12
|
-
|
13
|
-
def initialize(options={})
|
14
|
-
@port = options[:port] || 6600
|
15
|
-
@host = options[:host] || 'localhost'
|
16
|
-
end
|
17
|
-
|
18
|
-
def player=(player)
|
19
|
-
@player = player
|
20
|
-
|
21
|
-
# register typical player commands
|
22
|
-
COMMANDS.each do |command|
|
23
|
-
@player.register_event command do |data|
|
24
|
-
if data
|
25
|
-
self.send(command, data)
|
26
|
-
else
|
27
|
-
self.send(command)
|
28
|
-
end
|
29
|
-
end
|
30
|
-
end
|
31
|
-
|
32
|
-
# register new playlist events
|
33
|
-
@player.register_event :playlist do |playlist|
|
34
|
-
self.playlist = playlist
|
35
|
-
end
|
36
|
-
end
|
37
|
-
|
38
|
-
def playlist=(playlist)
|
39
|
-
# get rid of current playlist, stop playback
|
40
|
-
clear
|
41
|
-
|
42
|
-
if enqueue playlist
|
43
|
-
play playlist.song_number
|
44
|
-
else
|
45
|
-
raise "Cannot load playlist #{playlist}"
|
46
|
-
end
|
47
|
-
end
|
48
|
-
|
49
|
-
def enqueue(playlist)
|
50
|
-
playlist.files.each do |file|
|
51
|
-
cmd(%Q{add "#{file}"})
|
52
|
-
end
|
53
|
-
end
|
54
|
-
|
55
|
-
def play(song_number=nil)
|
56
|
-
cmd("play #{song_number}")
|
57
|
-
end
|
58
|
-
|
59
|
-
def state
|
60
|
-
@state = cmd("status")
|
61
|
-
playlist = cmd("playlistinfo")
|
62
|
-
|
63
|
-
unless playlist == true
|
64
|
-
@state.merge!(playlist)
|
65
|
-
end
|
66
|
-
|
67
|
-
OpenStruct.new(@state)
|
68
|
-
end
|
69
|
-
|
70
|
-
def respond_to?(method)
|
71
|
-
if COMMANDS.include?(method.to_s)
|
72
|
-
true
|
73
|
-
else
|
74
|
-
super
|
75
|
-
end
|
76
|
-
end
|
77
|
-
|
78
|
-
private
|
79
|
-
def method_missing(method, *args, &block)
|
80
|
-
if COMMANDS.include?(method.to_s)
|
81
|
-
cmd(method.to_s, *args, &block)
|
82
|
-
else
|
83
|
-
super
|
84
|
-
end
|
85
|
-
end
|
86
|
-
|
87
|
-
def cmd(command, options={})
|
88
|
-
options = {match: /^(OK|ACK)/}.merge(options)
|
89
|
-
response = false
|
90
|
-
|
91
|
-
connect do |c|
|
92
|
-
begin
|
93
|
-
logger.debug command
|
94
|
-
response = c.cmd(command, options).strip
|
95
|
-
rescue Exception => e
|
96
|
-
logger.error "#{command}, #{options} - #{e.to_s}"
|
97
|
-
raise
|
98
|
-
end
|
99
|
-
end
|
100
|
-
|
101
|
-
format_response(response)
|
102
|
-
end
|
103
|
-
|
104
|
-
def connect(&blk)
|
105
|
-
EM::P::SimpleTelnet.new(host: @host, port: @port, prompt: /^(OK|ACK)(.*)$/) do |host|
|
106
|
-
host.waitfor(/^OK MPD \d{1,2}\.\d{1,2}\.\d{1,2}$/)
|
107
|
-
yield(host)
|
108
|
-
end
|
109
|
-
end
|
110
|
-
|
111
|
-
# returns true or hash of values
|
112
|
-
def format_response(response)
|
113
|
-
case
|
114
|
-
when response == 'OK'
|
115
|
-
true
|
116
|
-
when response =~ /^ACK/
|
117
|
-
ack = format_ack(response)
|
118
|
-
logger.warn ack
|
119
|
-
|
120
|
-
ack
|
121
|
-
when response.split.size == 1
|
122
|
-
# set value -> value
|
123
|
-
Hash[*(response.split.*2)]
|
124
|
-
else
|
125
|
-
response = response.split("\n")
|
126
|
-
# remove first response: "OK"
|
127
|
-
response.pop
|
128
|
-
|
129
|
-
split_response = response.collect do |r|
|
130
|
-
split = r.split(':')
|
131
|
-
key = split.shift
|
132
|
-
value = split.join(':')
|
133
|
-
[key.strip, value.strip]
|
134
|
-
end.flatten
|
135
|
-
|
136
|
-
Hash[*split_response]
|
137
|
-
end
|
138
|
-
end
|
139
|
-
|
140
|
-
def format_ack(ack)
|
141
|
-
matches = /ACK \[(\d)+@(\d)+\] \{(.*)\} (.*)/.match(ack)
|
142
|
-
AckError.new(*matches[1..-1].join)
|
143
|
-
end
|
144
|
-
end
|
145
|
-
end
|
data/lib/radiodan/state.rb
DELETED
@@ -1,24 +0,0 @@
|
|
1
|
-
=begin
|
2
|
-
The State object determines what the player
|
3
|
-
should be doing at any given moment.
|
4
|
-
|
5
|
-
Attributes:
|
6
|
-
playback: boolean
|
7
|
-
content: content object to be playing
|
8
|
-
|
9
|
-
It can be updated by any of the stimulus objects.
|
10
|
-
=end
|
11
|
-
|
12
|
-
require 'radiodan/content'
|
13
|
-
|
14
|
-
class Radiodan
|
15
|
-
class State
|
16
|
-
include Logging
|
17
|
-
attr_reader :playback, :content
|
18
|
-
|
19
|
-
def initialize(config={})
|
20
|
-
@playback = config[:playback] || 'play'
|
21
|
-
@content = config[:content]
|
22
|
-
end
|
23
|
-
end
|
24
|
-
end
|