diff --git a/src/components/CallView/shared/LocalVideo.vue b/src/components/CallView/shared/LocalVideo.vue
index 534d192bb5f..ef2089b14e7 100644
--- a/src/components/CallView/shared/LocalVideo.vue
+++ b/src/components/CallView/shared/LocalVideo.vue
@@ -25,12 +25,16 @@
@mouseover="showShadow"
@mouseleave="hideShadow"
@click="handleClickVideo">
-
+
+ :display-name="displayName"
+ :class="avatarClass" />
{{ firstLetterOfGuestName }}
@@ -83,6 +88,7 @@ import {
} from '@nextcloud/dialogs'
import video from '../../../mixins/video.js'
import VideoBackground from './VideoBackground'
+import { ConnectionState } from '../../../utils/webrtc/models/CallParticipantModel'
export default {
@@ -134,8 +140,17 @@ export default {
return t('spreed', 'Back')
},
+ isNotConnected() {
+ // When there is no sender participant (when the MCU is not used, or
+ // if it is used but no peer object has been set yet) the local
+ // video is shown as connected.
+ return this.localCallParticipantModel.attributes.connectionState !== null
+ && this.localCallParticipantModel.attributes.connectionState !== ConnectionState.CONNECTED && this.localCallParticipantModel.attributes.connectionState !== ConnectionState.COMPLETED
+ },
+
videoContainerClass() {
return {
+ 'not-connected': this.isNotConnected,
'speaking': this.localMediaModel.attributes.speaking,
'video-container-grid': this.isGrid,
'video-container-stripe': this.isStripe,
@@ -167,12 +182,26 @@ export default {
)
},
+ videoWrapperClass() {
+ return {
+ 'icon-loading': this.isNotConnected,
+ }
+ },
+
avatarSize() {
return this.useConstrainedLayout ? 64 : 128
},
- avatarSizeClass() {
- return 'avatar-' + this.avatarSize + 'px'
+ avatarClass() {
+ return {
+ 'icon-loading': this.isNotConnected,
+ }
+ },
+
+ guestAvatarClass() {
+ return Object.assign(this.avatarClass, {
+ ['avatar-' + this.avatarSize + 'px']: true,
+ })
},
localStreamVideoError() {
@@ -291,6 +320,13 @@ export default {
@include avatar-mixin(64px);
@include avatar-mixin(128px);
+.not-connected {
+ video,
+ .avatar-container {
+ opacity: 0.5;
+ }
+}
+
.video-container-grid {
position:relative;
height: 100%;
@@ -309,11 +345,18 @@ export default {
flex-direction: column;
}
+.videoWrapper,
.video {
height: 100%;
width: 100%;
}
+.videoWrapper.icon-loading:after {
+ height: 60px;
+ width: 60px;
+ margin: -32px 0 0 -32px;
+}
+
.video--fit {
/* Fit the frame */
object-fit: contain;
diff --git a/src/utils/webrtc/models/LocalCallParticipantModel.js b/src/utils/webrtc/models/LocalCallParticipantModel.js
index 60bffab1b64..c97b3c94af6 100644
--- a/src/utils/webrtc/models/LocalCallParticipantModel.js
+++ b/src/utils/webrtc/models/LocalCallParticipantModel.js
@@ -21,6 +21,8 @@
import store from '../../../store/index.js'
+import { ConnectionState } from './CallParticipantModel'
+
export default function LocalCallParticipantModel() {
this.attributes = {
@@ -28,11 +30,13 @@ export default function LocalCallParticipantModel() {
peer: null,
screenPeer: null,
guestName: null,
+ connectionState: null,
}
this._handlers = []
this._handleForcedMuteBound = this._handleForcedMute.bind(this)
+ this._handleExtendedIceConnectionStateChangeBound = this._handleExtendedIceConnectionStateChange.bind(this)
}
@@ -107,7 +111,22 @@ LocalCallParticipantModel.prototype = {
console.warn('Mismatch between stored peer ID and ID of given peer: ', this.get('peerId'), peer.id)
}
+ if (this.get('peer')) {
+ this.get('peer').off('extendedIceConnectionStateChange', this._handleExtendedIceConnectionStateChangeBound)
+ }
+
this.set('peer', peer)
+
+ if (!this.get('peer')) {
+ this.set('connectionState', null)
+
+ return
+ }
+
+ // Reset state that depends on the Peer object.
+ this._handleExtendedIceConnectionStateChange(this.get('peer').pc.iceConnectionState)
+
+ this.get('peer').on('extendedIceConnectionStateChange', this._handleExtendedIceConnectionStateChangeBound)
},
setScreenPeer: function(screenPeer) {
@@ -132,4 +151,36 @@ LocalCallParticipantModel.prototype = {
this._trigger('forcedMute')
},
+ _handleExtendedIceConnectionStateChange: function(extendedIceConnectionState) {
+ switch (extendedIceConnectionState) {
+ case 'new':
+ this.set('connectionState', ConnectionState.NEW)
+ break
+ case 'checking':
+ this.set('connectionState', ConnectionState.CHECKING)
+ break
+ case 'connected':
+ this.set('connectionState', ConnectionState.CONNECTED)
+ break
+ case 'completed':
+ this.set('connectionState', ConnectionState.COMPLETED)
+ break
+ case 'disconnected':
+ this.set('connectionState', ConnectionState.DISCONNECTED)
+ break
+ case 'disconnected-long':
+ this.set('connectionState', ConnectionState.DISCONNECTED_LONG)
+ break
+ case 'failed':
+ this.set('connectionState', ConnectionState.FAILED)
+ break
+ // 'failed-no-restart' is not emitted by own peer
+ case 'closed':
+ this.set('connectionState', ConnectionState.CLOSED)
+ break
+ default:
+ console.error('Unexpected (extended) ICE connection state: ', extendedIceConnectionState)
+ }
+ },
+
}
diff --git a/src/utils/webrtc/webrtc.js b/src/utils/webrtc/webrtc.js
index 085e32ffe32..93f325fe737 100644
--- a/src/utils/webrtc/webrtc.js
+++ b/src/utils/webrtc/webrtc.js
@@ -660,6 +660,43 @@ export default function initWebRTC(signaling, _callParticipantCollection, _local
})
}
+ function setHandlerForOwnIceConnectionStateChange(peer) {
+ peer.pc.addEventListener('iceconnectionstatechange', function() {
+ peer.emit('extendedIceConnectionStateChange', peer.pc.iceConnectionState)
+
+ switch (peer.pc.iceConnectionState) {
+ case 'checking':
+ console.debug('Connecting own peer...', peer)
+
+ break
+ case 'connected':
+ case 'completed':
+ console.debug('Connection established (own peer).', peer)
+
+ break
+ case 'disconnected':
+ console.debug('Disconnected (own peer).', peer)
+
+ setTimeout(function() {
+ if (peer.pc.iceConnectionState !== 'disconnected') {
+ return
+ }
+
+ peer.emit('extendedIceConnectionStateChange', 'disconnected-long')
+ }, 5000)
+ break
+ case 'failed':
+ console.debug('Connection failed (own peer).', peer)
+
+ break
+ case 'closed':
+ console.debug('Connection closed (own peer).', peer)
+
+ break
+ }
+ })
+ }
+
const forceReconnect = function(signaling, flags) {
if (ownPeer) {
webrtc.removePeers(ownPeer.id)
@@ -714,7 +751,7 @@ export default function initWebRTC(signaling, _callParticipantCollection, _local
if (peer.type === 'video') {
if (peer.id === signaling.getSessionId()) {
- console.debug('Not adding ICE connection state handler for own peer', peer)
+ setHandlerForOwnIceConnectionStateChange(peer)
} else {
setHandlerForIceConnectionStateChange(peer)
}