easy_mplayer 1.0.0
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/.document +5 -0
- data/.gitignore +2 -0
- data/LICENSE +20 -0
- data/README.rdoc +17 -0
- data/Rakefile +29 -0
- data/VERSION +1 -0
- data/easy_mplayer.gemspec +67 -0
- data/examples/basic.rb +66 -0
- data/examples/callbacks.rb +98 -0
- data/examples/inherit_class.rb +98 -0
- data/examples/minimal.rb +15 -0
- data/lib/easy_mplayer.rb +17 -0
- data/lib/easy_mplayer/callback.rb +54 -0
- data/lib/easy_mplayer/commands.rb +83 -0
- data/lib/easy_mplayer/errors.rb +106 -0
- data/lib/easy_mplayer/helpers.rb +91 -0
- data/lib/easy_mplayer/main.rb +180 -0
- data/lib/easy_mplayer/worker.rb +354 -0
- metadata +95 -0
@@ -0,0 +1,354 @@
|
|
1
|
+
class MPlayer
|
2
|
+
class Worker # :nodoc:all
|
3
|
+
include ColorDebugMessages
|
4
|
+
|
5
|
+
class Stream
|
6
|
+
include ColorDebugMessages
|
7
|
+
|
8
|
+
MATCH_STDOUT = { # :nodoc:
|
9
|
+
:version => {
|
10
|
+
:re => /^MPlayer\s(\S+)\s\(C\)/,
|
11
|
+
:stat => [:version]
|
12
|
+
},
|
13
|
+
:server => {
|
14
|
+
:re => /^Connecting to server (\S+)\[(\d+\.\d+\.\d+\.\d+)\]:/,
|
15
|
+
:stat => [:server, :server_ip]
|
16
|
+
},
|
17
|
+
:stream_info => {
|
18
|
+
:re => /^ICY Info: StreamTitle='(.*?)';StreamUrl='(.*?)';/,
|
19
|
+
:stat => [:stream_title, :stream_url]
|
20
|
+
},
|
21
|
+
:update_position => {
|
22
|
+
:re => /^A:\s+(\d+\.\d+)\s+\(\S+\)\s+of\s+(\d+\.\d+)/,
|
23
|
+
:stat => [:played_time, :total_time],
|
24
|
+
},
|
25
|
+
:audio_info => {
|
26
|
+
:re => /^AUDIO: (\d+) Hz, (\d+) ch, (\S+), ([0-9.]+) kbit/,
|
27
|
+
:stat => [:audio_sample_rate, :audio_channels,
|
28
|
+
:audio_format, :audio_data_rate],
|
29
|
+
:call => :audio_stats
|
30
|
+
},
|
31
|
+
:video_info => {
|
32
|
+
:re => /^VIDEO:\s+\[(\S{4})\]\s+(\d+)x(\d+)\s+(\d+)bpp\s+(\d+\.\d+)\s+fps/,
|
33
|
+
:stat => [:video_fourcc, :video_x_size, :video_y_size,
|
34
|
+
:video_bpp, :video_fps],
|
35
|
+
:call => :video_stats
|
36
|
+
},
|
37
|
+
:video_decoder => {
|
38
|
+
:re => /^Opening video decoder: \[(\S+)\]/,
|
39
|
+
:stat => [:video_decoder]
|
40
|
+
},
|
41
|
+
:audio_decoder => {
|
42
|
+
:re => /^Opening audio decoder: \[(\S+)\]/,
|
43
|
+
:stat => [:audio_decoder]
|
44
|
+
},
|
45
|
+
:video_codec => {
|
46
|
+
:re => /^Selected video codec: \[(\S+)\]/,
|
47
|
+
:stat => [:video_codec]
|
48
|
+
},
|
49
|
+
:audio_codec => {
|
50
|
+
:re => /^Selected audio codec: \[(\S+)\]/,
|
51
|
+
:stat => [:audio_codec]
|
52
|
+
}
|
53
|
+
}
|
54
|
+
|
55
|
+
MATCH_STDERR = { # :nodoc:
|
56
|
+
:file_not_found => {
|
57
|
+
:re => /^File not found: /,
|
58
|
+
:call => :file_error
|
59
|
+
}
|
60
|
+
}
|
61
|
+
|
62
|
+
attr_reader :parent, :type, :io
|
63
|
+
|
64
|
+
def initialize(p, w, stream_type, stream_io)
|
65
|
+
@parent = p
|
66
|
+
@worker = w
|
67
|
+
@type = stream_type
|
68
|
+
@io = stream_io
|
69
|
+
@line = ''
|
70
|
+
@outlist = Array.new
|
71
|
+
@stats = Hash.new
|
72
|
+
@select_wait_time = p.opts[:select_wait_time]
|
73
|
+
@sent_update_position = false
|
74
|
+
end
|
75
|
+
|
76
|
+
def prefix(msg)
|
77
|
+
"STREAM [#{@type}] #{msg}"
|
78
|
+
end
|
79
|
+
|
80
|
+
def debug(msg); super prefix(msg); end
|
81
|
+
def info(msg); super prefix(msg); end
|
82
|
+
def warn(msg); super prefix(msg); end
|
83
|
+
|
84
|
+
def stream_error(type)
|
85
|
+
@worker.flag_stream_error(type)
|
86
|
+
end
|
87
|
+
|
88
|
+
def callback!(name, *args)
|
89
|
+
case name
|
90
|
+
when :update_stat
|
91
|
+
stat = args[0]
|
92
|
+
val = args[1]
|
93
|
+
if @stats[stat] == val
|
94
|
+
return # only propagate changes
|
95
|
+
else
|
96
|
+
@stats[stat] = val
|
97
|
+
end
|
98
|
+
end
|
99
|
+
@worker.queue_callback [name, args]
|
100
|
+
end
|
101
|
+
|
102
|
+
def check_line(patterns, line)
|
103
|
+
patterns.each_pair do |name, pat|
|
104
|
+
if md = pat[:re].match(line)
|
105
|
+
args = md.captures.map do |x|
|
106
|
+
case x
|
107
|
+
when /^\d+$/ then Integer(x)
|
108
|
+
when /^\d+\.\d+$/ then Float(x)
|
109
|
+
else x
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
(pat[:stat] || []).each do |field|
|
114
|
+
callback! :update_stat, field, args.shift
|
115
|
+
end
|
116
|
+
|
117
|
+
callback! pat[:call] if pat[:call]
|
118
|
+
return name
|
119
|
+
end
|
120
|
+
end
|
121
|
+
nil
|
122
|
+
end
|
123
|
+
|
124
|
+
def process_stdout(line)
|
125
|
+
check_line(MATCH_STDOUT, line)
|
126
|
+
end
|
127
|
+
|
128
|
+
def process_stderr(line)
|
129
|
+
if check_line(MATCH_STDERR, line)
|
130
|
+
stream_error(:stderr)
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
def process_line
|
135
|
+
debug "LINE> \"#{@line}\""
|
136
|
+
send "process_#{@type}", @line
|
137
|
+
# callback! @type, @line
|
138
|
+
@line = ''
|
139
|
+
end
|
140
|
+
|
141
|
+
def process_stream
|
142
|
+
result = IO.select([@io], nil, nil, @select_wait_time)
|
143
|
+
return if result.nil? or result.empty?
|
144
|
+
|
145
|
+
c = @io.read(1)
|
146
|
+
return stream_error(:eof) if c.nil?
|
147
|
+
|
148
|
+
@line << c
|
149
|
+
process_line if c == "\n" or c == "\r"
|
150
|
+
end
|
151
|
+
|
152
|
+
def run
|
153
|
+
@thread = Thread.new do
|
154
|
+
@alive = true
|
155
|
+
begin
|
156
|
+
debug "start"
|
157
|
+
process_stream while @alive
|
158
|
+
debug "clean end!"
|
159
|
+
rescue IOError => e
|
160
|
+
if e.to_s =~ /stream closed/
|
161
|
+
debug "stream closed!"
|
162
|
+
else
|
163
|
+
raise BadStream, e.to_s
|
164
|
+
end
|
165
|
+
rescue => e
|
166
|
+
warn "Unexpected error when parsing MPlayer's IO stream!"
|
167
|
+
warn "error was: #{e}"
|
168
|
+
stream_error(:exception)
|
169
|
+
ensure
|
170
|
+
cleanup
|
171
|
+
end
|
172
|
+
end
|
173
|
+
end
|
174
|
+
|
175
|
+
def cleanup
|
176
|
+
@io.close unless @io.closed?
|
177
|
+
end
|
178
|
+
|
179
|
+
def kill
|
180
|
+
@alive = false
|
181
|
+
cleanup
|
182
|
+
end
|
183
|
+
|
184
|
+
def join
|
185
|
+
@thread.join if @thread
|
186
|
+
@thread = nil
|
187
|
+
end
|
188
|
+
end
|
189
|
+
|
190
|
+
attr_reader :parent, :io
|
191
|
+
|
192
|
+
def initialize(p)
|
193
|
+
@parent = p
|
194
|
+
@pid = nil
|
195
|
+
@streams = Array.new
|
196
|
+
@pending = Array.new
|
197
|
+
@mutex = Mutex.new
|
198
|
+
@failed = nil
|
199
|
+
|
200
|
+
@thread_safe_callbacks = @parent.opts[:thread_safe_callbacks]
|
201
|
+
@shutdown_in_progress = false
|
202
|
+
|
203
|
+
begin
|
204
|
+
info "running mplayer >>> #{cmdline}"
|
205
|
+
@io_stdin, @io_stdout, @io_stderr = Open3.popen3(cmdline)
|
206
|
+
|
207
|
+
create_stream(:stdout, @io_stdout)
|
208
|
+
create_stream(:stderr, @io_stderr)
|
209
|
+
send_each_stream :run
|
210
|
+
rescue
|
211
|
+
raise BadStream, "couldn't create streams to mplayer: #{$!}"
|
212
|
+
end
|
213
|
+
|
214
|
+
debug "mplayer threads created!"
|
215
|
+
end
|
216
|
+
|
217
|
+
def cmdline(target = parent.opts[:path])
|
218
|
+
cmd = "#{parent.opts[:program]} -slave "
|
219
|
+
cmd += "-playlist " if target=~ /\.m3u$/
|
220
|
+
cmd += target.to_s
|
221
|
+
end
|
222
|
+
|
223
|
+
def lock!
|
224
|
+
@mutex.synchronize do
|
225
|
+
yield
|
226
|
+
end
|
227
|
+
end
|
228
|
+
|
229
|
+
def queue_callback(args)
|
230
|
+
if @thread_safe_callbacks
|
231
|
+
lock! do
|
232
|
+
@pending.push(args)
|
233
|
+
end
|
234
|
+
else
|
235
|
+
@parent.callback! args.first, *(args.last)
|
236
|
+
end
|
237
|
+
end
|
238
|
+
|
239
|
+
def dispatch_callbacks
|
240
|
+
return unless @thread_safe_callbacks
|
241
|
+
list = nil
|
242
|
+
lock! do
|
243
|
+
list = @pending
|
244
|
+
@pending = Array.new
|
245
|
+
end
|
246
|
+
list.each do |args|
|
247
|
+
@parent.callback! args.first, *(args.last)
|
248
|
+
end
|
249
|
+
end
|
250
|
+
|
251
|
+
def send_to_stdin(str)
|
252
|
+
begin
|
253
|
+
@io_stdin.puts str
|
254
|
+
rescue => e
|
255
|
+
warn "Couldn't write to mplayer's stdin!"
|
256
|
+
warn "error was: #{e}"
|
257
|
+
shutdown!
|
258
|
+
end
|
259
|
+
end
|
260
|
+
|
261
|
+
def send_command(*args)
|
262
|
+
cmd = args.join(' ')
|
263
|
+
if @io_stdin.nil?
|
264
|
+
debug "cannot send \"#{cmd}\" - stdin closed"
|
265
|
+
else
|
266
|
+
Command.validate! args
|
267
|
+
send_to_stdin cmd
|
268
|
+
end
|
269
|
+
end
|
270
|
+
|
271
|
+
def create_stream(type, io)
|
272
|
+
returning Stream.new(parent, self, type, io) do |stream|
|
273
|
+
@streams.push(stream)
|
274
|
+
end
|
275
|
+
end
|
276
|
+
|
277
|
+
def send_each_stream(*args)
|
278
|
+
cmd = args.shift
|
279
|
+
cmd_str = "#{cmd.to_s}(#{args.join(', ')})"
|
280
|
+
if @streams.length < 1
|
281
|
+
warn "No streams available for \"cmd_str\""
|
282
|
+
else
|
283
|
+
debug "Sending each stream: #{cmd_str}"
|
284
|
+
@streams.each do |stream|
|
285
|
+
if stream.respond_to? cmd
|
286
|
+
stream.send(cmd, *args)
|
287
|
+
else
|
288
|
+
raise BadStream, "stream command not valid: #{cmd_str}"
|
289
|
+
end
|
290
|
+
end
|
291
|
+
end
|
292
|
+
end
|
293
|
+
|
294
|
+
def flag_stream_error(type)
|
295
|
+
lock! do
|
296
|
+
@failed = type if @failed.nil?
|
297
|
+
end
|
298
|
+
end
|
299
|
+
|
300
|
+
def ok?
|
301
|
+
dispatch_callbacks
|
302
|
+
err = nil
|
303
|
+
lock! do
|
304
|
+
err = @failed
|
305
|
+
end
|
306
|
+
return true if err.nil? and @streams.length > 0
|
307
|
+
|
308
|
+
case err
|
309
|
+
when :eof
|
310
|
+
info "MPlayer process shut itself down!"
|
311
|
+
close_stdin
|
312
|
+
when :stderr
|
313
|
+
warn "Caugh error message on MPlayer's STDERR"
|
314
|
+
when :exception
|
315
|
+
warn "Unexpected IO stream failure!"
|
316
|
+
end
|
317
|
+
shutdown!
|
318
|
+
end
|
319
|
+
|
320
|
+
def close_stdin
|
321
|
+
@io_stdin.close if @io_stdin and !@io_stdin.closed?
|
322
|
+
@io_stdin = nil
|
323
|
+
end
|
324
|
+
|
325
|
+
def startup!
|
326
|
+
@parent.callback! :startup
|
327
|
+
end
|
328
|
+
|
329
|
+
def shutdown!
|
330
|
+
if @shutdown_in_progress
|
331
|
+
debug "shutdown already in progress, skipping shutdown call..."
|
332
|
+
return
|
333
|
+
end
|
334
|
+
|
335
|
+
@parent.callback! :pre_shutdown
|
336
|
+
|
337
|
+
# give mplayer it's close signal
|
338
|
+
debug "Sending QUIT to mplayer..."
|
339
|
+
@shutdown_in_progress = true
|
340
|
+
send_command :quit
|
341
|
+
|
342
|
+
# close our side of the IO
|
343
|
+
close_stdin
|
344
|
+
|
345
|
+
# then wait for the threads to cleanup after themselves
|
346
|
+
info "Waiting for worker thread to exit..."
|
347
|
+
send_each_stream :kill
|
348
|
+
send_each_stream :join
|
349
|
+
@streams = Array.new
|
350
|
+
info "MPlayer process cleaned up!"
|
351
|
+
@parent.callback! :shutdown
|
352
|
+
end
|
353
|
+
end
|
354
|
+
end
|
metadata
ADDED
@@ -0,0 +1,95 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: easy_mplayer
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Brent Sanders
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2010-01-05 00:00:00 -08:00
|
13
|
+
default_executable:
|
14
|
+
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: color_debug_messages
|
17
|
+
type: :runtime
|
18
|
+
version_requirement:
|
19
|
+
version_requirements: !ruby/object:Gem::Requirement
|
20
|
+
requirements:
|
21
|
+
- - ">="
|
22
|
+
- !ruby/object:Gem::Version
|
23
|
+
version: 1.1.2
|
24
|
+
version:
|
25
|
+
- !ruby/object:Gem::Dependency
|
26
|
+
name: facets
|
27
|
+
type: :runtime
|
28
|
+
version_requirement:
|
29
|
+
version_requirements: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: 2.8.0
|
34
|
+
version:
|
35
|
+
description: A wrapper to manage mplayer, that supports callbacks to easyily support event-driven GUIs
|
36
|
+
email: gem-mplayer@thoughtnoise.net
|
37
|
+
executables: []
|
38
|
+
|
39
|
+
extensions: []
|
40
|
+
|
41
|
+
extra_rdoc_files:
|
42
|
+
- LICENSE
|
43
|
+
- README.rdoc
|
44
|
+
files:
|
45
|
+
- .document
|
46
|
+
- .gitignore
|
47
|
+
- LICENSE
|
48
|
+
- README.rdoc
|
49
|
+
- Rakefile
|
50
|
+
- VERSION
|
51
|
+
- easy_mplayer.gemspec
|
52
|
+
- examples/basic.rb
|
53
|
+
- examples/callbacks.rb
|
54
|
+
- examples/inherit_class.rb
|
55
|
+
- examples/minimal.rb
|
56
|
+
- lib/easy_mplayer.rb
|
57
|
+
- lib/easy_mplayer/callback.rb
|
58
|
+
- lib/easy_mplayer/commands.rb
|
59
|
+
- lib/easy_mplayer/errors.rb
|
60
|
+
- lib/easy_mplayer/helpers.rb
|
61
|
+
- lib/easy_mplayer/main.rb
|
62
|
+
- lib/easy_mplayer/worker.rb
|
63
|
+
has_rdoc: true
|
64
|
+
homepage: http://github.com/pdkl95/easy_mplayer
|
65
|
+
licenses: []
|
66
|
+
|
67
|
+
post_install_message:
|
68
|
+
rdoc_options:
|
69
|
+
- --charset=UTF-8
|
70
|
+
require_paths:
|
71
|
+
- lib
|
72
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
73
|
+
requirements:
|
74
|
+
- - ">="
|
75
|
+
- !ruby/object:Gem::Version
|
76
|
+
version: "0"
|
77
|
+
version:
|
78
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - ">="
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: "0"
|
83
|
+
version:
|
84
|
+
requirements: []
|
85
|
+
|
86
|
+
rubyforge_project:
|
87
|
+
rubygems_version: 1.3.5
|
88
|
+
signing_key:
|
89
|
+
specification_version: 3
|
90
|
+
summary: Wrapper to launch and control MPlayer
|
91
|
+
test_files:
|
92
|
+
- examples/minimal.rb
|
93
|
+
- examples/basic.rb
|
94
|
+
- examples/inherit_class.rb
|
95
|
+
- examples/callbacks.rb
|