@live-change/peer-connection-frontend 0.8.80 → 0.8.81

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 { useInterval, useEventListener } from "@vueuse/core"
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
- userMedia: {
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,98 @@
261
295
  }, { immediate: true })
262
296
 
263
297
  const userMedia = ref(null)
264
- async function updateUserMedia() {
265
- if(userMedia.value) {
266
- console.log("CLOSE USER MEDIA")
267
- userMedia.value.getTracks().forEach(track => track.stop())
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
- console.log("GET USER MEDIA")
281
- const mediaStream = await getUserMediaNative(constraints)
282
- console.log("Got User Media", mediaStream)
283
- userMedia.value = mediaStream
284
- } catch(e) {
285
- console.error("Failed to get user media", e)
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
+ console.log("RETRY MEDIA ON ERROR", model.value.cameraAccessError, retryMediaOnError.value)
385
+ if(!retryMediaOnError.value) return
386
+ if(!model.value.cameraAccessError) return
387
+ console.log("RETRY CAMERA ACCESS!")
388
+ updateUserMedia(true)
389
+ }, 1000)
290
390
 
291
391
  const userMediaMuted = true
292
392
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@live-change/peer-connection-frontend",
3
- "version": "0.8.80",
3
+ "version": "0.8.81",
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.80",
26
- "@live-change/dao": "^0.8.80",
27
- "@live-change/dao-vue3": "^0.8.80",
28
- "@live-change/dao-websocket": "^0.8.80",
29
- "@live-change/framework": "^0.8.80",
30
- "@live-change/password-authentication-service": "^0.8.80",
31
- "@live-change/secret-code-service": "^0.8.80",
32
- "@live-change/secret-link-service": "^0.8.80",
33
- "@live-change/session-service": "^0.8.80",
34
- "@live-change/user-frontend": "^0.8.80",
35
- "@live-change/user-service": "^0.8.80",
36
- "@live-change/vue3-components": "^0.8.80",
37
- "@live-change/vue3-ssr": "^0.8.80",
25
+ "@live-change/cli": "^0.8.81",
26
+ "@live-change/dao": "^0.8.81",
27
+ "@live-change/dao-vue3": "^0.8.81",
28
+ "@live-change/dao-websocket": "^0.8.81",
29
+ "@live-change/framework": "^0.8.81",
30
+ "@live-change/password-authentication-service": "^0.8.81",
31
+ "@live-change/secret-code-service": "^0.8.81",
32
+ "@live-change/secret-link-service": "^0.8.81",
33
+ "@live-change/session-service": "^0.8.81",
34
+ "@live-change/user-frontend": "^0.8.81",
35
+ "@live-change/user-service": "^0.8.81",
36
+ "@live-change/vue3-components": "^0.8.81",
37
+ "@live-change/vue3-ssr": "^0.8.81",
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.80",
57
+ "@live-change/codeceptjs-helper": "^0.8.81",
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": "06b8c7071b68ee297239912e0f63890648a3b124"
68
+ "gitHead": "bbc862b339eae9c68cb4b06cad0abdbb0405b409"
69
69
  }