webrtc-ruby 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.dockerignore +19 -0
- data/.rspec +3 -0
- data/CHANGELOG.md +12 -0
- data/Dockerfile +49 -0
- data/LICENSE +21 -0
- data/README.md +257 -0
- data/Rakefile +42 -0
- data/examples/signaling_server/server.rb +200 -0
- data/examples/simple_data_channel.rb +81 -0
- data/examples/video_call.rb +152 -0
- data/ext/webrtc_ruby/CMakeLists.txt +84 -0
- data/ext/webrtc_ruby/Makefile +31 -0
- data/ext/webrtc_ruby/webrtc_ruby.c +757 -0
- data/ext/webrtc_ruby/webrtc_ruby.h +169 -0
- data/lib/webrtc/configuration.rb +99 -0
- data/lib/webrtc/data_channel.rb +154 -0
- data/lib/webrtc/dtls_transport.rb +54 -0
- data/lib/webrtc/dtmf_sender.rb +81 -0
- data/lib/webrtc/errors.rb +10 -0
- data/lib/webrtc/ffi/library.rb +100 -0
- data/lib/webrtc/ice_candidate.rb +62 -0
- data/lib/webrtc/ice_transport.rb +95 -0
- data/lib/webrtc/media_stream.rb +67 -0
- data/lib/webrtc/media_stream_track.rb +83 -0
- data/lib/webrtc/peer_connection.rb +346 -0
- data/lib/webrtc/promise.rb +59 -0
- data/lib/webrtc/rtp_receiver.rb +51 -0
- data/lib/webrtc/rtp_sender.rb +85 -0
- data/lib/webrtc/rtp_transceiver.rb +34 -0
- data/lib/webrtc/sctp_transport.rb +31 -0
- data/lib/webrtc/session_description.rb +64 -0
- data/lib/webrtc/stats_report.rb +199 -0
- data/lib/webrtc/version.rb +5 -0
- data/lib/webrtc.rb +38 -0
- data/webrtc-ruby.gemspec +33 -0
- metadata +107 -0
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
#ifndef WEBRTC_RUBY_H
|
|
2
|
+
#define WEBRTC_RUBY_H
|
|
3
|
+
|
|
4
|
+
#include <stdint.h>
|
|
5
|
+
#include <stdbool.h>
|
|
6
|
+
#include <stddef.h>
|
|
7
|
+
|
|
8
|
+
#ifdef __cplusplus
|
|
9
|
+
extern "C" {
|
|
10
|
+
#endif
|
|
11
|
+
|
|
12
|
+
#ifdef _WIN32
|
|
13
|
+
#ifdef WEBRTC_RUBY_BUILDING
|
|
14
|
+
#define WEBRTC_RUBY_API __declspec(dllexport)
|
|
15
|
+
#else
|
|
16
|
+
#define WEBRTC_RUBY_API __declspec(dllimport)
|
|
17
|
+
#endif
|
|
18
|
+
#else
|
|
19
|
+
#define WEBRTC_RUBY_API __attribute__((visibility("default")))
|
|
20
|
+
#endif
|
|
21
|
+
|
|
22
|
+
typedef struct webrtc_peer_connection* webrtc_peer_connection_t;
|
|
23
|
+
typedef struct webrtc_data_channel* webrtc_data_channel_t;
|
|
24
|
+
typedef struct webrtc_session_description* webrtc_session_description_t;
|
|
25
|
+
typedef struct webrtc_ice_candidate* webrtc_ice_candidate_t;
|
|
26
|
+
|
|
27
|
+
typedef struct {
|
|
28
|
+
int code;
|
|
29
|
+
const char* message;
|
|
30
|
+
} webrtc_error_t;
|
|
31
|
+
|
|
32
|
+
typedef struct {
|
|
33
|
+
const char** ice_servers;
|
|
34
|
+
size_t ice_servers_count;
|
|
35
|
+
const char* ice_transport_policy;
|
|
36
|
+
const char* bundle_policy;
|
|
37
|
+
} webrtc_configuration_t;
|
|
38
|
+
|
|
39
|
+
typedef void (*webrtc_void_callback)(void* user_data);
|
|
40
|
+
typedef void (*webrtc_ice_candidate_callback)(const char* candidate,
|
|
41
|
+
const char* sdp_mid,
|
|
42
|
+
int sdp_mline_index,
|
|
43
|
+
void* user_data);
|
|
44
|
+
typedef void (*webrtc_data_channel_callback)(webrtc_data_channel_t dc,
|
|
45
|
+
void* user_data);
|
|
46
|
+
typedef void (*webrtc_message_callback)(const uint8_t* data,
|
|
47
|
+
size_t length,
|
|
48
|
+
bool is_binary,
|
|
49
|
+
void* user_data);
|
|
50
|
+
typedef void (*webrtc_state_change_callback)(int state, void* user_data);
|
|
51
|
+
|
|
52
|
+
WEBRTC_RUBY_API int webrtc_init(void);
|
|
53
|
+
WEBRTC_RUBY_API void webrtc_cleanup(void);
|
|
54
|
+
|
|
55
|
+
WEBRTC_RUBY_API webrtc_peer_connection_t webrtc_peer_connection_create(
|
|
56
|
+
const webrtc_configuration_t* config,
|
|
57
|
+
webrtc_error_t* error);
|
|
58
|
+
WEBRTC_RUBY_API void webrtc_peer_connection_destroy(webrtc_peer_connection_t pc);
|
|
59
|
+
|
|
60
|
+
WEBRTC_RUBY_API int webrtc_peer_connection_create_offer(
|
|
61
|
+
webrtc_peer_connection_t pc,
|
|
62
|
+
webrtc_session_description_t* out_sdp,
|
|
63
|
+
webrtc_error_t* error);
|
|
64
|
+
|
|
65
|
+
WEBRTC_RUBY_API int webrtc_peer_connection_create_answer(
|
|
66
|
+
webrtc_peer_connection_t pc,
|
|
67
|
+
webrtc_session_description_t* out_sdp,
|
|
68
|
+
webrtc_error_t* error);
|
|
69
|
+
|
|
70
|
+
WEBRTC_RUBY_API int webrtc_peer_connection_set_local_description(
|
|
71
|
+
webrtc_peer_connection_t pc,
|
|
72
|
+
webrtc_session_description_t sdp,
|
|
73
|
+
webrtc_error_t* error);
|
|
74
|
+
|
|
75
|
+
WEBRTC_RUBY_API int webrtc_peer_connection_set_remote_description(
|
|
76
|
+
webrtc_peer_connection_t pc,
|
|
77
|
+
webrtc_session_description_t sdp,
|
|
78
|
+
webrtc_error_t* error);
|
|
79
|
+
|
|
80
|
+
WEBRTC_RUBY_API int webrtc_peer_connection_add_ice_candidate(
|
|
81
|
+
webrtc_peer_connection_t pc,
|
|
82
|
+
webrtc_ice_candidate_t candidate,
|
|
83
|
+
webrtc_error_t* error);
|
|
84
|
+
|
|
85
|
+
WEBRTC_RUBY_API void webrtc_peer_connection_on_ice_candidate(
|
|
86
|
+
webrtc_peer_connection_t pc,
|
|
87
|
+
webrtc_ice_candidate_callback callback,
|
|
88
|
+
void* user_data);
|
|
89
|
+
|
|
90
|
+
WEBRTC_RUBY_API void webrtc_peer_connection_on_connection_state_change(
|
|
91
|
+
webrtc_peer_connection_t pc,
|
|
92
|
+
webrtc_state_change_callback callback,
|
|
93
|
+
void* user_data);
|
|
94
|
+
|
|
95
|
+
WEBRTC_RUBY_API void webrtc_peer_connection_on_data_channel(
|
|
96
|
+
webrtc_peer_connection_t pc,
|
|
97
|
+
webrtc_data_channel_callback callback,
|
|
98
|
+
void* user_data);
|
|
99
|
+
|
|
100
|
+
WEBRTC_RUBY_API int webrtc_peer_connection_get_signaling_state(
|
|
101
|
+
webrtc_peer_connection_t pc);
|
|
102
|
+
WEBRTC_RUBY_API int webrtc_peer_connection_get_ice_gathering_state(
|
|
103
|
+
webrtc_peer_connection_t pc);
|
|
104
|
+
WEBRTC_RUBY_API int webrtc_peer_connection_get_ice_connection_state(
|
|
105
|
+
webrtc_peer_connection_t pc);
|
|
106
|
+
WEBRTC_RUBY_API int webrtc_peer_connection_get_connection_state(
|
|
107
|
+
webrtc_peer_connection_t pc);
|
|
108
|
+
|
|
109
|
+
WEBRTC_RUBY_API webrtc_data_channel_t webrtc_peer_connection_create_data_channel(
|
|
110
|
+
webrtc_peer_connection_t pc,
|
|
111
|
+
const char* label,
|
|
112
|
+
bool ordered,
|
|
113
|
+
int max_retransmits,
|
|
114
|
+
int max_packet_life_time,
|
|
115
|
+
const char* protocol,
|
|
116
|
+
bool negotiated,
|
|
117
|
+
int id,
|
|
118
|
+
webrtc_error_t* error);
|
|
119
|
+
|
|
120
|
+
WEBRTC_RUBY_API void webrtc_data_channel_destroy(webrtc_data_channel_t dc);
|
|
121
|
+
WEBRTC_RUBY_API int webrtc_data_channel_send(webrtc_data_channel_t dc,
|
|
122
|
+
const uint8_t* data,
|
|
123
|
+
size_t length,
|
|
124
|
+
bool is_binary,
|
|
125
|
+
webrtc_error_t* error);
|
|
126
|
+
WEBRTC_RUBY_API void webrtc_data_channel_close(webrtc_data_channel_t dc);
|
|
127
|
+
WEBRTC_RUBY_API int webrtc_data_channel_get_ready_state(webrtc_data_channel_t dc);
|
|
128
|
+
WEBRTC_RUBY_API const char* webrtc_data_channel_get_label(webrtc_data_channel_t dc);
|
|
129
|
+
WEBRTC_RUBY_API size_t webrtc_data_channel_get_buffered_amount(webrtc_data_channel_t dc);
|
|
130
|
+
|
|
131
|
+
WEBRTC_RUBY_API void webrtc_data_channel_on_open(webrtc_data_channel_t dc,
|
|
132
|
+
webrtc_void_callback callback,
|
|
133
|
+
void* user_data);
|
|
134
|
+
WEBRTC_RUBY_API void webrtc_data_channel_on_message(webrtc_data_channel_t dc,
|
|
135
|
+
webrtc_message_callback callback,
|
|
136
|
+
void* user_data);
|
|
137
|
+
WEBRTC_RUBY_API void webrtc_data_channel_on_close(webrtc_data_channel_t dc,
|
|
138
|
+
webrtc_void_callback callback,
|
|
139
|
+
void* user_data);
|
|
140
|
+
|
|
141
|
+
WEBRTC_RUBY_API webrtc_session_description_t webrtc_session_description_create(
|
|
142
|
+
const char* type,
|
|
143
|
+
const char* sdp,
|
|
144
|
+
webrtc_error_t* error);
|
|
145
|
+
WEBRTC_RUBY_API void webrtc_session_description_destroy(
|
|
146
|
+
webrtc_session_description_t sd);
|
|
147
|
+
WEBRTC_RUBY_API const char* webrtc_session_description_get_type(
|
|
148
|
+
webrtc_session_description_t sd);
|
|
149
|
+
WEBRTC_RUBY_API const char* webrtc_session_description_get_sdp(
|
|
150
|
+
webrtc_session_description_t sd);
|
|
151
|
+
|
|
152
|
+
WEBRTC_RUBY_API webrtc_ice_candidate_t webrtc_ice_candidate_create(
|
|
153
|
+
const char* candidate,
|
|
154
|
+
const char* sdp_mid,
|
|
155
|
+
int sdp_mline_index,
|
|
156
|
+
webrtc_error_t* error);
|
|
157
|
+
WEBRTC_RUBY_API void webrtc_ice_candidate_destroy(webrtc_ice_candidate_t ic);
|
|
158
|
+
WEBRTC_RUBY_API const char* webrtc_ice_candidate_get_candidate(
|
|
159
|
+
webrtc_ice_candidate_t ic);
|
|
160
|
+
WEBRTC_RUBY_API const char* webrtc_ice_candidate_get_sdp_mid(
|
|
161
|
+
webrtc_ice_candidate_t ic);
|
|
162
|
+
WEBRTC_RUBY_API int webrtc_ice_candidate_get_sdp_mline_index(
|
|
163
|
+
webrtc_ice_candidate_t ic);
|
|
164
|
+
|
|
165
|
+
#ifdef __cplusplus
|
|
166
|
+
}
|
|
167
|
+
#endif
|
|
168
|
+
|
|
169
|
+
#endif
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module WebRTC
|
|
4
|
+
class RTCConfiguration
|
|
5
|
+
ICE_TRANSPORT_POLICIES = %i[all relay].freeze
|
|
6
|
+
BUNDLE_POLICIES = %i[balanced max_compat max_bundle].freeze
|
|
7
|
+
RTCP_MUX_POLICIES = %i[require].freeze
|
|
8
|
+
|
|
9
|
+
attr_accessor :ice_servers, :ice_transport_policy, :bundle_policy, :rtcp_mux_policy, :certificates,
|
|
10
|
+
:ice_candidate_pool_size
|
|
11
|
+
|
|
12
|
+
def initialize(options = {})
|
|
13
|
+
@ice_servers = normalize_ice_servers(options[:ice_servers] || options[:iceServers] || [])
|
|
14
|
+
@ice_transport_policy = options[:ice_transport_policy] || options[:iceTransportPolicy] || :all
|
|
15
|
+
@bundle_policy = options[:bundle_policy] || options[:bundlePolicy] || :balanced
|
|
16
|
+
@rtcp_mux_policy = options[:rtcp_mux_policy] || options[:rtcpMuxPolicy] || :require
|
|
17
|
+
@certificates = options[:certificates] || []
|
|
18
|
+
@ice_candidate_pool_size = options[:ice_candidate_pool_size] || options[:iceCandidatePoolSize] || 0
|
|
19
|
+
|
|
20
|
+
validate!
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def to_h
|
|
24
|
+
{
|
|
25
|
+
iceServers: @ice_servers.map(&:to_h),
|
|
26
|
+
iceTransportPolicy: @ice_transport_policy,
|
|
27
|
+
bundlePolicy: @bundle_policy,
|
|
28
|
+
rtcpMuxPolicy: @rtcp_mux_policy,
|
|
29
|
+
iceCandidatePoolSize: @ice_candidate_pool_size
|
|
30
|
+
}
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
private
|
|
34
|
+
|
|
35
|
+
def normalize_ice_servers(servers)
|
|
36
|
+
servers.map do |server|
|
|
37
|
+
server.is_a?(RTCIceServer) ? server : RTCIceServer.new(server)
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def validate!
|
|
42
|
+
unless ICE_TRANSPORT_POLICIES.include?(@ice_transport_policy)
|
|
43
|
+
raise InvalidParameterError, "Invalid ice_transport_policy: #{@ice_transport_policy}"
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
unless BUNDLE_POLICIES.include?(@bundle_policy)
|
|
47
|
+
raise InvalidParameterError, "Invalid bundle_policy: #{@bundle_policy}"
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
return if @ice_candidate_pool_size.between?(0, 255)
|
|
51
|
+
|
|
52
|
+
raise InvalidParameterError, 'ice_candidate_pool_size must be 0-255'
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
class RTCIceServer
|
|
57
|
+
attr_accessor :urls, :username, :credential, :credential_type
|
|
58
|
+
|
|
59
|
+
def initialize(options = {})
|
|
60
|
+
@urls = normalize_urls(options[:urls] || options[:url])
|
|
61
|
+
@username = options[:username]
|
|
62
|
+
@credential = options[:credential]
|
|
63
|
+
@credential_type = options[:credential_type] || options[:credentialType] || :password
|
|
64
|
+
|
|
65
|
+
validate!
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def to_h
|
|
69
|
+
hash = { urls: @urls }
|
|
70
|
+
hash[:username] = @username if @username
|
|
71
|
+
hash[:credential] = @credential if @credential
|
|
72
|
+
hash[:credentialType] = @credential_type if @credential
|
|
73
|
+
hash
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
private
|
|
77
|
+
|
|
78
|
+
def normalize_urls(urls)
|
|
79
|
+
case urls
|
|
80
|
+
when String
|
|
81
|
+
[urls]
|
|
82
|
+
when Array
|
|
83
|
+
urls
|
|
84
|
+
else
|
|
85
|
+
[]
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def validate!
|
|
90
|
+
raise InvalidParameterError, 'ICE server URLs required' if @urls.empty?
|
|
91
|
+
|
|
92
|
+
@urls.each do |url|
|
|
93
|
+
unless url.start_with?('stun:', 'stuns:', 'turn:', 'turns:')
|
|
94
|
+
raise InvalidParameterError, "Invalid ICE server URL: #{url}"
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
end
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module WebRTC
|
|
4
|
+
class RTCDataChannel
|
|
5
|
+
READY_STATES = %i[connecting open closing closed].freeze
|
|
6
|
+
BINARY_TYPES = %i[blob arraybuffer].freeze
|
|
7
|
+
|
|
8
|
+
attr_reader :label, :ordered, :max_packet_life_time, :max_retransmits, :protocol, :negotiated, :id
|
|
9
|
+
attr_accessor :buffered_amount_low_threshold, :binary_type
|
|
10
|
+
|
|
11
|
+
def initialize(ptr, options = {})
|
|
12
|
+
@ptr = ptr
|
|
13
|
+
@callbacks = {}
|
|
14
|
+
@binary_type = :arraybuffer
|
|
15
|
+
@buffered_amount_low_threshold = 0
|
|
16
|
+
|
|
17
|
+
@label = options[:label] || FFI.webrtc_data_channel_get_label(ptr)
|
|
18
|
+
@ordered = options.fetch(:ordered, true)
|
|
19
|
+
@max_packet_life_time = options[:max_packet_life_time]
|
|
20
|
+
@max_retransmits = options[:max_retransmits]
|
|
21
|
+
@protocol = options[:protocol] || ''
|
|
22
|
+
@negotiated = options.fetch(:negotiated, false)
|
|
23
|
+
@id = options[:id]
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def ready_state
|
|
27
|
+
return :closed if @ptr.nil?
|
|
28
|
+
|
|
29
|
+
state_index = FFI.webrtc_data_channel_get_ready_state(@ptr)
|
|
30
|
+
READY_STATES[state_index] || :unknown
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def buffered_amount
|
|
34
|
+
return 0 if @ptr.nil?
|
|
35
|
+
|
|
36
|
+
FFI.webrtc_data_channel_get_buffered_amount(@ptr)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def send(data)
|
|
40
|
+
raise InvalidStateError, 'DataChannel is not open' unless ready_state == :open
|
|
41
|
+
|
|
42
|
+
is_binary = data.is_a?(String) && data.encoding == Encoding::BINARY
|
|
43
|
+
send_data(data, is_binary)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def send_text(data)
|
|
47
|
+
raise InvalidStateError, 'DataChannel is not open' unless ready_state == :open
|
|
48
|
+
|
|
49
|
+
send_data(data.to_s, false)
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def send_binary(data)
|
|
53
|
+
raise InvalidStateError, 'DataChannel is not open' unless ready_state == :open
|
|
54
|
+
|
|
55
|
+
send_data(data, true)
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def close
|
|
59
|
+
return if @ptr.nil?
|
|
60
|
+
|
|
61
|
+
FFI.webrtc_data_channel_close(@ptr)
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def destroy
|
|
65
|
+
return if @ptr.nil?
|
|
66
|
+
|
|
67
|
+
FFI.webrtc_data_channel_destroy(@ptr)
|
|
68
|
+
@ptr = nil
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def on_open(&block)
|
|
72
|
+
@callbacks[:open] = block
|
|
73
|
+
setup_open_callback
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def on_close(&block)
|
|
77
|
+
@callbacks[:close] = block
|
|
78
|
+
setup_close_callback
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def on_error(&block)
|
|
82
|
+
@callbacks[:error] = block
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def on_message(&block)
|
|
86
|
+
@callbacks[:message] = block
|
|
87
|
+
setup_message_callback
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def on_buffered_amount_low(&block)
|
|
91
|
+
@callbacks[:buffered_amount_low] = block
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
private
|
|
95
|
+
|
|
96
|
+
def send_data(data, is_binary)
|
|
97
|
+
data_str = data.to_s
|
|
98
|
+
ptr = ::FFI::MemoryPointer.from_string(data_str)
|
|
99
|
+
error = FFI::Error.new
|
|
100
|
+
|
|
101
|
+
result = FFI.webrtc_data_channel_send(@ptr, ptr, data_str.bytesize, is_binary, error)
|
|
102
|
+
raise OperationError, error[:message] if result != 0
|
|
103
|
+
|
|
104
|
+
data_str.bytesize
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
def setup_open_callback
|
|
108
|
+
callback = proc do |_user_data|
|
|
109
|
+
@callbacks[:open]&.call
|
|
110
|
+
end
|
|
111
|
+
@callbacks[:open_proc] = callback
|
|
112
|
+
FFI.webrtc_data_channel_on_open(@ptr, callback, ::FFI::Pointer::NULL)
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
def setup_close_callback
|
|
116
|
+
callback = proc do |_user_data|
|
|
117
|
+
@callbacks[:close]&.call
|
|
118
|
+
end
|
|
119
|
+
@callbacks[:close_proc] = callback
|
|
120
|
+
FFI.webrtc_data_channel_on_close(@ptr, callback, ::FFI::Pointer::NULL)
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
def setup_message_callback
|
|
124
|
+
callback = proc do |data_ptr, length, is_binary, _user_data|
|
|
125
|
+
data = data_ptr.read_bytes(length)
|
|
126
|
+
data = data.force_encoding(Encoding::UTF_8) unless is_binary
|
|
127
|
+
|
|
128
|
+
message = MessageEvent.new(data, is_binary)
|
|
129
|
+
@callbacks[:message]&.call(message)
|
|
130
|
+
end
|
|
131
|
+
@callbacks[:message_proc] = callback
|
|
132
|
+
FFI.webrtc_data_channel_on_message(@ptr, callback, ::FFI::Pointer::NULL)
|
|
133
|
+
end
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
class MessageEvent
|
|
137
|
+
attr_reader :data, :origin, :last_event_id
|
|
138
|
+
|
|
139
|
+
def initialize(data, is_binary)
|
|
140
|
+
@data = data
|
|
141
|
+
@is_binary = is_binary
|
|
142
|
+
@origin = ''
|
|
143
|
+
@last_event_id = ''
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
def binary?
|
|
147
|
+
@is_binary
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
def text?
|
|
151
|
+
!@is_binary
|
|
152
|
+
end
|
|
153
|
+
end
|
|
154
|
+
end
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module WebRTC
|
|
4
|
+
class RTCDtlsTransport
|
|
5
|
+
STATES = %i[new connecting connected closed failed].freeze
|
|
6
|
+
|
|
7
|
+
attr_reader :ice_transport, :state
|
|
8
|
+
|
|
9
|
+
def initialize(options = {})
|
|
10
|
+
@ice_transport = options[:ice_transport] || RTCIceTransport.new
|
|
11
|
+
@state = :new
|
|
12
|
+
@remote_certificates = []
|
|
13
|
+
@callbacks = {}
|
|
14
|
+
@ptr = options[:ptr]
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def get_remote_certificates
|
|
18
|
+
@remote_certificates.dup
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def on_state_change(&block)
|
|
22
|
+
@callbacks[:state_change] = block
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def on_error(&block)
|
|
26
|
+
@callbacks[:error] = block
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
private
|
|
30
|
+
|
|
31
|
+
def set_state(new_state)
|
|
32
|
+
return if @state == new_state
|
|
33
|
+
|
|
34
|
+
@state = new_state
|
|
35
|
+
@callbacks[:state_change]&.call
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
class RTCDtlsFingerprint
|
|
40
|
+
attr_reader :algorithm, :value
|
|
41
|
+
|
|
42
|
+
def initialize(algorithm:, value:)
|
|
43
|
+
@algorithm = algorithm
|
|
44
|
+
@value = value
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def to_h
|
|
48
|
+
{
|
|
49
|
+
algorithm: @algorithm,
|
|
50
|
+
value: @value
|
|
51
|
+
}
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module WebRTC
|
|
4
|
+
class RTCDTMFSender
|
|
5
|
+
VALID_TONES = '0123456789ABCD*#,'
|
|
6
|
+
|
|
7
|
+
attr_reader :tone_buffer, :duration, :inter_tone_gap
|
|
8
|
+
|
|
9
|
+
def initialize(options = {})
|
|
10
|
+
@sender = options[:sender]
|
|
11
|
+
@tone_buffer = ''
|
|
12
|
+
@duration = 100
|
|
13
|
+
@inter_tone_gap = 70
|
|
14
|
+
@callbacks = {}
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def can_insert_dtmf?
|
|
18
|
+
@sender&.track&.kind == :audio
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def insert_dtmf(tones, duration: 100, inter_tone_gap: 70)
|
|
22
|
+
raise InvalidStateError, 'Cannot insert DTMF' unless can_insert_dtmf?
|
|
23
|
+
|
|
24
|
+
validate_tones!(tones)
|
|
25
|
+
validate_duration!(duration)
|
|
26
|
+
validate_inter_tone_gap!(inter_tone_gap)
|
|
27
|
+
|
|
28
|
+
@duration = duration
|
|
29
|
+
@inter_tone_gap = inter_tone_gap
|
|
30
|
+
@tone_buffer = tones.upcase
|
|
31
|
+
|
|
32
|
+
schedule_tone_playback
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def on_tone_change(&block)
|
|
36
|
+
@callbacks[:tone_change] = block
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
private
|
|
40
|
+
|
|
41
|
+
def validate_tones!(tones)
|
|
42
|
+
tones.each_char do |char|
|
|
43
|
+
raise InvalidParameterError, "Invalid DTMF tone: #{char}" unless VALID_TONES.include?(char.upcase)
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def validate_duration!(duration)
|
|
48
|
+
return if duration >= 40 && duration <= 6000
|
|
49
|
+
|
|
50
|
+
raise InvalidParameterError, 'Duration must be between 40 and 6000 ms'
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def validate_inter_tone_gap!(gap)
|
|
54
|
+
return if gap >= 30
|
|
55
|
+
|
|
56
|
+
raise InvalidParameterError, 'Inter-tone gap must be at least 30 ms'
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def schedule_tone_playback
|
|
60
|
+
return if @tone_buffer.empty?
|
|
61
|
+
|
|
62
|
+
tone = @tone_buffer[0]
|
|
63
|
+
@tone_buffer = @tone_buffer[1..]
|
|
64
|
+
|
|
65
|
+
emit_tone_change(tone)
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def emit_tone_change(tone)
|
|
69
|
+
event = RTCDTMFToneChangeEvent.new(tone: tone)
|
|
70
|
+
@callbacks[:tone_change]&.call(event)
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
class RTCDTMFToneChangeEvent
|
|
75
|
+
attr_reader :tone
|
|
76
|
+
|
|
77
|
+
def initialize(tone:)
|
|
78
|
+
@tone = tone
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
end
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'ffi'
|
|
4
|
+
|
|
5
|
+
module WebRTC
|
|
6
|
+
module FFI
|
|
7
|
+
extend ::FFI::Library
|
|
8
|
+
|
|
9
|
+
LIB_NAME = case RbConfig::CONFIG['host_os']
|
|
10
|
+
when /darwin/ then 'libwebrtc_ruby.dylib'
|
|
11
|
+
when /mswin|mingw/ then 'webrtc_ruby.dll'
|
|
12
|
+
else 'libwebrtc_ruby.so'
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
LIB_PATH = File.join(__dir__, '..', '..', '..', 'ext', 'webrtc_ruby', 'build', LIB_NAME)
|
|
16
|
+
|
|
17
|
+
ffi_lib LIB_PATH if File.exist?(LIB_PATH)
|
|
18
|
+
|
|
19
|
+
class Error < ::FFI::Struct
|
|
20
|
+
layout :code, :int,
|
|
21
|
+
:message, :string
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
class Configuration < ::FFI::Struct
|
|
25
|
+
layout :ice_servers, :pointer,
|
|
26
|
+
:ice_servers_count, :size_t,
|
|
27
|
+
:ice_transport_policy, :pointer,
|
|
28
|
+
:bundle_policy, :pointer
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
callback :void_callback, [:pointer], :void
|
|
32
|
+
callback :ice_candidate_callback, %i[string string int pointer], :void
|
|
33
|
+
callback :data_channel_callback, %i[pointer pointer], :void
|
|
34
|
+
callback :message_callback, %i[pointer size_t bool pointer], :void
|
|
35
|
+
callback :state_change_callback, %i[int pointer], :void
|
|
36
|
+
|
|
37
|
+
attach_function :webrtc_init, [], :int
|
|
38
|
+
attach_function :webrtc_cleanup, [], :void
|
|
39
|
+
|
|
40
|
+
attach_function :webrtc_peer_connection_create,
|
|
41
|
+
[Configuration.by_ref, Error.by_ref], :pointer
|
|
42
|
+
attach_function :webrtc_peer_connection_destroy, [:pointer], :void
|
|
43
|
+
|
|
44
|
+
attach_function :webrtc_peer_connection_create_offer,
|
|
45
|
+
[:pointer, :pointer, Error.by_ref], :int
|
|
46
|
+
attach_function :webrtc_peer_connection_create_answer,
|
|
47
|
+
[:pointer, :pointer, Error.by_ref], :int
|
|
48
|
+
|
|
49
|
+
attach_function :webrtc_peer_connection_set_local_description,
|
|
50
|
+
[:pointer, :pointer, Error.by_ref], :int
|
|
51
|
+
attach_function :webrtc_peer_connection_set_remote_description,
|
|
52
|
+
[:pointer, :pointer, Error.by_ref], :int
|
|
53
|
+
attach_function :webrtc_peer_connection_add_ice_candidate,
|
|
54
|
+
[:pointer, :pointer, Error.by_ref], :int
|
|
55
|
+
|
|
56
|
+
attach_function :webrtc_peer_connection_on_ice_candidate,
|
|
57
|
+
%i[pointer ice_candidate_callback pointer], :void
|
|
58
|
+
attach_function :webrtc_peer_connection_on_connection_state_change,
|
|
59
|
+
%i[pointer state_change_callback pointer], :void
|
|
60
|
+
attach_function :webrtc_peer_connection_on_data_channel,
|
|
61
|
+
%i[pointer data_channel_callback pointer], :void
|
|
62
|
+
|
|
63
|
+
attach_function :webrtc_peer_connection_get_signaling_state, [:pointer], :int
|
|
64
|
+
attach_function :webrtc_peer_connection_get_ice_gathering_state, [:pointer], :int
|
|
65
|
+
attach_function :webrtc_peer_connection_get_ice_connection_state, [:pointer], :int
|
|
66
|
+
attach_function :webrtc_peer_connection_get_connection_state, [:pointer], :int
|
|
67
|
+
|
|
68
|
+
attach_function :webrtc_peer_connection_create_data_channel,
|
|
69
|
+
[:pointer, :string, :bool, :int, :int, :string, :bool, :int, Error.by_ref],
|
|
70
|
+
:pointer
|
|
71
|
+
|
|
72
|
+
attach_function :webrtc_data_channel_destroy, [:pointer], :void
|
|
73
|
+
attach_function :webrtc_data_channel_send,
|
|
74
|
+
[:pointer, :pointer, :size_t, :bool, Error.by_ref], :int
|
|
75
|
+
attach_function :webrtc_data_channel_close, [:pointer], :void
|
|
76
|
+
attach_function :webrtc_data_channel_get_ready_state, [:pointer], :int
|
|
77
|
+
attach_function :webrtc_data_channel_get_label, [:pointer], :string
|
|
78
|
+
attach_function :webrtc_data_channel_get_buffered_amount, [:pointer], :size_t
|
|
79
|
+
|
|
80
|
+
attach_function :webrtc_data_channel_on_open,
|
|
81
|
+
%i[pointer void_callback pointer], :void
|
|
82
|
+
attach_function :webrtc_data_channel_on_message,
|
|
83
|
+
%i[pointer message_callback pointer], :void
|
|
84
|
+
attach_function :webrtc_data_channel_on_close,
|
|
85
|
+
%i[pointer void_callback pointer], :void
|
|
86
|
+
|
|
87
|
+
attach_function :webrtc_session_description_create,
|
|
88
|
+
[:string, :string, Error.by_ref], :pointer
|
|
89
|
+
attach_function :webrtc_session_description_destroy, [:pointer], :void
|
|
90
|
+
attach_function :webrtc_session_description_get_type, [:pointer], :string
|
|
91
|
+
attach_function :webrtc_session_description_get_sdp, [:pointer], :string
|
|
92
|
+
|
|
93
|
+
attach_function :webrtc_ice_candidate_create,
|
|
94
|
+
[:string, :string, :int, Error.by_ref], :pointer
|
|
95
|
+
attach_function :webrtc_ice_candidate_destroy, [:pointer], :void
|
|
96
|
+
attach_function :webrtc_ice_candidate_get_candidate, [:pointer], :string
|
|
97
|
+
attach_function :webrtc_ice_candidate_get_sdp_mid, [:pointer], :string
|
|
98
|
+
attach_function :webrtc_ice_candidate_get_sdp_mline_index, [:pointer], :int
|
|
99
|
+
end
|
|
100
|
+
end
|