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.
@@ -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