@live-change/peer-connection-frontend 0.8.80 → 0.8.82
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.
|
@@ -18,6 +18,7 @@
|
|
|
18
18
|
class="max-w-full max-h-full" style="object-fit: contain; transform: scaleX(-1)">
|
|
19
19
|
</video>
|
|
20
20
|
</div>
|
|
21
|
+
|
|
21
22
|
<div class="absolute top-0 left-0 w-full h-full flex flex-column justify-content-end align-items-center">
|
|
22
23
|
<div class="flex flex-row justify-content-between align-items-center h-5rem w-7rem media-buttons">
|
|
23
24
|
<MicrophoneButton v-model="model" @disabled-audio-click="handleDisabledAudioClick" />
|
|
@@ -29,6 +30,29 @@
|
|
|
29
30
|
<VolumeIndicator :stream="userMedia" />
|
|
30
31
|
</div>
|
|
31
32
|
</div>
|
|
33
|
+
<div class="absolute top-0 left-0 w-full h-full flex flex-column justify-content-center align-items-center">
|
|
34
|
+
<div v-if="model.cameraAccessError"
|
|
35
|
+
class="flex flex-column justify-content-center align-items-center m-3 p-2 bg-black-alpha-40">
|
|
36
|
+
<i class="bx bx-camera-off text-4xl text-red-600" />
|
|
37
|
+
<div class="text-red-500 text-xl mb-1">
|
|
38
|
+
Cannot access the camera.
|
|
39
|
+
</div>
|
|
40
|
+
<div class="text-red-500 text-sm text-center">
|
|
41
|
+
It might be in use by another application or there might be a hardware issue.
|
|
42
|
+
Please ensure no other applications are using the camera and try again.
|
|
43
|
+
</div>
|
|
44
|
+
</div>
|
|
45
|
+
<div v-else-if="model.mediaError"
|
|
46
|
+
class="flex flex-column justify-content-center align-items-center m-3 p-2 bg-black-alpha-40">
|
|
47
|
+
<i class="bx bx-camera-off text-4xl text-red-600" />
|
|
48
|
+
<div class="text-red-500 text-xl mb-1">
|
|
49
|
+
Cannot access media devices.
|
|
50
|
+
</div>
|
|
51
|
+
<div class="text-red-500 text-sm text-center">
|
|
52
|
+
{{ model.mediaError }}
|
|
53
|
+
</div>
|
|
54
|
+
</div>
|
|
55
|
+
</div>
|
|
32
56
|
</div>
|
|
33
57
|
<div class="flex flex-row gap-2 pt-2 justify-content-around">
|
|
34
58
|
<div v-if="audioInputRequest !== 'none' && audioInputs.length > 0"
|
|
@@ -140,7 +164,7 @@
|
|
|
140
164
|
import VolumeIndicator from './VolumeIndicator.vue'
|
|
141
165
|
|
|
142
166
|
import { defineProps, defineModel, computed, ref, toRefs, onMounted, watch, watchEffect } from 'vue'
|
|
143
|
-
import {
|
|
167
|
+
import { useIntervalFn, useEventListener } from "@vueuse/core"
|
|
144
168
|
import { getUserMedia as getUserMediaNative, getDisplayMedia as getDisplayMediaNative, isUserMediaPermitted }
|
|
145
169
|
from "./userMedia.js"
|
|
146
170
|
import MicrophoneButton from './MicrophoneButton.vue'
|
|
@@ -163,9 +187,13 @@
|
|
|
163
187
|
constraints: {
|
|
164
188
|
type: Object,
|
|
165
189
|
default: () => ({})
|
|
190
|
+
},
|
|
191
|
+
retryMediaOnError: {
|
|
192
|
+
type: Boolean,
|
|
193
|
+
default: false
|
|
166
194
|
}
|
|
167
195
|
})
|
|
168
|
-
const { audioInputRequest, audioOutputRequest, videoInputRequest, constraints } = toRefs(props)
|
|
196
|
+
const { audioInputRequest, audioOutputRequest, videoInputRequest, constraints, retryMediaOnError } = toRefs(props)
|
|
169
197
|
|
|
170
198
|
const model = defineModel({
|
|
171
199
|
required: true,
|
|
@@ -186,7 +214,13 @@
|
|
|
186
214
|
videoMuted: {
|
|
187
215
|
type: Boolean
|
|
188
216
|
},
|
|
189
|
-
|
|
217
|
+
media: {
|
|
218
|
+
type: Object
|
|
219
|
+
},
|
|
220
|
+
mediaError: {
|
|
221
|
+
type: Object
|
|
222
|
+
},
|
|
223
|
+
cameraAccessError: {
|
|
190
224
|
type: Object
|
|
191
225
|
}
|
|
192
226
|
}
|
|
@@ -261,32 +295,97 @@
|
|
|
261
295
|
}, { immediate: true })
|
|
262
296
|
|
|
263
297
|
const userMedia = ref(null)
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
userMedia.value = null
|
|
269
|
-
await new Promise(resolve => setTimeout(resolve, 100))
|
|
270
|
-
}
|
|
271
|
-
const constraints = selectedConstraints.value
|
|
272
|
-
const videoAllowed = videoInputRequest.value !== 'none' && constraints.video
|
|
273
|
-
const audioAllowed = audioInputRequest.value !== 'none' && constraints.audio
|
|
274
|
-
if(!videoAllowed && !audioAllowed) {
|
|
275
|
-
console.log("USER MEDIA NOT ALLOWED")
|
|
276
|
-
return
|
|
277
|
-
}
|
|
278
|
-
console.log("TRY GET USER MEDIA", JSON.stringify(constraints, null, 2))
|
|
298
|
+
let gettingUserMedia = false
|
|
299
|
+
async function updateUserMedia(retry = false) {
|
|
300
|
+
if(gettingUserMedia) return
|
|
301
|
+
gettingUserMedia = true
|
|
279
302
|
try {
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
303
|
+
if(userMedia.value && !retry) {
|
|
304
|
+
console.log("CLOSE USER MEDIA")
|
|
305
|
+
userMedia.value.getTracks().forEach(track => track.stop())
|
|
306
|
+
userMedia.value = null
|
|
307
|
+
await new Promise(resolve => setTimeout(resolve, 100))
|
|
308
|
+
}
|
|
309
|
+
const constraints = selectedConstraints.value
|
|
310
|
+
const videoAllowed = videoInputRequest.value !== 'none' && constraints.video
|
|
311
|
+
const audioAllowed = audioInputRequest.value !== 'none' && constraints.audio
|
|
312
|
+
if(!videoAllowed && !audioAllowed) {
|
|
313
|
+
console.log("USER MEDIA NOT ALLOWED")
|
|
314
|
+
return
|
|
315
|
+
}
|
|
316
|
+
console.log("TRY GET USER MEDIA", JSON.stringify(constraints, null, 2))
|
|
317
|
+
try {
|
|
318
|
+
console.log("GET USER MEDIA")
|
|
319
|
+
const mediaStream = await getUserMediaNative(constraints)
|
|
320
|
+
/* if(userMedia.value && retry) {
|
|
321
|
+
console.log("CLOSE USER MEDIA")
|
|
322
|
+
userMedia.value.getTracks().forEach(track => track.stop())
|
|
323
|
+
userMedia.value = null
|
|
324
|
+
await new Promise(resolve => setTimeout(resolve, 100))
|
|
325
|
+
}*/
|
|
326
|
+
console.log("Got User Media", mediaStream)
|
|
327
|
+
userMedia.value = mediaStream
|
|
328
|
+
if(model.value.cameraAccessError || model.value.mediaError) {
|
|
329
|
+
model.value = {
|
|
330
|
+
...model.value,
|
|
331
|
+
cameraAccessError: null,
|
|
332
|
+
mediaError: null
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
} catch(error) {
|
|
336
|
+
console.error("Failed to get user media", error)
|
|
337
|
+
if(error.name === 'NotReadableError') {
|
|
338
|
+
const isCameraRelated = error.message.includes('video') || error.message.includes('camera')
|
|
339
|
+
if(isCameraRelated) {
|
|
340
|
+
model.value = {
|
|
341
|
+
...model.value,
|
|
342
|
+
cameraAccessError: error
|
|
343
|
+
}
|
|
344
|
+
console.log("RE", retry)
|
|
345
|
+
if(retry) return
|
|
346
|
+
try {
|
|
347
|
+
console.log("GET USER MEDIA 2")
|
|
348
|
+
const mediaStream = await getUserMediaNative({
|
|
349
|
+
...constraints,
|
|
350
|
+
video: false
|
|
351
|
+
})
|
|
352
|
+
console.log("Got User Media 2", mediaStream)
|
|
353
|
+
userMedia.value = mediaStream
|
|
354
|
+
} catch(error) {
|
|
355
|
+
model.value = {
|
|
356
|
+
...model.value,
|
|
357
|
+
mediaError: error,
|
|
358
|
+
cameraAccessError: null
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
} else {
|
|
362
|
+
model.value = {
|
|
363
|
+
...model.value,
|
|
364
|
+
mediaError: error
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
} else if(error.name === 'PermissionDeniedError') {
|
|
368
|
+
showPermissionsDialog()
|
|
369
|
+
} else {
|
|
370
|
+
model.value = {
|
|
371
|
+
...model.value,
|
|
372
|
+
mediaError: error
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
} finally {
|
|
377
|
+
gettingUserMedia = false
|
|
286
378
|
}
|
|
287
379
|
}
|
|
288
380
|
|
|
289
|
-
watch(() => selectedConstraints.value, updateUserMedia, { immediate: true })
|
|
381
|
+
watch(() => selectedConstraints.value, () => updateUserMedia(), { immediate: true })
|
|
382
|
+
|
|
383
|
+
useIntervalFn(() => {
|
|
384
|
+
if(!retryMediaOnError.value) return
|
|
385
|
+
if(!model.value.cameraAccessError) return
|
|
386
|
+
console.log("RETRY CAMERA ACCESS!")
|
|
387
|
+
updateUserMedia(true)
|
|
388
|
+
}, 1000)
|
|
290
389
|
|
|
291
390
|
const userMediaMuted = true
|
|
292
391
|
|
|
@@ -1,68 +1,69 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<
|
|
3
|
-
<
|
|
4
|
-
<
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
2
|
+
<div>
|
|
3
|
+
<OverlayPanel ref="mediaSettingsOverlay">
|
|
4
|
+
<div class="flex flex-column gap-2 pt-2 justify-content-around" style="min-width: 20rem; max-width: 90vw">
|
|
5
|
+
<div v-if="audioInputRequest !== 'none' && audioInputs.length > 0"
|
|
6
|
+
class="flex flex-column align-items-stretch flex-grow-1">
|
|
7
|
+
<div class="text-sm mb-1 pl-1">Microphone</div>
|
|
8
|
+
<Dropdown :modelValue="model.audioInput"
|
|
9
|
+
@update:modelValue="value => updateAudioInput(value)"
|
|
10
|
+
:options="audioInputs"
|
|
11
|
+
optionLabel="label"
|
|
12
|
+
placeholder="Select">
|
|
13
|
+
<template #value="slotProps">
|
|
14
|
+
<div class="flex flex-row align-items-center">
|
|
15
|
+
<i class="pi pi-microphone mr-2" />
|
|
16
|
+
|
|
17
|
+
<div class="absolute overflow-hidden text-overflow-ellipsis" style="left: 2em; right: 2em;">
|
|
18
|
+
{{ slotProps.value ? slotProps.value.label : slotProps.placeholder }}
|
|
19
|
+
</div>
|
|
19
20
|
</div>
|
|
20
|
-
</
|
|
21
|
-
</
|
|
22
|
-
</
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
21
|
+
</template>
|
|
22
|
+
</Dropdown>
|
|
23
|
+
</div>
|
|
24
|
+
<div v-if="audioOutputRequest !== 'none' && audioOutputs.length > 0"
|
|
25
|
+
class="flex flex-column align-items-stretch flex-grow-1">
|
|
26
|
+
<div class="text-sm mb-1 pl-1">Audio output</div>
|
|
27
|
+
<Dropdown :modelValue="model.audioOutput"
|
|
28
|
+
@update:modelValue="value => updateAudioOutput(value)"
|
|
29
|
+
:options="audioOutputs" optionLabel="label"
|
|
30
|
+
placeholder="Select">
|
|
31
|
+
<template #value="slotProps">
|
|
32
|
+
<div class="flex flex-row align-items-center">
|
|
33
|
+
<i class="pi pi-volume-up mr-2" />
|
|
34
|
+
|
|
35
|
+
<div class="absolute overflow-hidden text-overflow-ellipsis" style="left: 2em; right: 2em;">
|
|
36
|
+
{{ slotProps.value ? slotProps.value.label : slotProps.placeholder }}
|
|
37
|
+
</div>
|
|
37
38
|
</div>
|
|
38
|
-
</
|
|
39
|
-
</
|
|
40
|
-
</
|
|
41
|
-
</div>
|
|
39
|
+
</template>
|
|
40
|
+
</Dropdown>
|
|
41
|
+
</div>
|
|
42
42
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
43
|
+
<div v-if="videoInputRequest !== 'none' && videoInputs.length > 0"
|
|
44
|
+
class="flex flex-column align-items-stretch flex-grow-1">
|
|
45
|
+
<div class="text-sm mb-1 pl-1">Camera</div>
|
|
46
|
+
<Dropdown :modelValue="model.videoInput"
|
|
47
|
+
@update:modelValue="value => updateVideoInput(value)"
|
|
48
|
+
:options="videoInputs" optionLabel="label"
|
|
49
|
+
placeholder="Select">
|
|
50
|
+
<template #value="slotProps">
|
|
51
|
+
<div class="flex flex-row align-items-center">
|
|
52
|
+
<i class="pi pi-camera mr-2" />
|
|
53
|
+
|
|
54
|
+
<div class="absolute overflow-hidden text-overflow-ellipsis" style="left: 2em; right: 2em;">
|
|
55
|
+
{{ slotProps.value ? slotProps.value.label : slotProps.placeholder }}
|
|
56
|
+
</div>
|
|
56
57
|
</div>
|
|
57
|
-
</
|
|
58
|
-
</
|
|
59
|
-
</
|
|
58
|
+
</template>
|
|
59
|
+
</Dropdown>
|
|
60
|
+
</div>
|
|
60
61
|
</div>
|
|
61
|
-
</
|
|
62
|
-
</OverlayPanel>
|
|
62
|
+
</OverlayPanel>
|
|
63
63
|
|
|
64
|
-
|
|
65
|
-
|
|
64
|
+
<Button @click="toggleMediaSettings" raised
|
|
65
|
+
icon="bx bx-cog" severity="" rounded v-ripple />
|
|
66
|
+
</div>
|
|
66
67
|
</template>
|
|
67
68
|
|
|
68
69
|
<script setup>
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@live-change/peer-connection-frontend",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.82",
|
|
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,19 +22,19 @@
|
|
|
22
22
|
},
|
|
23
23
|
"type": "module",
|
|
24
24
|
"dependencies": {
|
|
25
|
-
"@live-change/cli": "^0.8.
|
|
26
|
-
"@live-change/dao": "^0.8.
|
|
27
|
-
"@live-change/dao-vue3": "^0.8.
|
|
28
|
-
"@live-change/dao-websocket": "^0.8.
|
|
29
|
-
"@live-change/framework": "^0.8.
|
|
30
|
-
"@live-change/password-authentication-service": "^0.8.
|
|
31
|
-
"@live-change/secret-code-service": "^0.8.
|
|
32
|
-
"@live-change/secret-link-service": "^0.8.
|
|
33
|
-
"@live-change/session-service": "^0.8.
|
|
34
|
-
"@live-change/user-frontend": "^0.8.
|
|
35
|
-
"@live-change/user-service": "^0.8.
|
|
36
|
-
"@live-change/vue3-components": "^0.8.
|
|
37
|
-
"@live-change/vue3-ssr": "^0.8.
|
|
25
|
+
"@live-change/cli": "^0.8.82",
|
|
26
|
+
"@live-change/dao": "^0.8.82",
|
|
27
|
+
"@live-change/dao-vue3": "^0.8.82",
|
|
28
|
+
"@live-change/dao-websocket": "^0.8.82",
|
|
29
|
+
"@live-change/framework": "^0.8.82",
|
|
30
|
+
"@live-change/password-authentication-service": "^0.8.82",
|
|
31
|
+
"@live-change/secret-code-service": "^0.8.82",
|
|
32
|
+
"@live-change/secret-link-service": "^0.8.82",
|
|
33
|
+
"@live-change/session-service": "^0.8.82",
|
|
34
|
+
"@live-change/user-frontend": "^0.8.82",
|
|
35
|
+
"@live-change/user-service": "^0.8.82",
|
|
36
|
+
"@live-change/vue3-components": "^0.8.82",
|
|
37
|
+
"@live-change/vue3-ssr": "^0.8.82",
|
|
38
38
|
"@vueuse/core": "^10.11.0",
|
|
39
39
|
"boxicons": "^2.1.4",
|
|
40
40
|
"codeceptjs-assert": "^0.0.5",
|
|
@@ -54,7 +54,7 @@
|
|
|
54
54
|
"vue3-scroll-border": "0.1.6"
|
|
55
55
|
},
|
|
56
56
|
"devDependencies": {
|
|
57
|
-
"@live-change/codeceptjs-helper": "^0.8.
|
|
57
|
+
"@live-change/codeceptjs-helper": "^0.8.82",
|
|
58
58
|
"codeceptjs": "^3.6.5",
|
|
59
59
|
"generate-password": "1.7.1",
|
|
60
60
|
"playwright": "^1.41.2",
|
|
@@ -65,5 +65,5 @@
|
|
|
65
65
|
"author": "Michał Łaszczewski <michal@laszczewski.pl>",
|
|
66
66
|
"license": "BSD-3-Clause",
|
|
67
67
|
"description": "",
|
|
68
|
-
"gitHead": "
|
|
68
|
+
"gitHead": "72f303118f6bb5e324c5d77473b456fc10f7ed04"
|
|
69
69
|
}
|