rumplayer 0.1.1

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,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
+