rtsp 0.0.1.alpha

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