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.
@@ -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