rtsp_server 0.0.2-universal-java

Sign up to get free protection for your applications and to get access to all the features.
Files changed (44) hide show
  1. checksums.yaml +7 -0
  2. data/.gemtest +0 -0
  3. data/ChangeLog.rdoc +74 -0
  4. data/Gemfile +3 -0
  5. data/LICENSE.rdoc +20 -0
  6. data/README.rdoc +152 -0
  7. data/Rakefile +23 -0
  8. data/bin/rtsp_client +133 -0
  9. data/features/client_changes_state.feature +58 -0
  10. data/features/client_requests.feature +27 -0
  11. data/features/control_streams_as_client.feature +26 -0
  12. data/features/step_definitions/client_changes_state_steps.rb +52 -0
  13. data/features/step_definitions/client_requests_steps.rb +68 -0
  14. data/features/step_definitions/control_streams_as_client_steps.rb +34 -0
  15. data/features/support/env.rb +50 -0
  16. data/features/support/hooks.rb +3 -0
  17. data/lib/ext/logger.rb +8 -0
  18. data/lib/rtsp/client.rb +520 -0
  19. data/lib/rtsp/common.rb +148 -0
  20. data/lib/rtsp/error.rb +6 -0
  21. data/lib/rtsp/global.rb +63 -0
  22. data/lib/rtsp/helpers.rb +28 -0
  23. data/lib/rtsp/message.rb +272 -0
  24. data/lib/rtsp/request.rb +39 -0
  25. data/lib/rtsp/response.rb +47 -0
  26. data/lib/rtsp/server.rb +311 -0
  27. data/lib/rtsp/socat_streaming.rb +320 -0
  28. data/lib/rtsp/stream_server.rb +37 -0
  29. data/lib/rtsp/transport_parser.rb +96 -0
  30. data/lib/rtsp/version.rb +4 -0
  31. data/lib/rtsp.rb +6 -0
  32. data/rtsp.gemspec +44 -0
  33. data/spec/rtsp/client_spec.rb +326 -0
  34. data/spec/rtsp/helpers_spec.rb +53 -0
  35. data/spec/rtsp/message_spec.rb +420 -0
  36. data/spec/rtsp/response_spec.rb +306 -0
  37. data/spec/rtsp/transport_parser_spec.rb +137 -0
  38. data/spec/rtsp_spec.rb +27 -0
  39. data/spec/spec_helper.rb +88 -0
  40. data/spec/support/fake_rtsp_server.rb +123 -0
  41. data/tasks/roodi.rake +9 -0
  42. data/tasks/roodi_config.yaml +14 -0
  43. data/tasks/stats.rake +12 -0
  44. metadata +280 -0
@@ -0,0 +1,50 @@
1
+ require 'rubygems'
2
+ require 'socket'
3
+ require 'timeout'
4
+ require 'cucumber/rspec/doubles'
5
+
6
+ $:.unshift(File.dirname(__FILE__) + '/../../lib')
7
+ require 'rtsp/client'
8
+
9
+ $:.unshift(File.dirname(__FILE__) + '/../../spec/support')
10
+ require 'fake_rtsp_server'
11
+
12
+ DESCRIBE_RESPONSE = %Q{ RTSP/1.0 200 OK\r
13
+ Server: DSS/5.5 (Build/489.7; Platform/Linux; Release/Darwin; )\r
14
+ Cseq: 2\r
15
+ Cache-Control: no-cache\r
16
+ Content-length: 406\r
17
+ Date: Sun, 23 Jan 2011 00:36:45 GMT\r
18
+ Expires: Sun, 23 Jan 2011 00:36:45 GMT\r
19
+ Content-Type: application/sdp\r
20
+ x-Accept-Retransmit: our-retransmit\r
21
+ x-Accept-Dynamic-Rate: 1\r
22
+ Content-Base: rtsp://64.202.98.91:554/gs.sdp/\r
23
+ \r\n\r
24
+ v=0\r
25
+ o=- 545877020 467920391 IN IP4 127.0.0.1\r
26
+ s=Groove Salad from SomaFM [aacPlus]\r
27
+ i=Downtempo Ambient Groove\r
28
+ c=IN IP4 0.0.0.0\r
29
+ t=0 0\r
30
+ a=x-qt-text-cmt:Orban Opticodec-PC\r\n
31
+ a=x-qt-text-nam:Groove Salad from SomaFM [aacPlus]\r
32
+ a=x-qt-text-inf:Downtempo Ambient Groove\r
33
+ a=control:*\r
34
+ m=audio 0 RTP/AVP 96\r
35
+ b=AS:48\r
36
+ a=rtpmap:96 MP4A-LATM/44100/2\r
37
+ a=fmtp:96 cpresent=0;config=400027200000\r
38
+ a=control:trackID=1\r
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
@@ -0,0 +1,3 @@
1
+ Before do
2
+ @fake_server = FakeRTSPServer.new
3
+ end
data/lib/ext/logger.rb ADDED
@@ -0,0 +1,8 @@
1
+ require 'logger'
2
+
3
+ class Logger
4
+ # Redefining to output a smaller timestamp.
5
+ def format_message(level, time, progname, msg)
6
+ "[#{time}] #{msg.to_s}\n"
7
+ end
8
+ end
@@ -0,0 +1,520 @@
1
+ require 'socket'
2
+ require 'tempfile'
3
+ require 'timeout'
4
+ require 'rtp/receiver'
5
+
6
+ require_relative 'transport_parser'
7
+ require_relative 'error'
8
+ require_relative 'global'
9
+ require_relative 'helpers'
10
+ require_relative 'message'
11
+ require_relative 'response'
12
+
13
+ module RTSP
14
+
15
+ # This is the main interface to an RTSP server. A client object uses a couple
16
+ # main objects for configuration: an +RTP::Receiver+ and a Connection Struct.
17
+ # Use the capturer to configure how to capture the data which is the RTP
18
+ # stream provided by the RTSP server. Use the connection object to control
19
+ # the connection to the server.
20
+ #
21
+ # You can initialize your client object using a block:
22
+ # client = RTSP::Client.new("rtsp://192.168.1.10") do |connection, capturer|
23
+ # connection.timeout = 5
24
+ # capturer.rtp_file = File.open("my_file.rtp", "wb")
25
+ # end
26
+ #
27
+ # ...or, without the block:
28
+ # client = RTSP::Client.new("rtsp://192.168.1.10")
29
+ # client.connection.timeout = 5
30
+ # client.capturer.rtp_file = File.open("my_file.rtp", "wb")
31
+ #
32
+ # After setting up the client object, call RTSP methods, Ruby style:
33
+ # client.options
34
+ #
35
+ # Remember that, unlike HTTP, RTSP is state-based (and thus the ability to
36
+ # call certain methods depends on calling other methods first). Your client
37
+ # object tells you the current RTSP state that it's in:
38
+ # client.options
39
+ # client.session_state # => :init
40
+ # client.describe
41
+ # client.session_state # => :init
42
+ # client.setup(client.media_control_tracks.first)
43
+ # client.session_state # => :ready
44
+ # client.play(client.aggregate_control_track)
45
+ # client.session_state # => :playing
46
+ # client.pause(client.aggregate_control_track)
47
+ # client.session_state # => :ready
48
+ # client.teardown(client.aggregate_control_track)
49
+ # client.session_state # => :init
50
+ #
51
+ # To enable/disable logging for clients, class methods:
52
+ # RTSP::Client.log? # => true
53
+ # RTSP::Client.log = false
54
+ # @todo Break Stream out in to its own class.
55
+ class Client
56
+ include RTSP::Helpers
57
+ extend RTSP::Global
58
+
59
+ MAX_BYTES_TO_RECEIVE = 3000
60
+
61
+ # @return [URI] The URI that points to the RTSP server's resource.
62
+ attr_reader :server_uri
63
+
64
+ # @return [Fixnum] Also known as the "sequence" number, this starts at 1 and
65
+ # increments after every request to the server. It is reset after
66
+ # calling #teardown.
67
+ attr_reader :cseq
68
+
69
+ # @return [Fixnum] A session is only established after calling #setup;
70
+ # otherwise returns nil.
71
+ attr_reader :session
72
+
73
+ # @return [Array<Symbol>] Only populated after calling #options; otherwise
74
+ # returns nil. There's no sense in making any other requests than these
75
+ # since the server doesn't support them.
76
+ attr_reader :supported_methods
77
+
78
+ # @return [Struct::Connection]
79
+ attr_accessor :connection
80
+
81
+ # Use to get/set an object for capturing received data.
82
+ # @param [RTP::Receiver]
83
+ # @return [RTP::Receiver]
84
+ attr_accessor :capturer
85
+
86
+ # @return [Symbol] See {RFC section A.1.}[http://tools.ietf.org/html/rfc2326#page-76]
87
+ attr_reader :session_state
88
+
89
+ # Use to configure options for all clients.
90
+ # @see RTSP::Global
91
+ def self.configure
92
+ yield self if block_given?
93
+ end
94
+
95
+ # @param [String] server_url URL to the resource to stream. If no scheme is
96
+ # given, "rtsp" is assumed. If no port is given, 554 is assumed.
97
+ # @yield [Struct::Connection, RTP::Receiver]
98
+ # @yieldparam [Struct::Connection] server_url=
99
+ # @yieldparam [Struct::Connection] timeout=
100
+ # @yieldparam [Struct::Connection] socket=
101
+ # @yieldparam [Struct::Connection] do_capture=
102
+ # @yieldparam [Struct::Connection] interleave=
103
+ # @todo Use server_url everywhere; just use URI to ensure the port & rtspu.
104
+ def initialize(server_url=nil)
105
+ Thread.abort_on_exception = true
106
+
107
+ unless defined? Struct::Connection
108
+ Struct.new("Connection", :server_url, :timeout, :socket,
109
+ :do_capture, :interleave)
110
+ end
111
+
112
+ @connection = Struct::Connection.new
113
+ @capturer = RTP::Receiver.new
114
+
115
+ yield(connection, capturer) if block_given?
116
+
117
+ @connection.server_url = server_url || @connection.server_url
118
+ @server_uri = build_resource_uri_from(@connection.server_url)
119
+ @connection.timeout ||= 30
120
+ @connection.socket ||= TCPSocket.new(@server_uri.host, @server_uri.port)
121
+ @connection.do_capture ||= true
122
+ @connection.interleave ||= false
123
+ @capturer.rtp_port ||= 9000
124
+ @capturer.transport_protocol ||= :UDP
125
+ @capturer.broadcast_type ||= :unicast
126
+ @capturer.rtp_file ||= Tempfile.new(RTP::Receiver::DEFAULT_CAPFILE_NAME)
127
+
128
+ @play_thread = nil
129
+ @cseq = 1
130
+ reset_state
131
+ end
132
+
133
+ # The URL for the RTSP server to talk to can change if multiple servers are
134
+ # involved in delivering content. This method can be used to change the
135
+ # server to talk to on the fly.
136
+ #
137
+ # @param [String] new_url The new server URL to use to communicate over.
138
+ def server_url=(new_url)
139
+ @server_uri = build_resource_uri_from new_url
140
+ end
141
+
142
+ # Sends the message over the socket.
143
+ #
144
+ # @param [RTSP::Message] message
145
+ # @return [RTSP::Response]
146
+ # @raise [RTSP::Error] If the timeout value is reached and the server hasn't
147
+ # responded.
148
+ def send_message message
149
+ RTSP::Client.log "Sending #{message.method_type.upcase} to #{message.request_uri}"
150
+ message.to_s.each_line { |line| RTSP::Client.log line.strip }
151
+
152
+ begin
153
+ response = Timeout::timeout(@connection.timeout) do
154
+ @connection.socket.send(message.to_s, 0)
155
+ socket_data = @connection.socket.recvfrom MAX_BYTES_TO_RECEIVE
156
+ RTSP::Response.new socket_data.first
157
+ end
158
+ rescue Timeout::Error
159
+ raise RTSP::Error, "Request took more than #{@connection.timeout} seconds to send."
160
+ end
161
+
162
+ RTSP::Client.log "Received response:"
163
+
164
+ if response
165
+ response.to_s.each_line { |line| RTSP::Client.log line.strip }
166
+ end
167
+
168
+ response
169
+ end
170
+
171
+ # Sends an OPTIONS message to the server specified by +@server_uri+. Sets
172
+ # +@supported_methods+ based on the list of supported methods returned in
173
+ # the Public headers.
174
+ #
175
+ # @param [Hash] additional_headers
176
+ # @return [RTSP::Response]
177
+ # @see http://tools.ietf.org/html/rfc2326#page-30 RFC 2326, Section 10.1.
178
+ def options(additional_headers={})
179
+ message = RTSP::Message.options(@server_uri.to_s).with_headers({
180
+ cseq: @cseq })
181
+ message.add_headers additional_headers
182
+
183
+ request(message) do |response|
184
+ @supported_methods = extract_supported_methods_from response.public
185
+ end
186
+ end
187
+
188
+ # Sends the DESCRIBE request, then extracts the SDP description into
189
+ # +@session_description+, extracts the session +@start_time+ and +@stop_time+,
190
+ # +@content_base+, +@media_control_tracks+, and +@aggregate_control_track+.
191
+ #
192
+ # @todo get tracks, IP's, ports, multicast/unicast
193
+ # @param [Hash] additional_headers
194
+ # @return [RTSP::Response]
195
+ # @see http://tools.ietf.org/html/rfc2326#page-31 RFC 2326, Section 10.2.
196
+ # @see #media_control_tracks
197
+ # @see #aggregate_control_track
198
+ def describe additional_headers={}
199
+ message = RTSP::Message.describe(@server_uri.to_s).with_headers({
200
+ cseq: @cseq })
201
+ message.add_headers additional_headers
202
+
203
+ request(message) do |response|
204
+ @session_description = response.body
205
+ #@session_start_time = response.body.start_time
206
+ #@session_stop_time = response.body.stop_time
207
+ @content_base = build_resource_uri_from response.content_base
208
+
209
+ @media_control_tracks = media_control_tracks
210
+ @aggregate_control_track = aggregate_control_track
211
+ end
212
+ end
213
+
214
+ # Sends an ANNOUNCE Request to the provided URL. This method also requires
215
+ # an SDP description to send to the server.
216
+ #
217
+ # @param [String] request_url The URL to post the presentation or media
218
+ # object to.
219
+ # @param [SDP::Description] description The SDP description to send to the
220
+ # server.
221
+ # @param [Hash] additional_headers
222
+ # @return [RTSP::Response]
223
+ # @see http://tools.ietf.org/html/rfc2326#page-32 RFC 2326, Section 10.3.
224
+ def announce(request_url, description, additional_headers={})
225
+ message = RTSP::Message.announce(request_url).with_headers({ cseq: @cseq })
226
+ message.add_headers additional_headers
227
+ message.body = description.to_s
228
+
229
+ request(message)
230
+ end
231
+
232
+ # Builds the Transport header fields string based on info used in setting up
233
+ # the Client instance.
234
+ #
235
+ # @return [String] The String to use with the Transport header.
236
+ # @see http://tools.ietf.org/html/rfc2326#page-58 RFC 2326, Section 12.39.
237
+ def request_transport
238
+ value = "RTP/AVP;#{@capturer.broadcast_type};client_port="
239
+ value << "#{@capturer.rtp_port}-#{@capturer.rtp_port + 1}\r\n"
240
+ end
241
+
242
+ # Sends the SETUP request, then sets +@session+ to the value returned in the
243
+ # Session header from the server, then sets the +@session_state+ to +:ready+.
244
+ #
245
+ # @todo +@session+ numbers are relevant to tracks, and a client must be able
246
+ # to play multiple tracks at the same time.
247
+ # @param [String] track
248
+ # @param [Hash] additional_headers
249
+ # @return [RTSP::Response] The response formatted as a Hash.
250
+ # @see http://tools.ietf.org/html/rfc2326#page-33 RFC 2326, Section 10.4.
251
+ def setup(track, additional_headers={})
252
+ message = RTSP::Message.setup(track).with_headers({
253
+ cseq: @cseq, transport: request_transport })
254
+ message.add_headers additional_headers
255
+
256
+ request(message) do |response|
257
+ if @session_state == :init
258
+ @session_state = :ready
259
+ end
260
+
261
+ @session = response.session
262
+ parser = RTSP::TransportParser.new
263
+ @transport = parser.parse response.transport
264
+
265
+ unless @transport[:transport_protocol].nil?
266
+ @capturer.transport_protocol = @transport[:transport_protocol]
267
+ end
268
+
269
+ @capturer.rtp_port = @transport[:client_port][:rtp].to_i
270
+ @capturer.broadcast_type = @transport[:broadcast_type]
271
+ end
272
+ end
273
+
274
+ # Sends the PLAY request and sets +@session_state+ to +:playing+.
275
+ #
276
+ # @param [String] track
277
+ # @param [Hash] additional_headers
278
+ # @return [RTSP::Response]
279
+ # @todo If playback over UDP doesn't result in any data coming in on the
280
+ # socket, re-setup with RTP/AVP/TCP;unicast;interleaved=0-1.
281
+ # @raise [RTSP::Error] If +#play+ is called but the session hasn't yet been
282
+ # set up via +#setup+.
283
+ # @see http://tools.ietf.org/html/rfc2326#page-34 RFC 2326, Section 10.5.
284
+ def play(track, additional_headers={})
285
+ message = RTSP::Message.play(track).with_headers({
286
+ cseq: @cseq, session: @session[:session_id] })
287
+ message.add_headers additional_headers
288
+
289
+ request(message) do
290
+ unless @session_state == :ready
291
+ raise RTSP::Error, "Session not set up yet. Run #setup first."
292
+ end
293
+
294
+ if @play_thread.nil?
295
+ RTSP::Client.log "Capturing RTP data on port #{@transport[:client_port][:rtp]}"
296
+
297
+ unless @capturer.running?
298
+ @play_thread = Thread.new do
299
+ @capturer.run
300
+ end
301
+ end
302
+ end
303
+
304
+ @session_state = :playing
305
+ end
306
+ end
307
+
308
+ # Sends the PAUSE request and sets +@session_state+ to +:ready+.
309
+ #
310
+ # @param [String] track A track or presentation URL to pause.
311
+ # @param [Hash] additional_headers
312
+ # @return [RTSP::Response]
313
+ # @see http://tools.ietf.org/html/rfc2326#page-36 RFC 2326, Section 10.6.
314
+ def pause(track, additional_headers={})
315
+ message = RTSP::Message.pause(track).with_headers({
316
+ cseq: @cseq, session: @session[:session_id] })
317
+ message.add_headers additional_headers
318
+
319
+ request(message) do
320
+ if [:playing, :recording].include? @session_state
321
+ @session_state = :ready
322
+ end
323
+ end
324
+ end
325
+
326
+ # Sends the TEARDOWN request, then resets all state-related instance
327
+ # variables.
328
+ #
329
+ # @param [String] track The presentation or media track to teardown.
330
+ # @param [Hash] additional_headers
331
+ # @return [RTSP::Response]
332
+ # @see http://tools.ietf.org/html/rfc2326#page-37 RFC 2326, Section 10.7.
333
+ def teardown(track, additional_headers={})
334
+ message = RTSP::Message.teardown(track).with_headers({
335
+ cseq: @cseq, session: @session[:session_id] })
336
+ message.add_headers additional_headers
337
+
338
+ request(message) do
339
+ reset_state
340
+
341
+ if @play_thread
342
+ @capturer.stop
343
+ @capturer.rtp_file.close
344
+ @play_thread.exit
345
+ end
346
+ end
347
+ end
348
+
349
+ # Sets state related variables back to their starting values;
350
+ # +@session_state+ is set to +:init+; +@session+ is set to 0.
351
+ def reset_state
352
+ @session_state = :init
353
+ @session = {}
354
+ end
355
+
356
+ # Sends the GET_PARAMETERS request.
357
+ #
358
+ # @param [String] track The presentation or media track to ping.
359
+ # @param [String] body The string containing the parameters to send.
360
+ # @param [Hash] additional_headers
361
+ # @return [RTSP::Response]
362
+ # @see http://tools.ietf.org/html/rfc2326#page-37 RFC 2326, Section 10.8.
363
+ def get_parameter(track, body="", additional_headers={})
364
+ message = RTSP::Message.get_parameter(track).with_headers({
365
+ cseq: @cseq })
366
+ message.add_headers additional_headers
367
+ message.body = body
368
+
369
+ request(message)
370
+ end
371
+
372
+ # Sends the SET_PARAMETERS request.
373
+ #
374
+ # @param [String] track The presentation or media track to teardown.
375
+ # @param [String] parameters The string containing the parameters to send.
376
+ # @param [Hash] additional_headers
377
+ # @return [RTSP::Response]
378
+ # @see http://tools.ietf.org/html/rfc2326#page-38 RFC 2326, Section 10.9.
379
+ def set_parameter(track, parameters, additional_headers={})
380
+ message = RTSP::Message.set_parameter(track).with_headers({
381
+ cseq: @cseq })
382
+ message.add_headers additional_headers
383
+ message.body = parameters
384
+
385
+ request(message)
386
+ end
387
+
388
+ # Sends the RECORD request and sets +@session_state+ to +:recording+.
389
+ #
390
+ # @param [String] track
391
+ # @param [Hash] additional_headers
392
+ # @return [RTSP::Response]
393
+ # @see http://tools.ietf.org/html/rfc2326#page-39 RFC 2326, Section 10.11.
394
+ def record(track, additional_headers={})
395
+ message = RTSP::Message.record(track).with_headers({
396
+ cseq: @cseq, session: @session[:session_id] })
397
+ message.add_headers additional_headers
398
+
399
+ request(message) { @session_state = :recording }
400
+ end
401
+
402
+ # Executes the Request with the arguments passed in, yields the response to
403
+ # the calling block, checks the CSeq response and the session response,
404
+ # then increments +@cseq+ by 1. Handles any exceptions raised during the
405
+ # Request.
406
+ #
407
+ # @param [Hash] new_args
408
+ # @yield [RTSP::Response]
409
+ # @return [RTSP::Response]
410
+ # @raise [RTSP::Error] All 4xx & 5xx response codes & their messages.
411
+ def request message
412
+ response = send_message message
413
+ #compare_sequence_number response.cseq
414
+ @cseq += 1
415
+
416
+ if response.code.to_s =~ /2../
417
+ yield response if block_given?
418
+ elsif response.code.to_s =~ /(4|5)../
419
+ if (defined? response.connection) && response.connection == 'Close'
420
+ reset_state
421
+ end
422
+
423
+ raise RTSP::Error, "#{response.code}: #{response.message}"
424
+ else
425
+ raise RTSP::Error, "Unknown Response code: #{response.code}"
426
+ end
427
+
428
+ dont_ensure_list = [:options, :describe, :teardown, :set_parameter,
429
+ :get_parameter]
430
+ unless dont_ensure_list.include? message.method_type
431
+ ensure_session
432
+ end
433
+
434
+ response
435
+ end
436
+
437
+ # Ensures that +@session+ is set before continuing on.
438
+ #
439
+ # @raise [RTSP::Error] Raises if @session isn't set.
440
+ def ensure_session
441
+ if @session.empty? || @session[:session_id] <= 0
442
+ raise RTSP::Error, "Session number not retrieved from server yet. Run SETUP first."
443
+ end
444
+ end
445
+
446
+ # Extracts the URL associated with the "control" attribute from the main
447
+ # section of the session description.
448
+ #
449
+ # @return [String] The URL as a String.
450
+ def aggregate_control_track
451
+ aggregate_control = @session_description.attributes.find_all do |a|
452
+ a[:attribute] == "control"
453
+ end
454
+
455
+ "#{@content_base}#{aggregate_control.first[:value].gsub(/\*/, "")}"
456
+ end
457
+
458
+ # Extracts the value of the "control" attribute from all media sections of
459
+ # the session description (SDP). You have to call the +#describe+ method in
460
+ # order to get the session description info.
461
+ #
462
+ # @return [Array<String>] The tracks made up of the content base + control
463
+ # track value.
464
+ # @see #describe
465
+ def media_control_tracks
466
+ tracks = []
467
+
468
+ if @session_description.nil?
469
+ tracks << ""
470
+ else
471
+ @session_description.media_sections.each do |media_section|
472
+ media_section[:attributes].each do |a|
473
+ tracks << "#{@content_base}#{a[:value]}" if a[:attribute] == "control"
474
+ end
475
+ end
476
+ end
477
+
478
+ tracks
479
+ end
480
+
481
+ # Compares the sequence number passed in to the current client sequence
482
+ # number ( +@cseq+ ) and raises if they're not equal. If that's the case, the
483
+ # server responded to a different request.
484
+ #
485
+ # @param [Fixnum] server_cseq Sequence number returned by the server.
486
+ # @raise [RTSP::Error] If the server returns a CSeq value that's different
487
+ # from what the client sent.
488
+ def compare_sequence_number server_cseq
489
+ if @cseq != server_cseq
490
+ message = "Sequence number mismatch. Client: #{@cseq}, Server: #{server_cseq}"
491
+ raise RTSP::Error, message
492
+ end
493
+ end
494
+
495
+ # Compares the session number passed in to the current client session
496
+ # number ( +@session+ ) and raises if they're not equal. If that's the case,
497
+ # the server responded to a different request.
498
+ #
499
+ # @param [Fixnum] server_session Session number returned by the server.
500
+ # @raise [RTSP::Error] If the server returns a Session value that's different
501
+ # from what the client sent.
502
+ def compare_session_number server_session
503
+ if @session[:session_id] != server_session
504
+ message = "Session number mismatch. Client: #{@session[:session_id]}, Server: #{server_session}"
505
+ raise RTSP::Error, message
506
+ end
507
+ end
508
+
509
+ # Takes the methods returned from the Public header from an OPTIONS response
510
+ # and puts them to an Array.
511
+ #
512
+ # @param [String] method_list The string returned from the server containing
513
+ # the list of methods it supports.
514
+ # @return [Array<Symbol>] The list of methods as symbols.
515
+ # @see #options
516
+ def extract_supported_methods_from method_list
517
+ method_list.downcase.split(', ').map { |m| m.to_sym }
518
+ end
519
+ end
520
+ end