rtsp_server 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gemtest +0 -0
- data/ChangeLog.rdoc +74 -0
- data/Gemfile +3 -0
- data/LICENSE.rdoc +20 -0
- data/README.rdoc +152 -0
- data/Rakefile +23 -0
- data/bin/rtsp_client +133 -0
- data/features/client_changes_state.feature +58 -0
- data/features/client_requests.feature +27 -0
- data/features/control_streams_as_client.feature +26 -0
- data/features/step_definitions/client_changes_state_steps.rb +52 -0
- data/features/step_definitions/client_requests_steps.rb +68 -0
- data/features/step_definitions/control_streams_as_client_steps.rb +34 -0
- data/features/support/env.rb +50 -0
- data/features/support/hooks.rb +3 -0
- data/lib/ext/logger.rb +8 -0
- data/lib/rtsp/client.rb +520 -0
- data/lib/rtsp/common.rb +148 -0
- data/lib/rtsp/error.rb +6 -0
- data/lib/rtsp/global.rb +63 -0
- data/lib/rtsp/helpers.rb +28 -0
- data/lib/rtsp/message.rb +272 -0
- data/lib/rtsp/request.rb +39 -0
- data/lib/rtsp/response.rb +47 -0
- data/lib/rtsp/server.rb +303 -0
- data/lib/rtsp/socat_streaming.rb +309 -0
- data/lib/rtsp/stream_server.rb +37 -0
- data/lib/rtsp/transport_parser.rb +96 -0
- data/lib/rtsp/version.rb +4 -0
- data/lib/rtsp.rb +6 -0
- data/rtsp.gemspec +42 -0
- data/spec/rtsp/client_spec.rb +326 -0
- data/spec/rtsp/helpers_spec.rb +53 -0
- data/spec/rtsp/message_spec.rb +420 -0
- data/spec/rtsp/response_spec.rb +306 -0
- data/spec/rtsp/transport_parser_spec.rb +137 -0
- data/spec/rtsp_spec.rb +27 -0
- data/spec/spec_helper.rb +88 -0
- data/spec/support/fake_rtsp_server.rb +123 -0
- data/tasks/roodi.rake +9 -0
- data/tasks/roodi_config.yaml +14 -0
- data/tasks/stats.rake +12 -0
- metadata +280 -0
@@ -0,0 +1,306 @@
|
|
1
|
+
require_relative '../spec_helper'
|
2
|
+
require 'rtsp/response'
|
3
|
+
|
4
|
+
describe RTSP::Response do
|
5
|
+
describe "#initialize" do
|
6
|
+
it "expects a non-nil string on" do
|
7
|
+
expect { RTSP::Response.new(nil) }.to raise_exception RTSP::Error
|
8
|
+
end
|
9
|
+
|
10
|
+
it "expects a non-empty string on" do
|
11
|
+
expect { RTSP::Response.new("") }.to raise_exception RTSP::Error
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
describe "#parse_head" do
|
16
|
+
let(:head) do
|
17
|
+
head = double "head"
|
18
|
+
head.stub(:split).and_return(['', session_line])
|
19
|
+
head.stub(:each_with_index).and_yield
|
20
|
+
|
21
|
+
head
|
22
|
+
end
|
23
|
+
|
24
|
+
context "Session header contains session-id and timeout" do
|
25
|
+
let(:session_line) { "Session: 118;timeout=49" }
|
26
|
+
subject { RTSP::Response.new SETUP_RESPONSE_WITH_SESSION_TIMEOUT }
|
27
|
+
|
28
|
+
it "creates a :session reader with value being a Hash with key/value" do
|
29
|
+
subject.stub(:extract_status_line)
|
30
|
+
subject.should_receive(:create_reader).with("session",
|
31
|
+
{ session_id: 118, timeout: 49 })
|
32
|
+
subject.parse_head(head)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
context "Session header contains just session-id" do
|
37
|
+
let(:session_line) { "Session: 118" }
|
38
|
+
subject { RTSP::Response.new SETUP_RESPONSE_WITH_SESSION_TIMEOUT }
|
39
|
+
|
40
|
+
it "creates a :session reader with value being a Hash with key/value" do
|
41
|
+
subject.stub(:extract_status_line)
|
42
|
+
subject.should_receive(:create_reader).with("session",
|
43
|
+
{ session_id: 118 })
|
44
|
+
subject.parse_head(head)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
subject { RTSP::Response.new OPTIONS_RESPONSE }
|
49
|
+
|
50
|
+
it "raises when RTSP version is corrupted" do
|
51
|
+
expect { subject.parse_head "RTSP/ 200 OK\r\n" }.to raise_error RTSP::Error
|
52
|
+
end
|
53
|
+
|
54
|
+
it "raises when the response code is corrupted" do
|
55
|
+
expect { subject.parse_head "RTSP/1.0 2 OK\r\n" }.to raise_error RTSP::Error
|
56
|
+
end
|
57
|
+
|
58
|
+
it "raises when the response message is corrupted" do
|
59
|
+
expect { subject.parse_head "RTSP/1.0 200 \r\n" }.to raise_error RTSP::Error
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
describe "#parse_body" do
|
64
|
+
it "returns an SDP::Description when @content_type is 'application/sdp" do
|
65
|
+
response = RTSP::Response.new DESCRIBE_RESPONSE
|
66
|
+
sdp = SDP::Description.new
|
67
|
+
sdp.username = "me"
|
68
|
+
sdp.id = 12345
|
69
|
+
sdp.version = 12345
|
70
|
+
sdp.network_type = "IN"
|
71
|
+
sdp.address_type = "IP4"
|
72
|
+
body = response.parse_body sdp.to_s
|
73
|
+
body.class.should == SDP::Description
|
74
|
+
end
|
75
|
+
|
76
|
+
it "returns the text that was passed to it but with line feeds removed" do
|
77
|
+
response = RTSP::Response.new OPTIONS_RESPONSE
|
78
|
+
string = "hi\r\nguys\r\n\r\n"
|
79
|
+
body = response.parse_body string
|
80
|
+
body.class.should == String
|
81
|
+
body.should == string
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
describe "#to_s" do
|
86
|
+
it "returns the text that was passed in" do
|
87
|
+
response = RTSP::Response.new OPTIONS_RESPONSE
|
88
|
+
response.to_s.should == OPTIONS_RESPONSE
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
describe "#inspect" do
|
93
|
+
before do
|
94
|
+
@response = RTSP::Response.new OPTIONS_RESPONSE
|
95
|
+
end
|
96
|
+
|
97
|
+
it "contains the class name and object ID first" do
|
98
|
+
@response.inspect.should match(/^#<RTSP::Response:\d+/)
|
99
|
+
end
|
100
|
+
|
101
|
+
it "begins with <# and ends with >" do
|
102
|
+
@response.inspect.should match(/^#<.*>$/)
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
context "options" do
|
107
|
+
before do
|
108
|
+
@response = RTSP::Response.new OPTIONS_RESPONSE
|
109
|
+
end
|
110
|
+
|
111
|
+
it "returns a 200 code" do
|
112
|
+
@response.code.should == 200
|
113
|
+
end
|
114
|
+
|
115
|
+
it "returns 'OK' message" do
|
116
|
+
@response.message.should == 'OK'
|
117
|
+
end
|
118
|
+
|
119
|
+
it "returns the date header" do
|
120
|
+
@response.date.should == 'Fri, Jan 28 2011 01:14:42 GMT'
|
121
|
+
end
|
122
|
+
|
123
|
+
it "returns the supported methods in the Public header" do
|
124
|
+
@response.public.should == 'OPTIONS, DESCRIBE, SETUP, TEARDOWN, PLAY, PAUSE'
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
context "describe" do
|
129
|
+
before do
|
130
|
+
@response = RTSP::Response.new DESCRIBE_RESPONSE
|
131
|
+
end
|
132
|
+
|
133
|
+
it "returns a 200 code" do
|
134
|
+
@response.code.should == 200
|
135
|
+
end
|
136
|
+
|
137
|
+
it "returns 'OK' message" do
|
138
|
+
@response.message.should == "OK"
|
139
|
+
end
|
140
|
+
|
141
|
+
it "returns all header fields" do
|
142
|
+
@response.server.should == "DSS/5.5 (Build/489.7; Platform/Linux; Release/Darwin; )"
|
143
|
+
@response.cseq.should == 1
|
144
|
+
@response.cache_control.should == "no-cache"
|
145
|
+
@response.content_length.should == 406
|
146
|
+
@response.date.should == "Sun, 23 Jan 2011 00:36:45 GMT"
|
147
|
+
@response.expires.should == "Sun, 23 Jan 2011 00:36:45 GMT"
|
148
|
+
@response.content_type.should == "application/sdp"
|
149
|
+
@response.x_accept_retransmit.should == "our-retransmit"
|
150
|
+
@response.x_accept_dynamic_rate.should == 1
|
151
|
+
@response.content_base.should == "rtsp://64.202.98.91:554/gs.sdp/"
|
152
|
+
end
|
153
|
+
|
154
|
+
it "body is a parsed SDP::Description" do
|
155
|
+
@response.body.should be_kind_of SDP::Description
|
156
|
+
sdp_info = @response.body
|
157
|
+
sdp_info.protocol_version.should == "0"
|
158
|
+
sdp_info.name.should == "Groove Salad from SomaFM [aacPlus]"
|
159
|
+
end
|
160
|
+
end
|
161
|
+
|
162
|
+
context "setup" do
|
163
|
+
before do
|
164
|
+
@response = RTSP::Response.new SETUP_RESPONSE
|
165
|
+
end
|
166
|
+
|
167
|
+
it "returns a 200 code" do
|
168
|
+
@response.code.should == 200
|
169
|
+
end
|
170
|
+
|
171
|
+
it "returns 'OK' message" do
|
172
|
+
@response.message.should == 'OK'
|
173
|
+
end
|
174
|
+
|
175
|
+
it "returns the date header" do
|
176
|
+
@response.date.should == 'Fri, Jan 28 2011 01:14:42 GMT'
|
177
|
+
end
|
178
|
+
|
179
|
+
it "returns the supported transport" do
|
180
|
+
@response.transport.should == 'RTP/AVP;unicast;destination=10.221.222.186;source=10.221.222.235;client_port=9000-9001;server_port=6700-6701'
|
181
|
+
end
|
182
|
+
|
183
|
+
it "returns the session" do
|
184
|
+
@response.session[:session_id].should == 118
|
185
|
+
end
|
186
|
+
end
|
187
|
+
|
188
|
+
context "play" do
|
189
|
+
before do
|
190
|
+
@response = RTSP::Response.new PLAY_RESPONSE
|
191
|
+
end
|
192
|
+
|
193
|
+
it "returns a 200 code" do
|
194
|
+
@response.code.should == 200
|
195
|
+
end
|
196
|
+
|
197
|
+
it "returns 'OK' message" do
|
198
|
+
@response.message.should == 'OK'
|
199
|
+
end
|
200
|
+
|
201
|
+
it "returns the date header" do
|
202
|
+
@response.date.should == 'Fri, Jan 28 2011 01:14:42 GMT'
|
203
|
+
end
|
204
|
+
|
205
|
+
it "returns the supported range" do
|
206
|
+
@response.range.should == 'npt=0.000-'
|
207
|
+
end
|
208
|
+
|
209
|
+
it "returns the session" do
|
210
|
+
@response.session[:session_id].should == 118
|
211
|
+
end
|
212
|
+
|
213
|
+
it "returns the rtp_info" do
|
214
|
+
@response.rtp_info.should == 'url=rtsp://10.221.222.235/stream1/track1;seq=17320;rtptime=400880602'
|
215
|
+
end
|
216
|
+
end
|
217
|
+
|
218
|
+
context "teardown" do
|
219
|
+
before do
|
220
|
+
@response = RTSP::Response.new TEARDOWN_RESPONSE
|
221
|
+
end
|
222
|
+
|
223
|
+
it "returns a 200 code" do
|
224
|
+
@response.code.should == 200
|
225
|
+
end
|
226
|
+
|
227
|
+
it "returns 'OK' message" do
|
228
|
+
@response.message.should == 'OK'
|
229
|
+
end
|
230
|
+
|
231
|
+
it "returns the date header" do
|
232
|
+
@response.date.should == 'Fri, Jan 28 2011 01:14:47 GMT'
|
233
|
+
end
|
234
|
+
end
|
235
|
+
|
236
|
+
context "#parse_head" do
|
237
|
+
before do
|
238
|
+
@response = RTSP::Response.new OPTIONS_RESPONSE
|
239
|
+
end
|
240
|
+
|
241
|
+
it "extracts the RTSP version from the header" do
|
242
|
+
@response.rtsp_version.should == "1.0"
|
243
|
+
end
|
244
|
+
|
245
|
+
it "extracts the response code from the header as a Fixnum" do
|
246
|
+
@response.code.is_a?(Fixnum).should be_true
|
247
|
+
@response.code.should == 200
|
248
|
+
end
|
249
|
+
|
250
|
+
it "extracts the response message from the header" do
|
251
|
+
@response.message.should == "OK"
|
252
|
+
end
|
253
|
+
|
254
|
+
it "returns empty value string when header has no value" do
|
255
|
+
response = RTSP::Response.new NO_CSEQ_VALUE_RESPONSE
|
256
|
+
response.parse_head NO_CSEQ_VALUE_RESPONSE
|
257
|
+
response.instance_variable_get(:@cseq).should == ""
|
258
|
+
end
|
259
|
+
end
|
260
|
+
|
261
|
+
describe "#split_head_and_body_from" do
|
262
|
+
it "splits responses with headers and no body" do
|
263
|
+
response = RTSP::Response.new OPTIONS_RESPONSE
|
264
|
+
head_and_body = response.split_head_and_body_from OPTIONS_RESPONSE
|
265
|
+
head_and_body.first.should == %Q{RTSP/1.0 200 OK\r
|
266
|
+
CSeq: 1\r
|
267
|
+
Date: Fri, Jan 28 2011 01:14:42 GMT\r
|
268
|
+
Public: OPTIONS, DESCRIBE, SETUP, TEARDOWN, PLAY, PAUSE\r
|
269
|
+
}
|
270
|
+
end
|
271
|
+
|
272
|
+
it "splits responses with headers and body" do
|
273
|
+
response = RTSP::Response.new DESCRIBE_RESPONSE
|
274
|
+
head_and_body = response.split_head_and_body_from DESCRIBE_RESPONSE
|
275
|
+
head_and_body.first.should == %{RTSP/1.0 200 OK\r
|
276
|
+
Server: DSS/5.5 (Build/489.7; Platform/Linux; Release/Darwin; )\r
|
277
|
+
Cseq: 1\r
|
278
|
+
Cache-Control: no-cache\r
|
279
|
+
Content-length: 406\r
|
280
|
+
Date: Sun, 23 Jan 2011 00:36:45 GMT\r
|
281
|
+
Expires: Sun, 23 Jan 2011 00:36:45 GMT\r
|
282
|
+
Content-Type: application/sdp\r
|
283
|
+
x-Accept-Retransmit: our-retransmit\r
|
284
|
+
x-Accept-Dynamic-Rate: 1\r
|
285
|
+
Content-Base: rtsp://64.202.98.91:554/gs.sdp/}
|
286
|
+
|
287
|
+
head_and_body.last.should == %{\r
|
288
|
+
v=0\r
|
289
|
+
o=- 545877020 467920391 IN IP4 127.0.0.1\r
|
290
|
+
s=Groove Salad from SomaFM [aacPlus]\r
|
291
|
+
i=Downtempo Ambient Groove\r
|
292
|
+
c=IN IP4 0.0.0.0\r
|
293
|
+
t=0 0\r
|
294
|
+
a=x-qt-text-cmt:Orban Opticodec-PC\r
|
295
|
+
a=x-qt-text-nam:Groove Salad from SomaFM [aacPlus]\r
|
296
|
+
a=x-qt-text-inf:Downtempo Ambient Groove\r
|
297
|
+
a=control:*\r
|
298
|
+
m=audio 0 RTP/AVP 96\r
|
299
|
+
b=AS:48\r
|
300
|
+
a=rtpmap:96 MP4A-LATM/44100/2\r
|
301
|
+
a=fmtp:96 cpresent=0;config=400027200000\r
|
302
|
+
a=control:trackID=1\r
|
303
|
+
}
|
304
|
+
end
|
305
|
+
end
|
306
|
+
end
|
@@ -0,0 +1,137 @@
|
|
1
|
+
require_relative '../spec_helper'
|
2
|
+
require 'rtsp/transport_parser'
|
3
|
+
|
4
|
+
describe RTSP::TransportParser do
|
5
|
+
let(:result) { subject.parse transport }
|
6
|
+
|
7
|
+
describe :transport_specifier do
|
8
|
+
context "RTP/AVP" do
|
9
|
+
let(:transport) { "RTP/AVP" }
|
10
|
+
specify { result[:streaming_protocol].should == 'RTP' }
|
11
|
+
specify { result[:profile].should == 'AVP' }
|
12
|
+
end
|
13
|
+
|
14
|
+
context "RTP/AVP/TCP" do
|
15
|
+
let(:transport) { "RTP/AVP/TCP" }
|
16
|
+
specify { result[:streaming_protocol].should == 'RTP' }
|
17
|
+
specify { result[:profile].should == 'AVP' }
|
18
|
+
specify { result[:transport_protocol].should == 'TCP' }
|
19
|
+
end
|
20
|
+
|
21
|
+
context "rtp/avp/tcp" do
|
22
|
+
let(:transport) { "rtp/avp/tcp" }
|
23
|
+
specify { result[:streaming_protocol].should == 'rtp' }
|
24
|
+
specify { result[:profile].should == 'avp' }
|
25
|
+
specify { result[:transport_protocol].should == 'tcp' }
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
describe :broadcast_type do
|
30
|
+
context "RTP/AVP;unicast" do
|
31
|
+
let(:transport) { "RTP/AVP;unicast" }
|
32
|
+
specify { result[:broadcast_type].should == 'unicast' }
|
33
|
+
end
|
34
|
+
|
35
|
+
context "RTP/AVP;multicast" do
|
36
|
+
let(:transport) { "RTP/AVP;multicast" }
|
37
|
+
specify { result[:broadcast_type].should == 'multicast' }
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
describe :destination do
|
42
|
+
context "RTP/AVP;multicast;destination=224.2.0.1" do
|
43
|
+
let(:transport) { "RTP/AVP;multicast;destination=224.2.0.1" }
|
44
|
+
specify { result[:destination].should == '224.2.0.1' }
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
describe :source do
|
49
|
+
context "RTP/AVP;multicast;destination=22.2.0.1;source=10.0.0.10" do
|
50
|
+
let(:transport) { "RTP/AVP;multicast;destination=22.2.0.1;source=10.0.0.10" }
|
51
|
+
specify { result[:source].should == '10.0.0.10' }
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
describe :client_port do
|
56
|
+
context "RTP/AVP;unicast;client_port=9000-9001" do
|
57
|
+
let(:transport) { "RTP/AVP;unicast;client_port=9000-9001" }
|
58
|
+
specify { result[:client_port][:rtp].should == '9000' }
|
59
|
+
specify { result[:client_port][:rtcp].should == '9001' }
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
describe :server_port do
|
64
|
+
context "RTP/AVP/UCP;unicast;client_port=3058-3059;server_port=5002-5003" do
|
65
|
+
let(:transport) { "RTP/AVP/UCP;unicast;client_port=3058-3059;server_port=5002-5003" }
|
66
|
+
specify { result[:server_port][:rtp].should == "5002" }
|
67
|
+
specify { result[:server_port][:rtcp].should == "5003" }
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
describe :interleaved do
|
72
|
+
context "RTP/AVP/TCP;unicast;interleaved=0-1" do
|
73
|
+
let(:transport) { "RTP/AVP/TCP;unicast;interleaved=0-1" }
|
74
|
+
specify { result[:interleaved][:rtp_channel].should == '0' }
|
75
|
+
specify { result[:interleaved][:rtcp_channel].should == '1' }
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
describe :ttl do
|
80
|
+
context 'RTP/AVP;unicast;ttl=127' do
|
81
|
+
let(:transport) { 'RTP/AVP;unicast;ttl=127' }
|
82
|
+
specify { result[:ttl].should == "127" }
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
describe :port do
|
87
|
+
context 'RTP/AVP;unicast;port=1234' do
|
88
|
+
let(:transport) { 'RTP/AVP;unicast;port=1234' }
|
89
|
+
specify { result[:port][:rtp].should == "1234" }
|
90
|
+
specify { result[:port][:rtcp].should be_nil }
|
91
|
+
end
|
92
|
+
|
93
|
+
context 'RTP/AVP;unicast;port=1234-1235' do
|
94
|
+
let(:transport) { 'RTP/AVP;unicast;port=1234-1235' }
|
95
|
+
specify { result[:port][:rtp].should == "1234" }
|
96
|
+
specify { result[:port][:rtcp].should == "1235" }
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
describe :ssrc do
|
101
|
+
context 'RTP/AVP;unicast;ssrc=ABCD1234' do
|
102
|
+
let(:transport) { 'RTP/AVP;unicast;ssrc=ABCD1234' }
|
103
|
+
specify { result[:ssrc].should == "ABCD1234" }
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
describe :channel do
|
108
|
+
context 'RTP/AVP;unicast;channel=RTP' do
|
109
|
+
let(:transport) { 'RTP/AVP;unicast;channel=RTP' }
|
110
|
+
specify { result[:channel].should == "RTP" }
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
describe :address do
|
115
|
+
context 'RTP/AVP;unicast;address=192.168.14.18' do
|
116
|
+
let(:transport) { 'RTP/AVP;unicast;address=192.168.14.18' }
|
117
|
+
specify { result[:address].should == "192.168.14.18" }
|
118
|
+
end
|
119
|
+
|
120
|
+
context 'RTP/AVP;unicast;address=mycomputer.com' do
|
121
|
+
let(:transport) { 'RTP/AVP;unicast;address=mycomputer.com' }
|
122
|
+
specify { result[:address].should == "mycomputer.com" }
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
describe :mode do
|
127
|
+
context 'RTP/AVP;unicast;mode="PLAY"' do
|
128
|
+
let(:transport) { 'RTP/AVP;unicast;mode="PLAY"' }
|
129
|
+
specify { result[:mode].should == "PLAY" }
|
130
|
+
end
|
131
|
+
|
132
|
+
context 'with ttl=127;mode=RECORD' do
|
133
|
+
let(:transport) { 'RTP/AVP;unicast;mode=RECORD' }
|
134
|
+
specify { result[:mode].should == "RECORD" }
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|
data/spec/rtsp_spec.rb
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
require_relative 'spec_helper'
|
2
|
+
|
3
|
+
describe Kernel do
|
4
|
+
def self.get_requires
|
5
|
+
Dir[File.dirname(__FILE__) + '/../lib/**/*.rb']
|
6
|
+
end
|
7
|
+
|
8
|
+
# Try to require each of the files in RTSP
|
9
|
+
get_requires.each do |r|
|
10
|
+
it "should require #{r}" do
|
11
|
+
|
12
|
+
# A require returns true if it was required, false if it had already been
|
13
|
+
# required, and nil if it couldn't require.
|
14
|
+
Kernel.require(r.to_s).should_not be_nil
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
describe RTSP do
|
20
|
+
it "should have a VERSION constant" do
|
21
|
+
RTSP.const_defined?('VERSION').should be_true
|
22
|
+
end
|
23
|
+
|
24
|
+
it "has version 0.4.0" do
|
25
|
+
RTSP::VERSION.should == '0.4.0'
|
26
|
+
end
|
27
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,88 @@
|
|
1
|
+
require 'simplecov'
|
2
|
+
|
3
|
+
SimpleCov.start do
|
4
|
+
add_filter "/spec/"
|
5
|
+
end
|
6
|
+
|
7
|
+
require 'rspec'
|
8
|
+
$:.unshift(File.dirname(__FILE__) + '/../lib')
|
9
|
+
require 'rtsp'
|
10
|
+
|
11
|
+
OPTIONS_RESPONSE = %Q{RTSP/1.0 200 OK\r
|
12
|
+
CSeq: 1\r
|
13
|
+
Date: Fri, Jan 28 2011 01:14:42 GMT\r
|
14
|
+
Public: OPTIONS, DESCRIBE, SETUP, TEARDOWN, PLAY, PAUSE\r
|
15
|
+
}
|
16
|
+
|
17
|
+
DESCRIBE_RESPONSE = %{RTSP/1.0 200 OK\r
|
18
|
+
Server: DSS/5.5 (Build/489.7; Platform/Linux; Release/Darwin; )\r
|
19
|
+
Cseq: 1\r
|
20
|
+
Cache-Control: no-cache\r
|
21
|
+
Content-length: 406\r
|
22
|
+
Date: Sun, 23 Jan 2011 00:36:45 GMT\r
|
23
|
+
Expires: Sun, 23 Jan 2011 00:36:45 GMT\r
|
24
|
+
Content-Type: application/sdp\r
|
25
|
+
x-Accept-Retransmit: our-retransmit\r
|
26
|
+
x-Accept-Dynamic-Rate: 1\r
|
27
|
+
Content-Base: rtsp://64.202.98.91:554/gs.sdp/\r
|
28
|
+
\r\n\r
|
29
|
+
v=0\r
|
30
|
+
o=- 545877020 467920391 IN IP4 127.0.0.1\r
|
31
|
+
s=Groove Salad from SomaFM [aacPlus]\r
|
32
|
+
i=Downtempo Ambient Groove\r
|
33
|
+
c=IN IP4 0.0.0.0\r
|
34
|
+
t=0 0\r
|
35
|
+
a=x-qt-text-cmt:Orban Opticodec-PC\r
|
36
|
+
a=x-qt-text-nam:Groove Salad from SomaFM [aacPlus]\r
|
37
|
+
a=x-qt-text-inf:Downtempo Ambient Groove\r
|
38
|
+
a=control:*\r
|
39
|
+
m=audio 0 RTP/AVP 96\r
|
40
|
+
b=AS:48\r
|
41
|
+
a=rtpmap:96 MP4A-LATM/44100/2\r
|
42
|
+
a=fmtp:96 cpresent=0;config=400027200000\r
|
43
|
+
a=control:trackID=1\r
|
44
|
+
}
|
45
|
+
|
46
|
+
SETUP_RESPONSE = %Q{RTSP/1.0 200 OK\r
|
47
|
+
CSeq: 1\r
|
48
|
+
Date: Fri, Jan 28 2011 01:14:42 GMT\r
|
49
|
+
Transport: RTP/AVP;unicast;destination=10.221.222.186;source=10.221.222.235;client_port=9000-9001;server_port=6700-6701\r
|
50
|
+
Session: 118\r
|
51
|
+
\r}
|
52
|
+
|
53
|
+
SETUP_RESPONSE_WITH_SESSION_TIMEOUT = %Q{RTSP/1.0 200 OK\r
|
54
|
+
CSeq: 1\r
|
55
|
+
Date: Fri, Jan 28 2011 01:14:42 GMT\r
|
56
|
+
Transport: RTP/AVP;unicast;destination=10.221.222.186;source=10.221.222.235;client_port=9000-9001;server_port=6700-6701\r
|
57
|
+
Session: 118;timeout=49\r
|
58
|
+
\r}
|
59
|
+
|
60
|
+
PLAY_RESPONSE = %Q{RTSP/1.0 200 OK\r
|
61
|
+
CSeq: 1\r
|
62
|
+
Date: Fri, Jan 28 2011 01:14:42 GMT\r
|
63
|
+
Range: npt=0.000-\r
|
64
|
+
Session: 118\r
|
65
|
+
RTP-Info: url=rtsp://10.221.222.235/stream1/track1;seq=17320;rtptime=400880602\r
|
66
|
+
\r}
|
67
|
+
|
68
|
+
TEARDOWN_RESPONSE = %Q{RTSP/1.0 200 OK\r
|
69
|
+
CSeq: 1\r
|
70
|
+
Date: Fri, Jan 28 2011 01:14:47 GMT\r
|
71
|
+
\r}
|
72
|
+
|
73
|
+
NO_CSEQ_VALUE_RESPONSE = %Q{ RTSP/1.0 460 Only Aggregate Option Allowed\r
|
74
|
+
Server: DSS/5.5 (Build/489.7; Platform/Linux; Release/Darwin; )\r
|
75
|
+
Cseq: \r
|
76
|
+
Connection: Close\r
|
77
|
+
\r}
|
78
|
+
|
79
|
+
|
80
|
+
# Define #describe so when RTSP::Message calls #method_missing, RSpec doesn't
|
81
|
+
# get in the way (and cause tests to fail).
|
82
|
+
module RTSP
|
83
|
+
class Message
|
84
|
+
def self.describe request_uri
|
85
|
+
self.new(:describe, request_uri)
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
@@ -0,0 +1,123 @@
|
|
1
|
+
require 'time'
|
2
|
+
|
3
|
+
class FakeRTSPServer
|
4
|
+
def initialize(*args)
|
5
|
+
end
|
6
|
+
|
7
|
+
def send(*args)
|
8
|
+
message = args.first
|
9
|
+
message =~ /^(\w+) .+CSeq: (\S+)/m
|
10
|
+
@message_type = $1.downcase
|
11
|
+
@cseq = $2
|
12
|
+
@session = 1234567890
|
13
|
+
end
|
14
|
+
|
15
|
+
def recvfrom(size)
|
16
|
+
response = eval @message_type
|
17
|
+
[response]
|
18
|
+
end
|
19
|
+
|
20
|
+
def options
|
21
|
+
message = "RTSP/1.0 200 OK\r\n"
|
22
|
+
message << "CSeq: #{@cseq}\r\n"
|
23
|
+
message << "Date: #{Time.now.httpdate}\r\n"
|
24
|
+
message << "Public: DESCRIBE, SETUP, TEARDOWN, PLAY, PAUSE\r\n"
|
25
|
+
message << "\r\n"
|
26
|
+
end
|
27
|
+
|
28
|
+
def describe
|
29
|
+
message = "RTSP/1.0 200 OK\r\n"
|
30
|
+
message << "CSeq: #{@cseq}\r\n"
|
31
|
+
message << "Server: DSS/5.5 (Build/489.7; Platform/Linux; Release/Darwin; )\r\n"
|
32
|
+
message << "Cache-Control: no-cache\r\n"
|
33
|
+
message << "Content-length: 380\r\n"
|
34
|
+
message << "Date: Tue, 15 Mar 2011 01:28:57 GMT\r\n"
|
35
|
+
message << "Expires: Tue, 15 Mar 2011 01:28:57 GMT\r\n"
|
36
|
+
message << "Content-Type: application/sdp\r\n"
|
37
|
+
message << "x-Accept-Retransmit: our-retransmit\r\n"
|
38
|
+
message << "x-Accept-Dynamic-Rate: 1\r\n"
|
39
|
+
message << "Content-Base: rtsp://64.202.98.91:554/sa.sdp/\r\n"
|
40
|
+
message << "\r\n"
|
41
|
+
message << "v=\r\n"
|
42
|
+
message << "o=- 1905836198 1274535354 IN IP4 127.0.0.1\r\n"
|
43
|
+
message << "s=Secret Agent from SomaFM\r\n"
|
44
|
+
message << "i=Downtempo Spy Lounge\r\n"
|
45
|
+
message << "c=IN IP4 0.0.0.0\r\n"
|
46
|
+
message << "t=0 0\r\n"
|
47
|
+
message << "a=x-qt-text-cmt:Orban Opticodec-PC\r\n"
|
48
|
+
message << "a=x-qt-text-nam:Secret Agent from SomaFM\r\n"
|
49
|
+
message << "a=x-qt-text-inf:Downtempo Spy Lounge\r\n"
|
50
|
+
message << "a=control:*\r\n"
|
51
|
+
message << "m=audio 0 RTP/AVP 96\r\n"
|
52
|
+
message << "b=AS:40\r\n"
|
53
|
+
message << "a=rtpmap:96 MP4A-LATM/44100/2\r\n"
|
54
|
+
message << "a=fmtp:96 cpresent=0;config=400027200000\r\n"
|
55
|
+
message << "a=control:trackID=1\r\n"
|
56
|
+
message << "\r\n"
|
57
|
+
end
|
58
|
+
|
59
|
+
def announce
|
60
|
+
message = "RTSP/1.0 200 OK\r\n"
|
61
|
+
message << "CSeq: #{@cseq}\r\n"
|
62
|
+
message << "\r\n"
|
63
|
+
end
|
64
|
+
|
65
|
+
def setup
|
66
|
+
%Q{RTSP/1.0 200 OK\r
|
67
|
+
CSeq: #{@cseq}\r
|
68
|
+
Date: #{Time.now.httpdate}\r
|
69
|
+
Transport: RTP/AVP;unicast;destination=10.221.222.186;source=10.221.222.235;client_port=9000-9001;server_port=6700-6701\r
|
70
|
+
Session: #{@session}\r
|
71
|
+
\r\n}
|
72
|
+
end
|
73
|
+
|
74
|
+
def play
|
75
|
+
%Q{RTSP/1.0 200 OK\r
|
76
|
+
CSeq: #{@cseq}\r
|
77
|
+
Date: #{Time.now.httpdate}\r
|
78
|
+
Range: npt=0.000-\r
|
79
|
+
Session: #{@session}\r
|
80
|
+
RTP-Info: url=rtsp://10.221.222.235/stream1/track1;seq=17320;rtptime=400880602\r
|
81
|
+
\r\n}
|
82
|
+
end
|
83
|
+
|
84
|
+
def pause
|
85
|
+
%Q{RTSP/1.0 200 OK\r
|
86
|
+
CSeq: #{@cseq}\r
|
87
|
+
Date: #{Time.now.httpdate}\r
|
88
|
+
\r\n}
|
89
|
+
end
|
90
|
+
|
91
|
+
def teardown
|
92
|
+
%Q{RTSP/1.0 200 OK\r
|
93
|
+
CSeq: #{@cseq}\r
|
94
|
+
Server: DSS/5.5 (Build/489.7; Platform/Linux; Release/Darwin; )\r
|
95
|
+
Session: #{@session}\r
|
96
|
+
Connection: Close\r
|
97
|
+
\r\n}
|
98
|
+
end
|
99
|
+
|
100
|
+
def get_parameter
|
101
|
+
%Q{RTSP/1.0 200 OK\r
|
102
|
+
CSeq: #{@cseq}\r
|
103
|
+
Content-Length: 8\r
|
104
|
+
\r
|
105
|
+
response\r
|
106
|
+
\r\n}
|
107
|
+
end
|
108
|
+
|
109
|
+
def set_parameter
|
110
|
+
%Q{RTSP/1.0 200 OK\r
|
111
|
+
CSeq: #{@cseq}\r
|
112
|
+
Content-Length: 8\r
|
113
|
+
\r
|
114
|
+
response\r
|
115
|
+
\r\n}
|
116
|
+
end
|
117
|
+
|
118
|
+
def record
|
119
|
+
%Q{RTSP/1.0 200 OK\r
|
120
|
+
CSeq: #{@cseq}\r
|
121
|
+
\r\n}
|
122
|
+
end
|
123
|
+
end
|
data/tasks/roodi.rake
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
---
|
2
|
+
AssignmentInConditionalCheck: { }
|
3
|
+
CaseMissingElseCheck: { }
|
4
|
+
ClassLineCountCheck: { line_count: 300 }
|
5
|
+
ClassNameCheck: { pattern: !ruby/regexp '/^[A-Z][a-zA-Z0-9]*$/' }
|
6
|
+
CyclomaticComplexityBlockCheck: { complexity: 4 }
|
7
|
+
CyclomaticComplexityMethodCheck: { complexity: 8 }
|
8
|
+
EmptyRescueBodyCheck: { }
|
9
|
+
ForLoopCheck: { }
|
10
|
+
MethodLineCountCheck: { line_count: 30 }
|
11
|
+
MethodNameCheck: { pattern: !ruby/regexp '/^[_a-z<>=\[\]|+-\/\*`]+[_a-z0-9_<>=~@\[\]]*[=!\?]?$/' }
|
12
|
+
ModuleLineCountCheck: { line_count: 300 }
|
13
|
+
ModuleNameCheck: { pattern: !ruby/regexp '/^[A-Z][a-zA-Z0-9]*$/' }
|
14
|
+
ParameterNumberCheck: { parameter_count: 5 }
|