@live-change/peer-connection-frontend 0.8.33 → 0.8.35

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.
@@ -1,14 +1,16 @@
1
- import { ref, isRef, onUnmounted, getCurrentInstance, unref, reactive, computed } from 'vue'
2
- import { path, live, actions, api as useApi } from '@live-change/vue3-ssr'
1
+ import { ref, isRef, onUnmounted, getCurrentInstance, unref, reactive, computed, watch } from 'vue'
2
+ import { usePath, live, actions, api as useApi, inboxReader } from '@live-change/vue3-ssr'
3
3
  import { createPeerConnection } from "./PeerConnection.js"
4
4
 
5
- const messagesBucketSize = 32
6
-
7
5
  let lastInstanceId = 0
8
6
 
9
- const createPeer = async ({ channelType, channel, instance, localMediaStreams, online, onUnmountedCb }) => {
10
- if(!isRef(localMediaStreams)) {
11
- localMediaStreams = ref(localMediaStreams ?? [])
7
+ const createPeer = async ({
8
+ channelType, channel, instance, online, onUnmountedCb, appContext,
9
+ localTracks
10
+ }) => {
11
+ if(!appContext) appContext = (typeof window != 'undefined') && getCurrentInstance()?.appContext
12
+ if(!isRef(localTracks)) {
13
+ localTracks = ref(localTracks ?? [])
12
14
  }
13
15
  if(!isRef(online)) {
14
16
  online = ref(online ?? false)
@@ -22,306 +24,219 @@ const createPeer = async ({ channelType, channel, instance, localMediaStreams, o
22
24
  }
23
25
  }
24
26
  }
25
- if(!instance) instance = window.__WINDOW_ID__ + '.' + (++lastInstanceId)
26
27
 
28
+ if(!instance) instance = window.__WINDOW_ID__ + '.' + (++lastInstanceId)
27
29
 
28
30
  const api = useApi()
29
31
 
30
- const peerId = [channelType, channel, 'session_Session', api.client.value.session, instance].join(':')
32
+ const peerId = [channelType, channel, api.client.value.session, instance].join(':')
31
33
 
32
34
  console.log("CREATE PEER!")
33
35
 
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 }))
36
+ const path = usePath()
37
+ const [
38
+ peers,
39
+ peerOnline,
40
+ turnConfiguration
41
+ ] = await Promise.all([
42
+ live(path.peerConnection.peers({ channelType, channel }, appContext)
43
+ .with(peer => path.peerConnection.peerState({ peer: peer.id }).bind('peerState'))
44
+ ),
45
+ live(path.online.session({ group: 'peer', peer: peerId }), appContext),
46
+ live(path.peerConnection.turnConfiguration({ peer: peerId }), appContext)
38
47
  ])
39
48
 
40
49
  const localPeerState = ref(null)
41
50
 
42
51
  const finished = ref(false)
43
- const lastProcessedMessage = ref('')
44
52
  const connections = ref([])
45
53
  const waitingConnections = ref([]) // connections that are not initialized, but messages are received
46
- const localTracks = ref([])
47
54
 
48
- const otherPeers = computed(() => peers.value?.filter(peer => peer.id != peerId))
55
+ const otherPeers = computed(() => peers.value?.filter(peer => peer.id !== peerId))
56
+ const otherPeersOnline = computed(() => otherPeers.value?.filter(peer => peer.peerState?.online))
49
57
  const isConnectionPossible = computed(() => online.value && (!!turnConfiguration.value))
50
58
 
51
59
  const rtcConfiguration = computed(() => ({
52
- iceServers: [ turnConfiguration.value ],
53
- iceTransportPolicy: 'all', // 'all' or 'relay',
54
- bundlePolicy: 'balanced'
60
+ iceServers: [ turnConfiguration.value ],
61
+ iceTransportPolicy: 'all', // 'all' or 'relay',
62
+ bundlePolicy: 'balanced'
55
63
  }))
56
64
  const clientIp = computed(() => turnConfiguration.value?.clientIp)
57
65
 
58
66
  const anyLocalAudioEnabled = computed(() => localTracks.value
59
- .some(trackInfo => trackInfo.track.kind == 'audio' && trackInfo.enabled))
67
+ .some(trackInfo => trackInfo.track.kind === 'audio' && trackInfo.enabled))
60
68
  const anyLocalVideoEnabled = computed(() => localTracks.value
61
- .some(trackInfo => trackInfo.track.kind == 'video' && trackInfo.enabled))
69
+ .some(trackInfo => trackInfo.track.kind === 'video' && trackInfo.enabled))
62
70
  const anyLocalAudioAvailable = computed(() => localTracks.value
63
- .some(trackInfo => trackInfo.track.kind == 'audio'))
71
+ .some(trackInfo => trackInfo.track.kind === 'audio'))
64
72
  const anyLocalVideoAvailable = computed(() => localTracks.value
65
- .some(trackInfo => trackInfo.track.kind == 'video'))
73
+ .some(trackInfo => trackInfo.track.kind === 'video'))
66
74
  const computedLocalPeerState = computed(() => ({
75
+ online: online.value,
67
76
  audioState: anyLocalAudioAvailable.value ? (anyLocalAudioEnabled.value ? "enabled" : "muted") : "none",
68
77
  videoState: anyLocalVideoAvailable.value ? (anyLocalVideoEnabled.value ? "enabled" : "muted") : "none"
69
78
  }))
70
79
 
80
+ function sendPeerStateUpdate(update) {
81
+ const requestTimeout = 10000
82
+ api.requestWithSettings({ requestTimeout },
83
+ ['peerConnection', 'setPeerState'], update)
84
+ .catch(error => {
85
+ console.error("SET PEER STATE ERROR", error)
86
+ if(error === 'timeout' && !finished.value
87
+ && JSON.stringify({ ...localPeerState.value, ...update }) === JSON.stringify(localPeerState)
88
+ ) {
89
+ console.log("RETRYING")
90
+ sendPeerStateUpdate()
91
+ }
92
+ })
93
+ }
94
+ function updatePeerState(newState) {
95
+ const updated = { ...localPeerState.value, ...newState }
96
+ if(JSON.stringify(updated) !== JSON.stringify(localPeerState.value)) {
97
+ localPeerState.value = updated
98
+ const update = { ...updated, peer: peerId, _commandId: api.uid() }
99
+ sendPeerStateUpdate(update)
100
+ }
101
+ }
102
+ watch(computedLocalPeerState, (newState) => updatePeerState(newState), { immediate: true })
103
+
104
+ const messagesReader = inboxReader(
105
+ (rawPosition, bucketSize) => {
106
+ const path = ['peerConnection', 'messages', {
107
+ peer: peerId, gt: rawPosition, limit: bucketSize
108
+ }]
109
+ console.log("P", path)
110
+ return path
111
+ },
112
+ (message) => {
113
+ console.log("GOT MESSAGE!", message)
114
+ //console.log("HANDLE PEER MESSAGE", message)
115
+ if(message.from) {
116
+ let connection = connections.value.find(c => c.to === message.from)
117
+ if(!connection) connection = waitingConnections.value.find(c => c.to === message.from)
118
+ if(!connection) {
119
+ connection = createPeerConnection(peerInternal, message.from)
120
+ waitingConnections.value.push(connection)
121
+ }
122
+ connection.handleMessage(message)
123
+ } else {
124
+ throw new Error('messages from server not implemented')
125
+ }
126
+ },
127
+ '',
128
+ {
129
+ bucketSize: 32,
130
+ context: appContext
131
+ }
132
+ )
133
+
134
+ function sendMessage(message) {
135
+ console.log("SENDING PEER MESSAGE", message)
136
+ message.from = peerId
137
+ message.sent = message.sent || new Date().toISOString()
138
+ message._commandId = message._commandId || api.uid()
139
+ const requestTimeout = 10000
140
+ //console.log("SENDING PEER MESSAGE", message)
141
+ api.requestWithSettings({ requestTimeout }, ['peerConnection', 'postMessage'], message)
142
+ .catch(error => {
143
+ console.log("PEER MESSAGE ERROR", error)
144
+ if(error === 'timeout' && !finished.value) {
145
+ console.log("RETRYING")
146
+ sendMessage(message)
147
+ }
148
+ })
149
+ }
150
+
151
+ function updateConnections() {
152
+ const peers = isConnectionPossible.value ? otherPeersOnline.value : []
153
+ for(let connectionId = 0; connectionId < connections.value.length; connectionId++) {
154
+ const connection = connections.value[connectionId]
155
+ const connectionPeer = peers.find(peer => peer.id === connection.to)
156
+ if(!connectionPeer) {
157
+ connection.close()
158
+ connection.dispose()
159
+ connections.value.splice(connectionId, 1)
160
+ connectionId --
161
+ }
162
+ }
163
+ for(const peer of peers) {
164
+ let peerConnection = connections.value.find(connection => connection.to === peer.id)
165
+ if(peerConnection) continue;
166
+ const peerConnectionId = waitingConnections.value.findIndex(connection => connection.to === peer.id)
167
+ if(peerConnectionId !== -1) { // use waiting connection with cached messages
168
+ peerConnection = waitingConnections.value[peerConnectionId]
169
+ waitingConnections.value.splice(peerConnectionId, 1)
170
+ } else { // create connection
171
+ peerConnection = createPeerConnection(peerInternal, peer.id)
172
+ }
173
+ connections.value.push(peerConnection)
174
+ peerConnection.connect()
175
+ }
176
+ }
177
+
178
+ watch(() => isConnectionPossible.value && otherPeersOnline.value, () => {
179
+ updateConnections()
180
+ }, { immediate: true })
181
+
182
+ onUnmountedCb(() => {
183
+ finished.value = true
184
+ messagesReader.dispose()
185
+ for(const connection of waitingConnections.value) {
186
+ connection.dispose()
187
+ }
188
+ for(const connection of connections.value) {
189
+ connection.dispose()
190
+ }
191
+ })
192
+
71
193
  const summary = computed(() => ({
72
194
  peerId, online: online.value, finished: finished.value,
73
195
  computedLocalPeerState: computedLocalPeerState.value,
74
- lastProcessedMessage: lastProcessedMessage.value,
75
196
  peers: peers.value?.length,
76
- otherPeers: otherPeers.value?.map(p => p.id),
197
+ otherPeers: otherPeers.value?.map(p => p.peerState ?? p.id),
77
198
  connections: connections.value?.map(connection => connection.summary),
78
- tracks: localTracks.value?.map(({ track, stream }) => {
79
- const { id, kind, label, muted, enabled } = track
199
+ waitingConnections: waitingConnections.value?.map(connection => connection.summary),
200
+ localTracks: localTracks.value?.map(({ track, stream, enabled }) => {
201
+ const { id, kind, label, muted } = track
80
202
  return { id, kind, label, muted, enabled, stream: stream.id }
81
203
  }),
82
204
  turnConfiguration: turnConfiguration.value && {
83
205
  ...turnConfiguration.value,
84
206
  expire: new Date((+turnConfiguration.value.username.split(':')[0])*1000).toLocaleString()
85
207
  },
86
- isConnectionPossible: isConnectionPossible.value
208
+ isConnectionPossible: isConnectionPossible.value,
209
+ //rtcConfiguration: rtcConfiguration.value
87
210
  }))
88
211
 
89
- function setOnline(onlineValue) {
90
- online.value = onlineValue
212
+ function setTrackEnabled(track, v) {
213
+ track.enabled = v
214
+ track.track.enabled = v
91
215
  }
92
216
 
93
- return {
217
+ const peerPublic = {
94
218
  peerId, online, isConnectionPossible,
95
219
  connections, localTracks,
96
220
  otherPeers,
97
221
  summary,
98
- setOnline
222
+ setTrackEnabled,
99
223
  }
100
224
 
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('/') })))
225
+ const peerInternal = {
226
+ ...peerPublic,
227
+ rtcConfiguration,
228
+ clientIp,
229
+ sendMessage
230
+ }
118
231
 
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 {
232
+ return {
233
+ ...peerPublic
234
+ }
239
235
 
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)
236
+ /*
289
237
 
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
238
  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)
239
+
325
240
  }
326
241
  })*/
327
242
  }