Skip to content

Commit

Permalink
Merge pull request #6475 from nextcloud/backport/6466/stable23
Browse files Browse the repository at this point in the history
[stable23] Show message about problems in the connection with another participant
  • Loading branch information
nickvergessen authored Nov 11, 2021
2 parents 197208e + 1a1f776 commit f8f6758
Show file tree
Hide file tree
Showing 3 changed files with 128 additions and 0 deletions.
84 changes: 84 additions & 0 deletions src/components/CallView/shared/Video.vue
Original file line number Diff line number Diff line change
Expand Up @@ -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" />
Expand Down Expand Up @@ -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
},
Expand All @@ -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,
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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%;
Expand Down
34 changes: 34 additions & 0 deletions src/utils/webrtc/models/CallParticipantModel.js
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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)

Expand All @@ -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)
Expand Down Expand Up @@ -232,13 +238,16 @@ CallParticipantModel.prototype = {

if (this.get('peer')) {
this.get('peer').off('extendedIceConnectionStateChange', this._handleExtendedIceConnectionStateChangeBound)
this.get('peer').off('signalingStateChange', this._handleSignalingStateChangeBound)
}

this.set('peer', peer)

// 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)
Expand All @@ -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) {
Expand All @@ -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':
Expand All @@ -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)
Expand Down
10 changes: 10 additions & 0 deletions src/utils/webrtc/webrtc.js
Original file line number Diff line number Diff line change
Expand Up @@ -846,6 +846,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
*/
Expand Down Expand Up @@ -1067,6 +1076,7 @@ export default function initWebRtc(signaling, _callParticipantCollection, _local
} else {
setHandlerForIceConnectionStateChange(peer)
setHandlerForConnectionStateChange(peer)
setHandlerForSignalingStateChange(peer)
}

setHandlerForNegotiationNeeded(peer)
Expand Down

0 comments on commit f8f6758

Please sign in to comment.