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

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">
@@ -73,7 +73,7 @@
73
73
  import Dropdown from "primevue/dropdown"
74
74
  import Dialog from "primevue/dialog"
75
75
  import PermissionsDialog from './PermissionsDialog.vue'
76
- import DevicesSelect from './DevicesSelect.vue'
76
+ import DeviceSelect from './DeviceSelect.vue'
77
77
 
78
78
  import { ref, unref, computed, watch, onMounted, onUnmounted, getCurrentInstance } from 'vue'
79
79
  import { path, live, actions, api as useApi } from '@live-change/vue3-ssr'
@@ -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,209 @@
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
+
58
+ function measureAmplitudeProcess(cb) {
59
+ return function(event) {
60
+ const numberOfChannels = event.inputBuffer.numberOfChannels
61
+ let instant = 0
62
+ for(let channelId = 0; channelId < numberOfChannels; channelId++) {
63
+ let sum = 0
64
+ const input = event.inputBuffer.getChannelData(channelId)
65
+ for (let i = 0; i < input.length; ++i) {
66
+ sum += input[i] * input[i]
67
+ }
68
+ instant += sum / input.length
69
+ }
70
+ cb(Math.sqrt(instant))
71
+ }
72
+ }
73
+
74
+ function handleLow(instant) {
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
+ instant = Math.log(instant)
88
+ let value = midSlow
89
+ if(!Number.isFinite(value)) value = -1000
90
+ value = instant * speed + value * (1 - speed)
91
+ if(value > maxLogVolume) value = maxLogVolume
92
+ if(value < minLogVolume) value = minLogVolume
93
+ const height = ((value - minLogVolume)/(maxLogVolume - minLogVolume))
94
+ * (maxHeight - minHeight) + minHeight
95
+ midSlow = value
96
+ mid.value.style.height = height + 'px'
97
+ }
98
+ function handleHigh(instant) {
99
+ instant = Math.log(instant)
100
+ let value = highSlow
101
+ if(!Number.isFinite(value)) value = -1000
102
+ value = instant * speed + value * (1 - speed)
103
+ if(value > maxLogVolume) value = maxLogVolume
104
+ if(value < minLogVolume) value = minLogVolume
105
+ const height = ((value - minLogVolume)/(maxLogVolume - minLogVolume))
106
+ * (maxHeight - minHeight) + minHeight
107
+ highSlow = value
108
+ high.value.style.height = height + 'px'
109
+ }
110
+
111
+ function startProcessingAudioStream(sourceStream) {
112
+ audioContext = new (window.AudioContext || window.webkitAudioContext)()
113
+
114
+ lowFilter = audioContext.createBiquadFilter()
115
+ lowFilter.type = 'lowpass'
116
+ lowFilter.frequency.value = lowFrequency
117
+ lowFilter.detune.value = detune
118
+ lowFilter.Q.value = filterQ
119
+
120
+ midFilter = audioContext.createBiquadFilter()
121
+ midFilter.type = 'bandpass'
122
+ midFilter.frequency.value = midFrequency
123
+ midFilter.detune.value = detune
124
+ midFilter.Q.value = filterQ
125
+
126
+ highFilter = audioContext.createBiquadFilter()
127
+ highFilter.type = 'highpass'
128
+ highFilter.frequency.value = highFrequency
129
+ highFilter.detune.value = detune
130
+ highFilter.Q.value = filterQ
131
+
132
+ lowProcessor = audioContext.createScriptProcessor(
133
+ 2*2048, 1, 1)
134
+ lowProcessor.addEventListener('audioprocess', measureAmplitudeProcess(a => handleLow(a)))
135
+
136
+ midProcessor = audioContext.createScriptProcessor(
137
+ 2*2048, 1, 1)
138
+ midProcessor.addEventListener('audioprocess', measureAmplitudeProcess(a => handleMid(a)))
139
+
140
+ highProcessor = audioContext.createScriptProcessor(
141
+ 2*2048, 1, 1)
142
+ highProcessor.addEventListener('audioprocess', measureAmplitudeProcess(a => handleHigh(a)))
143
+
144
+ lowFilter.connect(lowProcessor)
145
+ midFilter.connect(midProcessor)
146
+ highFilter.connect(highProcessor)
147
+
148
+ lowProcessor.connect(audioContext.destination)
149
+ midProcessor.connect(audioContext.destination)
150
+ highProcessor.connect(audioContext.destination)
151
+
152
+ mediaStreamSource = audioContext.createMediaStreamSource(sourceStream)
153
+ mediaStreamSource.connect(lowFilter)
154
+ mediaStreamSource.connect(midFilter)
155
+ mediaStreamSource.connect(highFilter)
156
+ }
157
+
158
+ function stopProcessingAudioStream() {
159
+ if(mediaStreamSource) {
160
+ mediaStreamSource.disconnect()
161
+ mediaStreamSource = null
162
+ }
163
+ if(audioContext) audioContext.close()
164
+ }
165
+
166
+ function handleStreamChange() {
167
+ updateSource()
168
+ }
169
+
170
+ watch(stream, (newStream, oldStream) => {
171
+ if(oldStream) {
172
+ oldStream.removeEventListener('addtrack', handleStreamChange)
173
+ oldStream.removeEventListener('removetrack', handleStreamChange)
174
+ }
175
+ updateSource()
176
+ if(newStream) {
177
+ newStream.addEventListener('addtrack', handleStreamChange)
178
+ newStream.addEventListener('removetrack', handleStreamChange)
179
+ }
180
+ }, { immediate: true })
181
+
182
+ onMounted(() => {
183
+ updateSource()
184
+ })
185
+
186
+ function updateSource() {
187
+ console.log("UPDATE SOURCE", stream.value, "STARTED?")
188
+ if(mediaStreamSource) {
189
+ stopProcessingAudioStream()
190
+ }
191
+ if(stream.value && stream.value.getAudioTracks().length > 0) {
192
+ console.log("GOT AUDIO TRACKS!")
193
+ startProcessingAudioStream(stream.value)
194
+ }
195
+ }
196
+
197
+ onUnmounted(() => {
198
+ if(stream.value) {
199
+ stream.value.removeEventListener('addtrack', handleStreamChange)
200
+ stream.value.removeEventListener('removetrack', handleStreamChange)
201
+ }
202
+ stopProcessingAudioStream()
203
+ })
204
+
205
+ </script>
206
+
207
+ <style scoped>
208
+
209
+ </style>
@@ -6,25 +6,15 @@ import {
6
6
 
7
7
  import { dbAdminRoutes } from "@live-change/db-admin"
8
8
 
9
- export function routes(config = {}) {
9
+ export function peerConnectionRoutes(config = {}) {
10
10
  const { prefix = '/', route = (r) => r } = config
11
11
  return [
12
12
 
13
- ...dbAdminRoutes({ prefix: '/_db', route: r => ({ ...r, meta: { ...r.meta, raw: true }}) }),
14
-
15
13
  route({
16
- name: 'debugger', path: prefix + '/debugger/:channelType/:channel', meta: { },
14
+ name: 'peer-connection:debugger', path: prefix + '/debugger/:channelType/:channel', meta: { },
17
15
  component: () => import("./components/Debugger.vue"),
18
16
  props: true
19
17
  }),
20
- route({
21
- name: 'testDebugger', path: prefix + '', meta: { },
22
- component: () => import("./components/Debugger.vue"),
23
- props: {
24
- channelType: 'example_Example',
25
- channel: 'one'
26
- }
27
- })
28
18
 
29
19
  ]
30
20
  }
@@ -42,7 +32,18 @@ export function createRouter(app, config) {
42
32
  // use appropriate history implementation for server/client
43
33
  // import.meta.env.SSR is injected by Vite.
44
34
  history: import.meta.env.SSR ? createMemoryHistory() : createWebHistory(),
45
- routes: routes(config)
35
+ routes: [
36
+ ...dbAdminRoutes({ prefix: '/_db', route: r => ({ ...r, meta: { ...r.meta, raw: true }}) }),
37
+ ...peerConnectionRoutes({ ...config, prefix: '/peer-connection' }),
38
+ {
39
+ name: 'peer-connection:test-debugger', path: '/', meta: { },
40
+ component: () => import("./components/Debugger.vue"),
41
+ props: {
42
+ channelType: 'example_Example',
43
+ channel: 'one'
44
+ }
45
+ }
46
+ ]
46
47
  })
47
48
  router.beforeEach(async (to, from) => {
48
49
  if(to?.matched.find(m => m?.meta.signedIn)) {
package/index.js ADDED
@@ -0,0 +1,15 @@
1
+ import Debugger from './front/src/components/Debugger.vue'
2
+ import DeviceSelect from './front/src/components/DeviceSelect.vue'
3
+ import PermissionsDialog from './front/src/components/PermissionsDialog.vue'
4
+ import VolumeIndicator from './front/src/components/VolumeIndicator.vue'
5
+
6
+ export { Debugger, DeviceSelect, PermissionsDialog, VolumeIndicator }
7
+
8
+ import { createPeer } from './front/src/components/Peer.js'
9
+ import { createPeerConnection } from './front/src/components/PeerConnection.js'
10
+ import { getUserMedia, getDisplayMedia, isUserMediaPermitted } from './front/src/components/userMedia.js'
11
+
12
+ export { createPeer, createPeerConnection, getUserMedia, getDisplayMedia, isUserMediaPermitted }
13
+
14
+ import { peerConnectionRoutes } from './front/src/router.js'
15
+ export { peerConnectionRoutes }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@live-change/peer-connection-frontend",
3
- "version": "0.8.35",
3
+ "version": "0.8.37",
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.35",
26
- "@live-change/dao": "^0.8.35",
27
- "@live-change/dao-vue3": "^0.8.35",
28
- "@live-change/dao-websocket": "^0.8.35",
29
- "@live-change/framework": "^0.8.35",
30
- "@live-change/password-authentication-service": "^0.8.35",
31
- "@live-change/secret-code-service": "^0.8.35",
32
- "@live-change/secret-link-service": "^0.8.35",
33
- "@live-change/session-service": "^0.8.35",
34
- "@live-change/user-frontend": "^0.8.35",
35
- "@live-change/user-service": "^0.8.35",
36
- "@live-change/vue3-components": "^0.8.35",
37
- "@live-change/vue3-ssr": "^0.8.35",
25
+ "@live-change/cli": "^0.8.37",
26
+ "@live-change/dao": "^0.8.37",
27
+ "@live-change/dao-vue3": "^0.8.37",
28
+ "@live-change/dao-websocket": "^0.8.37",
29
+ "@live-change/framework": "^0.8.37",
30
+ "@live-change/password-authentication-service": "^0.8.37",
31
+ "@live-change/secret-code-service": "^0.8.37",
32
+ "@live-change/secret-link-service": "^0.8.37",
33
+ "@live-change/session-service": "^0.8.37",
34
+ "@live-change/user-frontend": "^0.8.37",
35
+ "@live-change/user-service": "^0.8.37",
36
+ "@live-change/vue3-components": "^0.8.37",
37
+ "@live-change/vue3-ssr": "^0.8.37",
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.35",
57
+ "@live-change/codeceptjs-helper": "^0.8.37",
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": "90fbb746dc7270895daf17b437ca48c0b0a01c01"
68
+ "gitHead": "8b97a83258a3eccd08534d0376015781c4eefdec"
67
69
  }