@live-change/peer-connection-frontend 0.8.36 → 0.8.38

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.
@@ -9,6 +9,7 @@ declare module 'vue' {
9
9
  export interface GlobalComponents {
10
10
  Button: typeof import('primevue/button')['default']
11
11
  Debugger: typeof import('./src/components/Debugger.vue')['default']
12
+ DeviceSelect: typeof import('./src/components/DeviceSelect.vue')['default']
12
13
  DevicesSelect: typeof import('./src/components/DevicesSelect.vue')['default']
13
14
  Dialog: typeof import('primevue/dialog')['default']
14
15
  Dropdown: typeof import('primevue/dropdown')['default']
@@ -16,5 +17,6 @@ declare module 'vue' {
16
17
  PermissionsDialog: typeof import('./src/components/PermissionsDialog.vue')['default']
17
18
  RouterLink: typeof import('vue-router')['RouterLink']
18
19
  RouterView: typeof import('vue-router')['RouterView']
20
+ VolumeIndicator: typeof import('./src/components/VolumeIndicator.vue')['default']
19
21
  }
20
22
  }
package/front/src/App.vue CHANGED
@@ -8,6 +8,7 @@
8
8
 
9
9
  <script setup>
10
10
  import 'primevue/resources/themes/lara-light-blue/theme.css'
11
+ import 'boxicons/css/boxicons.min.css'
11
12
 
12
13
  import { useLocale } from '@live-change/vue3-components'
13
14
  const locale = useLocale()
@@ -43,7 +43,7 @@
43
43
 
44
44
  <div>
45
45
  <h2>User media</h2>
46
- <DevicesSelect v-model="selectedDevices" />
46
+ <DeviceSelect v-model="selectedDevices" />
47
47
  <hr>
48
48
  <pre>{{ selectedDevices }}</pre>
49
49
  <div class="mt-1 mb-3 flex align-items-center">
@@ -64,16 +64,12 @@
64
64
  </video>
65
65
  </div>
66
66
 
67
-
68
67
  </div>
69
68
  </template>
70
69
 
71
70
  <script setup>
72
71
  import Button from "primevue/button"
73
- import Dropdown from "primevue/dropdown"
74
- import Dialog from "primevue/dialog"
75
- import PermissionsDialog from './PermissionsDialog.vue'
76
- import DevicesSelect from './DevicesSelect.vue'
72
+ import DeviceSelect from './DeviceSelect.vue'
77
73
 
78
74
  import { ref, unref, computed, watch, onMounted, onUnmounted, getCurrentInstance } from 'vue'
79
75
  import { path, live, actions, api as useApi } from '@live-change/vue3-ssr'
@@ -82,7 +78,7 @@
82
78
  const appContext = (typeof window != 'undefined') && getCurrentInstance()?.appContext
83
79
 
84
80
  import { createPeer } from "./Peer.js"
85
- import { getUserMedia as getUserMediaNative, getDisplayMedia as getDisplayMediaNative, isUserMediaPermitted }
81
+ import { getDisplayMedia as getDisplayMediaNative }
86
82
  from "./userMedia.js"
87
83
  import { mediaStreamsTracks } from './mediaStreamsTracks.js'
88
84
 
@@ -169,43 +165,6 @@
169
165
  createPeerPromise = null
170
166
  }
171
167
 
172
- const permissionsDialog = ref(false)
173
- const permissionsCallbacks = ref(null)
174
-
175
- async function showPermissionsDialog() {
176
- return new Promise((resolve, reject) => {
177
- permissionsCallbacks.value = {
178
- disabled: () => {
179
- resolve(false)
180
- },
181
- ok: () => {
182
- resolve(true)
183
- },
184
- cancel: () => {
185
- reject('canceled by user')
186
- }
187
- }
188
- permissionsDialog.value = {
189
- visible: true
190
- }
191
- })
192
- }
193
-
194
- const connectDeviceDialog = ref(false)
195
- const connectDeviceCallbacks = ref(null)
196
-
197
- async function askToConnectCamera(constraints) {
198
- return new Promise((resolve, reject) => {
199
- connectDeviceCallbacks.value = {
200
- connected: () => resolve({ ...constraints }),
201
- camera: () => resolve({ ...constraints, audio: false }),
202
- microphone: () => resolve({ ...constraints, video: false }),
203
- cancel: () => resolve(null)
204
- }
205
- connectDeviceDialog.value = true
206
- })
207
- }
208
-
209
168
  async function getDisplayMedia() { // media stream retrival logic
210
169
  let initialConstraints = { video: true } // make a copy
211
170
  let constraints = { ...initialConstraints }
@@ -231,7 +190,6 @@
231
190
  displayMedia.value = null
232
191
  }
233
192
 
234
-
235
193
  function sendTestMessage() {
236
194
  for(const connection of peer.value.connections) {
237
195
  peer.value.sendMessage({
@@ -242,7 +200,6 @@
242
200
  }
243
201
  }
244
202
 
245
-
246
203
  </script>
247
204
 
248
205
  <style scoped lang="scss">
@@ -19,22 +19,29 @@
19
19
  </video>
20
20
  </div>
21
21
  <div class="absolute top-0 left-0 w-full h-full flex flex-column justify-content-end align-items-center">
22
- <div class="flex flex-row justify-content-between align-items-center h-5rem w-6rem">
22
+ <div class="flex flex-row justify-content-between align-items-center h-5rem w-7rem media-buttons">
23
+
23
24
  <Button v-if="!selectedConstraints?.audio?.deviceId"
24
- @click="handleDisabledAudioClick" raised
25
- icon="pi pi-microphone" severity="secondary" rounded v-ripple />
25
+ @click="handleDisabledAudioClick" raised
26
+ icon="bx bx-microphone-off" severity="secondary" rounded v-ripple />
26
27
  <Button v-else-if="audioInputMuted" @click="audioInputMuted = false" raised
27
- icon="pi pi-microphone" severity="danger" rounded v-ripple />
28
+ icon="bx bx-microphone-off" severity="danger" rounded v-ripple />
28
29
  <Button v-else @click="audioInputMuted = true" raised
29
- icon="pi pi-microphone" severity="success" rounded v-ripple />
30
+ icon="bx bx-microphone" severity="success" rounded v-ripple />
30
31
 
31
32
  <Button v-if="!selectedConstraints?.video?.deviceId"
32
- @click="handleDisabledVideoClick" raised
33
- icon="pi pi-camera" severity="secondary" rounded v-ripple />
33
+ @click="handleDisabledVideoClick" raised
34
+ icon="bx bx-camera-off" severity="secondary" rounded v-ripple />
34
35
  <Button v-else-if="videoInputMuted" @click="videoInputMuted = false" raised
35
- icon="pi pi-camera" severity="danger" rounded v-ripple />
36
+ icon="bx bx-camera-off" severity="danger" rounded v-ripple />
36
37
  <Button v-else @click="videoInputMuted = true" raised
37
- icon="pi pi-camera" severity="success" rounded v-ripple />
38
+ icon="bx bx-camera" severity="success" rounded v-ripple />
39
+
40
+ </div>
41
+ </div>
42
+ <div class="absolute top-0 right-0">
43
+ <div class="m-3">
44
+ <VolumeIndicator :stream="userMedia" />
38
45
  </div>
39
46
  </div>
40
47
  </div>
@@ -125,6 +132,14 @@
125
132
  </div>
126
133
  </template>
127
134
 
135
+ <style lang="scss">
136
+ .media-buttons {
137
+ .bx::before {
138
+ font-size: 1.69em;
139
+ }
140
+ }
141
+ </style>
142
+
128
143
  <script setup>
129
144
 
130
145
  import { defineProps, defineModel, computed, ref, toRefs, onMounted, watch } from 'vue'
@@ -132,6 +147,7 @@
132
147
  import { getUserMedia as getUserMediaNative, getDisplayMedia as getDisplayMediaNative, isUserMediaPermitted }
133
148
  from "./userMedia.js"
134
149
  import PermissionsDialog from './PermissionsDialog.vue'
150
+ import VolumeIndicator from './VolumeIndicator.vue'
135
151
 
136
152
  const props = defineProps({
137
153
  audioInputRequest: {
@@ -184,8 +200,10 @@
184
200
  devices.value = await navigator.mediaDevices.enumerateDevices()
185
201
  console.log("DEVICES", JSON.stringify(devices.value))
186
202
  }
187
- useEventListener(navigator.mediaDevices, 'devicechange', updateDevices)
188
- onMounted(updateDevices)
203
+ if(typeof window !== 'undefined') {
204
+ useEventListener(navigator.mediaDevices, 'devicechange', updateDevices)
205
+ onMounted(updateDevices)
206
+ }
189
207
 
190
208
  const audioInputs = computed(() => devices.value.filter(device => device.kind === 'audioinput'))
191
209
  const audioOutputs = computed(() => devices.value.filter(device => device.kind === 'audiooutput'))
@@ -208,13 +226,22 @@
208
226
  })
209
227
 
210
228
  watch(audioInputs, (value) => {
211
- model.value.audioInput = value[0]
229
+ model.value = {
230
+ ...model.value,
231
+ audioInput: value[0]
232
+ }
212
233
  }, { immediate: true })
213
234
  watch(audioOutputs, (value) => {
214
- model.value.audioOutput = value[0]
235
+ model.value = {
236
+ ...model.value,
237
+ audioOutput: value[0]
238
+ }
215
239
  }, { immediate: true })
216
240
  watch(videoInputs, (value) => {
217
- model.value.videoInput = value[0]
241
+ model.value = {
242
+ ...model.value,
243
+ videoInput: value[0]
244
+ }
218
245
  }, { immediate: true })
219
246
 
220
247
  /* onMounted(() => {
@@ -234,9 +261,9 @@
234
261
  const selectedConstraints = ref({ video: false, audio: false })
235
262
  watch(() => ({
236
263
  video: limitedMedia.value === 'audio' ? false :
237
- { deviceId: model.value.videoInput?.deviceId, ...constraints.value.video },
264
+ { deviceId: model.value?.videoInput?.deviceId, ...constraints.value.video },
238
265
  audio: limitedMedia.value === 'video' ? false :
239
- { deviceId: model.value.audioInput?.deviceId, ...constraints.value.audio }
266
+ { deviceId: model.value?.audioInput?.deviceId, ...constraints.value.audio }
240
267
  }), ({ video, audio }) => {
241
268
  console.log("SELECTED CONSTRAINTS", {
242
269
  video: selectedConstraints.value.video?.deviceId,
@@ -383,9 +410,6 @@
383
410
  //updateUserMedia()
384
411
  })
385
412
 
386
- window.um = userMedia
387
- window.model = model
388
-
389
413
  </script>
390
414
 
391
415
  <style scoped>
@@ -96,6 +96,7 @@
96
96
  const emit = defineEmits(['ok'])
97
97
 
98
98
  watch(() => JSON.stringify(requiredPermissions.value), async value => {
99
+ if(typeof window === 'undefined') return
99
100
  console.log("requiredPermissions", value)
100
101
 
101
102
  for(const requiredPermission of JSON.parse(value)) {
@@ -0,0 +1,211 @@
1
+ <template>
2
+ <div class="flex flex-row border-circle w-2rem h-2rem bg-primary-800
3
+ align-items-center justify-content-center">
4
+ <div class="volume-indicator-bar bg-white border-round" ref="low"></div>
5
+ <div class="volume-indicator-bar bg-white border-round" ref="mid"></div>
6
+ <div class="volume-indicator-bar bg-white border-round" ref="high"></div>
7
+ </div>
8
+ </template>
9
+
10
+ <style scoped lang="scss">
11
+ .volume-indicator-bar {
12
+ width: 0.3rem;
13
+ margin: 0.1rem;
14
+ }
15
+ .border-circle {
16
+ border-radius: 50%;
17
+ }
18
+ .bg-primary-700 {
19
+ background-color: #1976D2;
20
+ }
21
+ </style>
22
+
23
+ <script setup>
24
+
25
+ import { ref, defineProps, computed, toRefs, onMounted, onUnmounted, watch } from 'vue'
26
+
27
+ const props = defineProps({
28
+ stream: {
29
+ type: Object,
30
+ required: true
31
+ }
32
+ })
33
+
34
+ const { stream } = toRefs(props)
35
+
36
+ const speed = 0.46
37
+ const maxLogVolume = -2
38
+ const minLogVolume = -7
39
+ const lowFrequency = 230
40
+ const midFrequency = 460
41
+ const highFrequency = 1230
42
+ const filterQ = 0.1
43
+ const detune = 100
44
+
45
+ const minHeight = 3
46
+ const maxHeight = 15
47
+
48
+ const low = ref(null)
49
+ const mid = ref(null)
50
+ const high = ref(null)
51
+
52
+ let audioContext, mediaStreamSource
53
+ let lowFilter, midFilter, highFilter
54
+ let lowProcessor, midProcessor, highProcessor
55
+ let lowSlow = 0, midSlow = 0, highSlow = 0
56
+
57
+ function measureAmplitudeProcess(cb) {
58
+ return function(event) {
59
+ const numberOfChannels = event.inputBuffer.numberOfChannels
60
+ let instant = 0
61
+ for(let channelId = 0; channelId < numberOfChannels; channelId++) {
62
+ let sum = 0
63
+ const input = event.inputBuffer.getChannelData(channelId)
64
+ for (let i = 0; i < input.length; ++i) {
65
+ sum += input[i] * input[i]
66
+ }
67
+ instant += sum / input.length
68
+ }
69
+ cb(Math.sqrt(instant))
70
+ }
71
+ }
72
+
73
+ function handleLow(instant) {
74
+ if(!low.value) return console.log("no low element")
75
+ instant = Math.log(instant)
76
+ let value = lowSlow
77
+ if(!Number.isFinite(value)) value = -1000
78
+ value = instant * speed + value * (1 - speed)
79
+ if(value > maxLogVolume) value = maxLogVolume
80
+ if(value < minLogVolume) value = minLogVolume
81
+ const height = ((value - minLogVolume)/(maxLogVolume - minLogVolume))
82
+ * (maxHeight - minHeight) + minHeight
83
+ lowSlow = value
84
+ low.value.style.height = height + 'px'
85
+ }
86
+ function handleMid(instant) {
87
+ if(!mid.value) return console.log("no mid element")
88
+ instant = Math.log(instant)
89
+ let value = midSlow
90
+ if(!Number.isFinite(value)) value = -1000
91
+ value = instant * speed + value * (1 - speed)
92
+ if(value > maxLogVolume) value = maxLogVolume
93
+ if(value < minLogVolume) value = minLogVolume
94
+ const height = ((value - minLogVolume)/(maxLogVolume - minLogVolume))
95
+ * (maxHeight - minHeight) + minHeight
96
+ midSlow = value
97
+ mid.value.style.height = height + 'px'
98
+ }
99
+ function handleHigh(instant) {
100
+ if(!high.value) return console.log("no high element")
101
+ instant = Math.log(instant)
102
+ let value = highSlow
103
+ if(!Number.isFinite(value)) value = -1000
104
+ value = instant * speed + value * (1 - speed)
105
+ if(value > maxLogVolume) value = maxLogVolume
106
+ if(value < minLogVolume) value = minLogVolume
107
+ const height = ((value - minLogVolume)/(maxLogVolume - minLogVolume))
108
+ * (maxHeight - minHeight) + minHeight
109
+ highSlow = value
110
+ high.value.style.height = height + 'px'
111
+ }
112
+
113
+ function startProcessingAudioStream(sourceStream) {
114
+ audioContext = new (window.AudioContext || window.webkitAudioContext)()
115
+
116
+ lowFilter = audioContext.createBiquadFilter()
117
+ lowFilter.type = 'lowpass'
118
+ lowFilter.frequency.value = lowFrequency
119
+ lowFilter.detune.value = detune
120
+ lowFilter.Q.value = filterQ
121
+
122
+ midFilter = audioContext.createBiquadFilter()
123
+ midFilter.type = 'bandpass'
124
+ midFilter.frequency.value = midFrequency
125
+ midFilter.detune.value = detune
126
+ midFilter.Q.value = filterQ
127
+
128
+ highFilter = audioContext.createBiquadFilter()
129
+ highFilter.type = 'highpass'
130
+ highFilter.frequency.value = highFrequency
131
+ highFilter.detune.value = detune
132
+ highFilter.Q.value = filterQ
133
+
134
+ lowProcessor = audioContext.createScriptProcessor(
135
+ 2*2048, 1, 1)
136
+ lowProcessor.addEventListener('audioprocess', measureAmplitudeProcess(a => handleLow(a)))
137
+
138
+ midProcessor = audioContext.createScriptProcessor(
139
+ 2*2048, 1, 1)
140
+ midProcessor.addEventListener('audioprocess', measureAmplitudeProcess(a => handleMid(a)))
141
+
142
+ highProcessor = audioContext.createScriptProcessor(
143
+ 2*2048, 1, 1)
144
+ highProcessor.addEventListener('audioprocess', measureAmplitudeProcess(a => handleHigh(a)))
145
+
146
+ lowFilter.connect(lowProcessor)
147
+ midFilter.connect(midProcessor)
148
+ highFilter.connect(highProcessor)
149
+
150
+ lowProcessor.connect(audioContext.destination)
151
+ midProcessor.connect(audioContext.destination)
152
+ highProcessor.connect(audioContext.destination)
153
+
154
+ mediaStreamSource = audioContext.createMediaStreamSource(sourceStream)
155
+ mediaStreamSource.connect(lowFilter)
156
+ mediaStreamSource.connect(midFilter)
157
+ mediaStreamSource.connect(highFilter)
158
+ }
159
+
160
+ function stopProcessingAudioStream() {
161
+ if(mediaStreamSource) {
162
+ mediaStreamSource.disconnect()
163
+ mediaStreamSource = null
164
+ }
165
+ if(audioContext) audioContext.close()
166
+ }
167
+
168
+ function handleStreamChange() {
169
+ updateSource()
170
+ }
171
+
172
+ watch(stream, (newStream, oldStream) => {
173
+ if(oldStream) {
174
+ oldStream.removeEventListener('addtrack', handleStreamChange)
175
+ oldStream.removeEventListener('removetrack', handleStreamChange)
176
+ }
177
+ updateSource()
178
+ if(newStream) {
179
+ newStream.addEventListener('addtrack', handleStreamChange)
180
+ newStream.addEventListener('removetrack', handleStreamChange)
181
+ }
182
+ }, { immediate: true })
183
+
184
+ onMounted(() => {
185
+ updateSource()
186
+ })
187
+
188
+ function updateSource() {
189
+ console.log("UPDATE SOURCE", stream.value, "STARTED?")
190
+ if(mediaStreamSource) {
191
+ stopProcessingAudioStream()
192
+ }
193
+ if(stream.value && stream.value.getAudioTracks().length > 0) {
194
+ console.log("GOT AUDIO TRACKS!")
195
+ startProcessingAudioStream(stream.value)
196
+ }
197
+ }
198
+
199
+ onUnmounted(() => {
200
+ if(stream.value) {
201
+ stream.value.removeEventListener('addtrack', handleStreamChange)
202
+ stream.value.removeEventListener('removetrack', handleStreamChange)
203
+ }
204
+ stopProcessingAudioStream()
205
+ })
206
+
207
+ </script>
208
+
209
+ <style scoped>
210
+
211
+ </style>
@@ -34,7 +34,7 @@ export function createRouter(app, config) {
34
34
  history: import.meta.env.SSR ? createMemoryHistory() : createWebHistory(),
35
35
  routes: [
36
36
  ...dbAdminRoutes({ prefix: '/_db', route: r => ({ ...r, meta: { ...r.meta, raw: true }}) }),
37
- ...peerConnectionRoutes(config),
37
+ ...peerConnectionRoutes({ ...config, prefix: '/peer-connection' }),
38
38
  {
39
39
  name: 'peer-connection:test-debugger', path: '/', meta: { },
40
40
  component: () => import("./components/Debugger.vue"),
package/index.js CHANGED
@@ -1,14 +1,18 @@
1
1
  import Debugger from './front/src/components/Debugger.vue'
2
2
  import DeviceSelect from './front/src/components/DeviceSelect.vue'
3
3
  import PermissionsDialog from './front/src/components/PermissionsDialog.vue'
4
+ import VolumeIndicator from './front/src/components/VolumeIndicator.vue'
4
5
 
5
- export { Debugger, DeviceSelect, PermissionsDialog }
6
+ export { Debugger, DeviceSelect, PermissionsDialog, VolumeIndicator }
6
7
 
7
8
  import { createPeer } from './front/src/components/Peer.js'
8
9
  import { createPeerConnection } from './front/src/components/PeerConnection.js'
9
10
  import { getUserMedia, getDisplayMedia, isUserMediaPermitted } from './front/src/components/userMedia.js'
11
+ import { mediaStreamsTracks } from './front/src/components/mediaStreamsTracks.js'
10
12
 
11
- export { createPeer, createPeerConnection, getUserMedia, getDisplayMedia, isUserMediaPermitted }
13
+ export {
14
+ createPeer, createPeerConnection, getUserMedia, getDisplayMedia, isUserMediaPermitted, mediaStreamsTracks
15
+ }
12
16
 
13
17
  import { peerConnectionRoutes } from './front/src/router.js'
14
18
  export { peerConnectionRoutes }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@live-change/peer-connection-frontend",
3
- "version": "0.8.36",
3
+ "version": "0.8.38",
4
4
  "scripts": {
5
5
  "memDev": "dotenvx run -- node server/start.js memDev --enableSessions --initScript ./init.js --dbAccess",
6
6
  "localDevInit": "rm tmp.db; dotenvx run -- node server/start.js localDev --enableSessions --initScript ./init.js",
@@ -22,23 +22,25 @@
22
22
  },
23
23
  "type": "module",
24
24
  "dependencies": {
25
- "@live-change/cli": "^0.8.36",
26
- "@live-change/dao": "^0.8.36",
27
- "@live-change/dao-vue3": "^0.8.36",
28
- "@live-change/dao-websocket": "^0.8.36",
29
- "@live-change/framework": "^0.8.36",
30
- "@live-change/password-authentication-service": "^0.8.36",
31
- "@live-change/secret-code-service": "^0.8.36",
32
- "@live-change/secret-link-service": "^0.8.36",
33
- "@live-change/session-service": "^0.8.36",
34
- "@live-change/user-frontend": "^0.8.36",
35
- "@live-change/user-service": "^0.8.36",
36
- "@live-change/vue3-components": "^0.8.36",
37
- "@live-change/vue3-ssr": "^0.8.36",
25
+ "@live-change/cli": "^0.8.38",
26
+ "@live-change/dao": "^0.8.38",
27
+ "@live-change/dao-vue3": "^0.8.38",
28
+ "@live-change/dao-websocket": "^0.8.38",
29
+ "@live-change/framework": "^0.8.38",
30
+ "@live-change/password-authentication-service": "^0.8.38",
31
+ "@live-change/secret-code-service": "^0.8.38",
32
+ "@live-change/secret-link-service": "^0.8.38",
33
+ "@live-change/session-service": "^0.8.38",
34
+ "@live-change/user-frontend": "^0.8.38",
35
+ "@live-change/user-service": "^0.8.38",
36
+ "@live-change/vue3-components": "^0.8.38",
37
+ "@live-change/vue3-ssr": "^0.8.38",
38
38
  "@vueuse/core": "^10.11.0",
39
+ "boxicons": "^2.1.4",
39
40
  "codeceptjs-assert": "^0.0.5",
40
41
  "compression": "^1.7.4",
41
42
  "cross-env": "^7.0.3",
43
+ "feather-icons": "^4.29.2",
42
44
  "get-port-sync": "1.0.1",
43
45
  "primeflex": "^3.3.1",
44
46
  "primeicons": "^7.0.0",
@@ -52,7 +54,7 @@
52
54
  "vue3-scroll-border": "0.1.6"
53
55
  },
54
56
  "devDependencies": {
55
- "@live-change/codeceptjs-helper": "^0.8.36",
57
+ "@live-change/codeceptjs-helper": "^0.8.38",
56
58
  "codeceptjs": "^3.5.12",
57
59
  "generate-password": "1.7.1",
58
60
  "playwright": "^1.41.2",
@@ -63,5 +65,5 @@
63
65
  "author": "Michał Łaszczewski <michal@laszczewski.pl>",
64
66
  "license": "BSD-3-Clause",
65
67
  "description": "",
66
- "gitHead": "24694d1687f0ab2d6eb7edd95e5274428cfd44eb"
68
+ "gitHead": "69b15b8d593c02482e1f36590089a176d72bb4cd"
67
69
  }
@@ -1,6 +1,9 @@
1
1
  import App from "@live-change/framework"
2
2
  const app = App.app()
3
3
 
4
+ import dotenv from 'dotenv'
5
+ dotenv.config()
6
+
4
7
  const contactTypes = ['email', 'phone']
5
8
  const remoteAccountTypes = ['google']
6
9
 
@@ -108,6 +111,10 @@ app.config = {
108
111
  turn: {
109
112
  urls: 'turn:turn.chaosu.pl:4433'
110
113
  }
114
+ },
115
+ {
116
+ name: 'videoCall',
117
+ path: "@live-change/video-call-service",
111
118
  }
112
119
  ]
113
120
  }