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.
- data/README.markdown +43 -0
- data/Rakefile +34 -0
- data/bin/rumplayer +48 -0
- data/lib/drbfire.rb +292 -0
- data/lib/rumplayer.rb +13 -0
- data/lib/rumplayer/client.rb +84 -0
- data/lib/rumplayer/config.rb +57 -0
- data/lib/rumplayer/mplayer.rb +85 -0
- data/lib/rumplayer/server.rb +89 -0
- metadata +62 -0
data/README.markdown
ADDED
@@ -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.
|
data/Rakefile
ADDED
@@ -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
|
data/bin/rumplayer
ADDED
@@ -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
|
+
|
data/lib/drbfire.rb
ADDED
@@ -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)
|
data/lib/rumplayer.rb
ADDED
@@ -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
|
+
|