@live-change/peer-connection-frontend 0.0.3

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,329 @@
1
+ import { ref, isRef, onUnmounted, getCurrentInstance, unref, reactive, computed } from 'vue'
2
+ import { path, live, actions, api as useApi } from '@live-change/vue3-ssr'
3
+ import { createPeerConnection } from "./PeerConnection.js"
4
+
5
+ const messagesBucketSize = 32
6
+
7
+ let lastInstanceId = 0
8
+
9
+ const createPeer = async ({ channelType, channel, instance, localMediaStreams, online, onUnmountedCb }) => {
10
+ if(!isRef(localMediaStreams)) {
11
+ localMediaStreams = ref(localMediaStreams ?? [])
12
+ }
13
+ if(!isRef(online)) {
14
+ online = ref(online ?? false)
15
+ }
16
+ if(!onUnmountedCb && typeof window != 'undefined') {
17
+ if(getCurrentInstance()) {
18
+ onUnmountedCb = onUnmounted
19
+ } else {
20
+ onUnmountedCb = () => {
21
+ console.error("peer outside component instance - leak possible")
22
+ }
23
+ }
24
+ }
25
+ if(!instance) instance = window.__WINDOW_ID__ + '.' + (++lastInstanceId)
26
+
27
+
28
+ const api = useApi()
29
+
30
+ const peerId = [channelType, channel, 'session_Session', api.client.value.session, instance].join(':')
31
+
32
+ console.log("CREATE PEER!")
33
+
34
+ const [ peers, peerOnline, turnConfiguration ] = await Promise.all([
35
+ live(path().peerConnection.peers({ channelType, channel })),
36
+ live(path().online.session({ group: 'peer', peer: peerId })),
37
+ live(path().peerConnection.turnConfiguration({ peer: peerId }))
38
+ ])
39
+
40
+ const localPeerState = ref(null)
41
+
42
+ const finished = ref(false)
43
+ const lastProcessedMessage = ref('')
44
+ const connections = ref([])
45
+ const waitingConnections = ref([]) // connections that are not initialized, but messages are received
46
+ const localTracks = ref([])
47
+
48
+ const otherPeers = computed(() => peers.value?.filter(peer => peer.id != peerId))
49
+ const isConnectionPossible = computed(() => online.value && (!!turnConfiguration.value))
50
+
51
+ const rtcConfiguration = computed(() => ({
52
+ iceServers: [ turnConfiguration.value ],
53
+ iceTransportPolicy: 'all', // 'all' or 'relay',
54
+ bundlePolicy: 'balanced'
55
+ }))
56
+ const clientIp = computed(() => turnConfiguration.value?.clientIp)
57
+
58
+ const anyLocalAudioEnabled = computed(() => localTracks.value
59
+ .some(trackInfo => trackInfo.track.kind == 'audio' && trackInfo.enabled))
60
+ const anyLocalVideoEnabled = computed(() => localTracks.value
61
+ .some(trackInfo => trackInfo.track.kind == 'video' && trackInfo.enabled))
62
+ const anyLocalAudioAvailable = computed(() => localTracks.value
63
+ .some(trackInfo => trackInfo.track.kind == 'audio'))
64
+ const anyLocalVideoAvailable = computed(() => localTracks.value
65
+ .some(trackInfo => trackInfo.track.kind == 'video'))
66
+ const computedLocalPeerState = computed(() => ({
67
+ audioState: anyLocalAudioAvailable.value ? (anyLocalAudioEnabled.value ? "enabled" : "muted") : "none",
68
+ videoState: anyLocalVideoAvailable.value ? (anyLocalVideoEnabled.value ? "enabled" : "muted") : "none"
69
+ }))
70
+
71
+ const summary = computed(() => ({
72
+ peerId, online: online.value, finished: finished.value,
73
+ computedLocalPeerState: computedLocalPeerState.value,
74
+ lastProcessedMessage: lastProcessedMessage.value,
75
+ peers: peers.value?.length,
76
+ otherPeers: otherPeers.value?.map(p => p.id),
77
+ connections: connections.value?.map(connection => connection.summary),
78
+ tracks: localTracks.value?.map(({ track, stream }) => {
79
+ const { id, kind, label, muted, enabled } = track
80
+ return { id, kind, label, muted, enabled, stream: stream.id }
81
+ }),
82
+ turnConfiguration: turnConfiguration.value && {
83
+ ...turnConfiguration.value,
84
+ expire: new Date((+turnConfiguration.value.username.split(':')[0])*1000).toLocaleString()
85
+ },
86
+ isConnectionPossible: isConnectionPossible.value
87
+ }))
88
+
89
+ function setOnline(onlineValue) {
90
+ online.value = onlineValue
91
+ }
92
+
93
+ return {
94
+ peerId, online, isConnectionPossible,
95
+ connections, localTracks,
96
+ otherPeers,
97
+ summary,
98
+ setOnline
99
+ }
100
+
101
+ /*
102
+
103
+ },
104
+ watch: {
105
+ otherPeers(peers) {
106
+ this.updateConnections()
107
+ },
108
+ isConnectionPossible(online) {
109
+ this.updateConnections()
110
+ },
111
+ computedLocalPeerState(newState) {
112
+ this.updatePeerState(newState)
113
+ },
114
+ localMediaStreams(newStreams, oldStreams) {
115
+ console.log("LOCAL MEDIA STREAMS CHANGE",
116
+ newStreams.map(stream => ({ id: stream.id, tracks: stream.getTracks().map(tr => tr.kind).join('/') })),
117
+ oldStreams.map(stream => ({ id: stream.id, tracks: stream.getTracks().map(tr => tr.kind).join('/') })))
118
+
119
+ let deletedTracks = []
120
+ let addedTracks = []
121
+ for(const oldStream of oldStreams) {
122
+ if(newStreams.indexOf(oldStream) != -1) continue; // existing stream
123
+ deletedTracks.push(...(oldStream.getTracks().map( track => ({ track, stream: oldStream }) )))
124
+ oldStream.removeEventListener('addtrack', this.mediaStreamAddTrackHandler)
125
+ oldStream.removeEventListener('removetrack', this.mediaStreamRemoveTrackHandler)
126
+ }
127
+ for(const newStream of newStreams) {
128
+ if(oldStreams.indexOf(newStream) != -1) continue; // existing stream
129
+ addedTracks.push(...(newStream.getTracks().map(track => {
130
+ const trackInfo = {
131
+ track, stream: newStream, muted: track.muted, enabled: track.enabled,
132
+ muteHandler: () => trackInfo.muted = track.muted,
133
+ unmuteHandler: () => trackInfo.muted = track.muted
134
+ }
135
+ return trackInfo
136
+ })))
137
+ newStream.addEventListener('addtrack', this.mediaStreamAddTrackHandler)
138
+ newStream.addEventListener('removetrack', this.mediaStreamRemoveTrackHandler)
139
+ }
140
+ for(const deletedTrack of deletedTracks) {
141
+ const trackIndex = this.localTracks.findIndex(track => track.track == deletedTrack.track)
142
+ if(trackIndex == -1) return console.error(`removal of non existing track ${deletedTrack.id}`)
143
+ const trackInfo = this.localTracks[trackIndex]
144
+ trackInfo.track.removeEventListener('mute', deletedTrack.muteHandler)
145
+ trackInfo.track.removeEventListener('unmute', deletedTrack.unmuteHandler)
146
+ this.localTracks.splice(trackIndex, 1)
147
+ }
148
+ for(const addedTrack of addedTracks) {
149
+ addedTrack.track.addEventListener('mute', addedTrack.muteHandler)
150
+ addedTrack.track.addEventListener('unmute', addedTrack.unmuteHandler)
151
+ this.localTracks.push(addedTrack)
152
+ }
153
+ }
154
+ },
155
+ methods: {
156
+ setTrackEnabled(track, v) {
157
+ track.enabled = v
158
+ track.track.enabled = v
159
+ },
160
+ updatePeerState(newState) {
161
+ const updated = { ...this.localPeerState, ...newState }
162
+ if(JSON.stringify(updated) != JSON.stringify(this.localPeerState)) {
163
+ this.localPeerState = updated
164
+ const update = { ...updated, peer: peerId, _commandId: api.guid() }
165
+ this.sendPeerStateUpdate(update)
166
+ }
167
+ },
168
+ sendPeerStateUpdate(update) {
169
+ const requestTimeout = 10000
170
+ dao.requestWithSettings({ requestTimeout },
171
+ ['peerConnection', 'setPeerState'], update)
172
+ .catch(error => {
173
+ console.log("SET PEER STATE ERROR", error)
174
+ if(error == 'timeout' && !this.finished
175
+ && JSON.stringify({ ...this.localPeerState, ...update }) === JSON.stringify(this.localPeerState)
176
+ ) {
177
+ console.log("RETRYING")
178
+ this.sendPeerStateUpdate()
179
+ }
180
+ })
181
+ },
182
+ observeMore() {
183
+ if(this.messagesObservable) {
184
+ this.messagesObservable.unobserve(this.messagesObserver)
185
+ this.messagesObservable = null
186
+ }
187
+ const path = ['peerConnection', 'messages', {
188
+ peer: peerId,
189
+ gt: this.lastProcessedMessage,
190
+ limit: messagesBucketSize
191
+ }]
192
+ this.messagesObservable = api.observable(path).observable
193
+ //console.log("MESSAGES OBSERVABLE", path, this.messagesObservable, this.messagesObservable.observable)
194
+ this.messagesObservable.observe(this.messagesObserver)
195
+ //this.messagesObservable.observe(this.messagesObserver)
196
+ },
197
+ handleMessagesSignal(signal, ...args) {
198
+ //console.log("HANDLE MESSAGE SIGNAL", signal, args)
199
+ if(signal == 'error') {
200
+ const error = args[0]
201
+ console.error("PEER MESSAGE ERROR", error.stack || error)
202
+ return
203
+ }
204
+ if(signal == 'putByField') {
205
+ const [field, id, message] = args
206
+ this.handleMessage(message)
207
+ } else if(signal == 'set') {
208
+ const value = args[0]
209
+ for(const message of value) {
210
+ this.handleMessage(message)
211
+ }
212
+ } else {
213
+ console.error("SIGNAL NOT HANDLED", signal)
214
+ /!*for(const message of this.messagesObservable.list) {
215
+ this.handleMessage(message)
216
+ }*!/
217
+ }
218
+ //console.log("PEER MESSAGES OBSERVABLE", this.messagesObservable)
219
+ if(this.messagesObservable.list.length >= messagesBucketSize) {
220
+ this.observeMore()
221
+ }
222
+ },
223
+ handleMessage(message) {
224
+ if(message.id <= this.lastProcessedMessage) {
225
+ console.log("IGNORE OLD MESSAGE", message.id)
226
+ return
227
+ }
228
+ this.lastProcessedMessage = message.id
229
+ //console.log("HANDLE PEER MESSAGE", message)
230
+ if(message.from) {
231
+ let connection = this.connections.find(c => c.to == message.from)
232
+ if(!connection) connection = this.waitingConnections.find(c => c.to == message.from)
233
+ if(!connection) {
234
+ connection = createPeerConnection(this, message.from)
235
+ this.waitingConnections.push(connection)
236
+ }
237
+ connection.handleMessage(message)
238
+ } else {
239
+
240
+ }
241
+ },
242
+ sendMessage(message) {
243
+ message.from = peerId
244
+ message.sent = message.sent || new Date().toISOString()
245
+ message._commandId = message._commandId || api.guid()
246
+ const requestTimeout = 10000
247
+ //console.log("SENDING PEER MESSAGE", message)
248
+ dao.requestWithSettings({ requestTimeout }, ['peerConnection', 'postMessage'], message)
249
+ .catch(error => {
250
+ console.log("PEER MESSAGE ERROR", error)
251
+ if(error == 'timeout' && !this.finished) {
252
+ console.log("RETRYING")
253
+ this.sendMessage(message)
254
+ }
255
+ })
256
+ },
257
+ updateConnections() {
258
+ const peers = this.isConnectionPossible ? this.otherPeers : []
259
+ for(let connectionId = 0; connectionId < this.connections.length; connectionId++) {
260
+ const connection = this.connections[connectionId]
261
+ const connectionPeer = peers.find(peer => peer.id == connection.to)
262
+ if(!connectionPeer) {
263
+ connection.close()
264
+ connection.$destroy()
265
+ this.connections.splice(connectionId, 1)
266
+ connectionId --
267
+ }
268
+ }
269
+ for(const peer of peers) {
270
+ let peerConnection = this.connections.find(connection => connection.to == peer.id)
271
+ if(peerConnection) continue;
272
+ const peerConnectionId = this.waitingConnections.findIndex(connection => connection.to == peer.id)
273
+ if(peerConnectionId != -1) { // use waiting connection with cached messages
274
+ peerConnection = this.waitingConnections[peerConnectionId]
275
+ this.waitingConnections.splice(peerConnectionId, 1)
276
+ } else { // create connection
277
+ peerConnection = createPeerConnection(this, peer.id)
278
+ }
279
+ this.connections.push(peerConnection)
280
+ peerConnection.connect()
281
+ }
282
+ }
283
+ },
284
+ created() {
285
+ this.messagesObserver = (...args) => this.handleMessagesSignal(...args)
286
+ this.observeMore()
287
+ this.disconnectHandler = () => this.observeMore() // to avoid redownloading processed messages
288
+ api.on('disconnect', this.disconnectHandler)
289
+
290
+ this.mediaStreamAddTrackHandler = (event) => {
291
+ const track = event.track
292
+ const trackInfo = {
293
+ track, stream: newStream, muted: track.muted, enabled: track.enabled,
294
+ muteHandler: () => trackInfo.muted = track.muted,
295
+ unmuteHandler: () => trackInfo.muted = track.muted
296
+ }
297
+ console.log("MEDIA STREAM ADD TRACK!", trackInfo)
298
+ trackInfo.track.addEventListener('mute', trackInfo.muteHandler)
299
+ trackInfo.track.addEventListener('unmute', trackInfo.unmuteHandler)
300
+ this.localTracks.push(trackInfo)
301
+ }
302
+ this.mediaStreamRemoveTrackHandler = (event) => {
303
+ const trackIndex = this.localTracks.indexOf(event.track)
304
+ if(trackIndex == -1) return console.error(`removal of non existing track ${event.track.id}`)
305
+ const trackInfo = this.localTracks[trackIndex]
306
+ console.log("MEDIA STREAM REMOVE TRACK!", trackInfo)
307
+ trackInfo.track.removeEventListener('mute', trackInfo.muteHandler)
308
+ trackInfo.track.removeEventListener('unmute', trackInfo.unmuteHandler)
309
+ this.localTracks.splice(trackIndex, 1)
310
+ }
311
+ },
312
+ beforeDestroy() {
313
+ this.finished = true
314
+ if(this.messagesObservable) {
315
+ this.messagesObservable.unobserve(this.messagesObserver)
316
+ this.messagesObservable = null
317
+ }
318
+ for(const connection of this.waitingConnections) {
319
+ connection.$destroy()
320
+ }
321
+ for(const connection of this.connections) {
322
+ connection.$destroy()
323
+ }
324
+ api.removeListener('disconnect', this.disconnectHandler)
325
+ }
326
+ })*/
327
+ }
328
+
329
+ export { createPeer }
@@ -0,0 +1,329 @@
1
+ //import Vue from "vue"
2
+
3
+ const createPeerConnection = (peer, to) => {
4
+ return new Vue({
5
+ data: {
6
+ waitingMessages: [],
7
+ state: "created",
8
+ rtc: null,
9
+ rtcSignalingState: "",
10
+ iceGatheringState: "",
11
+ iceConnectionState: "",
12
+ rtpSenders: [],
13
+ offerOptions: null,
14
+ answerOptions: null,
15
+ remoteTracks: [],
16
+ restartOnDisconnect: false // because could not set rtc configuration(firefox)
17
+ },
18
+ computed: {
19
+ to() {
20
+ return to
21
+ },
22
+ summary() {
23
+ return {
24
+ to: this.to,
25
+ state: this.state,
26
+ waitingMessages: this.waitingMessages.length,
27
+ rtpSenders: this.rtpSenders.map(({ sender, stream }) => {
28
+ const { id, kind, label } = sender.track
29
+ return { id, kind, label, stream: stream.id }
30
+ }),
31
+ rtcSignalingState: this.rtcSignalingState,
32
+ iceGatheringState: this.iceGatheringState,
33
+ iceConnectionState: this.iceConnectionState,
34
+ remoteTracks: this.remoteTracks.map(({ track, stream }) => {
35
+ const { id, kind, label, muted } = track
36
+ return { id, kind, label, muted, stream: stream.id }
37
+ }),
38
+ }
39
+ },
40
+ localTracks() {
41
+ return peer.localTracks
42
+ },
43
+ isEnabled() {
44
+ return this.state != 'closed' && this.state != 'created'
45
+ },
46
+ rtcConfiguration() {
47
+ return peer.rtcConfiguration
48
+ },
49
+ clientIp() {
50
+ return peer.clientIp
51
+ },
52
+ isPolite() {
53
+ return peer.peerId < this.to
54
+ }
55
+ },
56
+ watch: {
57
+ isEnabled() {
58
+ this.synchronizeLocalTracks()
59
+ },
60
+ localTracks() {
61
+ this.synchronizeLocalTracks()
62
+ },
63
+ rtcConfiguration(configuration) {
64
+ if(this.rtc) {
65
+ if(this.rtc.setConfiguration) {
66
+ this.rtc.setConfiguration(configuration)
67
+ } else {
68
+ this.restartOnDisconnect = true
69
+ }
70
+ }
71
+ },
72
+ clientIp(newIp, oldIp) {
73
+ if(this.rtc) {
74
+ this.restartConnection()
75
+ }
76
+ }
77
+ },
78
+ methods: {
79
+ async connect() {
80
+ console.log("PeerConnection connect")
81
+ if(this.rtc) throw new Error("can't connect twice!")
82
+ this.state = 'connecting'
83
+ this.rtc = new RTCPeerConnection(this.rtcConfiguration)
84
+ this.rtcSignalingState = this.rtc.signalingState
85
+ this.iceGatheringState = this.rtc.iceGatheringState
86
+ this.iceConnectionState = this.rtc.iceConnectionState
87
+ this.rtc.addEventListener('negotiationneeded', this.negotiationNeededHandler)
88
+ this.rtc.addEventListener('signalingstatechange', this.signalingStateChangeHandler)
89
+ this.rtc.addEventListener('icecandidate', this.iceCandidateHandler)
90
+ this.rtc.addEventListener('track', this.trackHandler)
91
+ this.rtc.addEventListener('icegatheringstatechange', this.iceGatheringStateChangeHandler)
92
+ this.rtc.addEventListener('iceconnectionstatechange', this.iceConnectionStateChangeHandler)
93
+ for(const message of this.waitingMessages) {
94
+ try {
95
+ await this.handleMessage(message)
96
+ } catch(error) {
97
+ console.error("MESSAGE", message, "HANDLING ERROR", error)
98
+ }
99
+ }
100
+ this.waitingMessages = []
101
+ },
102
+ close() {
103
+ console.log("PeerConnection close")
104
+ this.state = 'closed'
105
+ if(this.rtc) {
106
+ this.rtc.close()
107
+ this.rtc = null
108
+ }
109
+ },
110
+ async handleMessage(message) {
111
+ //console.log("PC", to, "HANDLE MESSAGE", message)
112
+ if(this.state == 'created') {
113
+ console.log("ADD MESSAGE TO WAITING QUEUE")
114
+ this.waitingMessages.push(message)
115
+ return
116
+ }
117
+ if(this.state == 'close') return;
118
+ //console.log("DO HANDLE MESSAGE")
119
+ switch(message.type) {
120
+ case "sdp": {
121
+ console.log("RECEIVED SDP", message.data.type, "IN STATE", this.rtc.signalingState)
122
+ if(message.data.type == 'offer') {
123
+ if(this.rtc.signalingState != "stable") {
124
+ console.log("SDP CONFLICT, RECEIVED OFFER IN UNSTABLE STATE")
125
+ if(this.isPolite) {
126
+ console.log("I AM POLITE SO I WILL ROLLBACK RTC STATE MACHINE")
127
+ await this.rtc.setLocalDescription({type: "rollback"}),
128
+ await this.rtc.setRemoteDescription(message.data)
129
+ console.log("ROLLBACK DONE")
130
+ const answer = await this.rtc.createAnswer(this.answerOptions || undefined)
131
+ console.log("GOT RTC ANSWER IN STATE", this.rtc.signalingState)
132
+ await this.rtc.setLocalDescription(answer)
133
+ console.log("LOCAL ANSWER DESCRIPTION SET! SENDING ANSWER!")
134
+ peer.sendMessage({ to, type: "sdp", data: answer })
135
+ } else {
136
+ console.log("I AM NOT POLITE SO I WILL IGNORE OFFER")
137
+ }
138
+ } else {
139
+ console.log("SDP STATE GOOD!")
140
+ await this.rtc.setRemoteDescription(message.data)
141
+ const answer = await this.rtc.createAnswer(this.answerOptions || undefined)
142
+ console.log("GOT RTC ANSWER IN STATE", this.rtc.signalingState)
143
+ await this.rtc.setLocalDescription(answer)
144
+ console.log("LOCAL ANSWER DESCRIPTION SET! SENDING ANSWER!")
145
+ peer.sendMessage({ to, type: "sdp", data: answer })
146
+ }
147
+ } else {
148
+ console.log("GOT ANSWER FROM REMOTE PEER")
149
+ await this.rtc.setRemoteDescription(message.data)
150
+ }
151
+ } break;
152
+ case "ice": {
153
+ console.log("RECEIVED ICE! IN STATE", this.rtc.signalingState)
154
+ let ice = message.data
155
+ //if(ice && ice.candidate === "") break;
156
+ if(ice && ice.candidate != "") {
157
+ console.log("ADDING ICE CANDIDATE", ice.candidate)
158
+ await this.rtc.addIceCandidate(new RTCIceCandidate(ice))
159
+ } else if(window.RTCPeerConnection.prototype.addIceCandidate.length === 0){
160
+ await this.rtc.addIceCandidate()
161
+ }
162
+ //console.log("REMOTE ICE CANDIDATE ADDED", ice && ice.candidate)
163
+ } break;
164
+ case "ping": {
165
+ peer.sendMessage({ to, type: "pong", data: message.data})
166
+ } break;
167
+ case "pong": break; // ignore pong
168
+ default:
169
+ console.error("Unknown peer message", message)
170
+ }
171
+ },
172
+ synchronizeLocalTracks() {
173
+ const tracks = this.isEnabled ? this.localTracks : []
174
+ let removedSenders = []
175
+ let somethingChanged = false
176
+ for(const senderInfo of this.rtpSenders) {
177
+ const trackInfo = tracks.find(trackInfo => trackInfo.track == senderInfo.sender.track)
178
+ if(!trackInfo) {
179
+ this.rtc.removeTrack(senderInfo.sender)
180
+ removedSenders.push(senderInfo)
181
+ somethingChanged = true
182
+ } else if(senderInfo.stream != trackInfo.stream) {
183
+ senderInfo.stream = trackInfo.stream
184
+ senderInfo.sender.setStreams(trackInfo.stream)
185
+ somethingChanged = true
186
+ }
187
+ }
188
+ for(const removedSenderInfo of removedSenders) {
189
+ this.rtpSenders.splice(this.rtpSenders.indexOf(removedSenderInfo), 1)
190
+ }
191
+ for(const trackInfo of tracks) {
192
+ if(this.rtpSenders.find(senderInfo => senderInfo.sender.track == trackInfo.track)) continue; // existing track
193
+ const sender = this.rtc.addTrack(trackInfo.track, trackInfo.stream)
194
+ this.rtpSenders.push({ sender, stream: trackInfo.stream })
195
+ somethingChanged = true
196
+ }
197
+ if(somethingChanged) {
198
+ //this.updateOffer() // wait for onnegotiationneeded
199
+ }
200
+ },
201
+ async handleNegotiationNeeded(event) {
202
+ console.log("NEGOTIATION NEEDED! IN STATE", this.rtc.signalingState)
203
+ if(!this.isEnabled) return
204
+ if(this.state == 'negotiating') {
205
+ console.log("SKIP NESTED NEGOTIATIONS WITH", this.to)
206
+ //return
207
+ }
208
+ this.state = 'negotiating'
209
+ // if it's disabled there is no need for offer
210
+ console.log("UPDATING OFFER")
211
+ const offer = await this.rtc.createOffer(this.offerOptions || undefined)
212
+ if(this.rtc.signalingState != "stable") {
213
+ console.log("RTC GOT OUT OF STABLE WHILE CREATING OFFER. IGNORE GENERATED OFFER!")
214
+ return;
215
+ }
216
+ await this.rtc.setLocalDescription(offer)
217
+ peer.sendMessage({ to, type: "sdp", data: offer })
218
+ console.log("SDP OFFER SET! RTC IN STATE", this.rtc.signalingState)
219
+
220
+ },
221
+ async handleSignalingStateChange(event) {
222
+ if(this.state == 'closed') return;
223
+ console.log("RTC SIGNALING STATE CHANGE", this.rtc.signalingState)
224
+ this.rtcSignalingState = this.rtc.signalingState
225
+ },
226
+ async handleIceCandidate(event) {
227
+ if(this.state == 'closed') return
228
+ //console.log("GOT ICE CANDIDATE", event.candidate && event.candidate.candidate)
229
+ peer.sendMessage({ to, type: "ice", data: event.candidate })
230
+ },
231
+ handleTrack(event) {
232
+ if(this.state == 'closed') return
233
+ const track = event.track
234
+ let stream = event.streams && event.streams[0]
235
+ if(!stream) {
236
+ console.error(`Streamless track ${track.id} ${track.kind} from peer ${to} - something is wrong!`)
237
+ stream = new MediaStream([track])
238
+ }
239
+ const trackInfo = {
240
+ track: event.track,
241
+ stream,
242
+ muted: track.muted,
243
+ removeTrackHandler: () => {
244
+ const trackIndex = this.remoteTracks.findIndex(remoteTrack =>
245
+ remoteTrack.track == track && remoteTrack.stream == stream)
246
+ if(trackIndex != -1) {
247
+ const trackInfo = this.remoteTracks[trackIndex]
248
+ trackInfo.track.removeEventListener('mute', trackInfo.muteHandler)
249
+ trackInfo.track.removeEventListener('unmute', trackInfo.unmuteHandler)
250
+ this.remoteTracks.splice(trackIndex, 1)
251
+ }
252
+ },
253
+ muteHandler: () => trackInfo.muted = track.muted,
254
+ unmuteHandler: () => trackInfo.muted = track.muted
255
+ }
256
+ if(stream) {
257
+ stream.addEventListener('removetrack', trackInfo.removeTrackHandler)
258
+ }
259
+ const existingTrackInfo = this.remoteTracks.find(remoteTrack => remoteTrack.track == track)
260
+ if(existingTrackInfo) {
261
+ existingTrackInfo.stream = stream // Track stream changed
262
+ } else {
263
+ trackInfo.track.addEventListener('mute', trackInfo.muteHandler)
264
+ trackInfo.track.addEventListener('unmute', trackInfo.unmuteHandler)
265
+ this.remoteTracks.push(trackInfo)
266
+ }
267
+ },
268
+ handleIceGatheringStateChange(event) {
269
+ if(this.state == 'closed') return
270
+ console.log("ICE GATHERING STATE CHANGED", this.rtc.iceGatheringState)
271
+ this.iceGatheringState = this.rtc.iceGatheringState
272
+ },
273
+ handleIceConnectionStateChange(event) {
274
+ if(this.state == 'closed') return
275
+ this.iceConnectionState = this.rtc.iceConnectionState
276
+ console.log("ICE GATHERING STATE CHANGED", this.rtc.iceConnectionState)
277
+ if(this.iceConnectionState == 'connected') {
278
+ this.state = 'connected'
279
+ }
280
+ if(this.iceConnectionState == 'failed') {
281
+ this.state = 'failed'
282
+ this.restartConnection()
283
+ }
284
+ if(this.iceConnectionState == 'disconnected') {
285
+ this.state = 'disconnected'
286
+ }
287
+ },
288
+ async restartConnection() {
289
+ console.log("RESTARTING CONNECTION")
290
+ if(false && this.rtc.restartIce) {
291
+ console.log("RESTART ICE!")
292
+ this.rtc.restartIce()
293
+ } else {
294
+ console.log("RESTART OFFER!")
295
+ const offer = await this.rtc.createOffer({ ...this.offerOptions, iceRestart: true })
296
+ if(this.rtc.signalingState != "stable") {
297
+ console.log("RTC GOT OUT OF STABLE WHILE CREATING OFFER. IGNORE GENERATED OFFER!")
298
+ return;
299
+ }
300
+ await this.rtc.setLocalDescription(offer)
301
+ peer.sendMessage({ to, type: "sdp", data: offer })
302
+ }
303
+ }
304
+ },
305
+ created() {
306
+ this.negotiationNeededHandler = (e) => this.handleNegotiationNeeded(e)
307
+ this.signalingStateChangeHandler = (e) => this.handleSignalingStateChange(e)
308
+ this.iceCandidateHandler = (e) => this.handleIceCandidate(e)
309
+ this.trackHandler = (e) => this.handleTrack(e)
310
+ this.iceGatheringStateChangeHandler = (e) => this.handleIceGatheringStateChange(e)
311
+ this.iceConnectionStateChangeHandler = (e) => this.handleIceConnectionStateChange(e)
312
+ },
313
+ beforeDestroy() {
314
+ if(this.state != 'closed') {
315
+ this.close()
316
+ }
317
+ if(this.rtc) {
318
+ this.rtc.removeEventListener('negotiationneeded', this.negotiationNeededHandler)
319
+ this.rtc.removeEventListener('signalingstatechange', this.signalingStateChangeHandler)
320
+ this.rtc.removeEventListener('icecandidate', this.iceCandidateHandler)
321
+ this.rtc.removeEventListener('track', this.trackHandler)
322
+ this.rtc.removeEventListener('icegatheringstatechanged', this.iceGatheringStateChangeHandler)
323
+ this.rtc.removeEventListener('iceconnectionstatechanged', this.iceConnectionStateChangeHandler)
324
+ }
325
+ }
326
+ })
327
+ }
328
+
329
+ export { createPeerConnection }