rtsp 0.3.0 → 0.4.0
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/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
|