diff options
author | Joas Schilling <213943+nickvergessen@users.noreply.github.com> | 2021-11-11 18:19:06 +0300 |
---|---|---|
committer | GitHub <noreply@github.com> | 2021-11-11 18:19:06 +0300 |
commit | f8f6758987a737e72f24b285404c10467c263033 (patch) | |
tree | c1f5812477d108d1b8fe5a30bacf5107dbf0594e | |
parent | 197208e4543dd331e7e300888e819c32c6dae67b (diff) | |
parent | 1a1f7765896416c05bbc3f7cd725063530f4b6aa (diff) |
Merge pull request #6475 from nextcloud/backport/6466/stable23
[stable23] Show message about problems in the connection with another participant
-rw-r--r-- | src/components/CallView/shared/Video.vue | 84 | ||||
-rw-r--r-- | src/utils/webrtc/models/CallParticipantModel.js | 34 | ||||
-rw-r--r-- | src/utils/webrtc/webrtc.js | 10 |
3 files changed, 128 insertions, 0 deletions
diff --git a/src/components/CallView/shared/Video.vue b/src/components/CallView/shared/Video.vue index 8f0d1d2fe..87539b12d 100644 --- a/src/components/CallView/shared/Video.vue +++ b/src/components/CallView/shared/Video.vue @@ -83,6 +83,11 @@ :size="36" /> </div> </transition-group> + <div v-if="connectionMessage" + :class="connectionMessageClass" + class="connection-message"> + {{ connectionMessage }} + </div> <VideoBottomBar v-bind="$props" :has-shadow="hasVideo" :participant-name="participantName" /> @@ -172,6 +177,10 @@ export default { } }, + wasConnectedAtLeastOnce() { + return this.model.attributes.connectedAtLeastOnce + }, + isNotConnected() { return this.model.attributes.connectionState !== ConnectionState.CONNECTED && this.model.attributes.connectionState !== ConnectionState.COMPLETED }, @@ -180,6 +189,57 @@ export default { return this.isNotConnected && this.model.attributes.connectionState !== ConnectionState.FAILED_NO_RESTART }, + isDisconnected() { + return this.model.attributes.connectionState !== ConnectionState.NEW && this.model.attributes.connectionState !== ConnectionState.CHECKING + && this.model.attributes.connectionState !== ConnectionState.CONNECTED && this.model.attributes.connectionState !== ConnectionState.COMPLETED + }, + + /** + * Whether the connection to the participant is being tried again. + * + * The initial connection to the participant is excluded. + * + * A "failed" connection state will trigger a reconnection, but that may + * not immediately change the "negotiating" or "connecting" attributes + * (for example, while the new offer requested to the HPB was not + * received yet). Similarly both "negotiating" and "connecting" need to + * be checked, as the negotiation will start before the connection + * attempt is started. + */ + isReconnecting() { + return this.model.attributes.connectionState === ConnectionState.FAILED + || (!this.model.attributes.initialConnection + && (this.model.attributes.negotiating || this.model.attributes.connecting)) + }, + + isNoLongerTryingToReconnect() { + return this.model.attributes.connectionState === ConnectionState.FAILED_NO_RESTART + }, + + connectionMessage() { + if (!this.wasConnectedAtLeastOnce && this.isNoLongerTryingToReconnect) { + return t('spreed', 'Connection could not be established …') + } + + if (this.isNoLongerTryingToReconnect) { + return t('spreed', 'Connection was lost and could not be re-established …') + } + + if (!this.wasConnectedAtLeastOnce && this.isReconnecting) { + return t('spreed', 'Connection could not be established. Trying again …') + } + + if (this.isReconnecting) { + return t('spreed', 'Connection lost. Trying to reconnect …') + } + + if (this.isDisconnected) { + return t('spreed', 'Connection problems …') + } + + return null + }, + containerClass() { return { 'videoContainer-dummy': this.placeholderForPromoted, @@ -214,6 +274,12 @@ export default { }) }, + connectionMessageClass() { + return { + 'below-avatar': this.showBackgroundAndAvatar, + } + }, + firstLetterOfGuestName() { const customName = this.participantName && this.participantName !== t('spreed', 'Guest') ? this.participantName : '?' return customName.charAt(0) @@ -514,6 +580,24 @@ export default { object-fit: cover; } +.connection-message { + width: 100%; + + position: absolute; + top: calc(50% + 50px); + + text-align: center; + + z-index: 1; + + color: white; + filter: drop-shadow(1px 1px 4px var(--color-box-shadow)); +} + +.connection-message.below-avatar { + top: calc(50% + 80px); +} + .speaking-shadow { position: absolute; height: 100%; diff --git a/src/utils/webrtc/models/CallParticipantModel.js b/src/utils/webrtc/models/CallParticipantModel.js index f10c47aa7..5c260f12c 100644 --- a/src/utils/webrtc/models/CallParticipantModel.js +++ b/src/utils/webrtc/models/CallParticipantModel.js @@ -55,6 +55,10 @@ export default function CallParticipantModel(options) { name: undefined, internal: undefined, connectionState: ConnectionState.NEW, + negotiating: false, + connecting: false, + initialConnection: true, + connectedAtLeastOnce: false, stream: null, // The audio element is part of the model to ensure that it can be // played if needed even if there is no view for it. @@ -79,6 +83,7 @@ export default function CallParticipantModel(options) { this._handleMuteBound = this._handleMute.bind(this) this._handleUnmuteBound = this._handleUnmute.bind(this) this._handleExtendedIceConnectionStateChangeBound = this._handleExtendedIceConnectionStateChange.bind(this) + this._handleSignalingStateChangeBound = this._handleSignalingStateChange.bind(this) this._handleChannelMessageBound = this._handleChannelMessage.bind(this) this._handleRaisedHandBound = this._handleRaisedHand.bind(this) @@ -96,6 +101,7 @@ CallParticipantModel.prototype = { destroy() { if (this.get('peer')) { this.get('peer').off('extendedIceConnectionStateChange', this._handleExtendedIceConnectionStateChangeBound) + this.get('peer').off('signalingStateChange', this._handleSignalingStateChangeBound) } this._webRtc.off('peerStreamAdded', this._handlePeerStreamAddedBound) @@ -232,6 +238,7 @@ CallParticipantModel.prototype = { if (this.get('peer')) { this.get('peer').off('extendedIceConnectionStateChange', this._handleExtendedIceConnectionStateChangeBound) + this.get('peer').off('signalingStateChange', this._handleSignalingStateChangeBound) } this.set('peer', peer) @@ -239,6 +246,8 @@ CallParticipantModel.prototype = { // Special case when the participant has no streams. if (!this.get('peer')) { this.set('connectionState', ConnectionState.COMPLETED) + this.set('negotiating', false) + this.set('connecting', false) this.set('audioAvailable', false) this.set('speaking', false) this.set('videoAvailable', false) @@ -254,9 +263,11 @@ CallParticipantModel.prototype = { } else { this._handleExtendedIceConnectionStateChange(this.get('peer').pc.iceConnectionState) } + this._handleSignalingStateChange(this.get('peer').pc.signalingState) this._handlePeerStreamAdded(this.get('peer')) this.get('peer').on('extendedIceConnectionStateChange', this._handleExtendedIceConnectionStateChangeBound) + this.get('peer').on('signalingStateChange', this._handleSignalingStateChangeBound) }, _handleExtendedIceConnectionStateChange(extendedIceConnectionState) { @@ -269,25 +280,38 @@ CallParticipantModel.prototype = { } }.bind(this) + // "connecting" state is not changed when entering the "disconnected" + // state, as it can be entered while still connecting (if done from + // "checking") or once already connected (from "connected" or + // "completed"). + switch (extendedIceConnectionState) { case 'new': this.set('connectionState', ConnectionState.NEW) + this.set('connecting', true) this.set('audioAvailable', undefined) this.set('speaking', undefined) this.set('videoAvailable', undefined) break case 'checking': this.set('connectionState', ConnectionState.CHECKING) + this.set('connecting', true) this.set('audioAvailable', undefined) this.set('speaking', undefined) this.set('videoAvailable', undefined) break case 'connected': this.set('connectionState', ConnectionState.CONNECTED) + this.set('connecting', false) + this.set('initialConnection', false) + this.set('connectedAtLeastOnce', true) setNameForUserFromPeerNick() break case 'completed': this.set('connectionState', ConnectionState.COMPLETED) + this.set('connecting', false) + this.set('initialConnection', false) + this.set('connectedAtLeastOnce', true) setNameForUserFromPeerNick() break case 'disconnected': @@ -298,18 +322,28 @@ CallParticipantModel.prototype = { break case 'failed': this.set('connectionState', ConnectionState.FAILED) + this.set('connecting', false) + this.set('initialConnection', false) break case 'failed-no-restart': this.set('connectionState', ConnectionState.FAILED_NO_RESTART) + this.set('connecting', false) + this.set('initialConnection', false) break case 'closed': this.set('connectionState', ConnectionState.CLOSED) + this.set('connecting', false) + this.set('initialConnection', false) break default: console.error('Unexpected (extended) ICE connection state: ', extendedIceConnectionState) } }, + _handleSignalingStateChange(signalingState) { + this.set('negotiating', signalingState !== 'stable' && signalingState !== 'closed') + }, + setScreenPeer(screenPeer) { if (screenPeer && this.get('peerId') !== screenPeer.id) { console.warn('Mismatch between stored peer ID and ID of given screen peer: ', this.get('peerId'), screenPeer.id) diff --git a/src/utils/webrtc/webrtc.js b/src/utils/webrtc/webrtc.js index e06bba446..4820ec176 100644 --- a/src/utils/webrtc/webrtc.js +++ b/src/utils/webrtc/webrtc.js @@ -849,6 +849,15 @@ export default function initWebRtc(signaling, _callParticipantCollection, _local /** * @param {object} peer The peer connection to handle the state on */ + function setHandlerForSignalingStateChange(peer) { + peer.pc.addEventListener('signalingstatechange', function() { + peer.emit('signalingStateChange', peer.pc.signalingState) + }) + } + + /** + * @param {object} peer The peer connection to handle the state on + */ function setHandlerForOwnIceConnectionStateChange(peer) { peer.pc.addEventListener('iceconnectionstatechange', function() { peer.emit('extendedIceConnectionStateChange', peer.pc.iceConnectionState) @@ -1067,6 +1076,7 @@ export default function initWebRtc(signaling, _callParticipantCollection, _local } else { setHandlerForIceConnectionStateChange(peer) setHandlerForConnectionStateChange(peer) + setHandlerForSignalingStateChange(peer) } setHandlerForNegotiationNeeded(peer) |