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)


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

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

  @max_authorization_tries = 3
  @cseq = 1
  reset_state
end

Instance Attribute Details

#capturerRTP::Receiver

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

Returns:

  • (RTP::Receiver)


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

def capturer
  @capturer
end

#connectionStruct::Connection

Returns:

  • (Struct::Connection)


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

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.



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

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.



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

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.



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

def session
  @session
end

#session_stateSymbol (readonly)

Returns See RFC section A.1..

Returns:



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

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.



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

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:



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

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.



431
432
433
434
435
436
437
# File 'lib/rtsp/client.rb', line 431

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:



233
234
235
236
237
238
239
# File 'lib/rtsp/client.rb', line 233

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

#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:



207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
# File 'lib/rtsp/client.rb', line 207

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

#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:



354
355
356
357
358
359
360
# File 'lib/rtsp/client.rb', line 354

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:



446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
# File 'lib/rtsp/client.rb', line 446

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:



187
188
189
190
191
192
193
194
195
# File 'lib/rtsp/client.rb', line 187

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:



317
318
319
320
321
322
323
324
325
326
327
# File 'lib/rtsp/client.rb', line 317

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 = {}, &block) ⇒ 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:



295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
# File 'lib/rtsp/client.rb', line 295

def play(track, additional_headers={}, &block)
  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

    RTSP::Client.log "Capturing RTP data on port #{@transport[:client_port][:rtp]}"
    @capturer.start(&block)
    @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:



383
384
385
386
387
388
389
# File 'lib/rtsp/client.rb', line 383

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:

Yields:

Returns:

Raises:

  • (RTSP::Error)

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



400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
# File 'lib/rtsp/client.rb', line 400

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 == 401
    send_authorization(message)
  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:



246
247
248
249
# File 'lib/rtsp/client.rb', line 246

def request_transport
  value = "RTP/AVP;#{@capturer.ip_addressing_type};client_port="
  value << "#{@capturer.rtp_port}-#{@capturer.rtcp_port}\r\n"
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.



159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
# File 'lib/rtsp/client.rb', line 159

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::Client.log "Received response:"
      socket_data.first.each_line { |line| RTSP::Client.log line.strip }

      RTSP::Response.new socket_data.first
    end
  rescue Timeout::Error
    raise RTSP::Error, "Request took more than #{@connection.timeout} seconds to send."
  end

  response
end

#server_password=(password) ⇒ Object

The password to be used in Basic Authentication

Parameters:

  • password (String)


149
150
151
# File 'lib/rtsp/client.rb', line 149

def server_password=(password)
  @server_uri.password = password
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.



135
136
137
# File 'lib/rtsp/client.rb', line 135

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

#server_user=(user) ⇒ Object

The user to be used in Basic Authentication

Parameters:

  • user (String)


142
143
144
# File 'lib/rtsp/client.rb', line 142

def server_user=(user)
  @server_uri.user = user
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:



369
370
371
372
373
374
375
# File 'lib/rtsp/client.rb', line 369

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:



260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
# File 'lib/rtsp/client.rb', line 260

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
    #pass up raw transport for debug and/or logging
    yield response.transport if block_given?
    @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.ip_address = @transport[:destination].to_s
  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:



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

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
    @capturer.stop
    reset_state
  end
end