webrtc-ruby 1.0.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 +201 -0
- data/README.md +264 -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 +994 -0
- data/ext/webrtc_ruby/webrtc_ruby.h +212 -0
- data/lib/webrtc/configuration.rb +99 -0
- data/lib/webrtc/data_channel.rb +216 -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/factory.rb +28 -0
- data/lib/webrtc/ffi/library.rb +122 -0
- data/lib/webrtc/ice_candidate.rb +63 -0
- data/lib/webrtc/ice_transport.rb +95 -0
- data/lib/webrtc/media_interfaces.rb +101 -0
- data/lib/webrtc/media_stream.rb +67 -0
- data/lib/webrtc/media_stream_track.rb +83 -0
- data/lib/webrtc/observers.rb +51 -0
- data/lib/webrtc/parity_types.rb +358 -0
- data/lib/webrtc/peer_connection.rb +577 -0
- data/lib/webrtc/promise.rb +59 -0
- data/lib/webrtc/rtp_receiver.rb +79 -0
- data/lib/webrtc/rtp_sender.rb +117 -0
- data/lib/webrtc/rtp_transceiver.rb +39 -0
- data/lib/webrtc/sctp_transport.rb +31 -0
- data/lib/webrtc/session_description.rb +65 -0
- data/lib/webrtc/stats_report.rb +199 -0
- data/lib/webrtc/version.rb +5 -0
- data/lib/webrtc/video_frame.rb +29 -0
- data/lib/webrtc.rb +43 -0
- data/webrtc-ruby.gemspec +33 -0
- metadata +113 -0
|
@@ -0,0 +1,122 @@
|
|
|
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
|
+
:rtcp_mux_policy, :pointer,
|
|
30
|
+
:enable_ice_tcp, :bool,
|
|
31
|
+
:enable_ice_udp_mux, :bool,
|
|
32
|
+
:disable_auto_negotiation, :bool,
|
|
33
|
+
:force_media_transport, :bool,
|
|
34
|
+
:mtu, :int,
|
|
35
|
+
:max_message_size, :int
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
callback :void_callback, [:pointer], :void
|
|
39
|
+
callback :ice_candidate_callback, %i[string string int pointer], :void
|
|
40
|
+
callback :data_channel_callback, %i[pointer pointer], :void
|
|
41
|
+
callback :message_callback, %i[pointer size_t bool pointer], :void
|
|
42
|
+
callback :state_change_callback, %i[int pointer], :void
|
|
43
|
+
callback :track_callback, %i[int pointer], :void
|
|
44
|
+
|
|
45
|
+
attach_function :webrtc_init, [], :int
|
|
46
|
+
attach_function :webrtc_cleanup, [], :void
|
|
47
|
+
|
|
48
|
+
attach_function :webrtc_peer_connection_create,
|
|
49
|
+
[Configuration.by_ref, Error.by_ref], :pointer
|
|
50
|
+
attach_function :webrtc_peer_connection_destroy, [:pointer], :void
|
|
51
|
+
|
|
52
|
+
attach_function :webrtc_peer_connection_create_offer,
|
|
53
|
+
[:pointer, :pointer, Error.by_ref], :int
|
|
54
|
+
attach_function :webrtc_peer_connection_create_answer,
|
|
55
|
+
[:pointer, :pointer, Error.by_ref], :int
|
|
56
|
+
|
|
57
|
+
attach_function :webrtc_peer_connection_set_local_description,
|
|
58
|
+
[:pointer, :pointer, Error.by_ref], :int
|
|
59
|
+
attach_function :webrtc_peer_connection_set_remote_description,
|
|
60
|
+
[:pointer, :pointer, Error.by_ref], :int
|
|
61
|
+
attach_function :webrtc_peer_connection_add_ice_candidate,
|
|
62
|
+
[:pointer, :pointer, Error.by_ref], :int
|
|
63
|
+
|
|
64
|
+
attach_function :webrtc_peer_connection_on_ice_candidate,
|
|
65
|
+
%i[pointer ice_candidate_callback pointer], :void
|
|
66
|
+
attach_function :webrtc_peer_connection_on_connection_state_change,
|
|
67
|
+
%i[pointer state_change_callback pointer], :void
|
|
68
|
+
attach_function :webrtc_peer_connection_on_ice_connection_state_change,
|
|
69
|
+
%i[pointer state_change_callback pointer], :void
|
|
70
|
+
attach_function :webrtc_peer_connection_on_ice_gathering_state_change,
|
|
71
|
+
%i[pointer state_change_callback pointer], :void
|
|
72
|
+
attach_function :webrtc_peer_connection_on_signaling_state_change,
|
|
73
|
+
%i[pointer state_change_callback pointer], :void
|
|
74
|
+
attach_function :webrtc_peer_connection_on_data_channel,
|
|
75
|
+
%i[pointer data_channel_callback pointer], :void
|
|
76
|
+
attach_function :webrtc_peer_connection_on_track,
|
|
77
|
+
%i[pointer track_callback pointer], :void
|
|
78
|
+
|
|
79
|
+
attach_function :webrtc_peer_connection_get_signaling_state, [:pointer], :int
|
|
80
|
+
attach_function :webrtc_peer_connection_get_ice_gathering_state, [:pointer], :int
|
|
81
|
+
attach_function :webrtc_peer_connection_get_ice_connection_state, [:pointer], :int
|
|
82
|
+
attach_function :webrtc_peer_connection_get_connection_state, [:pointer], :int
|
|
83
|
+
attach_function :webrtc_peer_connection_add_track,
|
|
84
|
+
[:pointer, :string, :int, :pointer, Error.by_ref], :int
|
|
85
|
+
attach_function :webrtc_peer_connection_remove_track,
|
|
86
|
+
[:pointer, :int, Error.by_ref], :int
|
|
87
|
+
attach_function :webrtc_track_get_direction, [:int], :int
|
|
88
|
+
attach_function :webrtc_track_get_kind, [:int], :string
|
|
89
|
+
|
|
90
|
+
attach_function :webrtc_peer_connection_create_data_channel,
|
|
91
|
+
[:pointer, :string, :bool, :int, :int, :string, :bool, :int, Error.by_ref],
|
|
92
|
+
:pointer
|
|
93
|
+
|
|
94
|
+
attach_function :webrtc_data_channel_destroy, [:pointer], :void
|
|
95
|
+
attach_function :webrtc_data_channel_send,
|
|
96
|
+
[:pointer, :pointer, :size_t, :bool, Error.by_ref], :int
|
|
97
|
+
attach_function :webrtc_data_channel_close, [:pointer], :void
|
|
98
|
+
attach_function :webrtc_data_channel_get_ready_state, [:pointer], :int
|
|
99
|
+
attach_function :webrtc_data_channel_get_label, [:pointer], :string
|
|
100
|
+
attach_function :webrtc_data_channel_get_buffered_amount, [:pointer], :size_t
|
|
101
|
+
|
|
102
|
+
attach_function :webrtc_data_channel_on_open,
|
|
103
|
+
%i[pointer void_callback pointer], :void
|
|
104
|
+
attach_function :webrtc_data_channel_on_message,
|
|
105
|
+
%i[pointer message_callback pointer], :void
|
|
106
|
+
attach_function :webrtc_data_channel_on_close,
|
|
107
|
+
%i[pointer void_callback pointer], :void
|
|
108
|
+
|
|
109
|
+
attach_function :webrtc_session_description_create,
|
|
110
|
+
[:string, :string, Error.by_ref], :pointer
|
|
111
|
+
attach_function :webrtc_session_description_destroy, [:pointer], :void
|
|
112
|
+
attach_function :webrtc_session_description_get_type, [:pointer], :string
|
|
113
|
+
attach_function :webrtc_session_description_get_sdp, [:pointer], :string
|
|
114
|
+
|
|
115
|
+
attach_function :webrtc_ice_candidate_create,
|
|
116
|
+
[:string, :string, :int, Error.by_ref], :pointer
|
|
117
|
+
attach_function :webrtc_ice_candidate_destroy, [:pointer], :void
|
|
118
|
+
attach_function :webrtc_ice_candidate_get_candidate, [:pointer], :string
|
|
119
|
+
attach_function :webrtc_ice_candidate_get_sdp_mid, [:pointer], :string
|
|
120
|
+
attach_function :webrtc_ice_candidate_get_sdp_mline_index, [:pointer], :int
|
|
121
|
+
end
|
|
122
|
+
end
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module WebRTC
|
|
4
|
+
class RTCIceCandidate
|
|
5
|
+
attr_reader :candidate, :sdp_mid, :sdp_m_line_index, :username_fragment
|
|
6
|
+
|
|
7
|
+
def initialize(init = {})
|
|
8
|
+
init = init.to_h if init.respond_to?(:to_h)
|
|
9
|
+
@candidate = init[:candidate] || ''
|
|
10
|
+
@sdp_mid = init[:sdp_mid] || init[:sdpMid]
|
|
11
|
+
@sdp_m_line_index = init[:sdp_m_line_index] || init[:sdpMLineIndex]
|
|
12
|
+
@username_fragment = init[:username_fragment] || init[:usernameFragment]
|
|
13
|
+
@ptr = nil
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def self.from_ptr(ptr)
|
|
17
|
+
return nil if ptr.nil? || ptr.null?
|
|
18
|
+
|
|
19
|
+
candidate_str = FFI.webrtc_ice_candidate_get_candidate(ptr)
|
|
20
|
+
sdp_mid_str = FFI.webrtc_ice_candidate_get_sdp_mid(ptr)
|
|
21
|
+
sdp_m_line_index = FFI.webrtc_ice_candidate_get_sdp_mline_index(ptr)
|
|
22
|
+
|
|
23
|
+
ice = new(
|
|
24
|
+
candidate: candidate_str,
|
|
25
|
+
sdp_mid: sdp_mid_str,
|
|
26
|
+
sdp_m_line_index: sdp_m_line_index
|
|
27
|
+
)
|
|
28
|
+
ice.instance_variable_set(:@ptr, ptr)
|
|
29
|
+
ice
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def to_ptr
|
|
33
|
+
return @ptr if @ptr && !@ptr.null?
|
|
34
|
+
|
|
35
|
+
error = FFI::Error.new
|
|
36
|
+
@ptr = FFI.webrtc_ice_candidate_create(
|
|
37
|
+
candidate,
|
|
38
|
+
sdp_mid,
|
|
39
|
+
sdp_m_line_index || 0,
|
|
40
|
+
error
|
|
41
|
+
)
|
|
42
|
+
raise OperationError, error[:message] if error[:code] != 0
|
|
43
|
+
|
|
44
|
+
@ptr
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def to_h
|
|
48
|
+
{
|
|
49
|
+
candidate: candidate,
|
|
50
|
+
sdpMid: sdp_mid,
|
|
51
|
+
sdpMLineIndex: sdp_m_line_index,
|
|
52
|
+
usernameFragment: username_fragment
|
|
53
|
+
}
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def release
|
|
57
|
+
return unless @ptr && !@ptr.null?
|
|
58
|
+
|
|
59
|
+
FFI.webrtc_ice_candidate_destroy(@ptr)
|
|
60
|
+
@ptr = nil
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module WebRTC
|
|
4
|
+
class RTCIceTransport
|
|
5
|
+
STATES = %i[new checking connected completed failed disconnected closed].freeze
|
|
6
|
+
GATHERING_STATES = %i[new gathering complete].freeze
|
|
7
|
+
ROLES = %i[controlling controlled unknown].freeze
|
|
8
|
+
|
|
9
|
+
attr_reader :role, :component, :state, :gathering_state
|
|
10
|
+
|
|
11
|
+
def initialize(options = {})
|
|
12
|
+
@state = :new
|
|
13
|
+
@gathering_state = :new
|
|
14
|
+
@role = options[:role] || :unknown
|
|
15
|
+
@component = options[:component] || :rtp
|
|
16
|
+
@local_candidates = []
|
|
17
|
+
@remote_candidates = []
|
|
18
|
+
@local_parameters = nil
|
|
19
|
+
@remote_parameters = nil
|
|
20
|
+
@callbacks = {}
|
|
21
|
+
@ptr = options[:ptr]
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def get_local_candidates
|
|
25
|
+
@local_candidates.dup
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def get_remote_candidates
|
|
29
|
+
@remote_candidates.dup
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def get_local_parameters
|
|
33
|
+
@local_parameters
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def get_remote_parameters
|
|
37
|
+
@remote_parameters
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def get_selected_candidate_pair
|
|
41
|
+
return nil if @local_candidates.empty? || @remote_candidates.empty?
|
|
42
|
+
|
|
43
|
+
RTCIceCandidatePair.new(
|
|
44
|
+
local: @local_candidates.first,
|
|
45
|
+
remote: @remote_candidates.first
|
|
46
|
+
)
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def on_state_change(&block)
|
|
50
|
+
@callbacks[:state_change] = block
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def on_gathering_state_change(&block)
|
|
54
|
+
@callbacks[:gathering_state_change] = block
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def on_selected_candidate_pair_change(&block)
|
|
58
|
+
@callbacks[:selected_candidate_pair_change] = block
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
private
|
|
62
|
+
|
|
63
|
+
def set_state(new_state)
|
|
64
|
+
return if @state == new_state
|
|
65
|
+
|
|
66
|
+
@state = new_state
|
|
67
|
+
@callbacks[:state_change]&.call
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
class RTCIceCandidatePair
|
|
72
|
+
attr_reader :local, :remote
|
|
73
|
+
|
|
74
|
+
def initialize(local:, remote:)
|
|
75
|
+
@local = local
|
|
76
|
+
@remote = remote
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
class RTCIceParameters
|
|
81
|
+
attr_reader :username_fragment, :password
|
|
82
|
+
|
|
83
|
+
def initialize(username_fragment:, password:)
|
|
84
|
+
@username_fragment = username_fragment
|
|
85
|
+
@password = password
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def to_h
|
|
89
|
+
{
|
|
90
|
+
usernameFragment: @username_fragment,
|
|
91
|
+
password: @password
|
|
92
|
+
}
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
end
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module WebRTC
|
|
4
|
+
class MediaStreamInterface
|
|
5
|
+
attr_reader :stream
|
|
6
|
+
|
|
7
|
+
def initialize(stream = MediaStream.new)
|
|
8
|
+
@stream = stream
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def add_track(track)
|
|
12
|
+
@stream.add_track(track)
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def remove_track(track)
|
|
16
|
+
@stream.remove_track(track)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def get_tracks
|
|
20
|
+
@stream.get_tracks
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
class AudioTrackSinkInterface
|
|
25
|
+
def on_data(_frame); end
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
class AudioTrackSourceInterface
|
|
29
|
+
def initialize
|
|
30
|
+
@sinks = []
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def add_sink(sink)
|
|
34
|
+
@sinks << sink unless @sinks.include?(sink)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def remove_sink(sink)
|
|
38
|
+
@sinks.delete(sink)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def push_data(frame)
|
|
42
|
+
@sinks.each { |sink| sink.on_data(frame) if sink.respond_to?(:on_data) }
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
class AudioTrackInterface < MediaStreamTrack
|
|
47
|
+
attr_reader :source
|
|
48
|
+
|
|
49
|
+
def initialize(source: AudioTrackSourceInterface.new, **options)
|
|
50
|
+
super({ kind: :audio }.merge(options))
|
|
51
|
+
@source = source
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def add_sink(sink)
|
|
55
|
+
@source.add_sink(sink)
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def remove_sink(sink)
|
|
59
|
+
@source.remove_sink(sink)
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
class VideoTrackSinkInterface
|
|
64
|
+
def on_frame(_frame); end
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
class VideoTrackSourceInterface
|
|
68
|
+
def initialize
|
|
69
|
+
@sinks = []
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def add_sink(sink)
|
|
73
|
+
@sinks << sink unless @sinks.include?(sink)
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def remove_sink(sink)
|
|
77
|
+
@sinks.delete(sink)
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def push_frame(frame)
|
|
81
|
+
@sinks.each { |sink| sink.on_frame(frame) if sink.respond_to?(:on_frame) }
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
class VideoTrackInterface < MediaStreamTrack
|
|
86
|
+
attr_reader :source
|
|
87
|
+
|
|
88
|
+
def initialize(source: VideoTrackSourceInterface.new, **options)
|
|
89
|
+
super({ kind: :video }.merge(options))
|
|
90
|
+
@source = source
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def add_sink(sink)
|
|
94
|
+
@source.add_sink(sink)
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def remove_sink(sink)
|
|
98
|
+
@source.remove_sink(sink)
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
end
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module WebRTC
|
|
4
|
+
class MediaStream
|
|
5
|
+
attr_reader :id, :active
|
|
6
|
+
|
|
7
|
+
def initialize(tracks = [])
|
|
8
|
+
@id = generate_id
|
|
9
|
+
@tracks = {}
|
|
10
|
+
@callbacks = {}
|
|
11
|
+
|
|
12
|
+
tracks.each { |track| add_track(track) }
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def active?
|
|
16
|
+
@tracks.values.any? { |track| track.ready_state == :live }
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def get_audio_tracks
|
|
20
|
+
@tracks.values.select { |track| track.kind == :audio }
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def get_video_tracks
|
|
24
|
+
@tracks.values.select { |track| track.kind == :video }
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def get_tracks
|
|
28
|
+
@tracks.values
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def get_track_by_id(id)
|
|
32
|
+
@tracks[id]
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def add_track(track)
|
|
36
|
+
return if @tracks.key?(track.id)
|
|
37
|
+
|
|
38
|
+
@tracks[track.id] = track
|
|
39
|
+
@callbacks[:add_track]&.call(track)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def remove_track(track)
|
|
43
|
+
removed = @tracks.delete(track.id)
|
|
44
|
+
@callbacks[:remove_track]&.call(track) if removed
|
|
45
|
+
removed
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def clone
|
|
49
|
+
cloned_tracks = @tracks.values.map(&:clone)
|
|
50
|
+
MediaStream.new(cloned_tracks)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def on_add_track(&block)
|
|
54
|
+
@callbacks[:add_track] = block
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def on_remove_track(&block)
|
|
58
|
+
@callbacks[:remove_track] = block
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
private
|
|
62
|
+
|
|
63
|
+
def generate_id
|
|
64
|
+
"stream-#{SecureRandom.uuid}"
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
end
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module WebRTC
|
|
4
|
+
class MediaStreamTrack
|
|
5
|
+
KINDS = %i[audio video].freeze
|
|
6
|
+
READY_STATES = %i[live ended].freeze
|
|
7
|
+
|
|
8
|
+
attr_reader :id, :kind, :label, :ready_state
|
|
9
|
+
attr_accessor :enabled, :muted, :content_hint
|
|
10
|
+
|
|
11
|
+
def initialize(options = {})
|
|
12
|
+
@id = options[:id] || generate_id
|
|
13
|
+
@kind = options[:kind]&.to_sym
|
|
14
|
+
@label = options[:label] || ''
|
|
15
|
+
@enabled = options.fetch(:enabled, true)
|
|
16
|
+
@muted = options.fetch(:muted, false)
|
|
17
|
+
@ready_state = :live
|
|
18
|
+
@content_hint = options[:content_hint] || ''
|
|
19
|
+
@callbacks = {}
|
|
20
|
+
@ptr = options[:ptr]
|
|
21
|
+
|
|
22
|
+
validate!
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def stop
|
|
26
|
+
@ready_state = :ended
|
|
27
|
+
@callbacks[:ended]&.call
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def clone
|
|
31
|
+
MediaStreamTrack.new(
|
|
32
|
+
kind: kind,
|
|
33
|
+
label: label,
|
|
34
|
+
enabled: enabled,
|
|
35
|
+
muted: muted,
|
|
36
|
+
content_hint: content_hint
|
|
37
|
+
)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def get_constraints
|
|
41
|
+
{}
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def get_capabilities
|
|
45
|
+
{}
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def get_settings
|
|
49
|
+
{
|
|
50
|
+
device_id: '',
|
|
51
|
+
group_id: ''
|
|
52
|
+
}
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def apply_constraints(_constraints = {})
|
|
56
|
+
Promise.resolve(nil)
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def on_ended(&block)
|
|
60
|
+
@callbacks[:ended] = block
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def on_mute(&block)
|
|
64
|
+
@callbacks[:mute] = block
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def on_unmute(&block)
|
|
68
|
+
@callbacks[:unmute] = block
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
private
|
|
72
|
+
|
|
73
|
+
def generate_id
|
|
74
|
+
"track-#{SecureRandom.uuid}"
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def validate!
|
|
78
|
+
return if kind.nil? || KINDS.include?(kind)
|
|
79
|
+
|
|
80
|
+
raise InvalidParameterError, "Invalid track kind: #{kind}"
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
end
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module WebRTC
|
|
4
|
+
class BaseObserver
|
|
5
|
+
def initialize(on_success: nil, on_failure: nil)
|
|
6
|
+
@on_success = on_success
|
|
7
|
+
@on_failure = on_failure
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def on_success(value = nil)
|
|
11
|
+
@on_success&.call(value)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def on_failure(error)
|
|
15
|
+
@on_failure&.call(error)
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
class CreateSessionDescriptionObserver < BaseObserver; end
|
|
20
|
+
class SetSessionDescriptionObserver < BaseObserver; end
|
|
21
|
+
class SetLocalDescriptionObserver < SetSessionDescriptionObserver; end
|
|
22
|
+
class SetRemoteDescriptionObserver < SetSessionDescriptionObserver; end
|
|
23
|
+
class AddIceCandidateObserver < BaseObserver; end
|
|
24
|
+
|
|
25
|
+
class DataChannelObserver
|
|
26
|
+
def initialize(on_open: nil, on_close: nil, on_message: nil, on_error: nil)
|
|
27
|
+
@on_open = on_open
|
|
28
|
+
@on_close = on_close
|
|
29
|
+
@on_message = on_message
|
|
30
|
+
@on_error = on_error
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def bind(channel)
|
|
34
|
+
channel.on_open { @on_open&.call }
|
|
35
|
+
channel.on_close { @on_close&.call }
|
|
36
|
+
channel.on_message { |message| @on_message&.call(message) }
|
|
37
|
+
channel.on_error { |error| @on_error&.call(error) }
|
|
38
|
+
channel
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
class RTCStatsCollectorCallback
|
|
43
|
+
def initialize(&block)
|
|
44
|
+
@block = block
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def on_stats_delivered(response)
|
|
48
|
+
@block&.call(response)
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|