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.
@@ -0,0 +1,51 @@
1
+ # frozen_string_literal: true
2
+
3
+ module WebRTC
4
+ class RTCRtpReceiver
5
+ attr_reader :track, :transport
6
+
7
+ def initialize(options = {})
8
+ @track = options[:track]
9
+ @transport = options[:transport]
10
+ @ptr = options[:ptr]
11
+ @parameters = default_parameters
12
+ end
13
+
14
+ def self.get_capabilities(kind)
15
+ {
16
+ codecs: default_codecs(kind),
17
+ header_extensions: []
18
+ }
19
+ end
20
+
21
+ def self.default_codecs(kind)
22
+ RTCRtpSender.default_codecs(kind)
23
+ end
24
+
25
+ def get_parameters
26
+ @parameters.dup
27
+ end
28
+
29
+ def get_contributing_sources
30
+ []
31
+ end
32
+
33
+ def get_synchronization_sources
34
+ []
35
+ end
36
+
37
+ def get_stats
38
+ Promise.resolve({})
39
+ end
40
+
41
+ private
42
+
43
+ def default_parameters
44
+ {
45
+ header_extensions: [],
46
+ rtcp: { cname: '', reduced_size: false },
47
+ codecs: []
48
+ }
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,85 @@
1
+ # frozen_string_literal: true
2
+
3
+ module WebRTC
4
+ class RTCRtpSender
5
+ attr_reader :track, :transport, :dtmf
6
+
7
+ def initialize(options = {})
8
+ @track = options[:track]
9
+ @transport = options[:transport]
10
+ @ptr = options[:ptr]
11
+ @parameters = default_parameters
12
+ @dtmf = create_dtmf_sender
13
+ end
14
+
15
+ private def create_dtmf_sender
16
+ return nil unless @track&.kind == :audio
17
+
18
+ RTCDTMFSender.new(sender: self)
19
+ end
20
+
21
+ public
22
+
23
+ def self.get_capabilities(kind)
24
+ {
25
+ codecs: default_codecs(kind),
26
+ header_extensions: []
27
+ }
28
+ end
29
+
30
+ def self.default_codecs(kind)
31
+ case kind.to_sym
32
+ when :audio
33
+ [
34
+ { mime_type: 'audio/opus', clock_rate: 48_000, channels: 2 },
35
+ { mime_type: 'audio/PCMU', clock_rate: 8000, channels: 1 },
36
+ { mime_type: 'audio/PCMA', clock_rate: 8000, channels: 1 }
37
+ ]
38
+ when :video
39
+ [
40
+ { mime_type: 'video/VP8', clock_rate: 90_000 },
41
+ { mime_type: 'video/VP9', clock_rate: 90_000 },
42
+ { mime_type: 'video/H264', clock_rate: 90_000 }
43
+ ]
44
+ else
45
+ []
46
+ end
47
+ end
48
+
49
+ def get_parameters
50
+ @parameters.dup
51
+ end
52
+
53
+ def set_parameters(parameters)
54
+ @parameters = parameters
55
+ Promise.resolve(nil)
56
+ end
57
+
58
+ def replace_track(new_track)
59
+ Promise.new do
60
+ @track = new_track
61
+ nil
62
+ end
63
+ end
64
+
65
+ def set_streams(*streams)
66
+ @streams = streams
67
+ end
68
+
69
+ def get_stats
70
+ Promise.resolve({})
71
+ end
72
+
73
+ private
74
+
75
+ def default_parameters
76
+ {
77
+ transaction_id: '',
78
+ encodings: [],
79
+ header_extensions: [],
80
+ rtcp: { cname: '', reduced_size: false },
81
+ codecs: []
82
+ }
83
+ end
84
+ end
85
+ end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ module WebRTC
4
+ class RTCRtpTransceiver
5
+ DIRECTIONS = %i[sendrecv sendonly recvonly inactive stopped].freeze
6
+
7
+ attr_reader :mid, :sender, :receiver, :stopped, :current_direction
8
+ attr_accessor :direction
9
+
10
+ def initialize(options = {})
11
+ @mid = options[:mid]
12
+ @sender = options[:sender] || RTCRtpSender.new
13
+ @receiver = options[:receiver] || RTCRtpReceiver.new
14
+ @direction = options[:direction] || :sendrecv
15
+ @current_direction = nil
16
+ @stopped = false
17
+ @ptr = options[:ptr]
18
+ end
19
+
20
+ def stop
21
+ @stopped = true
22
+ @direction = :stopped
23
+ @sender&.replace_track(nil)
24
+ end
25
+
26
+ def set_codec_preferences(codecs)
27
+ @codec_preferences = codecs
28
+ end
29
+
30
+ def stopped?
31
+ @stopped
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ module WebRTC
4
+ class RTCSctpTransport
5
+ STATES = %i[connecting connected closed].freeze
6
+
7
+ attr_reader :transport, :max_message_size, :max_channels, :state
8
+
9
+ def initialize(options = {})
10
+ @transport = options[:transport] || RTCDtlsTransport.new
11
+ @state = :connecting
12
+ @max_message_size = options[:max_message_size] || 262_144
13
+ @max_channels = options[:max_channels] || 65_535
14
+ @callbacks = {}
15
+ @ptr = options[:ptr]
16
+ end
17
+
18
+ def on_state_change(&block)
19
+ @callbacks[:state_change] = block
20
+ end
21
+
22
+ private
23
+
24
+ def set_state(new_state)
25
+ return if @state == new_state
26
+
27
+ @state = new_state
28
+ @callbacks[:state_change]&.call
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,64 @@
1
+ # frozen_string_literal: true
2
+
3
+ module WebRTC
4
+ class RTCSessionDescription
5
+ TYPES = %i[offer answer pranswer rollback].freeze
6
+
7
+ attr_reader :type, :sdp
8
+
9
+ def initialize(init = {})
10
+ @type = normalize_type(init[:type])
11
+ @sdp = init[:sdp] || ''
12
+ @ptr = nil
13
+
14
+ validate!
15
+ end
16
+
17
+ def self.from_ptr(ptr)
18
+ return nil if ptr.nil? || ptr.null?
19
+
20
+ type_str = FFI.webrtc_session_description_get_type(ptr)
21
+ sdp_str = FFI.webrtc_session_description_get_sdp(ptr)
22
+
23
+ desc = new(type: type_str&.to_sym, sdp: sdp_str)
24
+ desc.instance_variable_set(:@ptr, ptr)
25
+ desc
26
+ end
27
+
28
+ def to_ptr
29
+ return @ptr if @ptr && !@ptr.null?
30
+
31
+ error = FFI::Error.new
32
+ @ptr = FFI.webrtc_session_description_create(type.to_s, sdp, error)
33
+ raise OperationError, error[:message] if error[:code] != 0
34
+
35
+ @ptr
36
+ end
37
+
38
+ def to_h
39
+ { type: type, sdp: sdp }
40
+ end
41
+
42
+ def release
43
+ return unless @ptr && !@ptr.null?
44
+
45
+ FFI.webrtc_session_description_destroy(@ptr)
46
+ @ptr = nil
47
+ end
48
+
49
+ private
50
+
51
+ def normalize_type(type)
52
+ return nil if type.nil?
53
+
54
+ type.to_sym
55
+ end
56
+
57
+ def validate!
58
+ return if type.nil?
59
+ return if TYPES.include?(type)
60
+
61
+ raise InvalidParameterError, "Invalid session description type: #{type}"
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,199 @@
1
+ # frozen_string_literal: true
2
+
3
+ module WebRTC
4
+ class RTCStatsReport
5
+ include Enumerable
6
+
7
+ def initialize(stats = {})
8
+ @stats = stats
9
+ end
10
+
11
+ def [](id)
12
+ @stats[id]
13
+ end
14
+
15
+ def each(&block)
16
+ @stats.each(&block)
17
+ end
18
+
19
+ def size
20
+ @stats.size
21
+ end
22
+
23
+ def keys
24
+ @stats.keys
25
+ end
26
+
27
+ def values
28
+ @stats.values
29
+ end
30
+
31
+ def get(id)
32
+ @stats[id]
33
+ end
34
+
35
+ def has?(id)
36
+ @stats.key?(id)
37
+ end
38
+
39
+ def to_h
40
+ @stats.dup
41
+ end
42
+ end
43
+
44
+ class RTCStats
45
+ TYPES = %i[
46
+ codec inbound_rtp outbound_rtp remote_inbound_rtp remote_outbound_rtp
47
+ media_source csrc peer_connection data_channel transceiver sender
48
+ receiver transport candidate_pair local_candidate remote_candidate
49
+ certificate ice_server
50
+ ].freeze
51
+
52
+ attr_reader :id, :timestamp, :type
53
+
54
+ def initialize(options = {})
55
+ @id = options[:id] || generate_id
56
+ @timestamp = options[:timestamp] || Time.now.to_f * 1000
57
+ @type = options[:type]
58
+ end
59
+
60
+ def to_h
61
+ {
62
+ id: @id,
63
+ timestamp: @timestamp,
64
+ type: @type
65
+ }
66
+ end
67
+
68
+ private
69
+
70
+ def generate_id
71
+ "stats-#{SecureRandom.uuid}"
72
+ end
73
+ end
74
+
75
+ class RTCInboundRtpStreamStats < RTCStats
76
+ attr_reader :ssrc, :kind, :packets_received, :bytes_received, :packets_lost, :jitter, :frames_decoded,
77
+ :frames_dropped
78
+
79
+ def initialize(options = {})
80
+ super(options.merge(type: :inbound_rtp))
81
+ @ssrc = options[:ssrc]
82
+ @kind = options[:kind]
83
+ @packets_received = options[:packets_received] || 0
84
+ @bytes_received = options[:bytes_received] || 0
85
+ @packets_lost = options[:packets_lost] || 0
86
+ @jitter = options[:jitter] || 0.0
87
+ @frames_decoded = options[:frames_decoded] || 0
88
+ @frames_dropped = options[:frames_dropped] || 0
89
+ end
90
+
91
+ def to_h
92
+ super.merge(
93
+ ssrc: @ssrc,
94
+ kind: @kind,
95
+ packetsReceived: @packets_received,
96
+ bytesReceived: @bytes_received,
97
+ packetsLost: @packets_lost,
98
+ jitter: @jitter,
99
+ framesDecoded: @frames_decoded,
100
+ framesDropped: @frames_dropped
101
+ )
102
+ end
103
+ end
104
+
105
+ class RTCOutboundRtpStreamStats < RTCStats
106
+ attr_reader :ssrc, :kind, :packets_sent, :bytes_sent, :frames_encoded, :target_bitrate, :frames_per_second
107
+
108
+ def initialize(options = {})
109
+ super(options.merge(type: :outbound_rtp))
110
+ @ssrc = options[:ssrc]
111
+ @kind = options[:kind]
112
+ @packets_sent = options[:packets_sent] || 0
113
+ @bytes_sent = options[:bytes_sent] || 0
114
+ @frames_encoded = options[:frames_encoded] || 0
115
+ @target_bitrate = options[:target_bitrate]
116
+ @frames_per_second = options[:frames_per_second]
117
+ end
118
+
119
+ def to_h
120
+ super.merge(
121
+ ssrc: @ssrc,
122
+ kind: @kind,
123
+ packetsSent: @packets_sent,
124
+ bytesSent: @bytes_sent,
125
+ framesEncoded: @frames_encoded,
126
+ targetBitrate: @target_bitrate,
127
+ framesPerSecond: @frames_per_second
128
+ )
129
+ end
130
+ end
131
+
132
+ class RTCTransportStats < RTCStats
133
+ attr_reader :bytes_sent, :bytes_received, :dtls_state, :selected_candidate_pair_id
134
+
135
+ def initialize(options = {})
136
+ super(options.merge(type: :transport))
137
+ @bytes_sent = options[:bytes_sent] || 0
138
+ @bytes_received = options[:bytes_received] || 0
139
+ @dtls_state = options[:dtls_state] || :new
140
+ @selected_candidate_pair_id = options[:selected_candidate_pair_id]
141
+ end
142
+
143
+ def to_h
144
+ super.merge(
145
+ bytesSent: @bytes_sent,
146
+ bytesReceived: @bytes_received,
147
+ dtlsState: @dtls_state,
148
+ selectedCandidatePairId: @selected_candidate_pair_id
149
+ )
150
+ end
151
+ end
152
+
153
+ class RTCPeerConnectionStats < RTCStats
154
+ attr_reader :data_channels_opened, :data_channels_closed
155
+
156
+ def initialize(options = {})
157
+ super(options.merge(type: :peer_connection))
158
+ @data_channels_opened = options[:data_channels_opened] || 0
159
+ @data_channels_closed = options[:data_channels_closed] || 0
160
+ end
161
+
162
+ def to_h
163
+ super.merge(
164
+ dataChannelsOpened: @data_channels_opened,
165
+ dataChannelsClosed: @data_channels_closed
166
+ )
167
+ end
168
+ end
169
+
170
+ class RTCDataChannelStats < RTCStats
171
+ attr_reader :label, :protocol, :data_channel_identifier, :state, :messages_sent, :bytes_sent, :messages_received,
172
+ :bytes_received
173
+
174
+ def initialize(options = {})
175
+ super(options.merge(type: :data_channel))
176
+ @label = options[:label]
177
+ @protocol = options[:protocol]
178
+ @data_channel_identifier = options[:data_channel_identifier]
179
+ @state = options[:state] || :connecting
180
+ @messages_sent = options[:messages_sent] || 0
181
+ @bytes_sent = options[:bytes_sent] || 0
182
+ @messages_received = options[:messages_received] || 0
183
+ @bytes_received = options[:bytes_received] || 0
184
+ end
185
+
186
+ def to_h
187
+ super.merge(
188
+ label: @label,
189
+ protocol: @protocol,
190
+ dataChannelIdentifier: @data_channel_identifier,
191
+ state: @state,
192
+ messagesSent: @messages_sent,
193
+ bytesSent: @bytes_sent,
194
+ messagesReceived: @messages_received,
195
+ bytesReceived: @bytes_received
196
+ )
197
+ end
198
+ end
199
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module WebRTC
4
+ VERSION = '0.1.0'
5
+ end
data/lib/webrtc.rb ADDED
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'securerandom'
4
+
5
+ require_relative 'webrtc/version'
6
+ require_relative 'webrtc/errors'
7
+ require_relative 'webrtc/ffi/library'
8
+ require_relative 'webrtc/promise'
9
+ require_relative 'webrtc/session_description'
10
+ require_relative 'webrtc/ice_candidate'
11
+ require_relative 'webrtc/media_stream_track'
12
+ require_relative 'webrtc/media_stream'
13
+ require_relative 'webrtc/rtp_sender'
14
+ require_relative 'webrtc/rtp_receiver'
15
+ require_relative 'webrtc/rtp_transceiver'
16
+ require_relative 'webrtc/ice_transport'
17
+ require_relative 'webrtc/dtls_transport'
18
+ require_relative 'webrtc/sctp_transport'
19
+ require_relative 'webrtc/stats_report'
20
+ require_relative 'webrtc/dtmf_sender'
21
+ require_relative 'webrtc/configuration'
22
+ require_relative 'webrtc/data_channel'
23
+ require_relative 'webrtc/peer_connection'
24
+
25
+ module WebRTC
26
+ class << self
27
+ def init
28
+ result = FFI.webrtc_init
29
+ raise InitializationError, 'Failed to initialize WebRTC' unless result.zero?
30
+
31
+ true
32
+ end
33
+
34
+ def cleanup
35
+ FFI.webrtc_cleanup
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'lib/webrtc/version'
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = 'webrtc-ruby'
7
+ spec.version = WebRTC::VERSION
8
+ spec.authors = ['Yudai Takada']
9
+ spec.email = ['t.yudai92@gmail.com']
10
+
11
+ spec.summary = 'WebRTC bindings for Ruby'
12
+ spec.description = 'WebRTC (Web Real-Time Communication) bindings for Ruby, providing real-time audio, video, and data channel capabilities.'
13
+ spec.homepage = 'https://github.com/ydah/webrtc-ruby'
14
+ spec.license = 'MIT'
15
+ spec.required_ruby_version = '>= 3.1.0'
16
+
17
+ spec.metadata['homepage_uri'] = spec.homepage
18
+ spec.metadata['source_code_uri'] = spec.homepage
19
+ spec.metadata['changelog_uri'] = "#{spec.homepage}/blob/main/CHANGELOG.md"
20
+
21
+ spec.files = Dir.chdir(__dir__) do
22
+ `git ls-files -z`.split("\x0").reject do |f|
23
+ (File.expand_path(f) == __FILE__) ||
24
+ f.start_with?(*%w[bin/ test/ spec/ features/ .git .github appveyor Gemfile])
25
+ end
26
+ end
27
+ spec.bindir = 'exe'
28
+ spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
29
+ spec.require_paths = ['lib']
30
+
31
+ spec.add_dependency 'concurrent-ruby', '~> 1.2'
32
+ spec.add_dependency 'ffi', '~> 1.15'
33
+ end
metadata ADDED
@@ -0,0 +1,107 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: webrtc-ruby
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Yudai Takada
8
+ bindir: exe
9
+ cert_chain: []
10
+ date: 1980-01-02 00:00:00.000000000 Z
11
+ dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: concurrent-ruby
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - "~>"
17
+ - !ruby/object:Gem::Version
18
+ version: '1.2'
19
+ type: :runtime
20
+ prerelease: false
21
+ version_requirements: !ruby/object:Gem::Requirement
22
+ requirements:
23
+ - - "~>"
24
+ - !ruby/object:Gem::Version
25
+ version: '1.2'
26
+ - !ruby/object:Gem::Dependency
27
+ name: ffi
28
+ requirement: !ruby/object:Gem::Requirement
29
+ requirements:
30
+ - - "~>"
31
+ - !ruby/object:Gem::Version
32
+ version: '1.15'
33
+ type: :runtime
34
+ prerelease: false
35
+ version_requirements: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - "~>"
38
+ - !ruby/object:Gem::Version
39
+ version: '1.15'
40
+ description: WebRTC (Web Real-Time Communication) bindings for Ruby, providing real-time
41
+ audio, video, and data channel capabilities.
42
+ email:
43
+ - t.yudai92@gmail.com
44
+ executables: []
45
+ extensions: []
46
+ extra_rdoc_files: []
47
+ files:
48
+ - ".dockerignore"
49
+ - ".rspec"
50
+ - CHANGELOG.md
51
+ - Dockerfile
52
+ - LICENSE
53
+ - README.md
54
+ - Rakefile
55
+ - examples/signaling_server/server.rb
56
+ - examples/simple_data_channel.rb
57
+ - examples/video_call.rb
58
+ - ext/webrtc_ruby/CMakeLists.txt
59
+ - ext/webrtc_ruby/Makefile
60
+ - ext/webrtc_ruby/webrtc_ruby.c
61
+ - ext/webrtc_ruby/webrtc_ruby.h
62
+ - lib/webrtc.rb
63
+ - lib/webrtc/configuration.rb
64
+ - lib/webrtc/data_channel.rb
65
+ - lib/webrtc/dtls_transport.rb
66
+ - lib/webrtc/dtmf_sender.rb
67
+ - lib/webrtc/errors.rb
68
+ - lib/webrtc/ffi/library.rb
69
+ - lib/webrtc/ice_candidate.rb
70
+ - lib/webrtc/ice_transport.rb
71
+ - lib/webrtc/media_stream.rb
72
+ - lib/webrtc/media_stream_track.rb
73
+ - lib/webrtc/peer_connection.rb
74
+ - lib/webrtc/promise.rb
75
+ - lib/webrtc/rtp_receiver.rb
76
+ - lib/webrtc/rtp_sender.rb
77
+ - lib/webrtc/rtp_transceiver.rb
78
+ - lib/webrtc/sctp_transport.rb
79
+ - lib/webrtc/session_description.rb
80
+ - lib/webrtc/stats_report.rb
81
+ - lib/webrtc/version.rb
82
+ - webrtc-ruby.gemspec
83
+ homepage: https://github.com/ydah/webrtc-ruby
84
+ licenses:
85
+ - MIT
86
+ metadata:
87
+ homepage_uri: https://github.com/ydah/webrtc-ruby
88
+ source_code_uri: https://github.com/ydah/webrtc-ruby
89
+ changelog_uri: https://github.com/ydah/webrtc-ruby/blob/main/CHANGELOG.md
90
+ rdoc_options: []
91
+ require_paths:
92
+ - lib
93
+ required_ruby_version: !ruby/object:Gem::Requirement
94
+ requirements:
95
+ - - ">="
96
+ - !ruby/object:Gem::Version
97
+ version: 3.1.0
98
+ required_rubygems_version: !ruby/object:Gem::Requirement
99
+ requirements:
100
+ - - ">="
101
+ - !ruby/object:Gem::Version
102
+ version: '0'
103
+ requirements: []
104
+ rubygems_version: 4.0.3
105
+ specification_version: 4
106
+ summary: WebRTC bindings for Ruby
107
+ test_files: []