em-rtmp 0.0.3

Sign up to get free protection for your applications and to get access to all the features.
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source "http://rubygems.org"
2
+
3
+ gemspec
data/README.md ADDED
@@ -0,0 +1,26 @@
1
+ # EM-RTMP
2
+
3
+ Asynchronous RTMP client powered by EventMachine.
4
+
5
+ ## Usage
6
+
7
+ ```ruby
8
+ require "em-rtmp"
9
+ # Start the EventMachine
10
+ EventMachine.run do
11
+
12
+ # Establish a connection
13
+ connection = EventMachine::RTMP.connect 'flashserver.bigmediacompany.com'
14
+
15
+ # Issue an RTMP connect after the RTMP handshake
16
+ connection.on_handshake_complete do
17
+ EventMachine::RTMP::ConnectRequest.new(connection).send
18
+ end
19
+
20
+ # After the RTMP connect succeeds, continue on
21
+ connection.on_ready do
22
+ ...
23
+ end
24
+
25
+ end
26
+ ```
data/Rakefile ADDED
@@ -0,0 +1,96 @@
1
+ require 'bundler'
2
+ Bundler::GemHelper.install_tasks
3
+
4
+ require 'rspec/core/rake_task'
5
+
6
+ desc 'Run all specs in the spec directory'
7
+ RSpec::Core::RakeTask.new(:spec)
8
+
9
+ task :default => :spec
10
+
11
+ desc 'Load an interactive console in our environment'
12
+ task :console do
13
+ require "eventmachine"
14
+ require "em-rtmp"
15
+ require "irb"
16
+ require "irb/completion"
17
+ module EventMachine
18
+ module RTMP
19
+ ARGV.clear
20
+ IRB.start
21
+ end
22
+ end
23
+ end
24
+
25
+ desc 'Start the test'
26
+ task :test do
27
+ require "eventmachine"
28
+ require "em-rtmp"
29
+ EventMachine.run do
30
+ connection = EventMachine::RTMP.ssl_connect '216.133.234.22', 2099
31
+
32
+ connection.on_handshake_complete do
33
+ req = EventMachine::RTMP::ConnectRequest.new connection
34
+ req.swfUrl = 'app:/mod_ser.dat'
35
+ req.tcUrl = 'rtmps://prod.na1.lol.riotgames.com:2099'
36
+ req.send
37
+ end
38
+
39
+ connection.on_ready do
40
+ obj = RocketAMF::Values::RemotingMessage.new
41
+ obj.destination = "playerStatsService"
42
+ obj.operation = "getRecentGames"
43
+ obj.body = [32310898]
44
+
45
+ req = EventMachine::RTMP::Request.new connection
46
+ req.header.channel_id = 3
47
+ req.header.message_stream_id = 0
48
+ req.header.message_type_id = 17
49
+
50
+ req.message.version = 3
51
+ req.message.command = nil
52
+ req.message.values = [obj]
53
+
54
+ req.body = req.message.encode
55
+
56
+ req.callback do |res|
57
+ p "I GOT A MOTHERFUCKING CALLBACK!"
58
+ end
59
+
60
+ req.errback do |res|
61
+ p "Couldn't do it..."
62
+ end
63
+
64
+ req.send
65
+ end
66
+ #
67
+ # connection.on_ready do
68
+ # obj = RocketAMF::Values::RemotingMessage.new
69
+ # obj.destination = "playerStatsService"
70
+ # obj.operation = "getRecentGames"
71
+ # obj.body = [32310898]
72
+ #
73
+ # req = EventMachine::RTMP::Request.new connection
74
+ # req.header.channel_id = 3
75
+ # req.header.message_stream_id = 0
76
+ # req.header.message_type_id = 17
77
+ #
78
+ # req.message.version = 3
79
+ # req.message.command = nil
80
+ # req.message.values = [obj]
81
+ #
82
+ # req.body = req.message.encode
83
+ #
84
+ # req.callback do |res|
85
+ # p "I GOT A MOTHERFUCKING CALLBACK!"
86
+ # end
87
+ #
88
+ # req.errback do |res|
89
+ # p "Couldn't do it..."
90
+ # end
91
+ #
92
+ # req.send
93
+ # end
94
+
95
+ end
96
+ end
data/lib/em-rtmp.rb ADDED
@@ -0,0 +1,18 @@
1
+ require "eventmachine"
2
+ require "rocketamf"
3
+
4
+ require "em-rtmp/buffer"
5
+ require "em-rtmp/connection"
6
+ require "em-rtmp/connect_request"
7
+ require "em-rtmp/handshake"
8
+ require "em-rtmp/heartbeat"
9
+ require "em-rtmp/header"
10
+ require "em-rtmp/logger"
11
+ require "em-rtmp/message"
12
+ require "em-rtmp/pending_request"
13
+ require "em-rtmp/request"
14
+ require "em-rtmp/response"
15
+ require "em-rtmp/response_router"
16
+ require "em-rtmp/rtmp"
17
+ require "em-rtmp/uuid"
18
+ require "em-rtmp/version"
@@ -0,0 +1,33 @@
1
+ require "stringio"
2
+ require "em-rtmp/io_helpers"
3
+
4
+ module EventMachine
5
+ module RTMP
6
+ class Buffer < StringIO
7
+ include EventMachine::RTMP::IOHelpers
8
+
9
+ # Gets the number of remaining bytes in the buffer
10
+ #
11
+ # Returns an Integer representing the number of bytes left
12
+ def remaining
13
+ length - pos
14
+ end
15
+
16
+ # Truncate the buffer to nothing and set the position to zero
17
+ #
18
+ # Returns the new position (zero)
19
+ def reset
20
+ truncate 0
21
+ seek 0
22
+ end
23
+
24
+ # Append to the end of the string without changing our position
25
+ #
26
+ # Returns nothing
27
+ def append(string)
28
+ self.string << string
29
+ end
30
+
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,84 @@
1
+ require "em-rtmp/request"
2
+
3
+ module EventMachine
4
+ module RTMP
5
+ class ConnectRequest < Request
6
+
7
+ DEFAULT_PARAMETERS = {
8
+ app: '',
9
+ flashVer: 'WIN 10,1,85,3',
10
+ swfUrl: '',
11
+ tcUrl: '',
12
+ fpad: false,
13
+ capabilities: 239,
14
+ audioCodecs: 3191,
15
+ videoCodecs: 252,
16
+ videoFunction: 1,
17
+ pageUrl: nil,
18
+ objectEncoding: 3
19
+ }
20
+
21
+ attr_accessor :app, :flashVer, :swfUrl, :tcUrl, :fpad, :capabilities,
22
+ :audioCodecs, :videoCodecs, :videoFunction, :pageUrl, :objectEncoding
23
+
24
+ # Initialize the request and set sane defaults for an RTMP connect
25
+ # request (transaction_id, command, header values)
26
+ #
27
+ # Returns nothing
28
+ def initialize(connection)
29
+ super connection
30
+
31
+ self.header.channel_id = 3
32
+ self.header.message_type = :amf0
33
+ self.header
34
+
35
+ self.message.version = 0
36
+ self.message.transaction_id = 1
37
+ self.message.command = "connect"
38
+
39
+ callback do |res|
40
+ Logger.debug "rtmp connect failed, becoming ready"
41
+ @connection.change_state :ready
42
+ end
43
+
44
+ errback do |res|
45
+ Logger.debug "rtmp connect failed, closing connection"
46
+ @connection.close_connection
47
+ end
48
+ end
49
+
50
+ # Construct a list of parameters given our class attributes, used
51
+ # to form the connect message object.
52
+ #
53
+ # Returns a Hash of symbols => values
54
+ def parameters
55
+ instance_values = Hash[instance_variables.map {|k| [k.to_s[1..-1].to_sym, instance_variable_get(k)]}]
56
+ DEFAULT_PARAMETERS.merge instance_values.select {|k,v| DEFAULT_PARAMETERS.key? k }
57
+ end
58
+
59
+ def command_message
60
+ cm = RocketAMF::Values::CommandMessage.new
61
+ cm.operation = 5
62
+ cm.correlationId = ""
63
+ cm.timestamp = 0
64
+ cm.timeToLive = 0
65
+ cm.messageId = UUID.random
66
+ cm.body = {}
67
+ cm.destination = ""
68
+ cm.headers = { "DSMessagingVersion" => 1, "DSId" => "my-rtmps" }
69
+ cm
70
+ end
71
+
72
+ # Given the specific nature of a connect request, we can just set the message
73
+ # values to our params then encode that as our body before sending.
74
+ #
75
+ # Returns the result of super
76
+ def send
77
+ self.message.values = [parameters, false, "nil", "", command_message]
78
+ self.body = message.encode
79
+ super
80
+ end
81
+
82
+ end
83
+ end
84
+ end
@@ -0,0 +1,189 @@
1
+ require "em-rtmp/io_helpers"
2
+
3
+ module EventMachine
4
+ module RTMP
5
+ class Connection < EventMachine::Connection
6
+ include IOHelpers
7
+
8
+ attr_accessor :state, :chunk_size, :channels, :pending_requests
9
+
10
+ # Initialize the connection and setup our handshake
11
+ #
12
+ # Returns nothing
13
+ def initialize
14
+ super
15
+ @buffer = Buffer.new
16
+ @chunk_size = 128
17
+ @response_router = ResponseRouter.new(self)
18
+ @handshake = Handshake.new(self)
19
+ @callbacks = { :handshake_complete => [], :ready => [], :disconnected => [] }
20
+ @channels = []
21
+ @pending_requests = {}
22
+ @state = :connecting
23
+ end
24
+
25
+ # Used to track changes in state
26
+ #
27
+ # state - Symbol, new state to enter
28
+ #
29
+ # Returns nothing
30
+ def change_state(state)
31
+ return if @state == state
32
+ Logger.info "state changed from #{@state} to #{state}", caller: caller
33
+ @state = state
34
+ run_callbacks state
35
+ end
36
+
37
+ # Start the RTMP handshake process
38
+ #
39
+ # Returns nothing
40
+ def begin_rtmp_handshake
41
+ change_state :handshake
42
+ @handshake.issue_challenge
43
+ end
44
+
45
+ # Called to add a callback for when the RTMP handshake is
46
+ # completed. Most useful for issuing an RTMP connect request.
47
+ #
48
+ # blk - block to execute
49
+ #
50
+ # Returns nothing
51
+ def on_handshake_complete(&blk)
52
+ @callbacks[:handshake_complete] << blk
53
+ end
54
+
55
+ # Called to add a callback for when the RTMP connection has
56
+ # been established and is ready for work.
57
+ #
58
+ # blk - block to execute
59
+ #
60
+ # Returns nothing
61
+ def on_ready(&blk)
62
+ @callbacks[:ready] << blk
63
+ end
64
+
65
+ # Called to add a callback for when the TCP connection has
66
+ # been disconnected.
67
+ #
68
+ # blk - block to execute
69
+ #
70
+ # Returns nothing
71
+ def on_disconnected(&blk)
72
+ @callbacks[:disconnected] << blk
73
+ end
74
+
75
+ # Called to run the callbacks for a specific event
76
+ #
77
+ # event - symbol representing the event to run callbacks for
78
+ #
79
+ # Returns nothing
80
+ def run_callbacks(event)
81
+ if @callbacks.keys.include? event
82
+ @callbacks[event].each do |blk|
83
+ blk.call
84
+ end
85
+ end
86
+ end
87
+
88
+ # Reads from the buffer, to facilitate IO operations
89
+ #
90
+ # Returns the result of the read
91
+ def read(length)
92
+ Logger.debug "reading #{length} bytes from buffer"
93
+ @buffer.read length
94
+ end
95
+
96
+ # Writes data to the EventMachine connection
97
+ #
98
+ # Returns nothing
99
+ def write(data)
100
+ Logger.debug "sending #{data.length} bytes to stream"
101
+ send_data data
102
+ end
103
+
104
+ # Obtain the number of bytes waiting to be read in the buffer
105
+ #
106
+ # Returns an Integer
107
+ def bytes_waiting
108
+ @buffer.remaining
109
+ end
110
+
111
+ # Perform the next step after the connection has been established
112
+ # Called by the Event Machine
113
+ #
114
+ # Returns nothing
115
+ def connection_completed
116
+ Logger.info "connection completed, issuing rtmp handshake"
117
+ begin_rtmp_handshake
118
+ end
119
+
120
+ # Change our state to disconnected if we lose the connection.
121
+ # Called by the Event Machine
122
+ #
123
+ # Returns nothing
124
+ def unbind
125
+ Logger.info "disconnected from peer"
126
+ change_state :disconnected
127
+ end
128
+
129
+ # Receives data and offers it to the appropriate delegate object.
130
+ # Fires a method call to buffer_changed to take action.
131
+ # Called by the Event machine
132
+ #
133
+ # data - data received
134
+ #
135
+ # Returns nothing
136
+ def receive_data(data)
137
+ Logger.debug "received #{data.length} bytes"
138
+ @buffer.append data
139
+ buffer_changed
140
+ end
141
+
142
+ # Called when the buffer is changed, indicating that we may want
143
+ # to change state or delegate to another object
144
+ #
145
+ # Returns nothing
146
+ def buffer_changed
147
+
148
+ loop do
149
+ break if bytes_waiting < 1
150
+
151
+ case state
152
+
153
+ when :handshake
154
+ if @handshake.buffer_changed == :handshake_complete
155
+ @handshake = nil
156
+ change_state :handshake_complete
157
+ end
158
+ break
159
+
160
+ when :handshake_complete, :ready
161
+ @response_router.buffer_changed
162
+ next
163
+
164
+ end
165
+ end
166
+
167
+ end
168
+ end
169
+
170
+ # A secure connection behaves identically except that it delays the
171
+ # RTMP handshake until after the ssl handshake occurs.
172
+ class SecureConnection < Connection
173
+
174
+ # When the connection is established, make it secure before
175
+ # starting the RTMP handshake process.
176
+ def connection_completed
177
+ Logger.info "connection completed, starting tls"
178
+ start_tls verify_peer: false
179
+ end
180
+
181
+ # Connection is now secure, issue the RTMP handshake challenge
182
+ def ssl_handshake_completed
183
+ Logger.info "ssl handshake completed, issuing rtmp handshake"
184
+ begin_rtmp_handshake
185
+ end
186
+ end
187
+
188
+ end
189
+ end