rumplayer 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,43 @@
1
+ == RuMplayer
2
+
3
+ Given you and (at least) one of your buddies want to watch movies together, but
4
+ on different devices which may be located (very) far from each other.
5
+
6
+ With RuMplayer you can watch them simultaneously without having to leave your comfy home.
7
+
8
+ == Requirements
9
+
10
+ * a public server where you can install RuMplayer an run the server.
11
+ * all your buddies must have a copy of the movie to watch.
12
+
13
+
14
+ == Installation
15
+
16
+ (sudo) gem install gemcutter
17
+ (sudo) gem tumble
18
+ (sudo) gem install rumplayer
19
+
20
+ rumplayer
21
+ CTRL+C
22
+
23
+ Now, edit ~/.rumplayer.yml and add
24
+
25
+ == Usage
26
+
27
+ On the Server run:
28
+
29
+ rumplayer -v --server
30
+
31
+
32
+ All your buddies wait for the movie to start by running
33
+
34
+ cd /wherever/the/movie/is/located/
35
+ rumplayer
36
+
37
+ You start the movie by supplying the filename
38
+
39
+ cd /my/location/of/the/movie
40
+ rumplayer movie.mkv
41
+
42
+ Now all your buddies can pause, seek or stop the movie.
43
+ All these actions get distributed to all watchers.
@@ -0,0 +1,34 @@
1
+ begin
2
+ require 'rubygems'
3
+ require 'jeweler'
4
+ Jeweler::Tasks.new do |gemspec|
5
+ gemspec.name = %q{rumplayer}
6
+ gemspec.summary = %q{watch common movies simultaneously}
7
+ gemspec.description = %q{
8
+ RumPlayer allows you and your buddies to watch movies simultaneously
9
+ without sharing the room or continent. All participans must have a copy
10
+ of the movie to watch.
11
+ }
12
+ gemspec.email = %q{niklas+rumplayer@lanpartei.de}
13
+ gemspec.homepage = %q{http://github.com/niklas/rumplayer/}
14
+ gemspec.authors = ["Niklas Hofer"]
15
+ gemspec.executables = %w{rumplayer}
16
+
17
+ gemspec.has_rdoc = false
18
+ gemspec.files = %w{
19
+ README.mardown
20
+ Rakefile
21
+ rumplayer
22
+ lib/rumplayer.rb
23
+ lib/rumplayer/client.rb
24
+ lib/rumplayer/server.rb
25
+ lib/rumplayer/mplayer.rb
26
+ lib/rumplayer/config.rb
27
+
28
+ lib/drbfire.rb
29
+ }
30
+ end
31
+ Jeweler::GemcutterTasks.new
32
+ rescue LoadError
33
+ puts "Jeweler not available. Install it with: sudo gem install jeweler"
34
+ end
@@ -0,0 +1,48 @@
1
+ #!/usr/bin/ruby
2
+
3
+ require 'rubygems'
4
+ require 'optparse'
5
+
6
+ require 'active_support'
7
+
8
+ APP_ROOT = File.expand_path( File.join( File.dirname(__FILE__), '..' ) )
9
+ ActiveSupport::Dependencies::load_paths << File.join(APP_ROOT, "lib")
10
+
11
+
12
+ $options = {}
13
+
14
+ OptionParser.new do |opts|
15
+ opts.banner = <<-EOTEXT
16
+ RuMplayer - play common movies synchronously
17
+
18
+ Usage: #{$0} [options] [file(s)]
19
+ #{$0} (no files) start and listen for commands
20
+ #{$0} one or more files start distributed playback
21
+
22
+ Hint: All watchers must start this command from the same directory,
23
+ all paths are relative
24
+ EOTEXT
25
+
26
+ opts.separator ''
27
+ opts.separator "Options:"
28
+ opts.on("-v", "--[no-]verbose", "Run verbosely") do |v|
29
+ $options[:verbose] = v
30
+ end
31
+
32
+ opts.on("-s", "--server", "Start Server on configured uri") do |s|
33
+ $options[:server] = s
34
+ end
35
+
36
+
37
+ end.parse!
38
+
39
+ if $options[:server]
40
+ Rumplayer::Server.start
41
+ else
42
+ if ARGV.empty?
43
+ Rumplayer::Client.wait
44
+ else
45
+ Rumplayer::Client.run(ARGV)
46
+ end
47
+ end
48
+
@@ -0,0 +1,292 @@
1
+ # :include:README
2
+ #--
3
+ # Author:: Nathaniel Talbott.
4
+ # Copyright:: Copyright (c) 2004 Nathaniel Talbott. All rights reserved.
5
+ # License:: Ruby license.
6
+
7
+ require 'delegate'
8
+ require 'drb'
9
+ require 'timeout'
10
+
11
+ # = DRb Firewall Protocol
12
+ #
13
+ # == Prerequisites
14
+ #
15
+ # It is assumed that you already know how to use DRb; if you don't
16
+ # you'll need to go read up on it and understand the basics of how it
17
+ # works before using DRbFire. DRbFire actually wraps the standard
18
+ # protocols that DRb uses, so generally anything that applies to them
19
+ # applies to DRbFire.
20
+ #
21
+ #
22
+ # == Basic Usage
23
+ #
24
+ # Using DRbFire is quite simple, and can be summed up in four steps:
25
+ #
26
+ # 1. Start with <tt>require 'drb/drbfire'</tt>.
27
+ #
28
+ # 2. Use <tt>drbfire://</tt> instead of <tt>druby://</tt> when
29
+ # specifying the server url.
30
+ #
31
+ # 3. When calling <tt>DRb.start_service</tt> on the client, specify
32
+ # the server's uri as the uri (as opposed to the normal usage, which
33
+ # is to specify *no* uri).
34
+ #
35
+ # 4. Specify the right configuration when calling
36
+ # <tt>DRb.start_service</tt>, specifically the role to use:
37
+ # On the server:: <tt>DRbFire::ROLE => DRbFire::SERVER</tt>
38
+ # On the client:: <tt>DRbFire::ROLE => DRbFire::CLIENT</tt>
39
+ #
40
+ # So a simple server would look like:
41
+ #
42
+ # require 'drb/drbfire'
43
+ #
44
+ # front = ['a', 'b', 'c']
45
+ # DRb.start_service('drbfire://some.server.com:5555', front, DRbFire::ROLE => DRbFire::SERVER)
46
+ # DRb.thread.join
47
+ #
48
+ # And a simple client:
49
+ #
50
+ # require 'drb/drbfire'
51
+ #
52
+ # DRb.start_service('drbfire://some.server.com:5555', nil, DRbFire::ROLE => DRbFire::CLIENT)
53
+ # DRbObject.new(nil, 'drbfire://some.server.com:5555').each do |e|
54
+ # p e
55
+ # end
56
+ #
57
+ #
58
+ # == Advanced Usage
59
+ #
60
+ # You can do some more interesting tricks with DRbFire, too:
61
+ #
62
+ # <b>Using SSL</b>:: To do this, you have to set the delegate in the
63
+ # configuration (on both the server and the client) using
64
+ # <tt>DRbFire::DELEGATE => DRb::DRbSSLSocket</tt>. Other
65
+ # DRb protcols may also work as delegates, but only the
66
+ # SSL protocol is tested.
67
+ #
68
+ #
69
+ # == Caveats
70
+ #
71
+ # * DRbFire uses a 32-bit id space, meaning ids will wrap after
72
+ # approximately ~4.2 billion connections. If that's a non-theoretical
73
+ # problem for you, and you tell me about it, I'll figure out some
74
+ # way to fix it. It'd be worth it just to find out that DRbFire is
75
+ # being used in such a mind-blowing fashion.
76
+ #
77
+ # * You're limited to one _server_ per process at this point. You can
78
+ # have (and handle) as many clients as you want (well, ok, so I just
79
+ # said there's really a limit somewhere around 4.2 billion. I'm
80
+ # trying to simplify here). Again, this is possible to deal with,
81
+ # but not something that I've needed at this point and not something
82
+ # I'm guessing is terribly common. Let me know if it's a problem for
83
+ # you.
84
+
85
+
86
+ module DRbFire
87
+ # The current version.
88
+ VERSION = [0, 1, 0]
89
+
90
+ # The role configuration key.
91
+ ROLE = "#{self}::ROLE"
92
+
93
+ # The server role configuration value.
94
+ SERVER = "#{self}::SERVER"
95
+
96
+ # The client role configuration value.
97
+ CLIENT = "#{self}::CLIENT"
98
+
99
+ # The delegate configuration key.
100
+ DELEGATE = "#{self}::DELEGATE"
101
+
102
+ # Miscellaneous constants
103
+ SCHEME = "drbfire" #:nodoc:
104
+ ID_FORMAT = "N" #:nodoc:
105
+ INCOMING_CONN = "1" #:nodoc:
106
+ OUTGOING_CONN = "2" #:nodoc:
107
+ SIGNAL_CONN = "3" #:nodoc:
108
+
109
+ class Protocol < SimpleDelegator #nodoc:all
110
+ class ClientServer
111
+ attr_reader :signal_id
112
+
113
+ def initialize(uri, config)
114
+ @uri = uri
115
+ @config = config
116
+ @connection = Protocol.open(uri, config, SIGNAL_CONN)
117
+ @signal_id = @connection.read_signal_id
118
+ end
119
+
120
+ def uri
121
+ "#{@uri}?#{@signal_id}"
122
+ end
123
+
124
+ def accept
125
+ @connection.stream.read(1)
126
+ connection = Protocol.open(@uri, @config, OUTGOING_CONN)
127
+ connection.stream.write([@signal_id].pack(ID_FORMAT))
128
+ connection
129
+ end
130
+
131
+ def close
132
+ @connection.close
133
+ end
134
+ end
135
+
136
+ class ClientServerProxy
137
+ def initialize(connection, id)
138
+ @connection = connection
139
+ @id = id
140
+ @queue = Queue.new
141
+ end
142
+
143
+ def write_signal_id
144
+ @connection.stream.write([@id].pack(ID_FORMAT))
145
+ end
146
+
147
+ def push(connection)
148
+ @queue.push(connection)
149
+ end
150
+
151
+ def open
152
+ @connection.stream.write("0")
153
+ timeout(20) do
154
+ @queue.pop
155
+ end
156
+ rescue TimeoutError
157
+ raise DRb::DRbConnError, "Unable to get a client connection."
158
+ end
159
+ end
160
+
161
+ class << self
162
+ def open_server(uri, config)
163
+ # make sure the scheme is ours
164
+ parse_uri(uri)
165
+
166
+ if(server?(config))
167
+ @client_servers ||= {}
168
+
169
+ sock = delegate(config).open_server(uri, config)
170
+
171
+ # get the uri from the delegate, and replace the scheme with drbfire://
172
+ # this allows randomly chosen ports (:0) to work
173
+ scheme = sock.uri.match(/^(.*):\/\//)[1]
174
+ drbfire_uri = sock.uri.sub(scheme, SCHEME)
175
+
176
+ new(drbfire_uri, sock)
177
+ else
178
+ ClientServer.new(uri, config)
179
+ end
180
+ end
181
+
182
+ def open(uri, config, type=INCOMING_CONN)
183
+ unless(server?(config))
184
+ connection = new(uri, delegate(config).open(uri, config))
185
+ connection.stream.write(type)
186
+ connection
187
+ else
188
+ @client_servers[parse_uri(uri).last.to_i].open
189
+ end
190
+ end
191
+
192
+ def add_client_connection(id, connection)
193
+ if((c = @client_servers[id]))
194
+ c.push(connection)
195
+ else
196
+ end
197
+ end
198
+
199
+ def add_client_server(id, server)
200
+ @client_servers[id] = server
201
+ end
202
+
203
+ def parse_uri(uri)
204
+ if(%r{^#{SCHEME}://([^:]+):(\d+)(?:\?(.+))?$} =~ uri)
205
+ [$1, $2.to_i, $3]
206
+ else
207
+ raise DRb::DRbBadScheme, uri unless(/^#{SCHEME}/ =~ uri)
208
+ raise DRb::DRbBadURI, "Can't parse uri: #{uri}"
209
+ end
210
+ end
211
+
212
+ def uri_option(uri, config)
213
+ host, port, option = parse_uri(uri)
214
+ return "#{SCHEME}://#{host}:#{port}", option
215
+ end
216
+
217
+ private
218
+
219
+ def server?(config)
220
+ raise "Invalid configuration #{config.inspect}" unless(config.include?(ROLE))
221
+ config[ROLE] == SERVER
222
+ end
223
+
224
+ def delegate(config)
225
+ unless(defined?(@delegate))
226
+ @delegate = Class.new(config[DELEGATE] || DRb::DRbTCPSocket) do
227
+ class << self
228
+ attr_writer :delegate
229
+
230
+ def parse_uri(uri)
231
+ @delegate.parse_uri(uri)
232
+ end
233
+
234
+ def uri_option(uri, config)
235
+ @delegate.uri_option(uri, config)
236
+ end
237
+ end
238
+ end
239
+ @delegate.delegate = self
240
+ end
241
+ @delegate
242
+ end
243
+ end
244
+
245
+ attr_reader :signal_id, :uri
246
+
247
+ def initialize(uri, delegate)
248
+ super(delegate)
249
+ @uri = uri
250
+ @id = 0
251
+ @id_mutex = Mutex.new
252
+ end
253
+
254
+ def accept
255
+ while(__getobj__.instance_eval{@socket})
256
+ begin
257
+ connection = self.class.new(nil, __getobj__.accept)
258
+ rescue IOError
259
+ return nil
260
+ end
261
+ begin
262
+ type = connection.stream.read(1)
263
+ rescue
264
+ next
265
+ end
266
+ case type
267
+ when INCOMING_CONN
268
+ return connection
269
+ when OUTGOING_CONN
270
+ self.class.add_client_connection(connection.read_signal_id, connection)
271
+ next
272
+ when SIGNAL_CONN
273
+ new_id = nil
274
+ @id_mutex.synchronize do
275
+ new_id = (@id += 1)
276
+ end
277
+ client_server = ClientServerProxy.new(connection, new_id)
278
+ self.class.add_client_server(new_id, client_server)
279
+ client_server.write_signal_id
280
+ next
281
+ else
282
+ raise "Invalid type #{type}"
283
+ end
284
+ end
285
+ end
286
+
287
+ def read_signal_id
288
+ stream.read(4).unpack(ID_FORMAT).first
289
+ end
290
+ end
291
+ end
292
+ DRb::DRbProtocol.add_protocol(DRbFire::Protocol)
@@ -0,0 +1,13 @@
1
+ require_dependency 'drbfire'
2
+ module Rumplayer
3
+ include DRb::DRbUndumped
4
+
5
+
6
+ module Log
7
+ def log(message)
8
+ if $options[:verbose]
9
+ STDERR.puts message
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,84 @@
1
+ class Rumplayer::Client
2
+ include DRb::DRbUndumped
3
+ include Rumplayer::Log
4
+ include Rumplayer::Config
5
+ include Rumplayer::Mplayer
6
+
7
+ def self.wait
8
+ new.wait
9
+ end
10
+
11
+ def self.run(argv=[])
12
+ client = new(argv).run_and_tell
13
+ end
14
+
15
+ attr_reader :argv
16
+ def initialize(argv=[])
17
+ @argv = argv
18
+ end
19
+
20
+ def run_and_tell argv=argv
21
+ enter
22
+ tell(argv)
23
+ end
24
+
25
+ def run argv=argv
26
+ log "Running #{argv.inspect}"
27
+ if is_slave?
28
+ @mplayer_thread = Thread.start{ run_mplayer argv }
29
+ else
30
+ run_mplayer argv
31
+ end
32
+ end
33
+
34
+ def wait
35
+ enter
36
+ say "waiting for command"
37
+ sleep 100000
38
+ log "tired of waiting"
39
+ end
40
+
41
+ def enter
42
+ say "Connecting.."
43
+ log "Started as %s" % start_service.uri
44
+ buddies.register(self, name)
45
+ trap('INT') { leave }
46
+ end
47
+
48
+ def start_service
49
+ @service = DRb.start_service(config['uri'], self, DRbFire::ROLE => DRbFire::CLIENT)
50
+ @service
51
+ end
52
+
53
+ def leave
54
+ log "leaving"
55
+ buddies.unregister(self) if @buddies
56
+ DRb.stop_service
57
+ kill_mplayer!
58
+ log "joining loose threads"
59
+ @mplayer_thread.join(1) if @mplayer_thread
60
+ log "exiting"
61
+ DRb.thread.join if DRb.thread
62
+ say "killing remaining %i threads (you may want ctrl+c manually)" % Thread.list.size
63
+ Thread.list.map(&:kill)
64
+ exit
65
+ end
66
+
67
+ def say(message="no message")
68
+ STDERR.puts message
69
+ end
70
+
71
+ def tell argv=argv
72
+ log "Telling #{argv.inspect}"
73
+ buddies.run(argv.dup)
74
+ end
75
+
76
+ def buddies
77
+ @buddies ||= DRbObject.new(nil, config['uri'])
78
+ end
79
+
80
+ def is_slave?
81
+ argv.empty?
82
+ end
83
+
84
+ end
@@ -0,0 +1,57 @@
1
+ module Rumplayer
2
+ module Config
3
+ def username
4
+ `whoami`.chomp
5
+ end
6
+
7
+ def hostname
8
+ `hostname`.chomp
9
+ end
10
+
11
+ def name
12
+ @name ||= [username, hostname].join('@')
13
+ end
14
+
15
+ def config_path
16
+ File.join( ENV['HOME'], '.rumplayer.yml' )
17
+ end
18
+
19
+ def config_exists?
20
+ File.exists? config_path
21
+ end
22
+
23
+ def default_config
24
+ {
25
+ 'uri' => "drbfire://localhost:18383"
26
+ }
27
+ end
28
+
29
+ def config
30
+ return @config if @config
31
+ unless config_exists?
32
+ write_default_config
33
+ say "written default config to #{config_path}"
34
+ say "please edit it"
35
+ end
36
+ load_config
37
+ end
38
+
39
+ private
40
+ def write_default_config
41
+ write_config default_config
42
+ end
43
+
44
+ def write_config config=@config
45
+ File.open(config_path, 'w') do |f|
46
+ f.puts config.to_yaml
47
+ end
48
+ end
49
+
50
+ def load_config
51
+ @config = YAML.load( File.read(config_path ))
52
+ log "loaded config %s" % @config.inspect
53
+ @config
54
+ end
55
+ end
56
+ end
57
+
@@ -0,0 +1,85 @@
1
+ module Rumplayer::Mplayer
2
+ MplayerCommand = `which mplayer`.chomp
3
+ MplayerOptions = %w(-really-quiet)
4
+
5
+ def key(char)
6
+ leave if char == "\003"
7
+ leave if char == "q"
8
+ synchronize do
9
+ @mplayer_pipe.print char
10
+ end if @mplayer_pipe
11
+ end
12
+
13
+ def kill_mplayer!
14
+ Process.kill("TERM", @mplayer) if mplayer_is_running?
15
+ clean_up_all_traces_of_mplayer!
16
+ end
17
+
18
+ def mplayer_is_running?
19
+ @mplayer and `ps -p #{@mplayer}`.chomp.lines.count > 1
20
+ end
21
+
22
+ def clean_up_all_traces_of_mplayer!
23
+ @mplayer_pipe.close rescue nil
24
+ @mplayer = nil
25
+ end
26
+
27
+ private
28
+ def run_mplayer argv=argv, options ={}
29
+ kill_mplayer!
30
+ log "starting mplayer"
31
+ rd, wr = IO.pipe
32
+ @mplayer = fork do
33
+ STDIN.reopen(rd)
34
+ command = [MplayerCommand] + MplayerOptions + options.to_a.flatten + argv
35
+ exec(*command)
36
+ end
37
+ Process::detach(@mplayer)
38
+ @mplayer_pipe = wr
39
+ forward_keys
40
+ clean_up_all_traces_of_mplayer!
41
+ log "Finished playing #{argv.inspect}"
42
+ end
43
+
44
+ def forward_keys
45
+ while @mplayer
46
+ char = read_char
47
+ if ["\003", "q"].include? char
48
+ buddies.say("#{name} has quit. You should quit, too (with 'q' or ctrl+c)")
49
+ buddies.leave
50
+ else
51
+ buddies.key(char)
52
+ end
53
+ end
54
+ end
55
+
56
+ def read_char
57
+ begin
58
+ # save previous state of stty
59
+ old_state = `stty -g`
60
+ # disable echoing and enable raw (not having to press enter)
61
+ system "stty raw -echo"
62
+ c = STDIN.getc.chr
63
+ if c == "\e"
64
+ extra_thread = Thread.new do
65
+ c = c + STDIN.getc.chr
66
+ c = c + STDIN.getc.chr
67
+ end
68
+ extra_thread.join(0.00001)
69
+ extra_thread.kill
70
+ end
71
+ rescue => ex
72
+ puts "#{ex.class}: #{ex.message}"
73
+ puts ex.backtrace
74
+ ensure
75
+ # restore previous state of stty
76
+ system "stty #{old_state}"
77
+ end
78
+ return c
79
+ end
80
+
81
+ def synchronize(&block)
82
+ @mplayer_mutex ||= Mutex.new
83
+ @mplayer_mutex.synchronize(&block)
84
+ end
85
+ end
@@ -0,0 +1,89 @@
1
+ class Rumplayer::Server
2
+ include DRb::DRbUndumped
3
+ include Rumplayer::Log
4
+ include Rumplayer::Config
5
+
6
+ class Watcher < Struct.new(:handler, :name)
7
+ @@last_index = 0
8
+ attr_reader :index
9
+ def self.next_index
10
+ @@last_index += 1
11
+ @@last_index
12
+ end
13
+ def initialize(*args)
14
+ super
15
+ @index = self.class.next_index
16
+ end
17
+ def method_missing(method_name, *args, &block)
18
+ handler.__send__(method_name, *args, &block)
19
+ end
20
+ def to_s
21
+ "#{name} (#{index}) [#{handler.__drburi}]"
22
+ end
23
+ end
24
+
25
+ def self.start(argv=[])
26
+ new.start
27
+ end
28
+
29
+ def initialize
30
+ @watchers = []
31
+ end
32
+
33
+ def start
34
+ log "Starting Server"
35
+ DRb.start_service(config['uri'], self, DRbFire::ROLE => DRbFire::SERVER)
36
+ DRb.thread.join
37
+ end
38
+
39
+ def register(client, name=nil)
40
+ watcher = Watcher.new(client, name || client.inspect)
41
+ log "connected #{watcher}"
42
+
43
+ say("#{watcher.name} connected")
44
+ watcher.say "Connected."
45
+ if @watchers.empty?
46
+ watcher.say "no other buddies connected (yet)"
47
+ else
48
+ watcher.say "Buddies: %s" % @watchers.map(&:name).join(', ')
49
+ end
50
+ @watchers << watcher
51
+ log "currently connected: %i" % @watchers.size
52
+ end
53
+
54
+ def unregister(client)
55
+ if watcher = @watchers.find {|w| w.handler == client }
56
+ watcher.say "Goodbye"
57
+ remove_watcher watcher
58
+ end
59
+ end
60
+
61
+ def method_missing(method_name, *args, &block)
62
+ log "#{method_name} to all: #{args.inspect}"
63
+ each_watcher do |watcher|
64
+ watcher.__send__(method_name, *args, &block)
65
+ end
66
+ end
67
+
68
+ def each_watcher
69
+ unless @watchers.blank?
70
+ @watchers.each do |watcher|
71
+ begin
72
+ yield(watcher)
73
+ rescue DRb::DRbConnError => e
74
+ remove_watcher(watcher)
75
+ rescue RangeError => e
76
+ log "got RangeError, trying to continue"
77
+ end
78
+ end
79
+ end
80
+ end
81
+
82
+ def remove_watcher watcher
83
+ @watchers.delete(watcher)
84
+ log "disconnected #{watcher}"
85
+ log "currently connected: %i" % @watchers.size
86
+ say("#{watcher.name} disconnected")
87
+ end
88
+
89
+ end
metadata ADDED
@@ -0,0 +1,62 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: rumplayer
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.1
5
+ platform: ruby
6
+ authors:
7
+ - Niklas Hofer
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2009-12-15 00:00:00 +01:00
13
+ default_executable: rumplayer
14
+ dependencies: []
15
+
16
+ description: "\n RumPlayer allows you and your buddies to watch movies simultaneously\n without sharing the room or continent. All participans must have a copy\n of the movie to watch.\n "
17
+ email: niklas+rumplayer@lanpartei.de
18
+ executables:
19
+ - rumplayer
20
+ extensions: []
21
+
22
+ extra_rdoc_files:
23
+ - README.markdown
24
+ files:
25
+ - Rakefile
26
+ - lib/drbfire.rb
27
+ - lib/rumplayer.rb
28
+ - lib/rumplayer/client.rb
29
+ - lib/rumplayer/config.rb
30
+ - lib/rumplayer/mplayer.rb
31
+ - lib/rumplayer/server.rb
32
+ - README.markdown
33
+ has_rdoc: true
34
+ homepage: http://github.com/niklas/rumplayer/
35
+ licenses: []
36
+
37
+ post_install_message:
38
+ rdoc_options:
39
+ - --charset=UTF-8
40
+ require_paths:
41
+ - lib
42
+ required_ruby_version: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - ">="
45
+ - !ruby/object:Gem::Version
46
+ version: "0"
47
+ version:
48
+ required_rubygems_version: !ruby/object:Gem::Requirement
49
+ requirements:
50
+ - - ">="
51
+ - !ruby/object:Gem::Version
52
+ version: "0"
53
+ version:
54
+ requirements: []
55
+
56
+ rubyforge_project:
57
+ rubygems_version: 1.3.5
58
+ signing_key:
59
+ specification_version: 3
60
+ summary: watch common movies simultaneously
61
+ test_files: []
62
+