rtsp 0.3.0 → 0.4.0
Sign up to get free protection for your applications and to get access to all the features.
- data/ChangeLog.rdoc +19 -0
- data/README.rdoc +7 -7
- data/features/control_streams_as_client.feature +1 -1
- data/features/step_definitions/client_changes_state_steps.rb +7 -1
- data/features/step_definitions/client_requests_steps.rb +19 -25
- data/features/support/env.rb +11 -0
- data/lib/rtsp/client.rb +8 -8
- data/lib/rtsp/message.rb +11 -11
- data/lib/rtsp/response.rb +15 -3
- data/lib/rtsp/transport_parser.rb +49 -17
- data/lib/rtsp/version.rb +1 -1
- data/rtsp.gemspec +1 -1
- data/spec/rtsp/client_spec.rb +6 -6
- data/spec/rtsp/message_spec.rb +36 -36
- data/spec/rtsp/response_spec.rb +73 -42
- data/spec/rtsp/transport_parser_spec.rb +109 -26
- data/spec/rtsp_spec.rb +2 -2
- data/spec/spec_helper.rb +60 -53
- metadata +25 -25
data/ChangeLog.rdoc
CHANGED
@@ -1,3 +1,22 @@
|
|
1
|
+
=== 0.4.0 / 2012-03-08
|
2
|
+
|
3
|
+
* gh-11: Transport header parser bolstering:
|
4
|
+
* values that should be caps, will parse OK as lowercase now.
|
5
|
+
* :broadcast_type now parses 'multicast'.
|
6
|
+
* :destination now returns the value of the destination instead of
|
7
|
+
"destination=x.x.x.x"
|
8
|
+
* :source now returns the value of the source instead of "source=x.x.x.x"
|
9
|
+
* Added parsing for fields:
|
10
|
+
* ttl
|
11
|
+
* port
|
12
|
+
* ssrc
|
13
|
+
* channel
|
14
|
+
* address
|
15
|
+
* mode
|
16
|
+
* gh-10: Session header now detects timeout value. This means that where use
|
17
|
+
of the value extracted from this header used to be a simple Integer, now you
|
18
|
+
have a session Hash with :session_id and :timeout keys.
|
19
|
+
|
1
20
|
=== 0.3.0 / 2012-03-02
|
2
21
|
|
3
22
|
* Extracted RTP-esque functionality to an +rtp+ gem, on which this gem is now
|
data/README.rdoc
CHANGED
@@ -10,7 +10,7 @@ call, but we'll get there. ...eventually.
|
|
10
10
|
|
11
11
|
For more information
|
12
12
|
|
13
|
-
RTSP: http://
|
13
|
+
RTSP: http://tools.ietf.org/html/rfc2326
|
14
14
|
|
15
15
|
SDP: http://tools.ietf.org/html/rfc4566
|
16
16
|
|
@@ -61,10 +61,10 @@ gets implemented.
|
|
61
61
|
client.cseq # => 3
|
62
62
|
|
63
63
|
response = client.setup(client.media_control_tracks.first)
|
64
|
-
response.session
|
65
|
-
client.session
|
66
|
-
client.cseq
|
67
|
-
client.session_state
|
64
|
+
response.session[:session_id] # => 7098486223178290313
|
65
|
+
client.session[:session_id] # => 7098486223178290313
|
66
|
+
client.cseq # => 4
|
67
|
+
client.session_state # => :ready
|
68
68
|
|
69
69
|
|
70
70
|
response = client.play(client.aggregate_control_track)
|
@@ -82,8 +82,8 @@ gets implemented.
|
|
82
82
|
sleep 2
|
83
83
|
|
84
84
|
client.teardown(client.aggregate_control_track)
|
85
|
-
client.session
|
86
|
-
client.session_state
|
85
|
+
client.session[:session_id] # => 0
|
86
|
+
client.session_state # => :init
|
87
87
|
|
88
88
|
# Check the streamed file's contents
|
89
89
|
puts client.capturer.rtp_file # => (Lots of data)
|
@@ -2,7 +2,7 @@ Feature: Control stream from an RTSP server
|
|
2
2
|
As an RTSP consumer
|
3
3
|
I want to be able to control RTSP streams from a server
|
4
4
|
So that I can view its contents as I desire
|
5
|
-
|
5
|
+
|
6
6
|
@wip
|
7
7
|
Scenario: Play single stream
|
8
8
|
Given an RTSP server at "10.221.222.235" and port 9010 and URL ""
|
@@ -25,6 +25,7 @@ end
|
|
25
25
|
When /^I issue an "([^"]*)" request with "([^"]*)"$/ do |request_type, params|
|
26
26
|
unless @client
|
27
27
|
url = "rtsp://fake-rtsp-server/some_path"
|
28
|
+
|
28
29
|
@client = RTSP::Client.new(url) do |connection|
|
29
30
|
connection.socket = @fake_server
|
30
31
|
connection.timeout = 3
|
@@ -34,7 +35,12 @@ When /^I issue an "([^"]*)" request with "([^"]*)"$/ do |request_type, params|
|
|
34
35
|
@initial_state = @client.session_state
|
35
36
|
params = params.empty? ? {} : params
|
36
37
|
|
37
|
-
|
38
|
+
if request_type == 'play'
|
39
|
+
@client.setup(url)
|
40
|
+
@client.play(params)
|
41
|
+
else
|
42
|
+
@client.send(request_type.to_sym, params)
|
43
|
+
end
|
38
44
|
end
|
39
45
|
|
40
46
|
Then /^the state stays the same$/ do
|
@@ -1,5 +1,6 @@
|
|
1
1
|
Given /^a known RTSP server$/ do
|
2
2
|
@server_url = "rtsp://64.202.98.91:554/sa.sdp"
|
3
|
+
|
3
4
|
@client = RTSP::Client.new(@server_url) do |connection|
|
4
5
|
connection.socket = @fake_server
|
5
6
|
connection.timeout = 3
|
@@ -7,13 +8,12 @@ Given /^a known RTSP server$/ do
|
|
7
8
|
end
|
8
9
|
|
9
10
|
When /^I make a "([^"]*)" request$/ do |method|
|
10
|
-
=
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
@response = @client.send(method.to_sym)
|
11
|
+
@response = if method == 'announce'
|
12
|
+
@client.setup(@server_url)
|
13
|
+
@client.announce(@server_url, @client.describe)
|
14
|
+
else
|
15
|
+
@client.send(method.to_sym)
|
16
|
+
end
|
17
17
|
end
|
18
18
|
|
19
19
|
When /^I make a "([^"]*)" request with headers:$/ do |method, headers_table|
|
@@ -25,18 +25,15 @@ When /^I make a "([^"]*)" request with headers:$/ do |method, headers_table|
|
|
25
25
|
headers[header_type] = hash["value"]
|
26
26
|
end
|
27
27
|
|
28
|
-
=
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
)
|
34
|
-
=end
|
35
|
-
@response = @client.send(method.to_sym, headers)
|
28
|
+
@response = if method == 'setup'
|
29
|
+
@client.setup(@server_url, headers)
|
30
|
+
else
|
31
|
+
@client.send(method.to_sym, headers)
|
32
|
+
end
|
36
33
|
end
|
37
34
|
|
38
35
|
Then /^I should receive an RTSP response to that OPTIONS request$/ do
|
39
|
-
@response.
|
36
|
+
@response.should be_a RTSP::Response
|
40
37
|
@response.code.should == 200
|
41
38
|
@response.message.should == "OK"
|
42
39
|
@response.cseq.should == 1
|
@@ -45,30 +42,27 @@ Then /^I should receive an RTSP response to that OPTIONS request$/ do
|
|
45
42
|
end
|
46
43
|
|
47
44
|
Then /^I should receive an RTSP response to that DESCRIBE request$/ do
|
48
|
-
@response.
|
45
|
+
@response.should be_a RTSP::Response
|
49
46
|
@response.code.should == 200
|
50
47
|
@response.message.should == "OK"
|
51
48
|
@response.server.should == "DSS/5.5 (Build/489.7; Platform/Linux; Release/Darwin; )"
|
52
49
|
@response.cseq.should == 1
|
53
|
-
@response.body.
|
54
|
-
@response.body.protocol_version.should == "0"
|
50
|
+
@response.body.should be_a SDP::Description
|
55
51
|
@response.body.username.should == "-"
|
56
52
|
end
|
57
53
|
|
58
54
|
Then /^I should receive an RTSP response to that ANNOUNCE request$/ do
|
59
|
-
@response.
|
55
|
+
@response.should be_a RTSP::Response
|
60
56
|
@response.code.should == 200
|
61
57
|
@response.message.should == "OK"
|
62
|
-
@response.
|
63
|
-
@response.cseq.should == 1
|
58
|
+
@response.cseq.should == 2
|
64
59
|
end
|
65
60
|
|
66
61
|
Then /^I should receive an RTSP response to that SETUP request$/ do
|
67
|
-
@response.
|
62
|
+
@response.should be_a RTSP::Response
|
68
63
|
@response.code.should == 200
|
69
64
|
@response.message.should == "OK"
|
70
|
-
@response.server.should == "DSS/5.5 (Build/489.7; Platform/Linux; Release/Darwin; )"
|
71
65
|
@response.cseq.should == 1
|
72
|
-
@response.transport.should
|
66
|
+
@response.transport.should match(/RTP\/AVP;unicast;destination=\S+;source=\S+;client_port=\d+-\d+;server_port=\d+-\d+/)
|
73
67
|
end
|
74
68
|
|
data/features/support/env.rb
CHANGED
@@ -37,3 +37,14 @@ a=rtpmap:96 MP4A-LATM/44100/2\r
|
|
37
37
|
a=fmtp:96 cpresent=0;config=400027200000\r
|
38
38
|
a=control:trackID=1\r
|
39
39
|
\r\n}
|
40
|
+
|
41
|
+
|
42
|
+
# Define #describe so when RTSP::Message calls #method_missing, RSpec doesn't
|
43
|
+
# get in the way (and cause tests to fail).
|
44
|
+
module RTSP
|
45
|
+
class Message
|
46
|
+
def self.describe request_uri
|
47
|
+
self.new(:describe, request_uri)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
data/lib/rtsp/client.rb
CHANGED
@@ -283,7 +283,7 @@ module RTSP
|
|
283
283
|
# @see http://tools.ietf.org/html/rfc2326#page-34 RFC 2326, Section 10.5.
|
284
284
|
def play(track, additional_headers={})
|
285
285
|
message = RTSP::Message.play(track).with_headers({
|
286
|
-
cseq: @cseq, session: @session })
|
286
|
+
cseq: @cseq, session: @session[:session_id] })
|
287
287
|
message.add_headers additional_headers
|
288
288
|
|
289
289
|
request(message) do
|
@@ -313,7 +313,7 @@ module RTSP
|
|
313
313
|
# @see http://tools.ietf.org/html/rfc2326#page-36 RFC 2326, Section 10.6.
|
314
314
|
def pause(track, additional_headers={})
|
315
315
|
message = RTSP::Message.pause(track).with_headers({
|
316
|
-
cseq: @cseq, session: @session })
|
316
|
+
cseq: @cseq, session: @session[:session_id] })
|
317
317
|
message.add_headers additional_headers
|
318
318
|
|
319
319
|
request(message) do
|
@@ -332,7 +332,7 @@ module RTSP
|
|
332
332
|
# @see http://tools.ietf.org/html/rfc2326#page-37 RFC 2326, Section 10.7.
|
333
333
|
def teardown(track, additional_headers={})
|
334
334
|
message = RTSP::Message.teardown(track).with_headers({
|
335
|
-
cseq: @cseq, session: @session })
|
335
|
+
cseq: @cseq, session: @session[:session_id] })
|
336
336
|
message.add_headers additional_headers
|
337
337
|
|
338
338
|
request(message) do
|
@@ -350,7 +350,7 @@ module RTSP
|
|
350
350
|
# +@session_state+ is set to +:init+; +@session+ is set to 0.
|
351
351
|
def reset_state
|
352
352
|
@session_state = :init
|
353
|
-
@session
|
353
|
+
@session = {}
|
354
354
|
end
|
355
355
|
|
356
356
|
# Sends the GET_PARAMETERS request.
|
@@ -393,7 +393,7 @@ module RTSP
|
|
393
393
|
# @see http://tools.ietf.org/html/rfc2326#page-39 RFC 2326, Section 10.11.
|
394
394
|
def record(track, additional_headers={})
|
395
395
|
message = RTSP::Message.record(track).with_headers({
|
396
|
-
cseq: @cseq, session: @session })
|
396
|
+
cseq: @cseq, session: @session[:session_id] })
|
397
397
|
message.add_headers additional_headers
|
398
398
|
|
399
399
|
request(message) { @session_state = :recording }
|
@@ -438,7 +438,7 @@ module RTSP
|
|
438
438
|
#
|
439
439
|
# @raise [RTSP::Error] Raises if @session isn't set.
|
440
440
|
def ensure_session
|
441
|
-
|
441
|
+
if @session.empty? || @session[:session_id] <= 0
|
442
442
|
raise RTSP::Error, "Session number not retrieved from server yet. Run SETUP first."
|
443
443
|
end
|
444
444
|
end
|
@@ -500,8 +500,8 @@ module RTSP
|
|
500
500
|
# @raise [RTSP::Error] If the server returns a Session value that's different
|
501
501
|
# from what the client sent.
|
502
502
|
def compare_session_number server_session
|
503
|
-
if @session != server_session
|
504
|
-
message = "Session number mismatch. Client: #{@session}, Server: #{server_session}"
|
503
|
+
if @session[:session_id] != server_session
|
504
|
+
message = "Session number mismatch. Client: #{@session[:session_id]}, Server: #{server_session}"
|
505
505
|
raise RTSP::Error, message
|
506
506
|
end
|
507
507
|
end
|
data/lib/rtsp/message.rb
CHANGED
@@ -26,17 +26,17 @@ module RTSP
|
|
26
26
|
"RubyRTSP/#{RTSP::VERSION} (Ruby #{RUBY_VERSION}-p#{RUBY_PATCHLEVEL})"
|
27
27
|
|
28
28
|
@message_types = [
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
29
|
+
:announce,
|
30
|
+
:describe,
|
31
|
+
:get_parameter,
|
32
|
+
:options,
|
33
|
+
:play,
|
34
|
+
:pause,
|
35
|
+
:record,
|
36
|
+
:redirect,
|
37
|
+
:set_parameter,
|
38
|
+
:setup,
|
39
|
+
:teardown
|
40
40
|
]
|
41
41
|
|
42
42
|
# TODO: define #describe somewhere so I can actually test that method.
|
data/lib/rtsp/response.rb
CHANGED
@@ -92,7 +92,17 @@ module RTSP
|
|
92
92
|
next
|
93
93
|
end
|
94
94
|
|
95
|
-
if line.include? ": "
|
95
|
+
if line.include? "Session: "
|
96
|
+
value = {}
|
97
|
+
line =~ /Session: (\d+)/
|
98
|
+
value[:session_id] = $1.to_i
|
99
|
+
|
100
|
+
if line =~ /timeout=(.+)/
|
101
|
+
value[:timeout] = $1.to_i
|
102
|
+
end
|
103
|
+
|
104
|
+
create_reader("session", value)
|
105
|
+
elsif line.include? ": "
|
96
106
|
header_and_value = line.strip.split(":", 2)
|
97
107
|
header_name = header_and_value.first.downcase.gsub(/-/, "_")
|
98
108
|
create_reader(header_name, header_and_value[1].strip)
|
@@ -124,10 +134,12 @@ module RTSP
|
|
124
134
|
# that's given.
|
125
135
|
#
|
126
136
|
# @param [String] name
|
127
|
-
# @param [String] value
|
137
|
+
# @param [String,Hash] value
|
128
138
|
def create_reader(name, value)
|
129
139
|
unless value.empty?
|
130
|
-
|
140
|
+
if value.is_a? String
|
141
|
+
value = value =~ /^[0-9]*$/ ? value.to_i : value
|
142
|
+
end
|
131
143
|
end
|
132
144
|
|
133
145
|
instance_variable_set("@#{name}", value)
|
@@ -7,20 +7,21 @@ module RTSP
|
|
7
7
|
# other requests.
|
8
8
|
class TransportParser < Parslet::Parser
|
9
9
|
rule(:transport_specifier) do
|
10
|
-
match('[A-
|
11
|
-
|
12
|
-
|
10
|
+
match('[A-Za-z]').repeat(3).as(:streaming_protocol) >> forward_slash >>
|
11
|
+
match('[A-Za-z]').repeat(3).as(:profile) >>
|
12
|
+
(forward_slash >> match('[A-Za-z]').repeat(3).as(:transport_protocol)).maybe
|
13
13
|
end
|
14
14
|
|
15
15
|
rule(:broadcast_type) do
|
16
|
-
str('unicast')
|
16
|
+
str('unicast') | str('multicast')
|
17
17
|
end
|
18
18
|
|
19
19
|
rule(:destination) do
|
20
|
-
str('destination=') >> ip_address
|
20
|
+
str('destination=') >> ip_address.as(:destination)
|
21
21
|
end
|
22
|
+
|
22
23
|
rule(:source) do
|
23
|
-
str('source=') >> ip_address
|
24
|
+
str('source=') >> ip_address.as(:source)
|
24
25
|
end
|
25
26
|
|
26
27
|
rule(:client_port) do
|
@@ -33,14 +34,40 @@ module RTSP
|
|
33
34
|
|
34
35
|
rule(:interleaved) do
|
35
36
|
str('interleaved=') >> number.as(:rtp_channel) >> dash >>
|
36
|
-
|
37
|
+
number.as(:rtcp_channel)
|
38
|
+
end
|
39
|
+
|
40
|
+
rule(:ttl) do
|
41
|
+
str('ttl=') >> match('[\d]').repeat(1,3).as(:ttl)
|
42
|
+
end
|
43
|
+
|
44
|
+
rule(:port) do
|
45
|
+
str('port=') >> match('[\d]').repeat(1,5).as(:rtp) >>
|
46
|
+
dash.maybe >> match('[\d]').repeat(1,5).as(:rtcp).maybe
|
47
|
+
end
|
48
|
+
|
49
|
+
rule(:ssrc) do
|
50
|
+
str('ssrc=') >> match('[0-9A-Fa-f]').repeat(8).as(:ssrc)
|
51
|
+
end
|
52
|
+
|
53
|
+
rule(:channel) do
|
54
|
+
str('channel=') >> match('[\w]').repeat(1,3).as(:channel)
|
55
|
+
end
|
56
|
+
|
57
|
+
rule(:address) do
|
58
|
+
str('address=') >> match('[\S]').repeat.as(:address)
|
59
|
+
end
|
60
|
+
|
61
|
+
rule(:mode) do
|
62
|
+
str('mode=') >> str('"').maybe >> match('[A-Za-z]').repeat.as(:mode) >>
|
63
|
+
str('"').maybe
|
37
64
|
end
|
38
65
|
|
39
66
|
rule(:ip_address) do
|
40
67
|
match('[\d]').repeat(1,3) >> str('.') >>
|
41
|
-
|
42
|
-
|
43
|
-
|
68
|
+
match('[\d]').repeat(1,3) >> str('.') >>
|
69
|
+
match('[\d]').repeat(1,3) >> str('.') >>
|
70
|
+
match('[\d]').repeat(1,3)
|
44
71
|
end
|
45
72
|
|
46
73
|
rule(:number) { match('[\d]').repeat }
|
@@ -50,13 +77,18 @@ module RTSP
|
|
50
77
|
|
51
78
|
rule(:header_field) do
|
52
79
|
transport_specifier >>
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
80
|
+
(semi_colon >> broadcast_type.as(:broadcast_type)).maybe >>
|
81
|
+
(semi_colon >> destination).maybe >>
|
82
|
+
(semi_colon >> source).maybe >>
|
83
|
+
(semi_colon >> client_port.as(:client_port)).maybe >>
|
84
|
+
(semi_colon >> server_port.as(:server_port)).maybe >>
|
85
|
+
(semi_colon >> interleaved.as(:interleaved)).maybe >>
|
86
|
+
(semi_colon >> ttl).maybe >>
|
87
|
+
(semi_colon >> port.as(:port)).maybe >>
|
88
|
+
(semi_colon >> ssrc).maybe >>
|
89
|
+
(semi_colon >> channel).maybe >>
|
90
|
+
(semi_colon >> address).maybe >>
|
91
|
+
(semi_colon >> mode).maybe
|
60
92
|
end
|
61
93
|
|
62
94
|
root :header_field
|
data/lib/rtsp/version.rb
CHANGED
data/rtsp.gemspec
CHANGED
@@ -8,7 +8,7 @@ Gem::Specification.new do |s|
|
|
8
8
|
s.name = "rtsp"
|
9
9
|
s.version = RTSP::VERSION
|
10
10
|
|
11
|
-
s.homepage = %q{
|
11
|
+
s.homepage = %q{https://github.com/turboladen/rtsp}
|
12
12
|
s.authors = ["Steve Loveless, Mike Kirby", "Sujin Philip"]
|
13
13
|
s.summary = %q{Library to allow RTSP streaming from RTSP-enabled devices.}
|
14
14
|
s.description = %q{This library intends to follow the RTSP RFC document (2326)
|
data/spec/rtsp/client_spec.rb
CHANGED
@@ -181,9 +181,9 @@ describe RTSP::Client do
|
|
181
181
|
end
|
182
182
|
|
183
183
|
it "extracts the session number" do
|
184
|
-
@client.session.should
|
184
|
+
@client.session.should be_empty
|
185
185
|
@client.setup("rtsp://localhost/some_track")
|
186
|
-
@client.session.should == 1234567890
|
186
|
+
@client.session[:session_id].should == 1234567890
|
187
187
|
end
|
188
188
|
|
189
189
|
it "changes the session_state to :ready" do
|
@@ -265,12 +265,12 @@ describe RTSP::Client do
|
|
265
265
|
@client.session_state.should == :init
|
266
266
|
end
|
267
267
|
|
268
|
-
it "changes the
|
269
|
-
@client.session.should_not
|
268
|
+
it "changes the session_id back to 0" do
|
269
|
+
@client.session.should_not be_empty
|
270
270
|
@client.teardown("rtsp://localhost/some_track")
|
271
|
-
@client.session.should
|
271
|
+
@client.session.should be_empty
|
272
272
|
end
|
273
|
-
|
273
|
+
|
274
274
|
it "returns a Response" do
|
275
275
|
response = @client.teardown("rtsp://localhost/some_track")
|
276
276
|
response.is_a?(RTSP::Response).should be_true
|