Skip to content
This repository has been archived by the owner on May 17, 2023. It is now read-only.

Addded RTSP Server and Streaming functionality using SOCAT #13

Open
wants to merge 20 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
148 changes: 148 additions & 0 deletions lib/rtsp/common.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
module RTSP

# Contains common methods belonging to Request and Response classes.
module Common

# @return [String] The unparsed request as a String.
def to_s
@raw_body
end

# Custom redefine to make sure all the dynamically created instance
# variables are displayed when this method is called.
#
# @return [String]
def inspect
me = "#<#{self.class.name}:#{self.__id__} "

self.instance_variables.each do |variable|
me << "#{variable}=#{instance_variable_get(variable).inspect}, "
end

me.sub!(/, $/, "")
me << ">"

me
end

# Takes the raw request text and splits it into a 2-element Array, where 0
# is the text containing the headers and 1 is the text containing the body.
#
# @param [String] raw_request
# @return [Array<String>] 2-element Array containing the head and body of
# the request. Body will be nil if there wasn't one in the request.
def split_head_and_body_from raw_request
head_and_body = raw_request.split("\r\n\r\n", 2)
head = head_and_body.first
body = head_and_body.last == head ? nil : head_and_body.last

[head, body]
end

# Pulls out the RTSP version, request code, and request message (AKA the
# status line info) into instance variables.
#
# @param [String] line The String containing the status line info.
def extract_status_line(line)
/RTSP\/(?<rtsp_version>\d\.\d)/ =~ line
/(?<url>rtsp:\/\/.*) RTSP/ =~ line
/rtsp:\/\/.*stream(?<stream_index>\d*)m?\/?.* RTSP/ =~ line
@url = url
@stream_index = stream_index.to_i

if rtsp_version.nil?
raise RTSP::Error, "Status line corrupted: #{line}"
end
end

# Returns the transport URL.
#
# @return [String] Transport URL associated with the request.
def transport_url
/client_port=(?<port>.*)-/ =~ transport

if port.nil?
log("Could not find client port associated with transport", :warn)
else
"#{@remote_host}:#{port}"
end
end

# Checks if the request is for a multicast stream.
#
# @return [Boolean] true if the request is for a multicast stream.
def multicast?
return false if @url.nil?

@url.end_with? "m"
end

# Reads through each header line of the RTSP request, extracts the
# request code, request message, request version, and creates a
# snake-case accessor with that value set.
#
# @param [String] head The section of headers from the request text.
def parse_head head
lines = head.split "\r\n"

lines.each_with_index do |line, i|
if i == 0
extract_status_line(line)
next
end

if line.include? "Session: "
value = {}
line =~ /Session: (\d+)/
value[:session_id] = $1.to_i

if line =~ /timeout=(.+)/
value[:timeout] = $1.to_i
end

create_reader("session", value)
elsif line.include? ": "
header_and_value = line.strip.split(":", 2)
header_name = header_and_value.first.downcase.gsub(/-/, "_")
create_reader(header_name, header_and_value[1].strip)
end
end
end

# Reads through each line of the RTSP response body and parses it if
# needed. Returns a SDP::Description if the Content-Type is
# 'application/sdp', otherwise returns the String that was passed in.
#
# @param [String] body
# @return [SDP::Description,String]
def parse_body body
if body =~ /^(\r\n|\n)/
body.gsub!(/^(\r\n|\n)/, '')
end

if @content_type == "application/sdp"
SDP.parse body
else
body
end
end

private

# Creates an attr_reader with the name given and sets it to the value
# that's given.
#
# @param [String] name
# @param [String,Hash] value
def create_reader(name, value)
unless value.empty?
if value.is_a? String
value = value =~ /^[0-9]*$/ ? value.to_i : value
end
end

instance_variable_set("@#{name}", value)
self.instance_eval "def #{name}; @#{name}; end"
end
end
end
4 changes: 2 additions & 2 deletions lib/rtsp/global.rb
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,8 @@ def log_level
end

# @param [String] message The string to log.
def log(message)
logger.send(log_level, message) if log?
def log(message, level=log_level)
logger.send(level, message) if log?
end

# Use to disable the raising of +RTSP::Error+s.
Expand Down
39 changes: 39 additions & 0 deletions lib/rtsp/request.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
require 'rubygems'
require 'sdp'
require_relative 'error'
require_relative 'global'
require_relative 'common'

module RTSP

# Parses raw request data from the server/client and turns it into
# attr_readers.
class Request
extend RTSP::Global
include RTSP::Common

attr_reader :rtsp_version
attr_reader :code
attr_reader :message
attr_reader :body
attr_reader :url
attr_reader :stream_index
attr_accessor :remote_host

# @param [String] raw_request The raw request string returned from the
# server/client.
# @param [String] remote_host The IP address of the remote host.
def initialize(raw_request, remote_host)
if raw_request.nil? || raw_request.empty?
raise RTSP::Error,
"#{self.class} received nil or empty string--this shouldn't happen."
end

@raw_body = raw_request
@remote_host = remote_host

head, body = split_head_and_body_from @raw_body
parse_head(head)
end
end
end
110 changes: 4 additions & 106 deletions lib/rtsp/response.rb
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
require 'rubygems'
require 'sdp'
require_relative 'error'
require_relative 'common'

module RTSP

# Parses raw response data from the server/client and turns it into
# attr_readers.
class Response
include RTSP::Common
attr_reader :rtsp_version
attr_reader :code
attr_reader :message
Expand All @@ -20,49 +22,13 @@ def initialize(raw_response)
"#{self.class} received nil string--this shouldn't happen."
end

@raw_response = raw_response
@raw_body = raw_response

head, body = split_head_and_body_from @raw_response
head, body = split_head_and_body_from @raw_body
parse_head(head)
@body = parse_body(body)
end

# @return [String] The unparsed response as a String.
def to_s
@raw_response
end

# Custom redefine to make sure all the dynamically created instance
# variables are displayed when this method is called.
#
# @return [String]
def inspect
me = "#<#{self.class.name}:#{self.__id__} "

self.instance_variables.each do |variable|
me << "#{variable}=#{instance_variable_get(variable).inspect}, "
end

me.sub!(/, $/, "")
me << ">"

me
end

# Takes the raw response text and splits it into a 2-element Array, where 0
# is the text containing the headers and 1 is the text containing the body.
#
# @param [String] raw_response
# @return [Array<String>] 2-element Array containing the head and body of
# the response. Body will be nil if there wasn't one in the response.
def split_head_and_body_from raw_response
head_and_body = raw_response.split("\r\n\r\n", 2)
head = head_and_body.first
body = head_and_body.last == head ? nil : head_and_body.last

[head, body]
end

# Pulls out the RTSP version, response code, and response message (AKA the
# status line info) into instance variables.
#
Expand All @@ -77,73 +43,5 @@ def extract_status_line(line)
raise RTSP::Error, "Status line corrupted: #{line}"
end
end

# Reads through each header line of the RTSP response, extracts the
# response code, response message, response version, and creates a
# snake-case accessor with that value set.
#
# @param [String] head The section of headers from the response text.
def parse_head head
lines = head.split "\r\n"

lines.each_with_index do |line, i|
if i == 0
extract_status_line(line)
next
end

if line.include? "Session: "
value = {}
line =~ /Session: (\d+)/
value[:session_id] = $1.to_i

if line =~ /timeout=(.+)/
value[:timeout] = $1.to_i
end

create_reader("session", value)
elsif line.include? ": "
header_and_value = line.strip.split(":", 2)
header_name = header_and_value.first.downcase.gsub(/-/, "_")
create_reader(header_name, header_and_value[1].strip)
end
end
end

# Reads through each line of the RTSP response body and parses it if
# needed. Returns a SDP::Description if the Content-Type is
# 'application/sdp', otherwise returns the String that was passed in.
#
# @param [String] body
# @return [SDP::Description,String]
def parse_body body
if body =~ /^(\r\n|\n)/
body.gsub!(/^(\r\n|\n)/, '')
end

if @content_type == "application/sdp"
SDP.parse body
else
body
end
end

private

# Creates an attr_reader with the name given and sets it to the value
# that's given.
#
# @param [String] name
# @param [String,Hash] value
def create_reader(name, value)
unless value.empty?
if value.is_a? String
value = value =~ /^[0-9]*$/ ? value.to_i : value
end
end

instance_variable_set("@#{name}", value)
self.instance_eval "def #{name}; @#{name}; end"
end
end
end
Loading