Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
1a09ea7
Add analyzer for the quality of peer connections
danxuliu Jun 22, 2020
5c45e23
Take round trip time into account when calculating the quality
danxuliu Jul 7, 2020
949f10a
Trigger events when the quality changes
danxuliu Jun 22, 2020
e2aa17d
Make possible to enable and disable the analysis of audio or video
danxuliu Jul 3, 2020
97e12ca
Guard against null screen peer
danxuliu Jul 2, 2020
a205ca4
Move peer and screenPeer to attributes in CallParticipantModel
danxuliu Jul 3, 2020
790c981
Add peer and screenPeer attributes to LocalCallParticipantModel
danxuliu Jul 3, 2020
ca4fdd1
Add analyzer for participants
danxuliu Jul 3, 2020
b5e724e
Add analyzer for calls
danxuliu Jul 3, 2020
58fc1f5
Show warning when the quality of the connection is bad
danxuliu Jul 3, 2020
d4ec4d9
Add a grace period before hiding the quality warning
danxuliu Jul 3, 2020
581d68a
Show tooltip only if the quality warning has not been recently shown
danxuliu Jul 3, 2020
6cb8de5
Take video and screen quality into account in the quality warning
danxuliu Jul 6, 2020
ffe3cf6
Move setting class attributes to its own method
danxuliu Jul 9, 2020
a6b897a
Increase grace period for quality tooltip
danxuliu Jul 9, 2020
087330b
Fix duplicated event listeners in ParticipantAnalyzer
danxuliu Jul 16, 2020
ad27de1
Dedicated connection warning icon
nickvergessen Jul 15, 2020
e12ca19
Add buttons to disable video and screen share to quality warning tooltip
Jul 17, 2020
1bff844
Add button to explicitly dismiss the quality warning tooltip
Jul 17, 2020
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
"vue-at": "^2.5.0-beta.2",
"vue-clipboard2": "^0.3.1",
"vue-fragment": "^1.5.1",
"vue-material-design-icons": "^4.7.1",
"vue-observe-visibility": "^0.4.6",
"vue-prevent-unload": "^0.2.3",
"vue-router": "^3.1.5",
Expand Down
87 changes: 87 additions & 0 deletions src/components/CallView/LocalMediaControls.vue
Original file line number Diff line number Diff line change
Expand Up @@ -80,13 +80,48 @@
</li>
</ul>
</div>
<div class="network-connection-state">
<Popover
v-if="qualityWarningTooltip"
:boundaries-element="boundaryElement"
:aria-label="qualityWarningAriaLabel"
trigger="hover"
:auto-hide="false"
:open="showQualityWarningTooltip">
<NetworkStrength2Alert
slot="trigger"
fill-color="#e9322d"
title=""
:size="24"
@mouseover="mouseover = true"
@mouseleave="mouseover = false" />
<div class="hint">
<span>{{ qualityWarningTooltip.content }}</span>
<div class="hint__actions">
<button
v-if="qualityWarningTooltip.action"
class="primary"
@click="executeQualityWarningTooltipAction">
{{ qualityWarningTooltip.actionLabel }}
</button>
<button
v-if="!isQualityWarningTooltipDismissed"
@click="isQualityWarningTooltipDismissed = true">
{{ t('spreed', 'Dismiss') }}
</button>
</div>
</div>
</Popover>
</div>
</div>
</template>

<script>
import escapeHtml from 'escape-html'
import Popover from '@nextcloud/vue/dist/Components/Popover'
import Tooltip from '@nextcloud/vue/dist/Directives/Tooltip'
import SpeakingWhileMutedWarner from '../../utils/webrtc/SpeakingWhileMutedWarner'
import NetworkStrength2Alert from 'vue-material-design-icons/NetworkStrength2Alert'

export default {

Expand All @@ -96,6 +131,11 @@ export default {
tooltip: Tooltip,
},

components: {
NetworkStrength2Alert,
Popover,
},

props: {
model: {
type: Object,
Expand All @@ -109,6 +149,14 @@ export default {
type: Boolean,
default: false,
},
qualityWarningAriaLabel: {
type: String,
default: '',
},
qualityWarningTooltip: {
type: Object,
default: null,
},
},

data() {
Expand All @@ -117,6 +165,9 @@ export default {
speakingWhileMutedNotification: null,
screenSharingMenuOpen: false,
splitScreenSharingMenu: false,
boundaryElement: document.querySelector('.main-view'),
isQualityWarningTooltipDismissed: false,
mouseover: false,
}
},

Expand Down Expand Up @@ -246,6 +297,10 @@ export default {
return (this.model.attributes.localScreen || this.splitScreenSharingMenu) ? t('spreed', 'Screensharing options') : t('spreed', 'Enable screensharing')
},

showQualityWarningTooltip() {
return this.qualityWarningTooltip && (!this.isQualityWarningTooltipDismissed || this.mouseover)
},

},

created() {
Expand Down Expand Up @@ -382,6 +437,18 @@ export default {
}
})
},
executeQualityWarningTooltipAction() {
if (this.qualityWarningTooltip.action === '') {
return
}
if (this.qualityWarningTooltip.action === 'disableScreenShare') {
this.model.stopSharingScreen()
this.isQualityWarningTooltipDismissed = true
} else if (this.qualityWarningTooltip.action === 'disableVideo') {
this.model.disableVideo()
this.isQualityWarningTooltipDismissed = true
}
},
},
}
</script>
Expand Down Expand Up @@ -478,4 +545,24 @@ export default {
#muteWrapper .icon-audio-off + .volume-indicator {
background: linear-gradient(0deg, gray, white 36px);
}

.network-connection-state {
position: absolute;
bottom: 0;
right: 2px;
width: 32px;
height: 32px;
filter: drop-shadow(1px 1px 4px var(--color-box-shadow));
}

.hint {
padding: 4px;
text-align: left;
&__actions{
display: flex;
flex-direction: row-reverse;
justify-content: space-between;
padding-top:4px;
}
}
</style>
121 changes: 120 additions & 1 deletion src/components/CallView/LocalVideo.vue
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
-->

<template>
<div id="localVideoContainer" class="videoContainer videoView" :class="{ speaking: localMediaModel.attributes.speaking }">
<div id="localVideoContainer" class="videoContainer videoView" :class="videoContainerClass">
<video v-show="localMediaModel.attributes.videoEnabled" id="localVideo" ref="video" />
<div v-if="!localMediaModel.attributes.videoEnabled" class="avatar-container">
<Avatar v-if="userId"
Expand All @@ -38,6 +38,8 @@
:model="localMediaModel"
:local-call-participant-model="localCallParticipantModel"
:screen-sharing-button-hidden="useConstrainedLayout"
:quality-warning-aria-label="qualityWarningAriaLabel"
:quality-warning-tooltip="qualityWarningTooltip"
@switchScreenToId="$emit('switchScreenToId', $event)" />
</div>
</template>
Expand All @@ -49,6 +51,8 @@ import LocalMediaControls from './LocalMediaControls'
import Hex from 'crypto-js/enc-hex'
import SHA1 from 'crypto-js/sha1'
import { showError } from '@nextcloud/dialogs'
import { callAnalyzer } from '../../utils/webrtc/index'
import { CONNECTION_QUALITY } from '../../utils/webrtc/analyzers/PeerConnectionAnalyzer'

export default {

Expand All @@ -74,8 +78,21 @@ export default {
},
},

data() {
return {
callAnalyzer: callAnalyzer,
qualityWarningInGracePeriodTimeout: null,
}
},

computed: {

videoContainerClass() {
return {
'speaking': this.localMediaModel.attributes.speaking,
}
},

userId() {
return this.$store.getters.getUserId()
},
Expand Down Expand Up @@ -112,6 +129,93 @@ export default {
return this.localMediaModel.attributes.localStream && this.localMediaModel.attributes.localStreamRequestVideoError
},

showQualityWarning() {
return this.senderConnectionQualityIsBad || this.qualityWarningInGracePeriodTimeout
},

senderConnectionQualityIsBad() {
return this.senderConnectionQualityAudioIsBad
|| this.senderConnectionQualityVideoIsBad
|| this.senderConnectionQualityScreenIsBad
},

senderConnectionQualityAudioIsBad() {
return callAnalyzer
&& (callAnalyzer.attributes.senderConnectionQualityAudio === CONNECTION_QUALITY.VERY_BAD
|| callAnalyzer.attributes.senderConnectionQualityAudio === CONNECTION_QUALITY.NO_TRANSMITTED_DATA)
},

senderConnectionQualityVideoIsBad() {
return callAnalyzer
&& (callAnalyzer.attributes.senderConnectionQualityVideo === CONNECTION_QUALITY.VERY_BAD
|| callAnalyzer.attributes.senderConnectionQualityVideo === CONNECTION_QUALITY.NO_TRANSMITTED_DATA)
},

senderConnectionQualityScreenIsBad() {
return callAnalyzer
&& (callAnalyzer.attributes.senderConnectionQualityScreen === CONNECTION_QUALITY.VERY_BAD
|| callAnalyzer.attributes.senderConnectionQualityScreen === CONNECTION_QUALITY.NO_TRANSMITTED_DATA)
},

qualityWarningAriaLabel() {
let label = ''
if (!this.localMediaModel.attributes.audioEnabled && this.localMediaModel.attributes.videoEnabled && this.localMediaModel.attributes.localScreen) {
label = t('spreed', 'Bad sent video and screen quality.')
} else if (!this.localMediaModel.attributes.audioEnabled && this.localMediaModel.attributes.localScreen) {
label = t('spreed', 'Bad sent screen quality.')
} else if (!this.localMediaModel.attributes.audioEnabled && this.localMediaModel.attributes.videoEnabled) {
label = t('spreed', 'Bad sent video quality.')
} else if (this.localMediaModel.attributes.videoEnabled && this.localMediaModel.attributes.localScreen) {
label = t('spreed', 'Bad sent audio, video and screen quality.')
} else if (this.localMediaModel.attributes.localScreen) {
label = t('spreed', 'Bad sent audio and screen quality.')
} else if (this.localMediaModel.attributes.videoEnabled) {
label = t('spreed', 'Bad sent audio and video quality.')
} else {
label = t('spreed', 'Bad sent audio quality.')
}

return label
},

qualityWarningTooltip() {
if (!this.showQualityWarning) {
return null
}

const tooltip = {}
if (!this.localMediaModel.attributes.audioEnabled && this.localMediaModel.attributes.videoEnabled && this.localMediaModel.attributes.localScreen) {
tooltip.content = t('spreed', 'Your internet connection or computer are busy and other participants might be unable to see you. To improve the situation try to disable your video while doing a screenshare.')
tooltip.actionLabel = t('spreed', 'Disable video')
tooltip.action = 'disableVideo'
} else if (!this.localMediaModel.attributes.audioEnabled && this.localMediaModel.attributes.localScreen) {
tooltip.content = t('spreed', 'Your internet connection or computer are busy and other participants might be unable to see your screen.')
tooltip.actionLabel = ''
tooltip.action = ''
} else if (!this.localMediaModel.attributes.audioEnabled && this.localMediaModel.attributes.videoEnabled) {
tooltip.content = t('spreed', 'Your internet connection or computer are busy and other participants might be unable to see you.')
tooltip.actionLabel = ''
tooltip.action = ''
} else if (this.localMediaModel.attributes.videoEnabled && this.localMediaModel.attributes.localScreen) {
tooltip.content = t('spreed', 'Your internet connection or computer are busy and other participants might be unable to understand and see you. To improve the situation try to disable your video while doing a screenshare.')
tooltip.actionLabel = t('spreed', 'Disable video')
tooltip.action = 'disableVideo'
} else if (this.localMediaModel.attributes.localScreen) {
tooltip.content = t('spreed', 'Your internet connection or computer are busy and other participants might be unable to understand and see your screen. To improve the situation try to disable your screenshare.')
tooltip.actionLabel = t('spreed', 'Disable screenshare')
tooltip.action = 'disableScreenShare'
} else if (this.localMediaModel.attributes.videoEnabled) {
tooltip.content = t('spreed', 'Your internet connection or computer are busy and other participants might be unable to understand and see you. To improve the situation try to disable your video.')
tooltip.actionLabel = t('spreed', 'Disable video')
tooltip.action = 'disableVideo'
} else {
tooltip.content = t('spreed', 'Your internet connection or computer are busy and other participants might be unable to understand you.')
tooltip.actionLabel = ''
tooltip.action = ''
}

return tooltip
},
},

watch: {
Expand All @@ -131,6 +235,20 @@ export default {
},
},

senderConnectionQualityIsBad: function(senderConnectionQualityIsBad) {
if (!senderConnectionQualityIsBad) {
return
}

if (this.qualityWarningInGracePeriodTimeout) {
window.clearTimeout(this.qualityWarningInGracePeriodTimeout)
}

this.qualityWarningInGracePeriodTimeout = window.setTimeout(() => {
this.qualityWarningInGracePeriodTimeout = null
}, 10000)
},

},

mounted() {
Expand Down Expand Up @@ -165,4 +283,5 @@ export default {
@import '../../assets/avatar.scss';
@include avatar-mixin(64px);
@include avatar-mixin(128px);

</style>
Loading