Skip to content

Commit 3b4a827

Browse files
committed
WIP
Signed-off-by: Grigorii K. Shartsev <me@shgk.me>
1 parent 11caa1f commit 3b4a827

File tree

4 files changed

+183
-93
lines changed

4 files changed

+183
-93
lines changed

src/talk/renderer/TitleBar/TitleBar.vue

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,12 @@ function logout() {
3838
3939
// Unselect chat by escape key
4040
useHotKey('Escape', pushToRoot)
41+
/**
42+
*
43+
*/
44+
async function handleScreenshare() {
45+
console.log(await window.OCA.Talk.Desktop.getDesktopMediaSource())
46+
}
4147
</script>
4248

4349
<template>
@@ -54,6 +60,13 @@ useHotKey('Escape', pushToRoot)
5460

5561
<div class="spacer" />
5662

63+
64+
<div class="title-bar__item" data-theme-dark>
65+
<button @click="handleScreenshare">
66+
Screenshare
67+
</button>
68+
</div>
69+
5770
<div class="title-bar__item" data-theme-dark>
5871
<MainMenu />
5972
</div>

src/talk/renderer/screensharing/DesktopMediaSourceDialog.vue

Lines changed: 49 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -6,20 +6,22 @@
66
<script setup lang="ts">
77
import { computed, onBeforeUnmount, onMounted, ref } from 'vue'
88
import IconCancel from '@mdi/svg/svg/cancel.svg?raw'
9+
import IconMonitor from 'vue-material-design-icons/Monitor.vue'
910
import IconMonitorShare from '@mdi/svg/svg/monitor-share.svg?raw'
1011
import NcDialog from '@nextcloud/vue/dist/Components/NcDialog.js'
1112
import NcEmptyContent from '@nextcloud/vue/dist/Components/NcEmptyContent.js'
1213
import NcLoadingIcon from '@nextcloud/vue/dist/Components/NcLoadingIcon.js'
1314
import { translate as t } from '@nextcloud/l10n'
1415
import DesktopMediaSourcePreview from './DesktopMediaSourcePreview.vue'
1516
import type { ScreensharingSource, ScreensharingSourceId } from './screensharing.types.ts'
17+
import DesktopMediaSourcePreviewVideo from './DesktopMediaSourcePreviewVideo.vue'
1618
1719
const emit = defineEmits<{
1820
(event: 'submit', sourceId: ScreensharingSourceId): void
1921
(event: 'cancel'): void
2022
}>()
2123
22-
const RE_REQUEST_SOURCES_TIMEOUT = 1000
24+
const RE_REQUEST_SOURCES_TIMEOUT = 5000
2325
2426
// On Wayland getting each stream for the live preview requests user to select the source via system dialog again
2527
// Instead - show static images.
@@ -29,6 +31,11 @@ const previewType = window.systemInfo.isWayland ? 'thumbnail' : 'live'
2931
const selectedSourceId = ref<ScreensharingSourceId | null>(null)
3032
const sources = ref<ScreensharingSource[] | null>(null)
3133
34+
const selectedSource = computed(() => {
35+
console.log('changed', sources.value, selectedSourceId.value)
36+
return sources.value?.find((source) => source.id === selectedSourceId.value)
37+
})
38+
3239
const handleSubmit = () => emit('submit', selectedSourceId.value!)
3340
const handleCancel = () => emit('cancel')
3441
@@ -110,9 +117,9 @@ onMounted(async () => {
110117
await requestDesktopCapturerSources()
111118
112119
// Preselect the first media source if any
113-
if (!selectedSourceId.value) {
114-
selectedSourceId.value = sources.value?.[0]?.id ?? null
115-
}
120+
// if (!selectedSourceId.value) {
121+
// selectedSourceId.value = sources.value?.[0]?.id ?? null
122+
// }
116123
117124
if (previewType === 'live') {
118125
scheduleRequestDesktopCaprutererSources()
@@ -131,13 +138,24 @@ onBeforeUnmount(() => {
131138
size="normal"
132139
:buttons="dialogButtons"
133140
@update:open="handleCancel">
134-
<div v-if="sources" class="capture-source-grid">
135-
<DesktopMediaSourcePreview v-for="source in sources"
136-
:key="source.id"
137-
:source="source"
138-
:selected="selectedSourceId === source.id"
139-
@select="selectedSourceId = source.id"
140-
@suspend="handleVideoSuspend(source)" />
141+
<div v-if="sources" class="capture-source-container">
142+
<div v-if="previewType === 'live'" class="capture-source-preview">
143+
<!-- @vue-expect-error Vue 2 doesn't understand that selectedSourceId is not undefined here -->
144+
<DesktopMediaSourcePreviewVideo v-if="selectedSourceId" :key="selectedSource.id" :media-source-id="selectedSource.id" />
145+
<NcEmptyContent v-else :name="t('talk_desktop', 'Choose source')">
146+
<template #icon>
147+
<IconMonitor />
148+
</template>
149+
</NcEmptyContent>
150+
</div>
151+
<div class="capture-source-grid">
152+
<DesktopMediaSourcePreview v-for="source in sources"
153+
:key="source.id"
154+
:source="source"
155+
:selected="selectedSourceId === source.id"
156+
@select="selectedSourceId = source.id"
157+
@suspend="handleVideoSuspend(source)" />
158+
</div>
141159
</div>
142160
<NcEmptyContent v-else :name="t('talk_desktop', 'Loading …')">
143161
<template #icon>
@@ -148,6 +166,26 @@ onBeforeUnmount(() => {
148166
</template>
149167

150168
<style scoped lang="scss">
169+
.capture-source-container {
170+
display: flex;
171+
flex-direction: column;
172+
gap: calc(var(--default-grid-baseline) * 2);
173+
}
174+
175+
.capture-source-preview {
176+
aspect-ratio: 16 / 9;
177+
width: 100%;
178+
display: flex;
179+
flex-direction: column;
180+
background-color: var(--color-background-darker);
181+
border-radius: var(--border-radius-container);
182+
}
183+
184+
.capture-source-preview :deep(video) {
185+
aspect-ratio: 16 / 9;
186+
object-fit: scale-down;
187+
}
188+
151189
.capture-source-grid {
152190
display: grid;
153191
grid-template-columns: repeat(3, minmax(0, 1fr));

src/talk/renderer/screensharing/DesktopMediaSourcePreview.vue

Lines changed: 7 additions & 82 deletions
Original file line numberDiff line numberDiff line change
@@ -4,20 +4,19 @@
44
-->
55

66
<script setup lang="ts">
7-
import type { ScreensharingSource, ScreensharingSourceId } from './screensharing.types.ts'
8-
import { onBeforeUnmount, onMounted, ref } from 'vue'
7+
import type { ScreensharingSource } from './screensharing.types.ts'
98
import IconMonitor from 'vue-material-design-icons/Monitor.vue'
109
import IconApplicationOutline from 'vue-material-design-icons/ApplicationOutline.vue'
1110
import IconVolumeHigh from 'vue-material-design-icons/VolumeHigh.vue'
11+
import DesktopMediaSourcePreviewVideo from './DesktopMediaSourcePreviewVideo.vue'
1212
1313
// On Wayland getting each stream for the live preview requests user to select the source via system dialog again
1414
// Instead - show static images.
1515
// See: https://github.com/electron/electron/issues/27732
16-
const previewType = window.systemInfo.isWayland ? 'thumbnail' : 'live'
16+
// const previewType = window.systemInfo.isWayland ? 'thumbnail' : 'live'
17+
const previewType = 'thumbnail'
1718
18-
const videoElement = ref<HTMLVideoElement | null>(null)
19-
20-
const props = defineProps<{
19+
defineProps<{
2120
source: ScreensharingSource
2221
selected: boolean
2322
}>()
@@ -26,78 +25,6 @@ const emit = defineEmits<{
2625
(event: 'select'): void
2726
(event: 'suspend'): void
2827
}>()
29-
30-
const getStreamForMediaSource = (mediaSourceId: ScreensharingSourceId) => {
31-
const MAX_PREVIEW_SIZE = 320
32-
// Special case for sharing all the screens with desktop audio in Electron
33-
// In this case, it must have exactly these constraints
34-
// "entire-desktop:0:0" is a custom sourceId for this specific case
35-
const constraints = mediaSourceId === 'entire-desktop:0:0'
36-
? {
37-
audio: {
38-
mandatory: {
39-
chromeMediaSource: 'desktop',
40-
},
41-
},
42-
video: {
43-
mandatory: {
44-
chromeMediaSource: 'desktop',
45-
maxWidth: MAX_PREVIEW_SIZE,
46-
maxHeight: MAX_PREVIEW_SIZE,
47-
},
48-
},
49-
}
50-
: {
51-
audio: false,
52-
video: {
53-
mandatory: {
54-
chromeMediaSource: 'desktop',
55-
chromeMediaSourceId: mediaSourceId,
56-
maxWidth: MAX_PREVIEW_SIZE,
57-
maxHeight: MAX_PREVIEW_SIZE,
58-
},
59-
},
60-
}
61-
62-
// @ts-expect-error Each browser has a different API, the current object is compatible with Chromium
63-
return navigator.mediaDevices.getUserMedia(constraints)
64-
}
65-
66-
/**
67-
* Set the video source to the selected source
68-
*/
69-
async function setVideoSource() {
70-
videoElement.value!.srcObject = await getStreamForMediaSource(props.source.id)
71-
}
72-
73-
/**
74-
* Release the video source
75-
*/
76-
function releaseVideoSource() {
77-
const stream = videoElement.value!.srcObject! as MediaStream
78-
for (const track of stream.getTracks()) {
79-
track.stop()
80-
}
81-
}
82-
83-
onMounted(async () => {
84-
if (previewType === 'live') {
85-
await setVideoSource()
86-
}
87-
})
88-
89-
onBeforeUnmount(() => {
90-
// Release the stream, otherwise it is still captured even if no video element is using it
91-
releaseVideoSource()
92-
})
93-
94-
/**
95-
* Handle the loadedmetadata event of the video element
96-
* @param event - The event
97-
*/
98-
function onLoadedMetadata(event: Event) {
99-
(event.target as HTMLVideoElement).play()
100-
}
10128
</script>
10229

10330
<template>
@@ -109,11 +36,9 @@ function onLoadedMetadata(event: Event) {
10936
:checked="selected"
11037
@change="emit('select')">
11138

112-
<video v-if="previewType === 'live'"
113-
ref="videoElement"
39+
<DesktopMediaSourcePreviewVideo v-if="previewType === 'live'"
11440
class="capture-source__preview"
115-
muted
116-
@loadedmetadata="onLoadedMetadata"
41+
:source="source"
11742
@suspend="emit('suspend')" />
11843
<img v-else-if="previewType === 'thumbnail' && source.thumbnail"
11944
alt=""
Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
<!--
2+
- SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors
3+
- SPDX-License-Identifier: AGPL-3.0-or-later
4+
-->
5+
6+
<script setup lang="ts">
7+
import type { ScreensharingSourceId } from './screensharing.types.ts'
8+
import { onBeforeUnmount, onMounted, ref } from 'vue'
9+
10+
const props = defineProps<{
11+
mediaSourceId: ScreensharingSourceId
12+
}>()
13+
14+
const emit = defineEmits<{
15+
(event: 'suspend'): void
16+
}>()
17+
18+
const videoElement = ref<HTMLVideoElement | null>(null)
19+
20+
/**
21+
* Get the stream for the media source
22+
* @param mediaSourceId - The media source ID
23+
*/
24+
function getStreamForMediaSource(mediaSourceId: ScreensharingSourceId) {
25+
const MAX_PREVIEW_SIZE = 320
26+
// Special case for sharing all the screens with desktop audio in Electron
27+
// In this case, it must have exactly these constraints
28+
// "entire-desktop:0:0" is a custom sourceId for this specific case
29+
const constraints = mediaSourceId === 'entire-desktop:0:0'
30+
? {
31+
audio: {
32+
mandatory: {
33+
chromeMediaSource: 'desktop',
34+
},
35+
},
36+
video: {
37+
mandatory: {
38+
chromeMediaSource: 'desktop',
39+
maxWidth: MAX_PREVIEW_SIZE,
40+
maxHeight: MAX_PREVIEW_SIZE,
41+
},
42+
},
43+
}
44+
: {
45+
audio: false,
46+
video: {
47+
mandatory: {
48+
chromeMediaSource: 'desktop',
49+
chromeMediaSourceId: mediaSourceId,
50+
maxWidth: MAX_PREVIEW_SIZE,
51+
maxHeight: MAX_PREVIEW_SIZE,
52+
},
53+
},
54+
}
55+
56+
// @ts-expect-error Each browser has a different API, the current object is compatible with Chromium
57+
return navigator.mediaDevices.getUserMedia(constraints)
58+
}
59+
60+
/**
61+
* Set the video source to the selected source
62+
*/
63+
async function setVideoSource() {
64+
videoElement.value!.srcObject = await getStreamForMediaSource(props.mediaSourceId)
65+
}
66+
67+
/**
68+
* Release the video source
69+
*/
70+
function releaseVideoSource() {
71+
const stream = videoElement.value!.srcObject! as MediaStream
72+
for (const track of stream.getTracks()) {
73+
track.stop()
74+
}
75+
videoElement.value!.srcObject = null
76+
}
77+
78+
onMounted(async () => {
79+
await setVideoSource()
80+
})
81+
82+
onBeforeUnmount(() => {
83+
// Release the stream, otherwise it is still captured even if no video element is using it
84+
releaseVideoSource()
85+
})
86+
87+
/**
88+
* Handle the loadedmetadata event of the video element
89+
* @param event - The event
90+
*/
91+
function onLoadedMetadata(event: Event) {
92+
(event.target as HTMLVideoElement).play()
93+
}
94+
95+
/**
96+
*
97+
*/
98+
function onSuspend() {
99+
if (videoElement.value!.srcObject) {
100+
emit('suspend')
101+
}
102+
}
103+
</script>
104+
105+
<template>
106+
<video ref="videoElement"
107+
muted
108+
@loadedmetadata="onLoadedMetadata"
109+
@suspend="onSuspend" />
110+
</template>
111+
112+
<style scoped lang="scss">
113+
114+
</style>

0 commit comments

Comments
 (0)