@live-change/peer-connection-frontend 0.8.34 → 0.8.36
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.
- package/build-stats/ssr-srcentryserverjs-outDir-distserver.html +4842 -0
- package/build-stats/ssrManifest-outDir-distclient.html +4842 -0
- package/front/components.d.ts +20 -0
- package/front/public/images/cameraAccess/en.png +0 -0
- package/front/src/App.vue +42 -77
- package/front/src/components/Debugger.vue +68 -177
- package/front/src/components/DevicesSelect.vue +393 -0
- package/front/src/components/Peer.js +167 -252
- package/front/src/components/PeerConnection.js +296 -312
- package/front/src/components/PermissionsDialog.vue +146 -0
- package/front/src/components/mediaStreamsTracks.js +60 -0
- package/front/src/components/userMedia.js +2 -2
- package/front/src/entry-client.js +4 -22
- package/front/src/entry-server.js +5 -4
- package/front/src/router.js +18 -12
- package/front/vite.config.js +8 -107
- package/index.js +14 -0
- package/package-deps.json +41 -0
- package/package.json +21 -20
- package/server/app.config.js +114 -0
- package/server/init.js +10 -2
- package/server/security.config.js +53 -0
- package/server/services.list.js +50 -0
- package/server/start.js +37 -0
- package/server/services.config.js +0 -25
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/* eslint-disable */
|
|
2
|
+
// @ts-nocheck
|
|
3
|
+
// Generated by unplugin-vue-components
|
|
4
|
+
// Read more: https://github.com/vuejs/core/pull/3399
|
|
5
|
+
export {}
|
|
6
|
+
|
|
7
|
+
/* prettier-ignore */
|
|
8
|
+
declare module 'vue' {
|
|
9
|
+
export interface GlobalComponents {
|
|
10
|
+
Button: typeof import('primevue/button')['default']
|
|
11
|
+
Debugger: typeof import('./src/components/Debugger.vue')['default']
|
|
12
|
+
DevicesSelect: typeof import('./src/components/DevicesSelect.vue')['default']
|
|
13
|
+
Dialog: typeof import('primevue/dialog')['default']
|
|
14
|
+
Dropdown: typeof import('primevue/dropdown')['default']
|
|
15
|
+
InputSwitch: typeof import('primevue/inputswitch')['default']
|
|
16
|
+
PermissionsDialog: typeof import('./src/components/PermissionsDialog.vue')['default']
|
|
17
|
+
RouterLink: typeof import('vue-router')['RouterLink']
|
|
18
|
+
RouterView: typeof import('vue-router')['RouterView']
|
|
19
|
+
}
|
|
20
|
+
}
|
|
Binary file
|
package/front/src/App.vue
CHANGED
|
@@ -1,85 +1,50 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<
|
|
3
|
-
<template
|
|
4
|
-
<
|
|
2
|
+
<view-root>
|
|
3
|
+
<template #navbar>
|
|
4
|
+
<NavBar />
|
|
5
5
|
</template>
|
|
6
|
-
|
|
7
|
-
<template v-slot:loading>
|
|
8
|
-
<div class="fixed w-full h-full flex align-items-center justify-content-center top-0 left-0">
|
|
9
|
-
<ProgressSpinner animationDuration=".5s"/>
|
|
10
|
-
</div>
|
|
11
|
-
</template>
|
|
12
|
-
<template v-slot:default="{ isLoading }">
|
|
13
|
-
<page :loading="loading" :working="working">
|
|
14
|
-
<working-zone @isWorking="w => working = w">
|
|
15
|
-
<template v-slot:working>
|
|
16
|
-
<div class="fixed w-full h-full flex align-items-center justify-content-center top-0 left-0">
|
|
17
|
-
<ProgressSpinner animationDuration=".5s"/>
|
|
18
|
-
</div>
|
|
19
|
-
</template>
|
|
20
|
-
<template v-slot:default="{ isWorking }">
|
|
21
|
-
<component :is="Component"
|
|
22
|
-
:style="isWorking || isLoading ? 'filter: blur(4px)' : ''"
|
|
23
|
-
class="working-blur" />
|
|
24
|
-
</template>
|
|
25
|
-
</working-zone>
|
|
26
|
-
</page>
|
|
27
|
-
</template>
|
|
28
|
-
</loading-zone>
|
|
29
|
-
</router-view>
|
|
6
|
+
</view-root>
|
|
30
7
|
</template>
|
|
31
8
|
|
|
32
9
|
<script setup>
|
|
33
|
-
import 'primevue/resources/
|
|
34
|
-
|
|
35
|
-
import '
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
import
|
|
41
|
-
|
|
42
|
-
import { computed } from 'vue'
|
|
43
|
-
import { useHead } from '@vueuse/head'
|
|
44
|
-
useHead(computed(() => ({
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
10
|
+
import 'primevue/resources/themes/lara-light-blue/theme.css'
|
|
11
|
+
|
|
12
|
+
import { useLocale } from '@live-change/vue3-components'
|
|
13
|
+
const locale = useLocale()
|
|
14
|
+
locale.captureLocale()
|
|
15
|
+
|
|
16
|
+
import NavBar from "./NavBar.vue"
|
|
17
|
+
import ViewRoot from "@live-change/frontend-base/ViewRoot.vue"
|
|
18
|
+
|
|
19
|
+
import { computed } from 'vue'
|
|
20
|
+
import { useHead } from '@vueuse/head'
|
|
21
|
+
useHead(computed(() => ({
|
|
22
|
+
title: 'Title',
|
|
23
|
+
meta: [
|
|
24
|
+
{ charset: 'utf-8' },
|
|
25
|
+
{ name: 'viewport',
|
|
26
|
+
content: "user-scalable=no, initial-scale=1, maximum-scale=1, minimum-scale=1," +
|
|
50
27
|
" width=device-width, viewport-fit=cover" }
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
})))
|
|
57
|
-
|
|
58
|
-
import {
|
|
59
|
-
|
|
60
|
-
const
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
28
|
+
],
|
|
29
|
+
htmlAttrs: {
|
|
30
|
+
lang: 'en',
|
|
31
|
+
amp: true
|
|
32
|
+
}
|
|
33
|
+
})))
|
|
34
|
+
|
|
35
|
+
import { watch } from 'vue'
|
|
36
|
+
import { client as useClient, useApi } from '@live-change/vue3-ssr'
|
|
37
|
+
const client = useClient()
|
|
38
|
+
watch(client, (newClient, oldClient) => {
|
|
39
|
+
console.log("WATCH CLIENT", oldClient, '=>', newClient)
|
|
40
|
+
})
|
|
41
|
+
|
|
42
|
+
const api = useApi()
|
|
43
|
+
import emailValidator from "@live-change/email-service/clientEmailValidator.js"
|
|
44
|
+
import phoneValidator from "@live-change/phone-service/clientPhoneValidator.js"
|
|
45
|
+
import passwordValidator from "@live-change/password-authentication-service/clientPasswordValidator.js"
|
|
46
|
+
api.validators.email = emailValidator
|
|
47
|
+
api.validators.phone = phoneValidator
|
|
48
|
+
api.validators.password = passwordValidator
|
|
68
49
|
|
|
69
50
|
</script>
|
|
70
|
-
|
|
71
|
-
<style>
|
|
72
|
-
body {
|
|
73
|
-
margin: 0;
|
|
74
|
-
height: 100%;
|
|
75
|
-
overflow-x: hidden;
|
|
76
|
-
overflow-y: auto;
|
|
77
|
-
background-color: var(--surface-a);
|
|
78
|
-
font-family: var(--font-family);
|
|
79
|
-
font-weight: 400;
|
|
80
|
-
color: var(--text-color);
|
|
81
|
-
}
|
|
82
|
-
.working-blur {
|
|
83
|
-
transition: filter 0.3s;
|
|
84
|
-
}
|
|
85
|
-
</style>
|
|
@@ -9,107 +9,62 @@
|
|
|
9
9
|
<div v-if="peer">
|
|
10
10
|
<h2>Peer connection</h2>
|
|
11
11
|
<pre>{{ JSON.stringify(peer.summary, null, " ") }}</pre>
|
|
12
|
-
<div class="
|
|
13
|
-
<
|
|
14
|
-
|
|
15
|
-
|
|
12
|
+
<div class="flex justify-content-between align-items-center">
|
|
13
|
+
<div class="flex align-items-center">
|
|
14
|
+
<InputSwitch v-model="peer.online" />
|
|
15
|
+
<div class="ml-3">Peer online</div>
|
|
16
|
+
</div>
|
|
17
|
+
<Button @click="sendTestMessage">Test Message</Button>
|
|
16
18
|
</div>
|
|
17
19
|
</div>
|
|
18
20
|
<div v-for="remoteStream in remoteStreams">
|
|
19
21
|
<h2>Remote stream {{ remoteStream.stream.id }} from {{ remoteStream.from }}</h2>
|
|
20
|
-
<video autoplay playsinline :src-object.prop.camel="remoteStream.stream">
|
|
22
|
+
<video autoplay playsinline :src-object.prop.camel="remoteStream.stream" class="w-full">
|
|
21
23
|
</video>
|
|
22
24
|
</div>
|
|
23
25
|
|
|
24
|
-
|
|
25
|
-
<h2>
|
|
26
|
-
<
|
|
27
|
-
|
|
26
|
+
<div class="my-2">
|
|
27
|
+
<h2>Local tracks</h2>
|
|
28
|
+
<div v-for="(track, index) in (localTracks ?? [])">
|
|
29
|
+
Track #{{ index }} {{ track.track.kind }} ({{ track.track.label }}) enabled: {{ track.enabled }}
|
|
30
|
+
id: {{ track.track.id }}
|
|
31
|
+
<div class="buttons">
|
|
32
|
+
<button type="button" class="button" v-if="!track.enabled"
|
|
33
|
+
@click="() => peer.setTrackEnabled(track, true)">
|
|
34
|
+
Enable Track
|
|
35
|
+
</button>
|
|
36
|
+
<button type="button" class="button" v-if="track.enabled"
|
|
37
|
+
@click="() => peer.setTrackEnabled(track, false)">
|
|
38
|
+
Disable Track
|
|
39
|
+
</button>
|
|
40
|
+
</div>
|
|
41
|
+
</div>
|
|
42
|
+
</div>
|
|
28
43
|
|
|
29
44
|
<div>
|
|
30
45
|
<h2>User media</h2>
|
|
31
|
-
|
|
32
|
-
<
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
</Dropdown>
|
|
38
|
-
<Dropdown v-if="audioInputDevices && audioInputDevices.length>0"
|
|
39
|
-
v-model="selectedAudioInput"
|
|
40
|
-
:options="audioInputDevices"
|
|
41
|
-
:optionLabel="option => option ? (option.label || 'unknown') : 'Browser default'"
|
|
42
|
-
:placeholder="'Select audio device...'">
|
|
43
|
-
</Dropdown>
|
|
44
|
-
|
|
45
|
-
<div class="buttons" v-if="!userMedia">
|
|
46
|
-
<button class="button" @click="getUserMedia">getUserMedia</button>
|
|
47
|
-
</div>
|
|
48
|
-
<div class="buttons" v-if="userMedia">
|
|
49
|
-
<button class="button" @click="dropUserMedia">drop UserMedia</button>
|
|
50
|
-
<button v-if="userMediaMuted" type="button" class="button" @click="() => userMediaMuted = false">
|
|
51
|
-
Unmute user media
|
|
52
|
-
</button>
|
|
53
|
-
<button v-if="!userMediaMuted" type="button" class="button" @click="() => userMediaMuted = true">
|
|
54
|
-
Mute user media
|
|
55
|
-
</button>
|
|
46
|
+
<DevicesSelect v-model="selectedDevices" />
|
|
47
|
+
<hr>
|
|
48
|
+
<pre>{{ selectedDevices }}</pre>
|
|
49
|
+
<div class="mt-1 mb-3 flex align-items-center">
|
|
50
|
+
<InputSwitch v-model="userMediaEnabled" />
|
|
51
|
+
<div class="ml-3">User media stream enabled</div>
|
|
56
52
|
</div>
|
|
57
|
-
<video v-if="userMedia" autoplay playsinline :muted="userMediaMuted"
|
|
58
|
-
:src-object.prop.camel="userMedia">
|
|
59
|
-
</video>
|
|
60
53
|
</div>
|
|
61
54
|
|
|
62
|
-
|
|
63
|
-
|
|
64
55
|
<div>
|
|
65
56
|
<h2>Display media</h2>
|
|
66
57
|
|
|
67
|
-
<div class="
|
|
68
|
-
<
|
|
58
|
+
<div class="justify-content-between" v-if="!displayMedia">
|
|
59
|
+
<Button v-if="!displayMedia" @click="getDisplayMedia">getDisplayMedia</Button>
|
|
60
|
+
<Button v-if="displayMedia" @click="dropDisplayMedia">drop DisplayMedia</Button>
|
|
69
61
|
</div>
|
|
70
|
-
<
|
|
71
|
-
<button class="button" @click="dropDisplayMedia">drop DisplayMedia</button>
|
|
72
|
-
</div>
|
|
73
|
-
<video v-if="displayMedia" autoplay playsinline muted
|
|
62
|
+
<video class="mt-2 w-full" v-if="displayMedia" autoplay playsinline muted
|
|
74
63
|
:src-object.prop.camel="displayMedia">
|
|
75
64
|
</video>
|
|
76
65
|
</div>
|
|
77
66
|
|
|
78
|
-
<div v-for="(track, index) in (peer ? peer.localTracks : [])">
|
|
79
|
-
Track #{{ index }} {{ track.track.kind }} ({{ track.track.label }}) enabled: {{ track.enabled }}
|
|
80
|
-
id: {{ track.track.id }}
|
|
81
|
-
<div class="buttons">
|
|
82
|
-
<button type="button" class="button" v-if="!track.enabled"
|
|
83
|
-
@click="() => peer.setTrackEnabled(track, true)">
|
|
84
|
-
Enable Track
|
|
85
|
-
</button>
|
|
86
|
-
<button type="button" class="button" v-if="track.enabled"
|
|
87
|
-
@click="() => peer.setTrackEnabled(track, false)">
|
|
88
|
-
Disable Track
|
|
89
|
-
</button>
|
|
90
|
-
</div>
|
|
91
|
-
</div>
|
|
92
67
|
|
|
93
|
-
<Dialog header="Permissions" v-model:visible="permissionsDialog" modal>
|
|
94
|
-
|
|
95
|
-
</Dialog>
|
|
96
|
-
|
|
97
|
-
<Dialog header="Connect camera" v-model:visible="connectDeviceDialog" modal>
|
|
98
|
-
<template #header>
|
|
99
|
-
<h3>Connect camera and microphone</h3>
|
|
100
|
-
</template>
|
|
101
|
-
|
|
102
|
-
<template #footer>
|
|
103
|
-
<Button @click="connectDeviceCallbacks.connected()"
|
|
104
|
-
label="Ok, connected" icon="pi pi-check" class="p-button-success" autofocus />
|
|
105
|
-
<Button @click="connectDeviceCallbacks.camera()"
|
|
106
|
-
label="Use only camera" icon="pi pi-video" class="p-button-warning" />
|
|
107
|
-
<Button @click="connectDeviceCallbacks.microphone()"
|
|
108
|
-
label="Use only microphone" icon="pi pi-volume-up" class="p-button-warning" />
|
|
109
|
-
<Button @click="connectDeviceCallbacks.cancel()"
|
|
110
|
-
label="Cancel" icon="pi pi-times" class="p-button-danger" />
|
|
111
|
-
</template>
|
|
112
|
-
</Dialog>
|
|
113
68
|
</div>
|
|
114
69
|
</template>
|
|
115
70
|
|
|
@@ -117,14 +72,19 @@
|
|
|
117
72
|
import Button from "primevue/button"
|
|
118
73
|
import Dropdown from "primevue/dropdown"
|
|
119
74
|
import Dialog from "primevue/dialog"
|
|
75
|
+
import PermissionsDialog from './PermissionsDialog.vue'
|
|
76
|
+
import DevicesSelect from './DevicesSelect.vue'
|
|
120
77
|
|
|
121
|
-
import { ref, computed, watch, onMounted } from 'vue'
|
|
78
|
+
import { ref, unref, computed, watch, onMounted, onUnmounted, getCurrentInstance } from 'vue'
|
|
122
79
|
import { path, live, actions, api as useApi } from '@live-change/vue3-ssr'
|
|
123
80
|
const api = useApi()
|
|
124
81
|
|
|
82
|
+
const appContext = (typeof window != 'undefined') && getCurrentInstance()?.appContext
|
|
83
|
+
|
|
125
84
|
import { createPeer } from "./Peer.js"
|
|
126
85
|
import { getUserMedia as getUserMediaNative, getDisplayMedia as getDisplayMediaNative, isUserMediaPermitted }
|
|
127
86
|
from "./userMedia.js"
|
|
87
|
+
import { mediaStreamsTracks } from './mediaStreamsTracks.js'
|
|
128
88
|
|
|
129
89
|
const { channelType, channel } = defineProps({
|
|
130
90
|
channelType: {
|
|
@@ -140,38 +100,25 @@
|
|
|
140
100
|
const isMounted = ref(false)
|
|
141
101
|
onMounted( () => isMounted.value = true )
|
|
142
102
|
|
|
143
|
-
const
|
|
144
|
-
const
|
|
145
|
-
const audioInputDevices = computed(() => devices.value.filter(d => d.kind == 'audioinput'))
|
|
146
|
-
|
|
147
|
-
const selectedVideoInput = ref(null)
|
|
148
|
-
const selectedAudioInput = ref(null)
|
|
149
|
-
const userMediaConstraints = computed(() => ({
|
|
150
|
-
video: selectedVideoInput.value?.deviceId ? { deviceId: selectedVideoInput.value.deviceId } : true,
|
|
151
|
-
audio: selectedAudioInput.value?.deviceId ? { deviceId: selectedAudioInput.value.deviceId } : true,
|
|
152
|
-
}))
|
|
153
|
-
|
|
154
|
-
const userMedia = ref()
|
|
103
|
+
const selectedDevices = ref({ })
|
|
104
|
+
const userMediaEnabled = ref(false)
|
|
155
105
|
const displayMedia = ref()
|
|
156
106
|
const localMediaStreams = computed(() =>
|
|
157
|
-
(
|
|
107
|
+
( userMediaEnabled.value ? [selectedDevices.value.media] : []).concat(displayMedia.value ? [displayMedia.value] : [])
|
|
158
108
|
)
|
|
159
|
-
|
|
160
|
-
watch(() =>
|
|
161
|
-
if(
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
})
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
console.log("
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
oldMediaStream.getTracks().forEach(track => { if (track.readyState == 'live') track.stop() })
|
|
173
|
-
}
|
|
174
|
-
})
|
|
109
|
+
const localTracks = mediaStreamsTracks(localMediaStreams)
|
|
110
|
+
watch(() => ([selectedDevices.value.audioMuted, selectedDevices.value.media]), ([muted, media]) => {
|
|
111
|
+
if(!media) return
|
|
112
|
+
console.log("UPDATE MUTED", muted, media.getAudioTracks())
|
|
113
|
+
for(const track of media.getAudioTracks()) for(const localTrack of localTracks.value)
|
|
114
|
+
if(localTrack.track === track) localTrack.enabled = !muted
|
|
115
|
+
}, { immediate: true })
|
|
116
|
+
watch(() => ([selectedDevices.value.videoMuted, selectedDevices.value.media]), ([muted, media]) => {
|
|
117
|
+
if(!media) return
|
|
118
|
+
console.log("UPDATE MUTED", muted, media.getVideoTracks())
|
|
119
|
+
for(const track of media.getVideoTracks()) for(const localTrack of localTracks.value)
|
|
120
|
+
if(localTrack.track === track) localTrack.enabled = !muted
|
|
121
|
+
}, { immediate: true })
|
|
175
122
|
|
|
176
123
|
const displayMediaEndedHandler = () => displayMedia.value = null
|
|
177
124
|
watch(() => displayMedia.value, (mediaStream, oldMediaStream) => {
|
|
@@ -181,7 +128,7 @@
|
|
|
181
128
|
if(track) track.removeEventListener('ended', displayMediaEndedHandler)
|
|
182
129
|
|
|
183
130
|
console.log("OLD MEDIA STREAM", oldMediaStream)
|
|
184
|
-
oldMediaStream.getTracks().forEach(track => { if (track.readyState
|
|
131
|
+
oldMediaStream.getTracks().forEach(track => { if (track.readyState === 'live') track.stop() })
|
|
185
132
|
}
|
|
186
133
|
if(mediaStream) {
|
|
187
134
|
const track = mediaStream.getVideoTracks()[0]
|
|
@@ -193,9 +140,9 @@
|
|
|
193
140
|
const remoteStreams = computed(() => {
|
|
194
141
|
if(!peer.value) return []
|
|
195
142
|
let remoteStreams = []
|
|
196
|
-
for(const connection of peer.value.connections) {
|
|
197
|
-
for(const remoteTrack of connection.remoteTracks) {
|
|
198
|
-
if(remoteStreams.find(remoteStream => remoteStream.stream
|
|
143
|
+
for(const connection of unref(peer.value.connections)) {
|
|
144
|
+
for(const remoteTrack of unref(connection.remoteTracks)) {
|
|
145
|
+
if(remoteStreams.find(remoteStream => remoteStream.stream === remoteTrack.stream)) continue
|
|
199
146
|
remoteStreams.push({
|
|
200
147
|
from: connection.to,
|
|
201
148
|
stream: remoteTrack.stream
|
|
@@ -205,85 +152,27 @@
|
|
|
205
152
|
return remoteStreams
|
|
206
153
|
})
|
|
207
154
|
|
|
208
|
-
const userMediaMuted = ref(true)
|
|
209
|
-
|
|
210
|
-
const deviceChangeHandler = () => readDevices()
|
|
211
155
|
onMounted(async () => {
|
|
212
156
|
console.log("MOUNTED!")
|
|
213
157
|
await initPeer()
|
|
214
|
-
console.log(" PEER INITIALIZED!", peer.value)
|
|
215
|
-
readDevices()
|
|
216
|
-
|
|
217
|
-
if(navigator.mediaDevices && navigator.mediaDevices.enumerateDevices) {
|
|
218
|
-
navigator.mediaDevices.addEventListener('devicechange', deviceChangeHandler)
|
|
219
|
-
}
|
|
220
158
|
})
|
|
221
159
|
|
|
222
|
-
|
|
223
|
-
async function readDevices() {
|
|
224
|
-
if(navigator.mediaDevices && navigator.mediaDevices.enumerateDevices) {
|
|
225
|
-
const nativeDevices = await navigator.mediaDevices.enumerateDevices()
|
|
226
|
-
devices.value = nativeDevices.map(({ deviceId, groupId, kind, label }) => ({ deviceId, groupId, kind, label }))
|
|
227
|
-
}
|
|
228
|
-
}
|
|
229
|
-
|
|
230
160
|
let createPeerPromise = null
|
|
231
161
|
async function initPeer() {
|
|
232
162
|
if(createPeerPromise) return createPeerPromise
|
|
233
163
|
createPeerPromise = createPeer({
|
|
234
164
|
channelType, channel,
|
|
235
|
-
|
|
165
|
+
onUnmountedCb: onUnmounted, appContext,
|
|
166
|
+
localTracks,
|
|
236
167
|
})
|
|
237
168
|
peer.value = await createPeerPromise
|
|
238
169
|
createPeerPromise = null
|
|
239
170
|
}
|
|
240
171
|
|
|
241
|
-
async function getUserMedia() { // media stream retrival logic
|
|
242
|
-
let constraints = { ...userMediaConstraints.value } // make a copy
|
|
243
|
-
while(true) {
|
|
244
|
-
try {
|
|
245
|
-
console.log("TRY GET USER MEDIA", constraints)
|
|
246
|
-
const mediaStream = await getUserMediaNative(constraints)
|
|
247
|
-
const videoTracks = mediaStream.getVideoTracks()
|
|
248
|
-
const audioTracks = mediaStream.getAudioTracks()
|
|
249
|
-
console.log('Got stream with constraints:', constraints)
|
|
250
|
-
if(constraints.video) console.log(`Using video device: ${videoTracks[0] && videoTracks[0].label}`)
|
|
251
|
-
if(constraints.audio) console.log(`Using audio device: ${audioTracks[0] && audioTracks[0].label}`)
|
|
252
|
-
this.userMedia = mediaStream
|
|
253
|
-
return;
|
|
254
|
-
} catch(error) {
|
|
255
|
-
console.log("GET USER MEDIA ERROR", error)
|
|
256
|
-
const permitted = await isUserMediaPermitted(constraints)
|
|
257
|
-
if(permitted || error.code == error.NOT_FOUND_ERR) {
|
|
258
|
-
constraints = await askToConnectCamera({ ...userMediaConstraints.value })
|
|
259
|
-
if(!constraints) return
|
|
260
|
-
} else { // if not permitted display dialog
|
|
261
|
-
const permitted = await showPermissionsModal()
|
|
262
|
-
console.log("CAMERA PERMITTED", permitted)
|
|
263
|
-
if(!permitted) constraints.video = false
|
|
264
|
-
if(!(constraints.video || constraints.audio)) {
|
|
265
|
-
constraints = await askToConnectCamera({ ...userMediaConstraints.value })
|
|
266
|
-
if(!constraints) return
|
|
267
|
-
}
|
|
268
|
-
continue // retry get user media with new constraints
|
|
269
|
-
}
|
|
270
|
-
}
|
|
271
|
-
}
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
async function dropUserMedia() {
|
|
275
|
-
this.userMedia = null
|
|
276
|
-
}
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
import { usePermission } from "@vueuse/core"
|
|
280
|
-
const microphonePermission = usePermission('microphone')
|
|
281
|
-
const cameraPermission = usePermission('camera')
|
|
282
|
-
|
|
283
172
|
const permissionsDialog = ref(false)
|
|
284
173
|
const permissionsCallbacks = ref(null)
|
|
285
174
|
|
|
286
|
-
async function
|
|
175
|
+
async function showPermissionsDialog() {
|
|
287
176
|
return new Promise((resolve, reject) => {
|
|
288
177
|
permissionsCallbacks.value = {
|
|
289
178
|
disabled: () => {
|
|
@@ -296,7 +185,9 @@
|
|
|
296
185
|
reject('canceled by user')
|
|
297
186
|
}
|
|
298
187
|
}
|
|
299
|
-
permissionsDialog.value =
|
|
188
|
+
permissionsDialog.value = {
|
|
189
|
+
visible: true
|
|
190
|
+
}
|
|
300
191
|
})
|
|
301
192
|
}
|
|
302
193
|
|
|
@@ -337,12 +228,12 @@
|
|
|
337
228
|
}
|
|
338
229
|
|
|
339
230
|
async function dropDisplayMedia() {
|
|
340
|
-
|
|
231
|
+
displayMedia.value = null
|
|
341
232
|
}
|
|
342
233
|
|
|
343
234
|
|
|
344
235
|
function sendTestMessage() {
|
|
345
|
-
for(const connection of
|
|
236
|
+
for(const connection of peer.value.connections) {
|
|
346
237
|
peer.value.sendMessage({
|
|
347
238
|
to: connection.to,
|
|
348
239
|
type: "ping",
|