rtsp 0.3.0 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
@@ -10,7 +10,7 @@ call, but we'll get there. ...eventually.
10
10
 
11
11
  For more information
12
12
 
13
- RTSP: http://www.ietf.org/rfc/rfc2326.txt
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 # => 7098486223178290313
65
- client.session # => 7098486223178290313
66
- client.cseq # => 4
67
- client.session_state # => :ready
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 # => 0
86
- client.session_state # => :init
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
- @client.send(request_type.to_sym, params)
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
- =begin
11
- @response = RTSP::Request.execute({
12
- :method => method.to_sym,
13
- :resource_url => @server_url }
14
- )
15
- =end
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
- =begin
29
- @response = RTSP::Request.execute({
30
- :method => method.to_sym,
31
- :headers => headers,
32
- :resource_url => @server_url }
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.is_a?(RTSP::Response).should be_true
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.is_a?(RTSP::Response).should be_true
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.is_a?(SDP::Description).should be_true
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.is_a?(RTSP::Response).should be_true
55
+ @response.should be_a RTSP::Response
60
56
  @response.code.should == 200
61
57
  @response.message.should == "OK"
62
- @response.server.should == "DSS/5.5 (Build/489.7; Platform/Linux; Release/Darwin; )"
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.is_a?(RTSP::Response).should be_true
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
 
@@ -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
@@ -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 = 0
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
- unless @session > 0
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
@@ -26,17 +26,17 @@ module RTSP
26
26
  "RubyRTSP/#{RTSP::VERSION} (Ruby #{RUBY_VERSION}-p#{RUBY_PATCHLEVEL})"
27
27
 
28
28
  @message_types = [
29
- :announce,
30
- :describe,
31
- :get_parameter,
32
- :options,
33
- :play,
34
- :pause,
35
- :record,
36
- :redirect,
37
- :set_parameter,
38
- :setup,
39
- :teardown
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.
@@ -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
- value = value =~ /^[0-9]*$/ ? value.to_i : value
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-Z]').repeat(3).as(:streaming_protocol) >> forward_slash >>
11
- match('[A-Z]').repeat(3).as(:profile) >>
12
- (forward_slash >> match('[A-Z]').repeat(3).as(:transport_protocol)).maybe
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
- number.as(:rtcp_channel)
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
- match('[\d]').repeat(1,3) >> str('.') >>
42
- match('[\d]').repeat(1,3) >> str('.') >>
43
- match('[\d]').repeat(1,3)
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
- (semi_colon >> broadcast_type.as(:broadcast_type)).maybe >>
54
- (semi_colon >> destination.as(:destination)).maybe >>
55
- (semi_colon >> source.as(:source)).maybe >>
56
- (semi_colon >> client_port.as(:client_port)).maybe >>
57
- (semi_colon >> server_port.as(:server_port)).maybe >>
58
- (semi_colon >> interleaved.as(:interleaved)).maybe
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
@@ -1,4 +1,4 @@
1
1
  module RTSP
2
2
  # rtsp version
3
- VERSION = "0.3.0"
3
+ VERSION = "0.4.0"
4
4
  end
@@ -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{http://rubygems.org/gems/rtsp}
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)
@@ -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 be_zero
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 session back to 0" do
269
- @client.session.should_not be_zero
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 be_zero
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