Class: RTSP::Client

Inherits:
Object
  • Object
show all
Extended by:
Global
Includes:
Helpers
Defined in:
lib/rtsp/client.rb

Overview

TODO:

Break Stream out in to its own class.

This is the main interface to an RTSP server. A client object uses a couple main objects for configuration: an RTP::Receiver and a Connection Struct. Use the capturer to configure how to capture the data which is the RTP stream provided by the RTSP server. Use the connection object to control the connection to the server.

You can initialize your client object using a block:

client = RTSP::Client.new("rtsp://192.168.1.10") do |connection, capturer|
  connection.timeout = 5
  capturer.rtp_file = File.open("my_file.rtp", "wb")
end

…or, without the block:

client = RTSP::Client.new("rtsp://192.168.1.10")
client.connection.timeout = 5
client.capturer.rtp_file = File.open("my_file.rtp", "wb")

After setting up the client object, call RTSP methods, Ruby style:

client.options

Remember that, unlike HTTP, RTSP is state-based (and thus the ability to call certain methods depends on calling other methods first). Your client object tells you the current RTSP state that it’s in:

client.options
client.session_state            # => :init
client.describe
client.session_state            # => :init
client.setup(client.media_control_tracks.first)
client.session_state            # => :ready
client.play(client.aggregate_control_track)
client.session_state            # => :playing
client.pause(client.aggregate_control_track)
client.session_state            # => :ready
client.teardown(client.aggregate_control_track)
client.session_state            # => :init

To enable/disable logging for clients, class methods:

RTSP::Client.log?           # => true
RTSP::Client.log = false

Constant Summary collapse

MAX_BYTES_TO_RECEIVE =
3000

Constants included from Global

Global::DEFAULT_RTSP_PORT, Global::DEFAULT_VERSION

Instance Attribute Summary collapse

Attributes included from Global

#log, #log_level, #logger, #raise_errors

Class Method Summary collapse

Instance Method Summary collapse

Methods included from Global

log?, raise_errors?, reset_config!, rtsp_version

Methods included from Helpers

#build_resource_uri_from

Constructor Details

#initialize(server_url = nil) {|Struct::Connection, RTP::Receiver| ... } ⇒ Client

TODO:

Use server_url everywhere; just use URI to ensure the port & rtspu.

Returns a new instance of Client.

Parameters:

  • server_url (String) (defaults to: nil)

    URL to the resource to stream. If no scheme is given, “rtsp” is assumed. If no port is given, 554 is assumed.

Yields:

  • (Struct::Connection, RTP::Receiver)

Yield Parameters:

  • server_url= (Struct::Connection)
  • timeout= (Struct::Connection)
  • socket= (Struct::Connection)
  • do_capture= (Struct::Connection)
  • interleave= (Struct::Connection)


104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
# File 'lib/rtsp/client.rb', line 104

def initialize(server_url=nil)
  Thread.abort_on_exception = true

  unless defined? Struct::Connection
    Struct.new("Connection", :server_url, :timeout, :socket,
      :do_capture, :interleave)
  end

  @connection = Struct::Connection.new
  @capturer   = RTP::Receiver.new

  yield(connection, capturer) if block_given?

  @connection.server_url       = server_url || @connection.server_url
  @server_uri                  = build_resource_uri_from(@connection.server_url)
  @connection.timeout          ||= 30
  @connection.socket           ||= TCPSocket.new(@server_uri.host, @server_uri.port)
  @connection.do_capture       ||= true
  @connection.interleave       ||= false
  @capturer.rtp_port           ||= 9000
  @capturer.transport_protocol ||= :UDP
  @capturer.broadcast_type     ||= :unicast
  @capturer.rtp_file           ||= Tempfile.new(RTP::Receiver::DEFAULT_CAPFILE_NAME)

  @play_thread = nil
  @cseq        = 1
  reset_state
end

Instance Attribute Details

#capturerRTP::Receiver

Use to get/set an object for capturing received data.

Parameters:

  • (RTP::Receiver)

Returns:

  • (RTP::Receiver)


84
85
86
# File 'lib/rtsp/client.rb', line 84

def capturer
  @capturer
end

#connectionStruct::Connection

Returns:

  • (Struct::Connection)


79
80
81
# File 'lib/rtsp/client.rb', line 79

def connection
  @connection
end

#cseqFixnum (readonly)

Returns Also known as the “sequence” number, this starts at 1 and increments after every request to the server. It is reset after calling #teardown.

Returns:

  • (Fixnum)

    Also known as the “sequence” number, this starts at 1 and increments after every request to the server. It is reset after calling #teardown.



67
68
69
# File 'lib/rtsp/client.rb', line 67

def cseq
  @cseq
end

#server_uriURI (readonly)

Returns The URI that points to the RTSP server’s resource.

Returns:

  • (URI)

    The URI that points to the RTSP server’s resource.



62
63
64
# File 'lib/rtsp/client.rb', line 62

def server_uri
  @server_uri
end

#sessionFixnum (readonly)

Returns A session is only established after calling #setup; otherwise returns nil.

Returns:

  • (Fixnum)

    A session is only established after calling #setup; otherwise returns nil.



71
72
73
# File 'lib/rtsp/client.rb', line 71

def session
  @session
end

#session_stateSymbol (readonly)

Returns See RFC section A.1..

Returns:



87
88
89
# File 'lib/rtsp/client.rb', line 87

def session_state
  @session_state
end

#supported_methodsArray<Symbol> (readonly)

Only populated after calling #options; otherwise returns nil. There’s no sense in making any other requests than these since the server doesn’t support them.

Returns:

  • (Array<Symbol>)

    Only populated after calling #options; otherwise returns nil. There’s no sense in making any other requests than these since the server doesn’t support them.



76
77
78
# File 'lib/rtsp/client.rb', line 76

def supported_methods
  @supported_methods
end

Class Method Details

.configure {|_self| ... } ⇒ Object

Use to configure options for all clients.

Yields:

  • (_self)

Yield Parameters:

  • _self (RTSP::Client)

    the object that the method was called on

See Also:



91
92
93
# File 'lib/rtsp/client.rb', line 91

def self.configure
  yield self if block_given?
end

Instance Method Details

#aggregate_control_trackString

Extracts the URL associated with the “control” attribute from the main section of the session description.

Returns:

  • (String)

    The URL as a String.



450
451
452
453
454
455
456
# File 'lib/rtsp/client.rb', line 450

def aggregate_control_track
  aggregate_control = @session_description.attributes.find_all do |a|
    a[:attribute] == "control"
  end

  "#{@content_base}#{aggregate_control.first[:value].gsub(/\*/, "")}"
end

#announce(request_url, description, additional_headers = {}) ⇒ RTSP::Response

Sends an ANNOUNCE Request to the provided URL. This method also requires an SDP description to send to the server.

Parameters:

  • request_url (String)

    The URL to post the presentation or media object to.

  • description (SDP::Description)

    The SDP description to send to the server.

  • additional_headers (Hash) (defaults to: {})

Returns:

See Also:



224
225
226
227
228
229
230
# File 'lib/rtsp/client.rb', line 224

def announce(request_url, description, additional_headers={})
  message = RTSP::Message.announce(request_url).with_headers({ cseq: @cseq })
  message.add_headers additional_headers
  message.body = description.to_s

  request(message)
end

#compare_sequence_number(server_cseq) ⇒ Object

Compares the sequence number passed in to the current client sequence number ( @cseq ) and raises if they’re not equal. If that’s the case, the server responded to a different request.

Parameters:

  • server_cseq (Fixnum)

    Sequence number returned by the server.

Raises:

  • (RTSP::Error)

    If the server returns a CSeq value that’s different from what the client sent.



488
489
490
491
492
493
# File 'lib/rtsp/client.rb', line 488

def compare_sequence_number server_cseq
  if @cseq != server_cseq
    message = "Sequence number mismatch.  Client: #{@cseq}, Server: #{server_cseq}"
    raise RTSP::Error, message
  end
end

#compare_session_number(server_session) ⇒ Object

Compares the session number passed in to the current client session number ( @session ) and raises if they’re not equal. If that’s the case, the server responded to a different request.

Parameters:

  • server_session (Fixnum)

    Session number returned by the server.

Raises:

  • (RTSP::Error)

    If the server returns a Session value that’s different from what the client sent.



502
503
504
505
506
507
# File 'lib/rtsp/client.rb', line 502

def compare_session_number server_session
  if @session[:session_id] != server_session
    message = "Session number mismatch.  Client: #{@session[:session_id]}, Server: #{server_session}"
    raise RTSP::Error, message
  end
end

#describe(additional_headers = {}) ⇒ RTSP::Response

TODO:

get tracks, IP’s, ports, multicast/unicast

Sends the DESCRIBE request, then extracts the SDP description into @session_description, extracts the session @start_time and @stop_time, @content_base, @media_control_tracks, and @aggregate_control_track.

Parameters:

  • additional_headers (Hash) (defaults to: {})

Returns:

See Also:



198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
# File 'lib/rtsp/client.rb', line 198

def describe additional_headers={}
  message = RTSP::Message.describe(@server_uri.to_s).with_headers({
      cseq: @cseq })
  message.add_headers additional_headers

  request(message) do |response|
    @session_description = response.body
    #@session_start_time =   response.body.start_time
    #@session_stop_time =    response.body.stop_time
    @content_base = build_resource_uri_from response.content_base

    @media_control_tracks    = media_control_tracks
    @aggregate_control_track = aggregate_control_track
  end
end

#ensure_sessionObject

Ensures that @session is set before continuing on.

Raises:



440
441
442
443
444
# File 'lib/rtsp/client.rb', line 440

def ensure_session
  if @session.empty? || @session[:session_id] <= 0
    raise RTSP::Error, "Session number not retrieved from server yet.  Run SETUP first."
  end
end

#extract_supported_methods_from(method_list) ⇒ Array<Symbol>

Takes the methods returned from the Public header from an OPTIONS response and puts them to an Array.

Parameters:

  • method_list (String)

    The string returned from the server containing the list of methods it supports.

Returns:

  • (Array<Symbol>)

    The list of methods as symbols.

See Also:



516
517
518
# File 'lib/rtsp/client.rb', line 516

def extract_supported_methods_from method_list
  method_list.downcase.split(', ').map { |m| m.to_sym }
end

#get_parameter(track, body = "", additional_headers = {}) ⇒ RTSP::Response

Sends the GET_PARAMETERS request.

Parameters:

  • track (String)

    The presentation or media track to ping.

  • body (String) (defaults to: "")

    The string containing the parameters to send.

  • additional_headers (Hash) (defaults to: {})

Returns:

See Also:



363
364
365
366
367
368
369
370
# File 'lib/rtsp/client.rb', line 363

def get_parameter(track, body="", additional_headers={})
  message = RTSP::Message.get_parameter(track).with_headers({
      cseq: @cseq })
  message.add_headers additional_headers
  message.body = body

  request(message)
end

#media_control_tracksArray<String>

Extracts the value of the “control” attribute from all media sections of the session description (SDP). You have to call the #describe method in order to get the session description info.

Returns:

  • (Array<String>)

    The tracks made up of the content base + control track value.

See Also:



465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
# File 'lib/rtsp/client.rb', line 465

def media_control_tracks
  tracks = []

  if @session_description.nil?
    tracks << ""
  else
    @session_description.media_sections.each do |media_section|
      media_section[:attributes].each do |a|
        tracks << "#{@content_base}#{a[:value]}" if a[:attribute] == "control"
      end
    end
  end

  tracks
end

#options(additional_headers = {}) ⇒ RTSP::Response

Sends an OPTIONS message to the server specified by @server_uri. Sets @supported_methods based on the list of supported methods returned in the Public headers.

Parameters:

  • additional_headers (Hash) (defaults to: {})

Returns:

See Also:



178
179
180
181
182
183
184
185
186
# File 'lib/rtsp/client.rb', line 178

def options(additional_headers={})
  message = RTSP::Message.options(@server_uri.to_s).with_headers({
      cseq: @cseq })
  message.add_headers additional_headers

  request(message) do |response|
    @supported_methods = extract_supported_methods_from response.public
  end
end

#pause(track, additional_headers = {}) ⇒ RTSP::Response

Sends the PAUSE request and sets @session_state to :ready.

Parameters:

  • track (String)

    A track or presentation URL to pause.

  • additional_headers (Hash) (defaults to: {})

Returns:

See Also:



314
315
316
317
318
319
320
321
322
323
324
# File 'lib/rtsp/client.rb', line 314

def pause(track, additional_headers={})
  message = RTSP::Message.pause(track).with_headers({
      cseq: @cseq, session: @session[:session_id] })
  message.add_headers additional_headers

  request(message) do
    if [:playing, :recording].include? @session_state
      @session_state = :ready
    end
  end
end

#play(track, additional_headers = {}) ⇒ RTSP::Response

TODO:

If playback over UDP doesn’t result in any data coming in on the socket, re-setup with RTP/AVP/TCP;unicast;interleaved=0-1.

Sends the PLAY request and sets @session_state to :playing.

Parameters:

  • track (String)
  • additional_headers (Hash) (defaults to: {})

Returns:

Raises:

  • (RTSP::Error)

    If #play is called but the session hasn’t yet been set up via #setup.

See Also:



284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
# File 'lib/rtsp/client.rb', line 284

def play(track, additional_headers={})
  message = RTSP::Message.play(track).with_headers({
      cseq: @cseq, session: @session[:session_id] })
  message.add_headers additional_headers

  request(message) do
    unless @session_state == :ready
      raise RTSP::Error, "Session not set up yet.  Run #setup first."
    end

    if @play_thread.nil?
      RTSP::Client.log "Capturing RTP data on port #{@transport[:client_port][:rtp]}"

      unless @capturer.running?
        @play_thread = Thread.new do
          @capturer.run
        end
      end
    end

    @session_state = :playing
  end
end

#record(track, additional_headers = {}) ⇒ RTSP::Response

Sends the RECORD request and sets @session_state to :recording.

Parameters:

  • track (String)
  • additional_headers (Hash) (defaults to: {})

Returns:

See Also:



394
395
396
397
398
399
400
# File 'lib/rtsp/client.rb', line 394

def record(track, additional_headers={})
  message = RTSP::Message.record(track).with_headers({
      cseq: @cseq, session: @session[:session_id] })
  message.add_headers additional_headers

  request(message) { @session_state = :recording }
end

#request(message) {|RTSP::Response| ... } ⇒ RTSP::Response

Executes the Request with the arguments passed in, yields the response to the calling block, checks the CSeq response and the session response, then increments @cseq by 1. Handles any exceptions raised during the Request.

Parameters:

  • new_args (Hash)

Yields:

Returns:

Raises:

  • (RTSP::Error)

    All 4xx & 5xx response codes & their messages.



411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
# File 'lib/rtsp/client.rb', line 411

def request message
  response = send_message message
  #compare_sequence_number response.cseq
  @cseq += 1

  if response.code.to_s =~ /2../
    yield response if block_given?
  elsif response.code.to_s =~ /(4|5)../
    if (defined? response.connection) && response.connection == 'Close'
      reset_state
    end

    raise RTSP::Error, "#{response.code}: #{response.message}"
  else
    raise RTSP::Error, "Unknown Response code: #{response.code}"
  end

  dont_ensure_list = [:options, :describe, :teardown, :set_parameter,
      :get_parameter]
  unless dont_ensure_list.include? message.method_type
    ensure_session
  end

  response
end

#request_transportString

Builds the Transport header fields string based on info used in setting up the Client instance.

Returns:

  • (String)

    The String to use with the Transport header.

See Also:



237
238
239
240
# File 'lib/rtsp/client.rb', line 237

def request_transport
  value = "RTP/AVP;#{@capturer.broadcast_type};client_port="
  value << "#{@capturer.rtp_port}-#{@capturer.rtp_port + 1}\r\n"
end

#reset_stateObject

Sets state related variables back to their starting values; @session_state is set to :init; @session is set to 0.



351
352
353
354
# File 'lib/rtsp/client.rb', line 351

def reset_state
  @session_state = :init
  @session = {}
end

#send_message(message) ⇒ RTSP::Response

Sends the message over the socket.

Parameters:

Returns:

Raises:

  • (RTSP::Error)

    If the timeout value is reached and the server hasn’t responded.



148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
# File 'lib/rtsp/client.rb', line 148

def send_message message
  RTSP::Client.log "Sending #{message.method_type.upcase} to #{message.request_uri}"
  message.to_s.each_line { |line| RTSP::Client.log line.strip }

  begin
    response = Timeout::timeout(@connection.timeout) do
      @connection.socket.send(message.to_s, 0)
      socket_data = @connection.socket.recvfrom MAX_BYTES_TO_RECEIVE
      RTSP::Response.new socket_data.first
    end
  rescue Timeout::Error
    raise RTSP::Error, "Request took more than #{@connection.timeout} seconds to send."
  end

  RTSP::Client.log "Received response:"

  if response
    response.to_s.each_line { |line| RTSP::Client.log line.strip }
  end

  response
end

#server_url=(new_url) ⇒ Object

The URL for the RTSP server to talk to can change if multiple servers are involved in delivering content. This method can be used to change the server to talk to on the fly.

Parameters:

  • new_url (String)

    The new server URL to use to communicate over.



138
139
140
# File 'lib/rtsp/client.rb', line 138

def server_url=(new_url)
  @server_uri = build_resource_uri_from new_url
end

#set_parameter(track, parameters, additional_headers = {}) ⇒ RTSP::Response

Sends the SET_PARAMETERS request.

Parameters:

  • track (String)

    The presentation or media track to teardown.

  • parameters (String)

    The string containing the parameters to send.

  • additional_headers (Hash) (defaults to: {})

Returns:

See Also:



379
380
381
382
383
384
385
386
# File 'lib/rtsp/client.rb', line 379

def set_parameter(track, parameters, additional_headers={})
  message = RTSP::Message.set_parameter(track).with_headers({
      cseq: @cseq })
  message.add_headers additional_headers
  message.body = parameters

  request(message)
end

#setup(track, additional_headers = {}) ⇒ RTSP::Response

TODO:

@session numbers are relevant to tracks, and a client must be able to play multiple tracks at the same time.

Sends the SETUP request, then sets @session to the value returned in the Session header from the server, then sets the @session_state to :ready.

Parameters:

  • track (String)
  • additional_headers (Hash) (defaults to: {})

Returns:

See Also:



251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
# File 'lib/rtsp/client.rb', line 251

def setup(track, additional_headers={})
  message = RTSP::Message.setup(track).with_headers({
      cseq: @cseq, transport: request_transport })
  message.add_headers additional_headers

  request(message) do |response|
    if @session_state == :init
      @session_state = :ready
    end

    @session   = response.session
    parser     = RTSP::TransportParser.new
    @transport = parser.parse response.transport

    unless @transport[:transport_protocol].nil?
      @capturer.transport_protocol = @transport[:transport_protocol]
    end

    @capturer.rtp_port     = @transport[:client_port][:rtp].to_i
    @capturer.broadcast_type = @transport[:broadcast_type]
  end
end

#teardown(track, additional_headers = {}) ⇒ RTSP::Response

Sends the TEARDOWN request, then resets all state-related instance variables.

Parameters:

  • track (String)

    The presentation or media track to teardown.

  • additional_headers (Hash) (defaults to: {})

Returns:

See Also:



333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
# File 'lib/rtsp/client.rb', line 333

def teardown(track, additional_headers={})
  message = RTSP::Message.teardown(track).with_headers({
      cseq: @cseq, session: @session[:session_id] })
  message.add_headers additional_headers

  request(message) do
    reset_state

    if @play_thread
      @capturer.stop
      @capturer.rtp_file.close
      @play_thread.exit
    end
  end
end