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

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.
@@ -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
+ }
package/front/src/App.vue CHANGED
@@ -1,85 +1,50 @@
1
1
  <template>
2
- <router-view v-slot="{ route, Component }">
3
- <template v-if="route?.meta?.raw">
4
- <component :is="Component" />
2
+ <view-root>
3
+ <template #navbar>
4
+ <NavBar />
5
5
  </template>
6
- <loading-zone v-else suspense @isLoading="l => loading = l">
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/primevue.min.css'
34
- import 'primevue/resources/themes/vela-orange/theme.css'
35
- import 'primeflex/primeflex.css'
36
- import 'primeicons/primeicons.css'
37
-
38
- import ProgressSpinner from 'primevue/progressspinner'
39
-
40
- import Page from "./Page.vue"
41
-
42
- import { computed } from 'vue'
43
- import { useHead } from '@vueuse/head'
44
- useHead(computed(() => ({
45
- title: 'Title',
46
- meta: [
47
- { charset: 'utf-8' },
48
- { name: 'viewport',
49
- content: "user-scalable=no, initial-scale=1, maximum-scale=1, minimum-scale=1," +
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
- htmlAttrs: {
53
- lang: 'en',
54
- amp: true
55
- }
56
- })))
57
-
58
- import { ref } from 'vue'
59
- const working = ref(false)
60
- const loading = ref(false)
61
-
62
- import { watch } from 'vue'
63
- import { client as useClient } from '@live-change/vue3-ssr'
64
- const client = useClient()
65
- watch(client, (newClient, oldClient) => {
66
- console.log("WATCH CLIENT", oldClient, '=>', newClient)
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="buttons">
13
- <button type="button" role="button" class="button" @click="() => peer.setOnline(true)">Set Online</button>
14
- <button type="button" role="button" class="button" @click="() => peer.setOnline(false)">Set Offline</button>
15
- <button type="button" role="button" class="button" @click="sendTestMessage">Test Message</button>
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
- <!--<div>
25
- <h2>Devices</h2>
26
- <pre>{{ JSON.stringify(devices, null, " ") }}</pre>
27
- </div>-->
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
- <Dropdown v-if="videoInputDevices && videoInputDevices.length>0"
33
- v-model="selectedVideoInput"
34
- :options="videoInputDevices"
35
- :optionLabel="option => option ? (option.label || 'unknown') : 'Browser default'"
36
- :placeholder="'Select video device...'">
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="buttons" v-if="!displayMedia">
68
- <button class="button" @click="getDisplayMedia">getDisplayMedia</button>
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
- <div class="buttons" v-if="displayMedia">
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 devices = ref([])
144
- const videoInputDevices = computed(() => devices.value.filter(d => d.kind == 'videoinput'))
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
- (userMedia.value ? [userMedia.value] : []).concat(displayMedia.value ? [displayMedia.value] : [])
107
+ ( userMediaEnabled.value ? [selectedDevices.value.media] : []).concat(displayMedia.value ? [displayMedia.value] : [])
158
108
  )
159
-
160
- watch(() => userMediaConstraints.value, async value => {
161
- if(userMedia.value) {
162
- await dropUserMedia()
163
- await getUserMedia()
164
- }
165
- })
166
-
167
- watch(() => userMedia.value, (mediaStream, oldMediaStream) => {
168
- console.log("USER MEDIA STREAM CHANGE:", mediaStream, oldMediaStream)
169
- readDevices()
170
- if(oldMediaStream) {
171
- console.log("OLD MEDIA STREAM", oldMediaStream)
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 == 'live') track.stop() })
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 == remoteTrack.stream)) continue
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
- localMediaStreams
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 showPermissionsModal() {
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 = true
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
- this.displayMedia = null
231
+ displayMedia.value = null
341
232
  }
342
233
 
343
234
 
344
235
  function sendTestMessage() {
345
- for(const connection of this.peer.value.connections) {
236
+ for(const connection of peer.value.connections) {
346
237
  peer.value.sendMessage({
347
238
  to: connection.to,
348
239
  type: "ping",