TT#47718 Select participant in main content area

- selected ParticipantItem's video in fullscreen (#67702)
- placeholder icon for none existing video of the current selected participant (#67706)
- current selected ParticipantItem's name as title (#67703)
- local participant selected by default (#67704)

Change-Id: I6e2e47609adc407363be0c1636ff0ae8bd30ad38
changes/03/34803/11
Carlo Venusino 6 years ago
parent 81f46b5216
commit 8cf6234e63

@ -43,9 +43,37 @@
v-if="!isJoining && isJoined" v-if="!isJoining && isJoined"
/> />
</div> </div>
<q-icon
name="person"
class="csc-conf-selected-avatar"
v-show="!isJoining
&& isJoined
&& selectedParticipant
&& !selectedHasVideo"
>
</q-icon>
<div
id="csc-conf-selected-participant-name"
v-show="!isJoining
&& isJoined
&& selectedParticipantName"
>
{{selectedParticipantName}}
</div>
<div <div
id="csc-conf-main-media" id="csc-conf-main-media"
v-show="isMediaEnabled && (isCameraEnabled || isScreenEnabled)" v-show="!isJoined
&& isMediaEnabled
&& ( isCameraEnabled || isScreenEnabled )
|| (selectedParticipant
&& (selectedParticipant == 'local'
&& isMediaEnabled
&& (isCameraEnabled
|| isScreenEnabled)
)
|| selectedHasVideo
)"
> >
<csc-media <csc-media
ref="localMedia" ref="localMedia"
@ -100,6 +128,7 @@
<script> <script>
import { import {
mapGetters, mapGetters,
mapState,
mapActions mapActions
} from 'vuex' } from 'vuex'
import CscConferenceJoin from '../pages/Conference/CscConferenceJoin' import CscConferenceJoin from '../pages/Conference/CscConferenceJoin'
@ -109,12 +138,18 @@
import CscSpinner from "../CscSpinner"; import CscSpinner from "../CscSpinner";
import { import {
QLayout, QLayout,
QBtn QBtn,
QCard,
QCardMedia,
QIcon
} from 'quasar-framework' } from 'quasar-framework'
import CscConfirmDialog from "../CscConfirmationDialog"; import CscConfirmDialog from "../CscConfirmationDialog";
export default { export default {
data () { data: function () {
return {} return {
selectedMediaStream : null,
selectedParticipantName: null
}
}, },
mounted() { mounted() {
this.$store.dispatch('user/initUser'); this.$store.dispatch('user/initUser');
@ -127,9 +162,15 @@
CscConferenceJoined, CscConferenceJoined,
CscConferenceParticipants, CscConferenceParticipants,
QLayout, QLayout,
QBtn QBtn,
QCard,
QCardMedia,
QIcon
}, },
computed: { computed: {
...mapState('conference',[
'selectedParticipant'
]),
...mapGetters('conference', [ ...mapGetters('conference', [
'conferenceId', 'conferenceId',
'conferenceUrl', 'conferenceUrl',
@ -141,10 +182,13 @@
'isCameraEnabled', 'isCameraEnabled',
'isScreenEnabled', 'isScreenEnabled',
'isMediaEnabled', 'isMediaEnabled',
'localParticipant',
'localMediaStream', 'localMediaStream',
'participantsList', 'participantsList',
'remoteParticipant',
'remoteMediaStream', 'remoteMediaStream',
'remoteMediaStreams' 'remoteMediaStreams',
'hasRemoteVideo'
]), ]),
microphoneButtonColor() { microphoneButtonColor() {
if(this.isMicrophoneEnabled) { if(this.isMicrophoneEnabled) {
@ -169,6 +213,17 @@
else { else {
return 'grey'; return 'grey';
} }
},
selectedHasVideo(){
const selectedParticipant = this.selectedParticipant;
switch(true){
case !selectedParticipant:
return false;
case selectedParticipant == 'local':
return this.isMediaEnabled && (this.isCameraEnabled || this.isScreenEnabled);
default:
return this.hasRemoteVideo(selectedParticipant);
}
} }
}, },
methods: { methods: {
@ -204,6 +259,24 @@
if(this.hasConferenceId) { if(this.hasConferenceId) {
await this.$store.dispatch('conference/join', conferenceId); await this.$store.dispatch('conference/join', conferenceId);
} }
},
showSelectedParticipant: ( participant, scope )=>{
if(scope.$refs.localMedia) {
switch(participant){
case 'local':
if(scope.localParticipant){
scope.selectedParticipantName = scope.localParticipant.displayName
scope.selectedMediaStream = scope.localMediaStream
scope.$refs.localMedia.assignStream(scope.selectedMediaStream);
}
break;
default:
scope.selectedMediaStream = scope.remoteMediaStream(participant);
scope.$refs.localMedia.assignStream(scope.selectedMediaStream);
scope.selectedParticipantName = scope.remoteParticipant(participant).displayName
break;
}
}
} }
}, },
watch: { watch: {
@ -212,6 +285,14 @@
this.$store.commit('conference/disposeLocalMedia'); this.$store.commit('conference/disposeLocalMedia');
} }
}, },
selectedParticipant:{
handler: function(participant){
if(participant){
this.showSelectedParticipant(participant, this);
}
},
deep: true
},
localMediaStream(stream) { localMediaStream(stream) {
if (this.$refs.localMedia && (stream === null || stream === undefined)) { if (this.$refs.localMedia && (stream === null || stream === undefined)) {
this.$refs.localMedia.reset(); this.$refs.localMedia.reset();
@ -223,6 +304,19 @@
<style lang="stylus" rel="stylesheet/stylus"> <style lang="stylus" rel="stylesheet/stylus">
@import '../../themes/app.common.styl' @import '../../themes/app.common.styl'
#csc-conf-selected-participant-name
position absolute
top 20px
bottom 0
right 0
left 0
z-index 2
background-color $conf-participant-box-color
color $primary
font-size 20px
width 130px
height 45px
padding 10px
#csc-conf-main-media #csc-conf-main-media
position absolute position absolute
top 0 top 0

@ -2,14 +2,13 @@
<q-card <q-card
class="csc-conf-participant-cont" class="csc-conf-participant-cont"
> >
<q-card-media <q-icon
class="csc-avatar-cont" name="person"
v-if="!localMediaStream || localMediaStream && (!isCameraEnabled && !isScreenEnabled)" class="csc-conf-avatar"
> v-if="!localMediaStream || localMediaStream && (!isCameraEnabled && !isScreenEnabled)"
<img >
src="statics/avatar.png" </q-icon>
/>
</q-card-media>
<csc-media <csc-media
ref="cscMedia" ref="cscMedia"
v-show="localMediaStream && (isCameraEnabled || isScreenEnabled)" v-show="localMediaStream && (isCameraEnabled || isScreenEnabled)"
@ -26,25 +25,26 @@
</template> </template>
<script> <script>
import { QCard, QCardMedia, QCardTitle } from 'quasar-framework' import { QIcon, QCard, QCardMedia, QCardTitle } from 'quasar-framework'
import CscMedia from "../../CscMedia"; import CscMedia from "../../CscMedia";
export default { export default {
name: 'csc-conference-local-participant', name: 'csc-conference-local-participant',
components: { components: {
QCard, QIcon,
QCardMedia, QCard,
QCardTitle, QCardMedia,
CscMedia QCardTitle,
CscMedia
}, },
props: [ props: [
'localParticipant', 'localParticipant',
'localMediaStream', 'localMediaStream',
'isMicrophoneEnabled', 'isMicrophoneEnabled',
'isCameraEnabled', 'isCameraEnabled',
'isScreenEnabled' 'isScreenEnabled'
], ],
mounted(){ mounted(){
this.assignStream(this.localMediaStream); this.assignStream(this.localMediaStream);
}, },
methods: { methods: {
assignStream(stream) { assignStream(stream) {

@ -5,6 +5,7 @@
> >
<csc-conference-local-participant <csc-conference-local-participant
ref="localParticipant" ref="localParticipant"
@click.native="setSelectedParticipant('local')"
:local-participant="localParticipant" :local-participant="localParticipant"
:local-media-stream="localMediaStream" :local-media-stream="localMediaStream"
:is-microphone-enabled="isMicrophoneEnabled" :is-microphone-enabled="isMicrophoneEnabled"
@ -17,7 +18,7 @@
:key="participantId" :key="participantId"
> >
<csc-conference-remote-participant <csc-conference-remote-participant
:key="participantId" @click.native="setSelectedParticipant(participantId)"
:remote-participant="remoteParticipant(participantId)" :remote-participant="remoteParticipant(participantId)"
:has-remote-video="hasRemoteVideo(participantId)" :has-remote-video="hasRemoteVideo(participantId)"
:remote-media-stream="remoteMediaStream" :remote-media-stream="remoteMediaStream"
@ -60,7 +61,7 @@
'isScreenEnabled', 'isScreenEnabled',
'remoteParticipant', 'remoteParticipant',
'remoteMediaStream', 'remoteMediaStream',
'hasRemoteVideo' 'hasRemoteVideo',
]) ])
}, },
mounted() { mounted() {
@ -71,6 +72,9 @@
if(this.$refs.localParticipant) { if(this.$refs.localParticipant) {
this.$refs.localParticipant.assignStream(stream); this.$refs.localParticipant.assignStream(stream);
} }
},
setSelectedParticipant(participant){
this.$store.commit('conference/setSelectedParticipant', participant);
} }
}, },
watch: { watch: {

@ -2,14 +2,12 @@
<q-card <q-card
class="csc-conf-participant-cont" class="csc-conf-participant-cont"
> >
<q-card-media <q-icon
class="csc-avatar-cont" name="person"
v-show="!hasRemoteVideo" class="csc-conf-avatar"
v-if="!hasRemoteVideo"
> >
<img </q-icon>
src="statics/avatar.png"
/>
</q-card-media>
<csc-media <csc-media
v-show="hasRemoteVideo" v-show="hasRemoteVideo"
class="csc-media-cont" class="csc-media-cont"
@ -26,30 +24,48 @@
</template> </template>
<script> <script>
import {QCard, QCardMedia, QCardTitle} from 'quasar-framework' import { QIcon, QCard, QCardMedia, QCardTitle } from 'quasar-framework'
import CscMedia from "../../CscMedia"; import CscMedia from "../../CscMedia";
import {
mapGetters
} from 'vuex'
export default { export default {
name: 'csc-conference-remote-participant', name: 'csc-conference-remote-participant',
components: { components: {
QIcon,
QCard, QCard,
QCardMedia, QCardMedia,
QCardTitle, QCardTitle,
CscMedia CscMedia
}, },
data: function () {
return {
localMediaStream : null
}
},
props: [ props: [
'remoteParticipant', 'remoteParticipant',
'remoteMediaStream', 'remoteMediaStream',
'remoteMediaStreams', 'remoteMediaStreams',
'hasRemoteVideo', 'hasRemoteVideo',
], ],
computed: {
...mapGetters('conference', [
'selectedParticipant'
])
},
mounted() { mounted() {
this.assignStream(); this.assignStream(this);
}, },
methods: { methods: {
assignStream() { assignStream: (scope) => {
if (this.$refs.cscMedia && this.remoteMediaStreams[this.remoteParticipant.id] === this.remoteParticipant.id) { if (scope.$refs.cscMedia && scope.remoteMediaStreams[scope.remoteParticipant.id] === scope.remoteParticipant.id) {
this.$refs.cscMedia.assignStream(this.remoteMediaStream(this.remoteParticipant.id)); scope.localMediaStream = scope.remoteMediaStream(scope.remoteParticipant.id);
scope.$refs.cscMedia.assignStream(scope.localMediaStream);
if(scope.selectedParticipant == scope.remoteParticipant.id){
scope.$store.commit('conference/setSelectedParticipant', 'local'); // TODO improve (workaround to reset the mediaStream)
scope.$store.commit('conference/setSelectedParticipant', scope.remoteParticipant.id);
}
} }
else if (this.$refs.cscMedia) { else if (this.$refs.cscMedia) {
this.$refs.cscMedia.reset(); this.$refs.cscMedia.reset();
@ -58,7 +74,7 @@
}, },
watch: { watch: {
remoteMediaStreams() { remoteMediaStreams() {
this.assignStream(); this.assignStream(this);
} }
} }
} }

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

@ -25,7 +25,8 @@ export default {
leaveState: RequestState.initiated, leaveState: RequestState.initiated,
leaveError: null, leaveError: null,
participants: [], participants: [],
remoteMediaStreams: {} remoteMediaStreams: {},
selectedParticipant: null
}, },
getters: { getters: {
username(state, getters, rootState, rootGetters) { username(state, getters, rootState, rootGetters) {
@ -107,6 +108,9 @@ export default {
return participant.mediaStream ? participant.mediaStream.hasVideo() : false; return participant.mediaStream ? participant.mediaStream.hasVideo() : false;
} }
return false; return false;
},
selectedParticipant(state){
return state.selectedParticipant;
} }
}, },
mutations: { mutations: {
@ -169,6 +173,7 @@ export default {
state.joinState = RequestState.succeeded; state.joinState = RequestState.succeeded;
state.joinError = null; state.joinError = null;
state.leaveState = null; state.leaveState = null;
state.selectedParticipant = 'local'
}, },
joinFailed(state, error) { joinFailed(state, error) {
state.joinState = RequestState.failed; state.joinState = RequestState.failed;
@ -184,6 +189,7 @@ export default {
state.leaveError = null; state.leaveError = null;
state.joinState = RequestState.initiated; state.joinState = RequestState.initiated;
state.joinError = null; state.joinError = null;
state.selectedParticipant = null;
}, },
leaveFailed(state, error) { leaveFailed(state, error) {
state.leaveState = RequestState.failed; state.leaveState = RequestState.failed;
@ -199,10 +205,20 @@ export default {
}, },
participantLeft(state, participant) { participantLeft(state, participant) {
if(state.selectedParticipant == 'local' && !state.joinState === RequestState.succeeded){
state.selectedParticipant = null;
}
else if(state.selectedParticipant == participant.getId()){
state.selectedParticipant = 'local';
}
state.participants = state.participants.filter(($participant) => { state.participants = state.participants.filter(($participant) => {
return participant.getId() !== $participant; return participant.getId() !== $participant;
}); });
Vue.delete(state.remoteMediaStreams, participant.getId()); Vue.delete(state.remoteMediaStreams, participant.getId());
},
setSelectedParticipant(state, participant){
state.selectedParticipant = participant;
} }
}, },
actions: { actions: {

@ -129,6 +129,7 @@ export const store = new Vuex.Store({
}); });
}).onConferenceParticipantLeft((participant)=>{ }).onConferenceParticipantLeft((participant)=>{
store.commit('conference/participantLeft', participant); store.commit('conference/participantLeft', participant);
store.commit('conference/removeRemoteMedia', participant.id);
}).onConferenceEvent((event)=>{ }).onConferenceEvent((event)=>{
store.commit('conference/event', event); store.commit('conference/event', event);
}).onConferenceMessage((message)=>{ }).onConferenceMessage((message)=>{

@ -247,25 +247,50 @@ input.q-input-target
i i
margin 0 margin 0
.csc-conf-avatar
font-size 137px
color $conf-participant-icon-color
padding-left 11px
background-color transparent;
box-shadow none
position relative
width 100%
height 100%
top 0
bottom 0
left -22px
right 0
margin auto
.csc-conf-selected-avatar
width: 220px;
height: 220px;
font-size 220px
color $conf-participant-icon-color
background-color transparent;
box-shadow none
position absolute
top 0
bottom 0
left 0
right 0
margin auto
.csc-conf-participant-cont .csc-conf-participant-cont
margin-bottom 20px margin-bottom 20px
width 115px width 115px
height 115px height 115px
background white background $conf-participant-box-color
cursor pointer
.csc-conf-participants-item-title .csc-conf-participants-item-title
position relative position relative
background white background white
bottom 24px bottom 24px
text-align center text-align center
padding 0 padding 0
background rgba(0, 0, 0, 0.5) background $conf-participant-box-color
.q-card-title .q-card-title
color $primary color $primary
font-size 12px font-size 12px
line-height 24px line-height 24px
.csc-avatar-cont
height 100%
top 6px
.csc-media-cont .csc-media-cont
height 100% !important height 100% !important
width 100% !important width 100% !important

@ -94,3 +94,6 @@ $popover-box-shadow = $shadow-1
$loading-background = $main-menu-background $loading-background = $main-menu-background
$chip-color = $dark $chip-color = $dark
$conf-participant-icon-color = rgba(255, 255, 255, 0.6);
$conf-participant-box-color = rgba(21,29,48,0.8);

Loading…
Cancel
Save