rtsp 0.0.1.alpha

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,272 @@
1
+ require 'rubygems'
2
+ require 'logger'
3
+ require 'socket'
4
+ require 'tempfile'
5
+ require 'timeout'
6
+ require 'uri'
7
+
8
+ require 'rtsp/request_messages'
9
+ require 'rtsp/response'
10
+
11
+ module RTSP
12
+
13
+ # Allows for pulling streams from an RTSP server.
14
+ class Client
15
+ include RTSP::RequestMessages
16
+
17
+ MAX_BYTES_TO_RECEIVE = 1500
18
+
19
+ attr_reader :server_uri
20
+ attr_accessor :stream_tracks
21
+
22
+ # @param [String] rtsp_url URL to the resource to stream. If no scheme is given,
23
+ # "rtsp" is assumed. If no port is given, 554 is assumed. If no path is
24
+ # given, "/stream1"is assumed.
25
+ def initialize(rtsp_url, options={})
26
+ @server_uri = build_server_uri(rtsp_url)
27
+ @socket = options[:socket] || TCPSocket.new(@server_uri.host,
28
+ @server_uri.port)
29
+ @stream_tracks = options[:stream_tracks] || ["/track1"]
30
+ @timeout = options[:timeout] || 2
31
+ @session
32
+ @logger = Logger.new(STDOUT)
33
+
34
+ if options[:capture_file_path] && options[:capture_duration]
35
+ @capture_file_path = options[:capture_file_path]
36
+ @capture_duration = options[:capture_duration]
37
+ setup_capture
38
+ end
39
+ end
40
+
41
+ def setup_capture
42
+ @capture_file = File.open(@capture_file_path, File::WRONLY|File::EXCL|File::CREAT)
43
+ @capture_socket = UDPSocket.new
44
+ @capture_socket.bind "0.0.0.0", @server_uri.port
45
+ end
46
+
47
+ # TODO: update sequence
48
+ # @return [Hash] The response formatted as a Hash.
49
+ def options
50
+ @logger.debug "Sending OPTIONS to #{@server_uri.host}#{@stream_path}"
51
+ response = send_rtsp RequestMessages.options(@server_uri.to_s)
52
+ @logger.debug "Recieved response:"
53
+ @logger.debug response
54
+
55
+ @session = response.cseq
56
+
57
+ response
58
+ end
59
+
60
+ # TODO: update sequence
61
+ # TODO: get tracks, IP's, ports, multicast/unicast
62
+ # @return [Hash] The response formatted as a Hash.
63
+ def describe
64
+ @logger.debug "Sending DESCRIBE to #{@server_uri.host}#{@stream_path}"
65
+ response = send_rtsp(RequestMessages.describe("#{@server_uri.to_s}#{@stream_path}"))
66
+
67
+ @logger.debug "Recieved response:"
68
+ @logger.debug response.inspect
69
+
70
+ @session = response.cseq
71
+ @sdp_info = response.body
72
+ @content_base = response.content_base
73
+
74
+ response
75
+ end
76
+
77
+ # TODO: update sequence
78
+ # TODO: get session
79
+ # TODO: parse Transport header (http://tools.ietf.org/html/rfc2326#section-12.39)
80
+ # @return [Hash] The response formatted as a Hash.
81
+ def setup(options={})
82
+ @logger.debug "Sending SETUP to #{@server_uri.host}#{@stream_path}"
83
+ setup_url = @content_base || "#{@server_uri.to_s}#{@stream_path}"
84
+ response = send_rtsp RequestMessages.setup(setup_url, options)
85
+
86
+ @logger.debug "Recieved response:"
87
+ @logger.debug response
88
+
89
+ @session = response.cseq
90
+
91
+ response
92
+ end
93
+
94
+ # TODO: update sequence
95
+ # TODO: get session
96
+ # @return [Hash] The response formatted as a Hash.
97
+ def play(options={})
98
+ @logger.debug "Sending PLAY to #{@server_uri.host}#{@stream_path}"
99
+ session = options[:session] || @session
100
+ response = send_rtsp RequestMessages.play(@server_uri.to_s,
101
+ options[:session])
102
+
103
+ @logger.debug "Recieved response:"
104
+ @logger.debug response
105
+ @session = response.cseq
106
+
107
+ if @capture_file_path
108
+ begin
109
+ Timeout::timeout(@capture_duration) do
110
+ while data = @capture_socket.recvfrom(102400).first
111
+ @logger.debug "data size = #{data.size}"
112
+ @capture_file_path.write data
113
+ end
114
+ end
115
+ rescue Timeout::Error
116
+ # Blind rescue
117
+ end
118
+
119
+ @capture_socket.close
120
+ end
121
+
122
+ response
123
+ end
124
+
125
+ def pause(options={})
126
+ @logger.debug "Sending PAUSE to #{@server_uri.host}#{@stream_path}"
127
+ response = send_rtsp RequestMessages.pause(@stream_tracks.first,
128
+ options[:session],
129
+ options[:sequence])
130
+
131
+ @logger.debug "Recieved response:"
132
+ @logger.debug response
133
+ @session = response.cseq
134
+
135
+ response
136
+ end
137
+
138
+ # @return [Hash] The response formatted as a Hash.
139
+ def teardown
140
+ @logger.debug "Sending TEARDOWN to #{@server_uri.host}#{@stream_path}"
141
+ response = send_rtsp RequestMessages.teardown(@server_uri.to_s, @session)
142
+ @logger.debug "Recieved response:"
143
+ @logger.debug response
144
+ #@socket.close if @socket.open?
145
+ @socket = nil
146
+
147
+ response
148
+ end
149
+
150
+ =begin
151
+ def connect
152
+ timeout(@timeout) { @socket = TCPSocket.new(@host, @port) } #rescue @socket = nil
153
+ end
154
+
155
+ def connected?
156
+ @socket == nil ? true : false
157
+ end
158
+
159
+ def disconnect
160
+ timeout(@timeout) { @socket.close } rescue @socket = nil
161
+ end
162
+ =end
163
+
164
+ # @param []
165
+ def send_rtsp(message)
166
+ message.each_line { |line| @logger.debug line }
167
+ recv if timeout(@timeout) { @socket.send(message, 0) }
168
+ end
169
+
170
+ def aggregate_control_track
171
+ aggregate_control = @sdp_info.attributes.find_all do |a|
172
+ a[:attribute] == "control"
173
+ end
174
+
175
+ aggregate_control.first[:value]
176
+ end
177
+
178
+ def media_control_tracks
179
+ tracks = []
180
+ @sdp_info.media_sections.each do |media_section|
181
+ media_section[:attributes].each do |a|
182
+ tracks << a[:value] if a[:attribute] == "control"
183
+ end
184
+ end
185
+
186
+ tracks
187
+ end
188
+
189
+ # @return [Hash]
190
+ def recv
191
+ size = 0
192
+ socket_data, sender_sockaddr = @socket.recvfrom MAX_BYTES_TO_RECEIVE
193
+ response = RTSP::Response.new socket_data
194
+
195
+ #unless response.message == "OK"
196
+ # message = "Did not recieve RTSP/1.0 200 OK. Instead got '#{response.status}'"
197
+ # message = message + "Full response:\n#{response.inspect}"
198
+ # raise message
199
+ #
200
+ # end
201
+ =begin
202
+ response = parse_header
203
+ unless response[:status].include? "RTSP/1.0 200 OK"
204
+ message = "Did not recieve RTSP/1.0 200 OK. Instead got '#{response[:status]}'"
205
+ message = message + "Full response:\n#{response}"
206
+ raise message
207
+ end
208
+
209
+ response[:status] = readline
210
+ while line = readline
211
+ break if line == "\r\n"
212
+
213
+ if line.include? ": "
214
+ a = line.strip().split(": ")
215
+ response.merge!({a[0].downcase => a[1]})
216
+ end
217
+ end
218
+
219
+ size = response["content-length"].to_i if response.has_key?("content-length")
220
+ response[:body] = read_nonblock(size).split("\r\n") unless size == 0
221
+
222
+ response
223
+ =end
224
+ size = response.content_length.to_i if response.respond_to? 'content_length'
225
+ #response[:body] = read_nonblock(size).split("\r\n") unless size == 0
226
+
227
+ response
228
+ end
229
+
230
+ # @param [Number] size
231
+ # @param [Hash] options
232
+ # @option options [Number] time Duration to read on the non-blocking socket.
233
+ =begin
234
+ def read_nonblock(size, options={})
235
+ options[:time] ||= 1
236
+ buffer = nil
237
+ timeout(options[:time]) { buffer = @socket.read_nonblock(size) }
238
+
239
+ buffer
240
+ end
241
+
242
+ # @return [String]
243
+ def readline(options={})
244
+ options[:time] ||= 1
245
+ line = nil
246
+ timeout(options[:time]) { line = @socket.readline }
247
+
248
+ line
249
+ end
250
+ =end
251
+ #--------------------------------------------------------------------------
252
+ # Privates!
253
+ private
254
+
255
+ def build_server_uri(url)
256
+ unless url =~ /^rtsp/
257
+ url = "rtsp://#{url}"
258
+ end
259
+
260
+ server_uri = URI.parse url
261
+ server_uri.port ||= 554
262
+
263
+ #if @server_uri.path == @server_uri.host
264
+ # @server_uri.path = "/stream1"
265
+ #else
266
+ # @server_uri.path
267
+ #end
268
+
269
+ server_uri
270
+ end
271
+ end
272
+ end
@@ -0,0 +1,104 @@
1
+ module RTSP
2
+
3
+ # This module defines the template strings that make up RTSP methods. Other
4
+ # objects should use these for building request messages to communicate in
5
+ # RTSP.
6
+ module RequestMessages
7
+ RTSP_VER = "RTSP/1.0"
8
+ RTSP_ACCEPT_TYPE = "application/sdp"
9
+ RTP_DEFAULT_PORT = 9000
10
+ RTP_DEFAULT_PACKET_TYPE = "RTP/AVP"
11
+ RTP_DEFAULT_ROUTING = "unicast"
12
+ RTSP_DEFAULT_SEQUENCE_NUMBER = 1
13
+ RTSP_DEFAULT_NPT = "0.000-"
14
+
15
+ def self.options(stream, options={})
16
+ options[:sequence] ||= RTSP_DEFAULT_SEQUENCE_NUMBER
17
+ message = "OPTIONS #{stream} #{RTSP_VER}\r\n"
18
+ message << "CSeq: #{options[:sequence]}\r\n\r\n"
19
+ end
20
+
21
+ # See section 10.2
22
+ #
23
+ # @param [String] stream
24
+ # @param [Hash] options
25
+ # @option options [Number] sequence
26
+ # @option options [Array]
27
+ def self.describe(stream, options={})
28
+ options[:sequence] ||= RTSP_DEFAULT_SEQUENCE_NUMBER
29
+ options[:accept] ||= RTSP_ACCEPT_TYPE
30
+ message = "DESCRIBE #{stream} #{RTSP_VER}\r\n"
31
+ message << "CSeq: #{options[:sequence]}\r\n"
32
+ message << "Accept: #{options[:accept]}\r\n\r\n"
33
+ end
34
+
35
+ # @param [String] stream
36
+ # @param [Number] session
37
+ # @param [Hash]
38
+ def self.announce(stream, session, options={})
39
+ options[:content_type] ||= RTSP_ACCEPT_TYPE
40
+ message = "ANNOUNCE #{stream} #{RTSP_VER}\r\n"
41
+ message << "CSeq: #{options[:sequence]}\r\n"
42
+ message << "Date: "
43
+ message << "Session: #{session}"
44
+ message << "Content-Type: #{options[:content_type]}\r\n"
45
+ message << "Content-Length: #{options[:content_length]}\r\n"
46
+ end
47
+
48
+ def self.setup(track, options={})
49
+ options[:sequence] ||= RTSP_DEFAULT_SEQUENCE_NUMBER
50
+ options[:transport] ||= RTP_DEFAULT_PACKET_TYPE
51
+ options[:port] ||= RTP_DEFAULT_PORT
52
+ options[:routing] ||= RTP_DEFAULT_ROUTING
53
+ message = "SETUP #{track} #{RTSP_VER}\r\n"
54
+ message << "CSeq: #{options[:sequence]}\r\n"
55
+ message << "Transport: #{options[:transport]};"
56
+ message << "#{options[:destination]};"
57
+ message << "client_port=#{options[:port]}-#{options[:port]+1}\r\n\r\n"
58
+ end
59
+
60
+ def self.play(stream, session, options={})
61
+ options[:sequence] ||= RTSP_DEFAULT_SEQUENCE_NUMBER
62
+ options[:npt] ||= RTSP_DEFAULT_NPT
63
+ message = "PLAY #{stream} #{RTSP_VER}\r\n"
64
+ message << "CSeq: #{options[:sequence]}\r\n"
65
+ message << "Session: #{session}\r\n"
66
+ message << "Range: npt=#{options[:npt]}\r\n\r\n"
67
+ end
68
+
69
+ def self.pause(stream, session, sequence)
70
+ message = "PAUSE #{stream} #{RTSP_VER}\r\n"
71
+ message << "CSeq: #{sequence}\r\n"
72
+ message << "Session: #{session}\r\n"
73
+ end
74
+
75
+ def self.teardown(stream, session, options={})
76
+ options[:sequence] ||= RTSP_DEFAULT_SEQUENCE_NUMBER
77
+ message = "TEARDOWN #{stream} #{RTSP_VER}\r\n"
78
+ message << "CSeq: #{options[:sequence]}\r\n"
79
+ message << "Session: #{session}\r\n\r\n"
80
+ end
81
+
82
+ def self.get_parameter(stream, session, options={})
83
+ message = "GET_PARAMETER #{stream} #{RTSP_VER}\r\n"
84
+ message << "CSeq: #{options[:sequence]}\r\n"
85
+ message << "Content-Type: #{options[:content_type]}\r\n"
86
+ message << "Content-Length: #{options[:content_length]}\r\n"
87
+ message << "Session: #{session}\r\n\r\n"
88
+ end
89
+
90
+ def self.set_parameter(stream, options={})
91
+ message = "SET_PARAMETER #{stream} #{RTSP_VER}\r\n"
92
+ message << "CSeq: #{options[:sequence]}\r\n"
93
+ message << "Content-Type: #{options[:content_type]}\r\n"
94
+ message << "Content-Length: #{options[:content_length]}\r\n"
95
+ end
96
+
97
+ def self.record(stream, session, options={})
98
+ message = "RECORD #{stream} #{RTSP_VER}\r\n"
99
+ message << "CSeq: #{options[:sequence]}\r\n"
100
+ message << "Session: #{session}\r\n\r\n"
101
+ message << "Conference: #{options[:conference]}\r\n\r\n"
102
+ end
103
+ end
104
+ end
@@ -0,0 +1,76 @@
1
+ require 'rubygems'
2
+ require 'socket'
3
+ require 'sdp'
4
+
5
+ module RTSP
6
+ class Response
7
+ attr_reader :code
8
+ attr_reader :message
9
+ attr_reader :body
10
+
11
+ def initialize(response)
12
+ response_array = response.split "\r\n\r\n"
13
+
14
+ if response_array.empty?
15
+ response_array = response.split "\n\n"
16
+ end
17
+
18
+ head = response_array.first
19
+ body = response_array.last == head ? "" : response_array.last
20
+ parse_head(head)
21
+ @body = parse_body(body)
22
+ end
23
+
24
+ # Reads through each line of the RTSP response and creates a
25
+ # snake-case accessor with that value set.
26
+ #
27
+ # @param [String] head
28
+ def parse_head head
29
+ lines = head.split "\r\n"
30
+
31
+ lines.each_with_index do |line, i|
32
+ if i == 0
33
+ line =~ /RTSP\/1.0 (\d\d\d) ([^\r\n]+)/
34
+ @code = $1.to_i
35
+ @message = $2
36
+ next
37
+ end
38
+
39
+ if line.include? ": "
40
+ header_field = line.strip.split(": ")
41
+ header_name = header_field.first.downcase.gsub(/-/, "_")
42
+ create_reader(header_name, header_field.last)
43
+ end
44
+ end
45
+ end
46
+
47
+ def parse_body body
48
+ #response[:body] = read_nonblock(size).split("\r\n") unless @content_length == 0
49
+ if body =~ /^(\r\n|\n)/
50
+ body.gsub!(/^(\r\n|\n)/, '')
51
+ end
52
+
53
+ if @content_type == "application/sdp"
54
+ SDP.parse body
55
+ end
56
+ end
57
+
58
+ # @param [Number] size
59
+ # @param [Hash] options
60
+ # @option options [Number] time Duration to read on the non-blocking socket.
61
+ def read_nonblock(size, options={})
62
+ options[:time] ||= 1
63
+ buffer = nil
64
+ timeout(options[:time]) { buffer = @socket.read_nonblock(size) }
65
+
66
+ buffer
67
+ end
68
+
69
+ private
70
+
71
+ def create_reader(name, value)
72
+ instance_variable_set("@#{name}", value)
73
+ self.instance_eval "def #{name}; @#{name}; end"
74
+ end
75
+ end
76
+ end
@@ -0,0 +1,7 @@
1
+ module RTSP
2
+ module StatusCode
3
+ CONTINUE = 100
4
+ OK = 200
5
+
6
+ end
7
+ end
data/lib/rtsp.rb ADDED
@@ -0,0 +1,12 @@
1
+ require 'pathname'
2
+
3
+ # This base module simply defines properties about the library. See child
4
+ # classes/modules for the meat.
5
+ module RTSP
6
+ VERSION = '0.0.1.alpha'
7
+ WWW = 'http://github.com/turboladen/rtsp'
8
+ LIBRARY_ROOT = File.dirname(__FILE__)
9
+ PROJECT_ROOT = Pathname.new(LIBRARY_ROOT).parent
10
+
11
+ RTSP_VERSION = '1.0'
12
+ end
data/spec/.rspec ADDED
@@ -0,0 +1 @@
1
+ --format documentation --color
@@ -0,0 +1,38 @@
1
+ require File.dirname(__FILE__) + '/../spec_helper'
2
+ require 'rtsp/client'
3
+
4
+ describe RTSP::Client do
5
+ context "parses the URI on init" do
6
+ it "with scheme, IP, port, and path" do
7
+ @rtsp_client = RTSP::Client.new "rtsp://64.202.98.91:554/sa.sdp"
8
+ @rtsp_client.server_uri.scheme.should == "rtsp"
9
+ @rtsp_client.server_uri.host.should == "64.202.98.91"
10
+ @rtsp_client.server_uri.port.should == 554
11
+ @rtsp_client.server_uri.path.should == "/sa.sdp"
12
+ end
13
+
14
+ it "with scheme, IP, path; port defaults to 554" do
15
+ @rtsp_client = RTSP::Client.new "rtsp://64.202.98.91/sa.sdp"
16
+ @rtsp_client.server_uri.scheme.should == "rtsp"
17
+ @rtsp_client.server_uri.host.should == "64.202.98.91"
18
+ @rtsp_client.server_uri.port.should == 554
19
+ @rtsp_client.server_uri.path.should == "/sa.sdp"
20
+ end
21
+
22
+ it "with IP, path; port defaults to 554; scheme defaults to 'rtsp'" do
23
+ @rtsp_client = RTSP::Client.new "64.202.98.91/sa.sdp"
24
+ @rtsp_client.server_uri.scheme.should == "rtsp"
25
+ @rtsp_client.server_uri.host.should == "64.202.98.91"
26
+ @rtsp_client.server_uri.port.should == 554
27
+ @rtsp_client.server_uri.path.should == "/sa.sdp"
28
+ end
29
+
30
+ it "with scheme, IP, port" do
31
+ @rtsp_client = RTSP::Client.new "rtsp://64.202.98.91:554"
32
+ @rtsp_client.server_uri.scheme.should == "rtsp"
33
+ @rtsp_client.server_uri.host.should == "64.202.98.91"
34
+ @rtsp_client.server_uri.port.should == 554
35
+ @rtsp_client.server_uri.path.should == ""
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,189 @@
1
+ require File.dirname(__FILE__) + '/../spec_helper'
2
+ require 'rtsp/response'
3
+
4
+ OPTIONS_RESPONSE = %Q{ RTSP/1.0 200 OK\r\n
5
+ CSeq: 1\r\n
6
+ Date: Fri, Jan 28 2011 01:14:42 GMT\r\n
7
+ Public: OPTIONS, DESCRIBE, SETUP, TEARDOWN, PLAY, PAUSE\r\n
8
+ }
9
+
10
+ DESCRIBE_RESPONSE = %Q{ RTSP/1.0 200 OK\r\n
11
+ Server: DSS/5.5 (Build/489.7; Platform/Linux; Release/Darwin; )\r\n
12
+ Cseq: 1\r\n
13
+ Cache-Control: no-cache\r\n
14
+ Content-length: 406\r\n
15
+ Date: Sun, 23 Jan 2011 00:36:45 GMT\r\n
16
+ Expires: Sun, 23 Jan 2011 00:36:45 GMT\r\n
17
+ Content-Type: application/sdp\r\n
18
+ x-Accept-Retransmit: our-retransmit\r\n
19
+ x-Accept-Dynamic-Rate: 1\r\n
20
+ Content-Base: rtsp://64.202.98.91:554/gs.sdp/\r\n
21
+ \r\n\r\n
22
+ v=0
23
+ o=- 545877020 467920391 IN IP4 127.0.0.1
24
+ s=Groove Salad from SomaFM [aacPlus]
25
+ i=Downtempo Ambient Groove
26
+ c=IN IP4 0.0.0.0
27
+ t=0 0
28
+ a=x-qt-text-cmt:Orban Opticodec-PC
29
+ a=x-qt-text-nam:Groove Salad from SomaFM [aacPlus]
30
+ a=x-qt-text-inf:Downtempo Ambient Groove
31
+ a=control:*
32
+ m=audio 0 RTP/AVP 96
33
+ b=AS:48
34
+ a=rtpmap:96 MP4A-LATM/44100/2
35
+ a=fmtp:96 cpresent=0;config=400027200000
36
+ a=control:trackID=1
37
+ }
38
+
39
+ SETUP_RESPONSE = %Q{ RTSP/1.0 200 OK\r\n
40
+ CSeq: 1\r\n
41
+ Date: Fri, Jan 28 2011 01:14:42 GMT\r\n
42
+ Transport: RTP/AVP;unicast;destination=10.221.222.186;source=10.221.222.235;client_port=9000-9001;server_port=6700-6701\r\n
43
+ Session: 118\r\n
44
+ }
45
+
46
+ PLAY_RESPONSE = %Q{ RTSP/1.0 200 OK\r\n
47
+ CSeq: 1\r\n
48
+ Date: Fri, Jan 28 2011 01:14:42 GMT\r\n
49
+ Range: npt=0.000-\r\n
50
+ Session: 118\r\n
51
+ RTP-Info: url=rtsp://10.221.222.235/stream1/track1;seq=17320;rtptime=400880602\r\n
52
+ }
53
+
54
+ TEARDOWN_RESPONSE = %Q{ RTSP/1.0 200 OK\r\n
55
+ CSeq: 1\r\n
56
+ Date: Fri, Jan 28 2011 01:14:47 GMT\r\n
57
+ }
58
+
59
+ describe RTSP::Response do
60
+ context "options" do
61
+ before do
62
+ @response = RTSP::Response.new OPTIONS_RESPONSE
63
+ end
64
+
65
+ it "returns a 200 code" do
66
+ @response.code.should == 200
67
+ end
68
+
69
+ it "returns 'OK' message" do
70
+ @response.message.should == 'OK'
71
+ end
72
+
73
+ it "returns the date header" do
74
+ @response.date.should == 'Fri, Jan 28 2011 01:14:42 GMT'
75
+ end
76
+
77
+ it "returns the supported methods in the Public header" do
78
+ @response.public.should == 'OPTIONS, DESCRIBE, SETUP, TEARDOWN, PLAY, PAUSE'
79
+ end
80
+ end
81
+
82
+ context "describe" do
83
+ before do
84
+ @response = RTSP::Response.new DESCRIBE_RESPONSE
85
+ end
86
+
87
+ it "returns a 200 code" do
88
+ @response.code.should == 200
89
+ end
90
+
91
+ it "returns 'OK' message" do
92
+ @response.message.should == "OK"
93
+ end
94
+
95
+ it "returns all header fields" do
96
+ @response.server.should == "DSS/5.5 (Build/489.7; Platform/Linux; Release/Darwin; )"
97
+ @response.cseq.should == "1"
98
+ @response.cache_control.should == "no-cache"
99
+ @response.content_length.should == "406"
100
+ @response.date.should == "Sun, 23 Jan 2011 00:36:45 GMT"
101
+ @response.expires.should == "Sun, 23 Jan 2011 00:36:45 GMT"
102
+ @response.content_type.should == "application/sdp"
103
+ @response.x_accept_retransmit.should == "our-retransmit"
104
+ @response.x_accept_dynamic_rate.should == "1"
105
+ @response.content_base.should == "rtsp://64.202.98.91:554/gs.sdp/"
106
+ end
107
+
108
+ it "body is a parsed SDP::Description" do
109
+ @response.body.should be_kind_of SDP::Description
110
+ sdp_info = @response.body
111
+ sdp_info.protocol_version.should == "0"
112
+ sdp_info.name.should == "Groove Salad from SomaFM [aacPlus]"
113
+ end
114
+ end
115
+
116
+ context "setup" do
117
+ before do
118
+ @response = RTSP::Response.new SETUP_RESPONSE
119
+ end
120
+
121
+ it "returns a 200 code" do
122
+ @response.code.should == 200
123
+ end
124
+
125
+ it "returns 'OK' message" do
126
+ @response.message.should == 'OK'
127
+ end
128
+
129
+ it "returns the date header" do
130
+ @response.date.should == 'Fri, Jan 28 2011 01:14:42 GMT'
131
+ end
132
+
133
+ it "returns the supported transport" do
134
+ @response.transport.should == 'RTP/AVP;unicast;destination=10.221.222.186;source=10.221.222.235;client_port=9000-9001;server_port=6700-6701'
135
+ end
136
+
137
+ it "returns the session" do
138
+ @response.session.should == '118'
139
+ end
140
+ end
141
+
142
+ context "play" do
143
+ before do
144
+ @response = RTSP::Response.new PLAY_RESPONSE
145
+ end
146
+
147
+ it "returns a 200 code" do
148
+ @response.code.should == 200
149
+ end
150
+
151
+ it "returns 'OK' message" do
152
+ @response.message.should == 'OK'
153
+ end
154
+
155
+ it "returns the date header" do
156
+ @response.date.should == 'Fri, Jan 28 2011 01:14:42 GMT'
157
+ end
158
+
159
+ it "returns the supported range" do
160
+ @response.range.should == 'npt=0.000-'
161
+ end
162
+
163
+ it "returns the session" do
164
+ @response.session.should == '118'
165
+ end
166
+
167
+ it "returns the rtp_info" do
168
+ @response.rtp_info.should == 'url=rtsp://10.221.222.235/stream1/track1;seq=17320;rtptime=400880602'
169
+ end
170
+ end
171
+
172
+ context "teardown" do
173
+ before do
174
+ @response = RTSP::Response.new TEARDOWN_RESPONSE
175
+ end
176
+
177
+ it "returns a 200 code" do
178
+ @response.code.should == 200
179
+ end
180
+
181
+ it "returns 'OK' message" do
182
+ @response.message.should == 'OK'
183
+ end
184
+
185
+ it "returns the date header" do
186
+ @response.date.should == 'Fri, Jan 28 2011 01:14:47 GMT'
187
+ end
188
+ end
189
+ end