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

  @cseq = 1
  reset_state
end

Instance Attribute Details

#capturerRTP::Receiver

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

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.



412
413
414
415
416
417
418
# File 'lib/rtsp/client.rb', line 412

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:



217
218
219
220
221
222
223
# File 'lib/rtsp/client.rb', line 217

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:



191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
# File 'lib/rtsp/client.rb', line 191

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:



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

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:



427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
# File 'lib/rtsp/client.rb', line 427

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:



171
172
173
174
175
176
177
178
179
# File 'lib/rtsp/client.rb', line 171

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:



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

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:



277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
# File 'lib/rtsp/client.rb', line 277

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:



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

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.



382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
# File 'lib/rtsp/client.rb', line 382

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:



230
231
232
233
# File 'lib/rtsp/client.rb', line 230

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.



143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
# File 'lib/rtsp/client.rb', line 143

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



133
134
135
# File 'lib/rtsp/client.rb', line 133

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:



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

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:



244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
# File 'lib/rtsp/client.rb', line 244

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



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

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