easy_mplayer 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,54 @@
1
+ class MPlayer
2
+ class Callback # :nodoc:all
3
+ def initialize(callback_options)
4
+ @block = callback_options[:block]
5
+ @type = callback_options[:type]
6
+ @scope = callback_options[:scope]
7
+ end
8
+
9
+ def run!(args)
10
+ unless @block.nil?
11
+ case @type
12
+ when :instance then @block.call(*args)
13
+ when :class then @scope.instance_exec(*args, &@block)
14
+ end
15
+ end
16
+ end
17
+ end
18
+
19
+ class CallbackList < Array # :nodoc:all
20
+ attr_reader :name
21
+
22
+ def initialize(list_name)
23
+ @name = list_name.to_sym
24
+ end
25
+
26
+ def register(opts)
27
+ push Callback.new(opts)
28
+ end
29
+
30
+ def run!(args)
31
+ each do |x|
32
+ x.run!(args)
33
+ end
34
+ end
35
+
36
+ class << self
37
+ def all
38
+ @all ||= Hash.new
39
+ end
40
+
41
+ def find(name)
42
+ all[name.to_sym] ||= new(name)
43
+ end
44
+
45
+ def register(opts)
46
+ find(opts[:name]).register(opts)
47
+ end
48
+
49
+ def run!(name, args)
50
+ find(name).run!(args)
51
+ end
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,83 @@
1
+ class MPlayer
2
+ class Command # :nodoc:all
3
+ class << self
4
+ def cmdlist_raw
5
+ @cmdlist_raw ||= `mplayer -input cmdlist`.split(/\n/)
6
+ end
7
+
8
+ def list
9
+ @list ||= returning Hash.new do |hsh|
10
+ cmdlist_raw.map do |line|
11
+ cmd, *opts = line.split(/\s+/)
12
+ hsh[cmd.to_sym] = new(cmd, opts)
13
+ end
14
+ end
15
+ end
16
+
17
+ def find(name)
18
+ list[name.to_sym]
19
+ end
20
+
21
+ def validate!(args)
22
+ cmd = args.shift
23
+ obj = find(cmd)
24
+ raise BadCallName.new(cmd, args) unless obj
25
+ obj.validate!(args)
26
+ end
27
+ end
28
+
29
+ attr_reader :cmd, :names, :opts, :max, :min
30
+
31
+ def initialize(command_name, opt_list)
32
+ @cmd = command_name
33
+ @min = 0
34
+ @max = opt_list.length
35
+ @names = opt_list
36
+ @opts = opt_list.map do |opt|
37
+ @min += 1 if opt[0,1] != '['
38
+ case opt
39
+ when 'Integer', '[Integer]' then :int
40
+ when 'Float', '[Float]' then :float
41
+ when 'String', '[String]' then :string
42
+ else raise "Unknown cmd option type: #{opt}"
43
+ end
44
+ end
45
+ end
46
+
47
+ def usage
48
+ "#{cmd}(" + names.join(", ") + ")"
49
+ end
50
+
51
+ def to_s
52
+ usage
53
+ end
54
+
55
+ def inspect
56
+ "#<#{self.class} \"#{usage}\">"
57
+ end
58
+
59
+ def convert_arg_type(val, type)
60
+ begin
61
+ case type
62
+ when :int then Integer(val)
63
+ when :float then Float(val)
64
+ when :string then val.to_s
65
+ end
66
+ rescue
67
+ nil
68
+ end
69
+ end
70
+
71
+ def validate!(args)
72
+ len = args.length
73
+ raise BadCallArgs.new(self, args, "not enough args") if len < min
74
+ raise BadCallArgs.new(self, args, "too many args") if len > max
75
+ returning Array.new do |new_args|
76
+ args.each_with_index do |x,i|
77
+ new_args.push convert_arg_type(x, opts[i]) or
78
+ raise BadCallArgs.new(self, args, "type mismatch")
79
+ end
80
+ end
81
+ end
82
+ end
83
+ end
@@ -0,0 +1,106 @@
1
+ class MPlayer
2
+ module Error # :nodoc:all
3
+ # all errors thrown form this library will be of this type
4
+ class MPlayerError < RuntimeError
5
+ end
6
+
7
+ class StartupError < MPlayerError
8
+ attr_reader :path
9
+
10
+ def to_s
11
+ str = "Missing startup requirement!\n"
12
+ str += "File \"#{path}\" does not exist!\n" unless File.exists?(path)
13
+ end
14
+
15
+ def initialize(path)
16
+ @path = path
17
+ super(to_s)
18
+ end
19
+ end
20
+
21
+ class NoPlayerFound < StartupError
22
+ def to_s
23
+ str = super
24
+ str += "File \"#{path}\" is not executable!\n" unless File.executable?(path)
25
+ end
26
+ end
27
+
28
+ class NoTargetPath < StartupError
29
+ def to_s
30
+ str = super
31
+ str += "file \"#{path}\" is not readable!\n" unless File.readable?(path)
32
+ end
33
+ end
34
+
35
+ # some unknown error having to do with the streams/threads that
36
+ # connect us to the mplayer process
37
+ class BadStream < MPlayerError
38
+ end
39
+
40
+ # tried to change to a different level of output that doesn't exist
41
+ class BadMsgType < MPlayerError
42
+ attr_reader :badtype
43
+
44
+ def valid_types # :nodoc:
45
+ DEBUG_MESSAGE_TYPES.keys.inspect
46
+ end
47
+
48
+ def to_s # :nodoc:
49
+ "Bad debug message type \"#{badtype}\"\nValid types " + valid_types
50
+ end
51
+
52
+ def initialize(type)
53
+ @badtype = type
54
+ super(to_s)
55
+ end
56
+ end
57
+
58
+ # an error in sending a command to mplayer over its slave-mode API
59
+ class BadCall < MPlayerError
60
+ attr_reader :cmd, :args
61
+
62
+ # a type-prototype of how we attempted the mplayer API call
63
+ def called_as
64
+ "#{cmd}(" + args.map do |x|
65
+ x.class
66
+ end.join(", ") + ")"
67
+ end
68
+
69
+ def to_s
70
+ "\nBad MPlayer call: #{called_as}"
71
+ end
72
+
73
+ def initialize(command, called_args)
74
+ @cmd = command
75
+ @args = called_args
76
+ super(to_s)
77
+ end
78
+ end
79
+
80
+ # tried to pass a slave-mode command to mplayer, but the call didn't
81
+ # match the API prototype mplayer itself provided
82
+ class BadCallArgs < BadCall
83
+ attr_reader :msg, :usage
84
+
85
+ def to_s # :nodoc:
86
+ super + " - #{msg}\nusage: #{usage}"
87
+ end
88
+
89
+
90
+ def initialize(command, called_args, message)
91
+ @msg = message
92
+ @usage = command.usage
93
+ super(command.cmd, called_args)
94
+ end
95
+ end
96
+
97
+ # not a valid command name
98
+ class BadCallName < BadCall
99
+ def to_s
100
+ super + "\nNo such command \"#{cmd.inspect}\""
101
+ end
102
+ end
103
+ end
104
+
105
+ include Error
106
+ end
@@ -0,0 +1,91 @@
1
+ class MPlayer
2
+ # a blocking version of #play for trivial uses. It returns only
3
+ # after the mplayer process finally terminates itself
4
+ def play_to_end
5
+ play
6
+ sleep 1 while running?
7
+ end
8
+
9
+ # spawn the mplayer process, which also starts the media playing. It
10
+ # is requires that #opts[:path] point to a valid media file
11
+ def play
12
+ stop if running?
13
+
14
+ info "PLAY: #{opts[:path]}"
15
+ worker.startup!
16
+ end
17
+
18
+ # kill off the mplayer process. This invalidates any running media,
19
+ # though it can be restarted again with another call to #play
20
+ def stop
21
+ info "STOP!"
22
+ @worker.shutdown! if @worker
23
+ end
24
+
25
+ # pause playback if we are running
26
+ def pause
27
+ return if paused?
28
+ info "PAUSE!"
29
+ send_command :pause
30
+ @paused = true
31
+ callback! :pause, true
32
+ end
33
+
34
+ # opposite of #pause
35
+ def unpause
36
+ return unless paused?
37
+ info "UNPAUSE!"
38
+ send_command :pause
39
+ @paused = false
40
+ callback! :unpause, false
41
+ end
42
+
43
+ # use this instead of #pause or #unpause, and the flag will be
44
+ # toggled with each call
45
+ def pause_or_unpause
46
+ paused? ? unpause : pause
47
+ end
48
+
49
+ # Seek to an absolute position in a file, by percent of the total size.
50
+ # requires a float argument, that is <tt>(0.0 <= percent <= 100.0)</tt>
51
+ def seek_to_percent(percent)
52
+ return if percent.to_i == @stats[:position]
53
+ percent = percent.to_f
54
+ percent = 0.0 if percent < 0
55
+ percent = 100.0 if percent > 100
56
+ info "SEEK TO: #{percent}%"
57
+ send_command :seek, percent, 1
58
+ end
59
+
60
+ # seek to an absolute position in a file, by seconds. requires a
61
+ # float between 0.0 and the length (in seconds) of the file being played.
62
+ def seek_to_time(seconds)
63
+ info "SEEK TO: #{seconds} seconds"
64
+ send_command :seek, seconds, 1
65
+ end
66
+
67
+ # seek by a relative amount, in seconds. requires a float. Negative
68
+ # values rewind to a previous point.
69
+ def seek_by(amount)
70
+ info "SEEK BY: #{amount}"
71
+ send_command :seek, amount, 0
72
+ end
73
+
74
+ # seek forward a given number of seconds, or
75
+ # <tt>opts[:seek_size]</tt> seconds by default
76
+ def seek_forward(amount = opts[:seek_size])
77
+ seek_by(amount)
78
+ end
79
+
80
+ # seek backwards (rewind) by a given number of seconds, or
81
+ # <tt>opts[:seek_size]</tt> seconds by default. Note that a
82
+ # /positive/ value here rewinds!
83
+ def seek_reverse(amount = opts[:seek_size])
84
+ seek_by(-amount)
85
+ end
86
+
87
+ # reset back to the beginning of the file
88
+ def seek_start
89
+ seek_to_percent(0.0)
90
+ end
91
+ end
@@ -0,0 +1,180 @@
1
+ class MPlayer
2
+ # all of these can be overridden by passing them to #new
3
+ DEFAULT_OPTS = {
4
+ :program => '/usr/bin/mplayer',
5
+ :message_style => :info,
6
+ :seek_size => 10,
7
+ :select_wait_time => 1,
8
+ :thread_safe_callbacks => true
9
+ }
10
+
11
+ # the color_debug_message parameter sets we can switch
12
+ # between, for convenience. (flags for ColorDebugMessages)
13
+ DEBUG_MESSAGE_TYPES = {
14
+ :quiet => {
15
+ },
16
+ :error_only => {
17
+ :warn => true
18
+ },
19
+ :info => {
20
+ :warn => true,
21
+ :info => true
22
+ },
23
+ :debug => {
24
+ :warn => true,
25
+ :info => true,
26
+ :debug => true,
27
+ :class_only => false
28
+ }
29
+ }
30
+
31
+ attr_reader :callbacks, :stats, :opts
32
+
33
+ class << self
34
+ def class_callbacks # :nodoc:
35
+ @@class_callbacks ||= Array.new
36
+ end
37
+
38
+ # register a block with the named callback(s). This is the same
39
+ # as the instance-method, generally, but it uses instance_exec to
40
+ # give the block the same scope (the MPlayer instance)
41
+ def callback(*names, &block)
42
+ names.each do |name|
43
+ class_callbacks << {
44
+ :name => name,
45
+ :type => :class,
46
+ :block => block
47
+ }
48
+ end
49
+ end
50
+ end
51
+
52
+ # create a new object. The option hash must have, at a minimum, a
53
+ # :path reference to the file we want to play, or an exception will
54
+ # be raised.
55
+ def initialize(new_opts=Hash.new)
56
+ @opts = DEFAULT_OPTS.merge(new_opts)
57
+ set_message_style opts[:message_style]
58
+
59
+ unless File.executable?(@opts[:program])
60
+ raise NoPlayerFound.new(@opts[:program])
61
+ end
62
+ unless @opts[:path] and File.readable?(new_opts[:path])
63
+ raise NoTargetPath.new(@opts[:path])
64
+ end
65
+
66
+ @stats = Hash.new
67
+ @callbacks = Hash.new
68
+ @worker = nil
69
+
70
+ self.class.class_callbacks.each do |opts|
71
+ opts[:scope] = self
72
+ CallbackList.register opts
73
+ end
74
+ end
75
+
76
+ callback :update_stat do |*args|
77
+ update_stat *args
78
+ end
79
+
80
+ callback :file_error do
81
+ warn "File error!"
82
+ stop!
83
+ end
84
+
85
+ callback :played_time do |played_time|
86
+ update_stat :played_seconds, played_time.to_i
87
+ total = stats[:total_time]
88
+ if total and total != 0.0
89
+ pos = (100 * played_time / total)
90
+ update_stat :raw_position, pos
91
+ update_stat :position, pos.to_i
92
+ end
93
+ end
94
+
95
+ callback :startup do
96
+ callback! :play
97
+ end
98
+
99
+ callback :shutdown do
100
+ @worker = nil
101
+ callback! :stop
102
+ end
103
+
104
+ # can be any of:
105
+ # :quiet Supperss all output!
106
+ # :error_only Off except for errors
107
+ # :info Also show information messages
108
+ # :debug Heavy debug output (spammy)
109
+ def set_message_style(type)
110
+ hsh = DEBUG_MESSAGE_TYPES[type.to_sym] or
111
+ raise BadMsgType.new(type.inspect)
112
+ hsh = hsh.dup
113
+ hsh[:debug] ||= false
114
+ hsh[:info] ||= false
115
+ hsh[:warn] ||= false
116
+ hsh[:class_only] ||= true
117
+ hsh[:prefix_only] ||= false
118
+ ColorDebugMessages.global_debug_flags(hsh)
119
+ opts[:message_style] = type
120
+ end
121
+
122
+ def inspect # :nodoc:
123
+ vals = [['running', running?],
124
+ ['paused', paused?]]
125
+ vals << ['info', stats.inspect] if running?
126
+ "#<#{self.class} " + vals.map do |x|
127
+ x.first + '=' + x.last.to_s
128
+ end.join(' ') + '>'
129
+ end
130
+
131
+ # call an entire callback chain, passing in a list of args
132
+ def callback!(name, *args) # :nodoc:
133
+ #puts "CALLBACK! #{name.inspect} #{args.inspect}"
134
+ CallbackList.run!(name, args)
135
+ end
136
+
137
+ # register a function into each of the named callback chains
138
+ def callback(*names, &block)
139
+ names.each do |name|
140
+ CallbackList.register :name => name, :block => block, :type => :instance
141
+ end
142
+ end
143
+
144
+ # true if we are running, yet the media has stopped
145
+ def paused?
146
+ @paused
147
+ end
148
+
149
+ # true if the mplayer process is active and running
150
+ def running?
151
+ !!@worker and @worker.ok?
152
+ end
153
+
154
+ # pipe a command to mplayer via slave mode
155
+ def send_command(*args)
156
+ worker.send_command(*args)
157
+ end
158
+
159
+ def worker # :nodoc:
160
+ create_worker if @worker.nil?
161
+ @worker
162
+ end
163
+
164
+ def create_worker # :nodoc:
165
+ callback! :creating_worker
166
+ @worker = Worker.new(self)
167
+ @stats = Hash.new
168
+ @paused = false
169
+ callback! :worker_running
170
+ end
171
+
172
+ def update_stat(name, newval) # :nodoc:
173
+ name = name.to_sym
174
+ if @stats[name] != newval
175
+ debug "STATS[:#{name}] -> #{newval.inspect}"
176
+ @stats[name] = newval
177
+ callback! name, newval
178
+ end
179
+ end
180
+ end