rtsp_server 0.0.2-universal-java
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.
- 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 +311 -0
- data/lib/rtsp/socat_streaming.rb +320 -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 +44 -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 }
|