rtsp_server 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (44) hide show
  1. checksums.yaml +7 -0
  2. data/.gemtest +0 -0
  3. data/ChangeLog.rdoc +74 -0
  4. data/Gemfile +3 -0
  5. data/LICENSE.rdoc +20 -0
  6. data/README.rdoc +152 -0
  7. data/Rakefile +23 -0
  8. data/bin/rtsp_client +133 -0
  9. data/features/client_changes_state.feature +58 -0
  10. data/features/client_requests.feature +27 -0
  11. data/features/control_streams_as_client.feature +26 -0
  12. data/features/step_definitions/client_changes_state_steps.rb +52 -0
  13. data/features/step_definitions/client_requests_steps.rb +68 -0
  14. data/features/step_definitions/control_streams_as_client_steps.rb +34 -0
  15. data/features/support/env.rb +50 -0
  16. data/features/support/hooks.rb +3 -0
  17. data/lib/ext/logger.rb +8 -0
  18. data/lib/rtsp/client.rb +520 -0
  19. data/lib/rtsp/common.rb +148 -0
  20. data/lib/rtsp/error.rb +6 -0
  21. data/lib/rtsp/global.rb +63 -0
  22. data/lib/rtsp/helpers.rb +28 -0
  23. data/lib/rtsp/message.rb +272 -0
  24. data/lib/rtsp/request.rb +39 -0
  25. data/lib/rtsp/response.rb +47 -0
  26. data/lib/rtsp/server.rb +303 -0
  27. data/lib/rtsp/socat_streaming.rb +309 -0
  28. data/lib/rtsp/stream_server.rb +37 -0
  29. data/lib/rtsp/transport_parser.rb +96 -0
  30. data/lib/rtsp/version.rb +4 -0
  31. data/lib/rtsp.rb +6 -0
  32. data/rtsp.gemspec +42 -0
  33. data/spec/rtsp/client_spec.rb +326 -0
  34. data/spec/rtsp/helpers_spec.rb +53 -0
  35. data/spec/rtsp/message_spec.rb +420 -0
  36. data/spec/rtsp/response_spec.rb +306 -0
  37. data/spec/rtsp/transport_parser_spec.rb +137 -0
  38. data/spec/rtsp_spec.rb +27 -0
  39. data/spec/spec_helper.rb +88 -0
  40. data/spec/support/fake_rtsp_server.rb +123 -0
  41. data/tasks/roodi.rake +9 -0
  42. data/tasks/roodi_config.yaml +14 -0
  43. data/tasks/stats.rake +12 -0
  44. metadata +280 -0
@@ -0,0 +1,420 @@
1
+ require 'sdp'
2
+ require_relative '../spec_helper'
3
+ require 'rtsp/message'
4
+
5
+ describe "RTSP::Message" do
6
+ let(:stream) { "rtsp://1.2.3.4/stream1" }
7
+
8
+ it "raises if the header type isn't a Symbol" do
9
+ message = RTSP::Message.options(stream)
10
+
11
+ lambda {
12
+ message.header "hi", "everyone"
13
+ }.should raise_error RTSP::Error
14
+ end
15
+
16
+ RTSP::Message.instance_variable_get(:@message_types).each do |method|
17
+ it "adds a User-Agent header to the #{method} method" do
18
+ message = RTSP::Message.send(method, stream)
19
+ message.to_s.should include "User-Agent: RubyRTSP/"
20
+ end
21
+ end
22
+
23
+ context "builds an OPTIONS string" do
24
+ it "with default sequence number" do
25
+ message = RTSP::Message.options(stream)
26
+ message.to_s.should == "OPTIONS rtsp://1.2.3.4:554/stream1 RTSP/1.0\r\nCSeq: 1\r\nUser-Agent: RubyRTSP/#{RTSP::VERSION} (Ruby #{RUBY_VERSION}-p#{RUBY_PATCHLEVEL})\r\n\r\n"
27
+ end
28
+
29
+ it "with new sequence number" do
30
+ message = RTSP::Message.options(stream)
31
+ message.header :cseq, 2345
32
+ message.to_s.should == "OPTIONS rtsp://1.2.3.4:554/stream1 RTSP/1.0\r\nCSeq: 2345\r\nUser-Agent: RubyRTSP/#{RTSP::VERSION} (Ruby #{RUBY_VERSION}-p#{RUBY_PATCHLEVEL})\r\n\r\n"
33
+ end
34
+ end
35
+
36
+ context "builds a DESCRIBE string" do
37
+ it "with default sequence and accept values" do
38
+ message = RTSP::Message.describe(stream)
39
+ message.to_s.should match(/^DESCRIBE rtsp:/)
40
+ message.to_s.should include "DESCRIBE rtsp://1.2.3.4:554/stream1 RTSP/1.0\r\n"
41
+ message.to_s.should include "CSeq: 1\r\n"
42
+ message.to_s.should include "Accept: application/sdp\r\n"
43
+ message.to_s.should match(/\r\n\r\n$/)
44
+ end
45
+
46
+ it "with default sequence value" do
47
+ message = RTSP::Message.describe(stream).with_headers({
48
+ accept: 'application/sdp, application/rtsl' })
49
+ message.to_s.should match(/^DESCRIBE rtsp:/)
50
+ message.to_s.should include "DESCRIBE rtsp://1.2.3.4:554/stream1 RTSP/1.0\r\n"
51
+ message.to_s.should include "CSeq: 1\r\n"
52
+ message.to_s.should include "Accept: application/sdp, application/rtsl\r\n"
53
+ message.to_s.should match(/\r\n\r\n$/)
54
+ end
55
+
56
+ it "with new sequence and accept values" do
57
+ message = RTSP::Message.describe(stream).with_headers({
58
+ accept: 'application/sdp, application/rtsl',
59
+ cseq: 2345 })
60
+
61
+ message.to_s.should match(/^DESCRIBE rtsp:/)
62
+ message.to_s.should include "DESCRIBE rtsp://1.2.3.4:554/stream1 RTSP/1.0\r\n"
63
+ message.to_s.should include "CSeq: 2345\r\n"
64
+ message.to_s.should include "Accept: application/sdp, application/rtsl\r\n"
65
+ message.to_s.should match(/\r\n\r\n$/)
66
+ end
67
+ end
68
+
69
+ context "builds a ANNOUNCE string" do
70
+ it "with default sequence, content type, but no body" do
71
+ message = RTSP::Message.announce(stream).with_headers({ session: 123456789 })
72
+
73
+ message.to_s.should match(/^ANNOUNCE rtsp:/)
74
+ message.to_s.should include "ANNOUNCE rtsp://1.2.3.4:554/stream1 RTSP/1.0\r\n"
75
+ message.to_s.should include "CSeq: 1\r\n"
76
+ message.to_s.should include "Session: 123456789\r\n"
77
+ message.to_s.should include "Content-Type: application/sdp\r\n"
78
+ message.to_s.should match(/\r\n\r\n$/)
79
+ end
80
+
81
+ it "with passed-in session and content type but no body" do
82
+ message = RTSP::Message.announce(stream).with_headers({
83
+ session: 123456789,
84
+ content_type: 'application/sdp, application/rtsl' })
85
+
86
+ message.to_s.should match(/^ANNOUNCE rtsp:/)
87
+ message.to_s.should include "ANNOUNCE rtsp://1.2.3.4:554/stream1 RTSP/1.0\r\n"
88
+ message.to_s.should include "CSeq: 1\r\n"
89
+ message.to_s.should include "Session: 123456789\r\n"
90
+ message.to_s.should include "Content-Type: application/sdp, application/rtsl\r\n"
91
+ message.to_s.should match(/\r\n\r\n$/)
92
+ end
93
+
94
+ it "with passed-in sequence, session, content-type, but no body " do
95
+ message = RTSP::Message.announce(stream).with_headers({
96
+ session: 123456789,
97
+ content_type: 'application/sdp, application/rtsl',
98
+ cseq: 2345 })
99
+
100
+ message.to_s.should match(/^ANNOUNCE rtsp:/)
101
+ message.to_s.should include "ANNOUNCE rtsp://1.2.3.4:554/stream1 RTSP/1.0\r\n"
102
+ message.to_s.should include "CSeq: 2345\r\n"
103
+ message.to_s.should include "Session: 123456789\r\n"
104
+ message.to_s.should include "Content-Type: application/sdp, application/rtsl\r\n"
105
+ message.to_s.should match(/\r\n\r\n$/)
106
+ end
107
+
108
+ it "with passed-in sequence, session, content-type, and SDP body" do
109
+ sdp = SDP::Description.new
110
+ sdp.protocol_version = 1
111
+ sdp.username = 'bobo'
112
+
113
+ message = RTSP::Message.announce(stream).with_headers({
114
+ session: 123456789,
115
+ content_type: 'application/sdp',
116
+ cseq: 2345 })
117
+ message.body = sdp.to_s
118
+
119
+ message.to_s.should match(/^ANNOUNCE rtsp/)
120
+ message.to_s.should include "ANNOUNCE rtsp://1.2.3.4:554/stream1 RTSP/1.0\r\n"
121
+ message.to_s.should include "CSeq: 2345\r\n"
122
+ message.to_s.should include "Session: 123456789\r\n"
123
+ message.to_s.should include "Content-Type: application/sdp\r\n"
124
+ message.to_s.should include "Content-Length: 29\r\n"
125
+ message.to_s.should match(/\r\n\r\nv=1\r\no=bobo \r\ns=\r\nt= \r\n\r\n$/)
126
+ end
127
+ end
128
+
129
+ context "builds a SETUP string" do
130
+ it "with default sequence, client_port, and routing values" do
131
+ message = RTSP::Message.setup(stream)
132
+
133
+ message.to_s.should match(/^SETUP rtsp/)
134
+ message.to_s.should include "SETUP rtsp://1.2.3.4:554/stream1 RTSP/1.0\r\n"
135
+ message.to_s.should include "CSeq: 1\r\n"
136
+ message.to_s.should match(/\r\n\r\n$/)
137
+ end
138
+
139
+ it "with default sequence, transport, and client_port values" do
140
+ message = RTSP::Message.setup(stream).with_headers({
141
+ transport: ["RTP/AVP", "multicast", { :client_port => "9000-9001" }] })
142
+
143
+ message.to_s.should match(/^SETUP rtsp/)
144
+ message.to_s.should include "SETUP rtsp://1.2.3.4:554/stream1 RTSP/1.0\r\n"
145
+ message.to_s.should include "CSeq: 1\r\n"
146
+ message.to_s.should include "Transport: RTP/AVP;multicast;client_port=9000-9001\r\n"
147
+ message.to_s.should match(/\r\n\r\n$/)
148
+ end
149
+
150
+ it "with default transport, client_port, and routing values" do
151
+ message = RTSP::Message.setup(stream).with_headers({
152
+ transport: ["RTP/AVP", "multicast", { :client_port => "9000-9001" }],
153
+ cseq: 2345 })
154
+
155
+ message.to_s.should match(/^SETUP rtsp/)
156
+ message.to_s.should include "SETUP rtsp://1.2.3.4:554/stream1 RTSP/1.0\r\n"
157
+ message.to_s.should include "CSeq: 2345\r\n"
158
+ message.to_s.should include "Transport: RTP/AVP;multicast;client_port=9000-9001\r\n"
159
+ message.to_s.should match(/\r\n\r\n$/)
160
+ end
161
+ end
162
+
163
+ context "builds a PLAY string" do
164
+ it "with default sequence and range values" do
165
+ message = RTSP::Message.play(stream).with_headers({
166
+ session: 123456789 })
167
+
168
+ message.to_s.should match(/^PLAY rtsp/)
169
+ message.to_s.should include "PLAY rtsp://1.2.3.4:554/stream1 RTSP/1.0\r\n"
170
+ message.to_s.should include "CSeq: 1\r\n"
171
+ message.to_s.should include "Session: 123456789\r\n"
172
+ message.to_s.should include "Range: npt=0.000-\r\n"
173
+ message.to_s.should match(/\r\n\r\n/)
174
+ end
175
+
176
+ it "with default sequence value" do
177
+ message = RTSP::Message.play(stream).with_headers({
178
+ session: 123456789,
179
+ range: { :npt => "0.000-1.234" } })
180
+
181
+ message.to_s.should match(/^PLAY rtsp/)
182
+ message.to_s.should include "PLAY rtsp://1.2.3.4:554/stream1 RTSP/1.0\r\n"
183
+ message.to_s.should include "CSeq: 1\r\n"
184
+ message.to_s.should include "Session: 123456789\r\n"
185
+ message.to_s.should include "Range: npt=0.000-1.234\r\n"
186
+ message.to_s.should match(/\r\n\r\n/)
187
+ end
188
+ end
189
+
190
+ context "builds a PAUSE string" do
191
+ it "with required Request values" do
192
+ message = RTSP::Message.pause(stream)
193
+
194
+ message.to_s.should match(/^PAUSE rtsp/)
195
+ message.to_s.should include "PAUSE rtsp://1.2.3.4:554/stream1 RTSP/1.0\r\n"
196
+ message.to_s.should include "CSeq: 1\r\n"
197
+ message.to_s.should match(/\r\n\r\n/)
198
+ end
199
+
200
+ it "with session and range headers" do
201
+ message = RTSP::Message.pause(stream).with_headers({
202
+ session: 123456789,
203
+ range: { :npt => "0.000" } })
204
+
205
+ message.to_s.should match(/^PAUSE rtsp/)
206
+ message.to_s.should include "PAUSE rtsp://1.2.3.4:554/stream1 RTSP/1.0\r\n"
207
+ message.to_s.should include "CSeq: 1\r\n"
208
+ message.to_s.should include "Session: 123456789\r\n"
209
+ message.to_s.should include "Range: npt=0.000\r\n"
210
+ message.to_s.should match(/\r\n\r\n/)
211
+ end
212
+ end
213
+
214
+ context "builds a TEARDOWN string" do
215
+ it "with required Request values" do
216
+ message = RTSP::Message.teardown(stream)
217
+
218
+ message.to_s.should match(/^TEARDOWN rtsp/)
219
+ message.to_s.should include "TEARDOWN rtsp://1.2.3.4:554/stream1 RTSP/1.0\r\n"
220
+ message.to_s.should include "CSeq: 1\r\n"
221
+ message.to_s.should match(/\r\n\r\n/)
222
+ end
223
+
224
+ it "with session and range headers" do
225
+ message = RTSP::Message.teardown(stream).with_headers({
226
+ session: 123456789 })
227
+
228
+ message.to_s.should match(/^TEARDOWN rtsp/)
229
+ message.to_s.should include "TEARDOWN rtsp://1.2.3.4:554/stream1 RTSP/1.0\r\n"
230
+ message.to_s.should include "CSeq: 1\r\n"
231
+ message.to_s.should include "Session: 123456789\r\n"
232
+ message.to_s.should match(/\r\n\r\n/)
233
+ end
234
+ end
235
+
236
+ context "builds a GET_PARAMETER string" do
237
+ it "with required Request values" do
238
+ message = RTSP::Message.get_parameter(stream)
239
+
240
+ message.to_s.should match(/^GET_PARAMETER rtsp/)
241
+ message.to_s.should include "GET_PARAMETER rtsp://1.2.3.4:554/stream1 RTSP/1.0\r\n"
242
+ message.to_s.should include "CSeq: 1\r\n"
243
+ message.to_s.should match(/\r\n\r\n/)
244
+ end
245
+
246
+ it "with cseq, content type, session headers, and text body" do
247
+ the_body = "packets_received\r\njitter\r\n"
248
+
249
+ message = RTSP::Message.get_parameter(stream).with_headers({
250
+ cseq: 431,
251
+ content_type: 'text/parameters',
252
+ session: 123456789 })
253
+
254
+ message.body = the_body
255
+
256
+ message.to_s.should match(/^GET_PARAMETER rtsp/)
257
+ message.to_s.should include "GET_PARAMETER rtsp://1.2.3.4:554/stream1 RTSP/1.0\r\n"
258
+ message.to_s.should include "CSeq: 431\r\n"
259
+ message.to_s.should include "Session: 123456789\r\n"
260
+ message.to_s.should include "Content-Type: text/parameters\r\n"
261
+ message.to_s.should include "Content-Length: #{the_body.length}\r\n"
262
+ message.to_s.should include the_body
263
+ message.to_s.should match(/\r\n\r\n/)
264
+ end
265
+ end
266
+
267
+ context "builds a SET_PARAMETER string" do
268
+ it "with required Request values" do
269
+ message = RTSP::Message.set_parameter(stream)
270
+
271
+ message.to_s.should match(/^SET_PARAMETER rtsp/)
272
+ message.to_s.should include "SET_PARAMETER rtsp://1.2.3.4:554/stream1 RTSP/1.0\r\n"
273
+ message.to_s.should include "CSeq: 1\r\n"
274
+ message.to_s.should match(/\r\n\r\n/)
275
+ end
276
+
277
+ it "with cseq, content type, session headers, and text body" do
278
+ the_body = "barparam: barstuff\r\n"
279
+
280
+ message = RTSP::Message.set_parameter(stream).with_headers({
281
+ cseq: 431,
282
+ content_type: 'text/parameters',
283
+ session: 123456789 })
284
+ message.body = the_body
285
+
286
+ message.to_s.should match(/^SET_PARAMETER rtsp/)
287
+ message.to_s.should include "SET_PARAMETER rtsp://1.2.3.4:554/stream1 RTSP/1.0\r\n"
288
+ message.to_s.should include "CSeq: 431\r\n"
289
+ message.to_s.should include "Session: 123456789\r\n"
290
+ message.to_s.should include "Content-Type: text/parameters\r\n"
291
+ message.to_s.should include "Content-Length: #{the_body.length}\r\n"
292
+ message.to_s.should include the_body
293
+ message.to_s.should match(/\r\n\r\n/)
294
+ end
295
+ end
296
+
297
+ context "builds a REDIRECT string" do
298
+ it "with required Request values" do
299
+ message = RTSP::Message.redirect(stream)
300
+
301
+ message.to_s.should match(/^REDIRECT rtsp/)
302
+ message.to_s.should include "REDIRECT rtsp://1.2.3.4:554/stream1 RTSP/1.0\r\n"
303
+ message.to_s.should include "CSeq: 1\r\n"
304
+ message.to_s.should match(/\r\n\r\n/)
305
+ end
306
+
307
+ it "with cseq, location, and range headers" do
308
+ message = RTSP::Message.redirect(stream).with_headers({
309
+ cseq: 732,
310
+ location: "rtsp://bigserver.com:8001",
311
+ range: { :clock => "19960213T143205Z-" } })
312
+
313
+ message.to_s.should match(/^REDIRECT rtsp/)
314
+ message.to_s.should include "REDIRECT rtsp://1.2.3.4:554/stream1 RTSP/1.0\r\n"
315
+ message.to_s.should include "CSeq: 732\r\n"
316
+ message.to_s.should include "Location: rtsp://bigserver.com:8001\r\n"
317
+ message.to_s.should include "Range: clock=19960213T143205Z-\r\n"
318
+ message.to_s.should match(/\r\n\r\n/)
319
+ end
320
+ end
321
+
322
+ context "builds a RECORD string" do
323
+ it "with required Request values" do
324
+ message = RTSP::Message.record(stream)
325
+
326
+ message.to_s.should match(/^RECORD rtsp/)
327
+ message.to_s.should include "RECORD rtsp://1.2.3.4:554/stream1 RTSP/1.0\r\n"
328
+ message.to_s.should include "CSeq: 1\r\n"
329
+ message.to_s.should match(/\r\n\r\n/)
330
+ end
331
+
332
+ it "with cseq, session, and conference headers" do
333
+ message = RTSP::Message.record(stream).with_headers({
334
+ cseq: 954,
335
+ session: 12345678,
336
+ conference: "128.16.64.19/32492374" })
337
+
338
+ message.to_s.should match(/^RECORD rtsp/)
339
+ message.to_s.should include "RECORD rtsp://1.2.3.4:554/stream1 RTSP/1.0\r\n"
340
+ message.to_s.should include "CSeq: 954\r\n"
341
+ message.to_s.should include "Session: 12345678\r\n"
342
+ message.to_s.should include "Conference: 128.16.64.19/32492374\r\n"
343
+ message.to_s.should match(/\r\n\r\n/)
344
+ end
345
+ end
346
+
347
+ context "#to_s turns a Hash into an String of header strings" do
348
+ it "single header, non-hyphenated name, hash value" do
349
+ message = RTSP::Message.play(stream).with_headers({
350
+ range: { npt: "0.000-" }
351
+ })
352
+
353
+ string = message.to_s
354
+ string.is_a?(String).should be_true
355
+ string.should include "Range: npt=0.000-"
356
+ end
357
+
358
+ it "single header, hyphenated, non-hash value" do
359
+ message = RTSP::Message.play(stream).with_headers({
360
+ :if_modified_since => "Sat, 29 Oct 1994 19:43:31 GMT"
361
+ })
362
+
363
+ string = message.to_s
364
+ string.is_a?(String).should be_true
365
+ string.should include "If-Modified-Since: Sat, 29 Oct 1994 19:43:31 GMT"
366
+ end
367
+
368
+ it "two headers, mixed hyphenated, array & hash values" do
369
+ message = RTSP::Message.redirect(stream).with_headers({
370
+ :cache_control => ["no-cache", { :max_age => 12345 }],
371
+ :content_type => ['application/sdp', 'application/x-rtsp-mh']
372
+ })
373
+
374
+ string = message.to_s
375
+ string.is_a?(String).should be_true
376
+ string.should include "Cache-Control: no-cache;max_age=12345"
377
+ string.should include "Content-Type: application/sdp, application/x-rtsp-mh"
378
+ end
379
+ end
380
+
381
+ describe "#with_headers" do
382
+ it "returns an RTSP::Message" do
383
+ message = RTSP::Message.options(stream)
384
+ result = message.with_headers({ test: "test" })
385
+ result.class.should == RTSP::Message
386
+ end
387
+ end
388
+
389
+ describe "#with_body" do
390
+ it "adds the passed-in text to the body of the message" do
391
+ new_body = "1234567890"
392
+ message = RTSP::Message.record("rtsp://localhost/track").with_body(new_body)
393
+ message.to_s.should match(/\r\n\r\n#{new_body}$/)
394
+ end
395
+
396
+ it "adds the Content-Length header to reflect the body" do
397
+ new_body = "1234567890"
398
+ message = RTSP::Message.record("rtsp://localhost/track").with_body(new_body)
399
+ message.to_s.should match(/Content-Length: #{new_body.size}\r\n/)
400
+ end
401
+ end
402
+
403
+ describe "#respond_to?" do
404
+ it "returns true to each method in the list of supported method types" do
405
+ RTSP::Message.instance_variable_get(:@message_types).each do |m|
406
+ RTSP::Message.respond_to?(m).should be_true
407
+ end
408
+ end
409
+
410
+ it "returns false to a method that's not in the list of supported methods" do
411
+ RTSP::Message.respond_to?(:meow).should be_false
412
+ end
413
+ end
414
+
415
+ describe "#method_missing" do
416
+ it "returns " do
417
+ lambda { RTSP::Message.meow }.should raise_error NoMethodError
418
+ end
419
+ end
420
+ end