Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
63 changes: 53 additions & 10 deletions src/components/CallView/shared/LocalVideo.vue
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,16 @@
@mouseover="showShadow"
@mouseleave="hideShadow"
@click="handleClickVideo">
<video v-show="localMediaModel.attributes.videoEnabled"
id="localVideo"
ref="video"
disablePictureInPicture="true"
:class="videoClass"
class="video" />
<div v-show="localMediaModel.attributes.videoEnabled"
:class="videoWrapperClass"
class="videoWrapper">
<video
id="localVideo"
ref="video"
disablePictureInPicture="true"
:class="videoClass"
class="video" />
</div>
<div v-if="!localMediaModel.attributes.videoEnabled && !isSidebar" class="avatar-container">
<VideoBackground
v-if="isGrid || isStripe"
Expand All @@ -42,9 +46,10 @@
:disable-tooltip="true"
:show-user-status="false"
:user="userId"
:display-name="displayName" />
:display-name="displayName"
:class="avatarClass" />
<div v-if="!userId"
:class="avatarSizeClass"
:class="guestAvatarClass"
class="avatar guest">
{{ firstLetterOfGuestName }}
</div>
Expand Down Expand Up @@ -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 {

Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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() {
Expand Down Expand Up @@ -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%;
Expand All @@ -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;
Expand Down
51 changes: 51 additions & 0 deletions src/utils/webrtc/models/LocalCallParticipantModel.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,18 +21,22 @@

import store from '../../../store/index.js'

import { ConnectionState } from './CallParticipantModel'

export default function LocalCallParticipantModel() {

this.attributes = {
peerId: null,
peer: null,
screenPeer: null,
guestName: null,
connectionState: null,
}

this._handlers = []

this._handleForcedMuteBound = this._handleForcedMute.bind(this)
this._handleExtendedIceConnectionStateChangeBound = this._handleExtendedIceConnectionStateChange.bind(this)

}

Expand Down Expand Up @@ -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) {
Expand All @@ -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)
}
},

}
39 changes: 38 additions & 1 deletion src/utils/webrtc/webrtc.js
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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)
}
Expand Down