TT#44287 Conferencing - display the list of participants

- list of participants as column (subtask #67600)
- participant displayName added (subtask #67605)
- participants list scrollable (subtask #67601)
- self participant item on top of the list (subtask #67602)
- self participant fixed position on top of the list (subtask #67603)
- placeholder icon if video not shared by participant (subtask #67309)
- show/hear participants (subtask #67604)

Change-Id: I44836b67767d243554e674e76486681865931f84
changes/90/33890/19
Carlo Venusino 6 years ago committed by Hans-Peter Herzog
parent 37909a6bcc
commit 986d954a77

@ -43,7 +43,6 @@
],
data () {
return {
currentStream: null,
loading: true,
mediaHeight: 0,
mediaWidth: 0,
@ -57,6 +56,10 @@
this.$root.$on('window-resized', fitMedia);
this.$root.$on('content-resized', fitMedia);
this.$root.$on('orientation-changed', fitMedia);
this.$refs.media.addEventListener('playing', ()=>{
this.loading = false;
this.fitMedia();
});
},
components: {
QSpinnerDots,
@ -64,41 +67,19 @@
},
methods: {
assignStream(stream) {
if(stream !== this.currentStream && stream !== null && stream !== undefined) {
if(stream !== null && stream !== undefined) {
this.loading = true;
this.currentStream = stream;
if(_.isObject(this.currentStream) && _.isObject(this.$refs.media) &&
if(_.isObject(stream) && _.isObject(this.$refs.media) &&
!_.isUndefined(this.$refs.media.srcObject)) {
this.$refs.media.srcObject = this.currentStream;
this.$refs.media.srcObject = stream;
}
else if(_.isObject(this.currentStream) && _.isObject(this.$refs.media) &&
else if(_.isObject(stream) && _.isObject(this.$refs.media) &&
!_.isUndefined(this.$refs.media.mozSrcObject)) {
this.$refs.media.mozSrcObject = this.currentStream;
this.$refs.media.mozSrcObject = stream;
}
else if(_.isObject(this.currentStream) && _.isObject(this.$refs.media) &&
else if(_.isObject(stream) && _.isObject(this.$refs.media) &&
_.isObject(URL) && _.isFunction(URL.createObjectURL)) {
this.$refs.media.src = URL.createObjectURL(this.currentStream);
}
let timer = setInterval(()=>{
if(this.currentStream !== null && (this.$refs.media && (this.$refs.media.currentTime > 0 ||
this.$refs.media.readyState > 2))) {
this.loading = false;
clearInterval(timer);
this.fitMedia();
}
}, 100);
}
else {
this.loading = false;
this.currentStream = null;
if(this.$refs.media.srcObject) {
this.$refs.media.srcObject = null;
}
else if(this.$refs.media.mozSrcObject) {
this.$refs.media.mozSrcObject = null;
}
else {
this.$refs.media.src = null;
this.$refs.media.src = URL.createObjectURL(stream);
}
}
},
@ -192,10 +173,6 @@
}
},
computed: {
hasVideo() {
return this.currentStream !== null && _.isArray(this.currentStream.getVideoTracks()) &&
this.currentStream.getVideoTracks().length > 0;
},
componentClasses(){
return ['csc-media'];
},

@ -143,8 +143,7 @@
'localMediaStream',
'participantsList',
'remoteMediaStream',
'remoteMediaStreams',
'hasRemoteMediaStream'
'remoteMediaStreams'
]),
microphoneButtonColor() {
if(this.isMicrophoneEnabled) {

@ -14,7 +14,6 @@
class="csc-media-cont"
:muted="true"
:stream="localMediaStream"
:preview="true"
/>
<q-card-title
class="csc-conf-participants-item-title"
@ -26,7 +25,6 @@
<script>
import { QCard, QCardMedia, QCardTitle } from 'quasar-framework'
// import { mapGetters } from 'vuex'
import CscMedia from "../../CscMedia";
export default {
name: 'csc-conference-local-participant',

@ -13,9 +13,15 @@
/>
<div
id="csc-conf-remote-participants-cont"
v-for="participantId in participantsList" :key="participantId">
v-for="participantId in participantsList"
:key="participantId"
>
<csc-conference-remote-participant
:participant-id="participantId"
:key="participantId"
:remote-participant="remoteParticipant(participantId)"
:has-remote-video="hasRemoteVideo(participantId)"
:remote-media-stream="remoteMediaStream"
:remote-media-streams="remoteMediaStreams"
/>
</div>
</div>
@ -23,7 +29,10 @@
<script>
import {QCard, QCardMedia, QCardTitle} from 'quasar-framework'
import {mapGetters} from 'vuex'
import {
mapGetters,
mapState,
} from 'vuex'
import CscMedia from "../../CscMedia";
import CscConferenceRemoteParticipant from './CscConferenceRemoteParticipant'
import CscConferenceLocalParticipant from './CscConferenceLocalParticipant'
@ -39,13 +48,19 @@
CscConferenceLocalParticipant
},
computed: {
...mapState('conference', [
'remoteMediaStreams'
]),
...mapGetters('conference', [
'participantsList',
'localParticipant',
'localMediaStream',
'isMicrophoneEnabled',
'isCameraEnabled',
'isScreenEnabled'
'isScreenEnabled',
'remoteParticipant',
'remoteMediaStream',
'hasRemoteVideo'
])
},
mounted() {

@ -4,30 +4,29 @@
>
<q-card-media
class="csc-avatar-cont"
v-show="!hasRemoteMediaStream(participantId)"
v-show="!hasRemoteVideo"
>
<img src="/statics/avatar.png">
</q-card-media>
<csc-media
v-show="hasRemoteMediaStream(participantId)"
v-show="hasRemoteVideo"
class="csc-media-cont"
ref="{{participantId}}"
ref="cscMedia"
:muted="false"
:stream="remoteMediaStream(participantId)"
:preview="true"
/>
<q-card-title
class="csc-conf-participants-item-title"
>
{{remoteParticipant(participantId).displayName}}
{{ remoteParticipant.displayName }}
</q-card-title>
</q-card>
</template>
<script>
import { QCard, QCardMedia, QCardTitle } from 'quasar-framework'
import { mapGetters } from 'vuex'
import {QCard, QCardMedia, QCardTitle} from 'quasar-framework'
import CscMedia from "../../CscMedia";
export default {
name: 'csc-conference-remote-participant',
components: {
@ -36,18 +35,25 @@
QCardTitle,
CscMedia
},
props:['participantId'],
computed: {
...mapGetters('conference', [
props: [
'remoteParticipant',
'remoteMediaStream',
'hasRemoteMediaStream'
])
'remoteMediaStreams',
'hasRemoteVideo',
],
mounted() {
this.assignStream();
},
methods: {
assignStream() {
if (this.$refs.cscMedia && this.remoteMediaStreams[this.remoteParticipant.id] === this.remoteParticipant.id) {
this.$refs.cscMedia.assignStream(this.remoteMediaStream(this.remoteParticipant.id));
}
}
},
mounted(){
// workaround to retrigger :stream binding on csc-media component
if(this.hasRemoteMediaStream(this.participantId)){
this.$store.commit('conference/addRemoteMedia', this.participantId);
watch: {
remoteMediaStreams() {
this.assignStream();
}
}
}

@ -1,4 +1,3 @@
import Vue from 'vue'
import {
RequestState
@ -26,7 +25,7 @@ export default {
leaveState: RequestState.initiated,
leaveError: null,
participants: [],
remoteMediaStreams: []
remoteMediaStreams: {}
},
getters: {
username(state, getters, rootState, rootGetters) {
@ -70,7 +69,7 @@ export default {
state.localMediaState === RequestState.requesting) && Vue.$conference.hasLocalMediaStream();
},
localMediaStream(state) {
if((state.localMediaState === RequestState.succeeded ||
if ((state.localMediaState === RequestState.succeeded ||
state.localMediaState === RequestState.requesting) && Vue.$conference.hasLocalMediaStream()) {
return Vue.$conference.getLocalMediaStreamNative();
}
@ -81,16 +80,16 @@ export default {
state.localMediaState === RequestState.requesting) && Vue.$conference.hasLocalMediaStream();
},
localParticipant(state) {
if(state.joinState === RequestState.succeeded){
if (state.joinState === RequestState.succeeded) {
return Vue.$conference.getLocalParticipant();
}
},
remoteParticipant: () => (participantId) => {
return Vue.$conference.getRemoteParticipant(participantId);
},
remoteMediaStream: (state) => (participantId) => {
if(state.remoteMediaStreams.includes(participantId)){
remoteMediaStream: () => (participantId) => {
const participant = Vue.$conference.getRemoteParticipant(participantId);
if(participant !== null) {
return participant.mediaStream ? participant.mediaStream.getStream() : null;
}
return null;
@ -102,10 +101,13 @@ export default {
remoteMediaStreams(state) {
return state.remoteMediaStreams;
},
hasRemoteMediaStream: (state) => (participantId) => {
return state.remoteMediaStreams.includes(participantId)
hasRemoteVideo: () => (participantId) => {
const participant = Vue.$conference.getRemoteParticipant(participantId);
if(participant !== null) {
return participant.mediaStream ? participant.mediaStream.hasVideo() : false;
}
return false;
}
},
mutations: {
enableConferencing(state) {
@ -154,18 +156,10 @@ export default {
state.screenEnabled = false;
},
addRemoteMedia(state, participantId) {
if(state.remoteMediaStreams.includes(participantId)){
state.remoteMediaStreams = state.remoteMediaStreams.filter(($participant)=>{
return participantId !== $participant;
});
}
state.remoteMediaStreams.push(participantId);
Vue.set(state.remoteMediaStreams, participantId, participantId);
},
removeRemoteMedia(state, participant) {
state.remoteMediaStreams = state.remoteMediaStreams.filter(($participant)=>{
return participant !== $participant;
});
removeRemoteMedia(state, participantId) {
Vue.delete(state.remoteMediaStreams, participantId);
},
joinRequesting(state) {
state.joinState = RequestState.requesting;
@ -196,24 +190,26 @@ export default {
state.leaveError = error;
},
participantJoined(state, participant) {
if(state.participants.includes(participant.getId())){
state.participants = state.participants.filter(($participant)=>{
if (state.participants.includes(participant.getId())) {
state.participants = state.participants.filter(($participant) => {
return participant.getId() !== $participant;
});
}
state.participants.push(participant.getId())
state.participants.push(participant.getId());
},
participantLeft(state, participant) {
state.participants = state.participants.filter(($participant)=>{
state.participants = state.participants.filter(($participant) => {
return participant.getId() !== $participant;
});
Vue.delete(state.remoteMediaStreams, participant.getId());
}
},
actions: {
createLocalMedia(context, type) {
let media = Vue.$rtcEngine.createMedia();
context.commit('localMediaRequesting');
switch(type) {
switch (type) {
default:
case MediaTypes.mic:
media.enableMicrophone();
@ -234,13 +230,13 @@ export default {
break;
}
let localMediaStream;
return media.build().then(($localMediaStream)=>{
return media.build().then(($localMediaStream) => {
localMediaStream = $localMediaStream;
localMediaStream.onVideoEnded(()=>{
localMediaStream.onVideoEnded(() => {
context.dispatch('createLocalMedia', MediaTypes.mic);
});
Vue.$conference.setLocalMediaStream(localMediaStream);
switch(type) {
switch (type) {
default:
case MediaTypes.mic:
context.commit('enableMicrophone');
@ -269,43 +265,43 @@ export default {
break;
}
return Promise.resolve();
}).then(()=>{
if(context.getters.isJoined) {
}).then(() => {
if (context.getters.isJoined) {
return Vue.$conference.changeConferenceMedia();
}
else {
return Promise.resolve();
}
}).then(()=>{
}).then(() => {
context.commit('localMediaSucceeded', localMediaStream);
}).catch((err)=>{
if(!context.getters.hasLocalMediaStream) {
}).catch((err) => {
if (!context.getters.hasLocalMediaStream) {
context.commit('localMediaFailed', err.message);
}
});
},
async enableMicrophone(context) {
if(!context.getters.isLocalMediaRequesting) {
if (!context.getters.isLocalMediaRequesting) {
let mediaType = MediaTypes.mic;
if(context.getters.isCameraEnabled) {
if (context.getters.isCameraEnabled) {
mediaType = MediaTypes.micCam;
}
else if(context.getters.isScreenEnabled) {
else if (context.getters.isScreenEnabled) {
mediaType = MediaTypes.micScreen;
}
await context.dispatch('createLocalMedia', mediaType);
}
},
disableMicrophone(context) {
if(!context.getters.isLocalMediaRequesting) {
if (!context.getters.isLocalMediaRequesting) {
let mediaType = null;
if(context.getters.isCameraEnabled) {
if (context.getters.isCameraEnabled) {
mediaType = MediaTypes.cam;
}
else if(context.getters.isScreenEnabled) {
else if (context.getters.isScreenEnabled) {
mediaType = MediaTypes.screen;
}
if(mediaType === null) {
if (mediaType === null) {
context.commit('disposeLocalMedia');
}
else {
@ -314,7 +310,7 @@ export default {
}
},
toggleMicrophone(context) {
if(!context.getters.isMicrophoneEnabled) {
if (!context.getters.isMicrophoneEnabled) {
context.dispatch('enableMicrophone');
}
else {
@ -322,17 +318,17 @@ export default {
}
},
enableCamera(context) {
if(!context.getters.isLocalMediaRequesting) {
if (!context.getters.isLocalMediaRequesting) {
context.dispatch('createLocalMedia', MediaTypes.micCam);
}
},
disableCamera(context) {
if(!context.getters.isLocalMediaRequesting) {
if (!context.getters.isLocalMediaRequesting) {
let mediaType = null;
if(context.getters.isMicrophoneEnabled) {
if (context.getters.isMicrophoneEnabled) {
mediaType = MediaTypes.mic;
}
if(mediaType === null) {
if (mediaType === null) {
context.commit('disposeLocalMedia');
}
else {
@ -341,7 +337,7 @@ export default {
}
},
toggleCamera(context) {
if(!context.getters.isCameraEnabled) {
if (!context.getters.isCameraEnabled) {
context.dispatch('enableCamera');
}
else {
@ -349,17 +345,17 @@ export default {
}
},
enableScreen(context) {
if(!context.getters.isLocalMediaRequesting) {
if (!context.getters.isLocalMediaRequesting) {
context.dispatch('createLocalMedia', MediaTypes.micScreen);
}
},
disableScreen(context) {
if(!context.getters.isLocalMediaRequesting) {
if (!context.getters.isLocalMediaRequesting) {
let mediaType = null;
if(context.getters.isMicrophoneEnabled) {
if (context.getters.isMicrophoneEnabled) {
mediaType = MediaTypes.mic;
}
if(mediaType === null) {
if (mediaType === null) {
context.commit('disposeLocalMedia');
}
else {
@ -368,7 +364,7 @@ export default {
}
},
toggleScreen(context) {
if(!context.getters.isScreenEnabled) {
if (!context.getters.isScreenEnabled) {
context.dispatch('enableScreen');
}
else {
@ -376,25 +372,25 @@ export default {
}
},
join(context, conferenceId) {
if(context.getters.hasLocalMediaStream) {
if (context.getters.hasLocalMediaStream) {
context.commit('joinRequesting');
Vue.$conference.joinConference({
conferenceName: conferenceId,
displayName: context.getters.username
}).then(()=>{
}).then(() => {
context.commit('joinSucceeded');
}).catch((err)=>{
}).catch((err) => {
context.commit('joinFailed', err.message);
});
}
},
leave(context) {
if(context.getters.isJoined) {
if (context.getters.isJoined) {
context.commit('leaveRequesting');
Vue.$conference.leaveConference().then(()=>{
Vue.$conference.leaveConference().then(() => {
context.commit('leaveSucceeded');
context.commit('disposeLocalMedia');
}).catch((err)=>{
}).catch((err) => {
context.commit('leaveFailed', err.message);
});
}

@ -122,18 +122,11 @@ export const store = new Vuex.Store({
}).onConferenceParticipantJoined((participant)=>{
store.commit('conference/participantJoined', participant);
participant.onMediaStream(()=>{
store.commit('conference/removeRemoteMedia', participant.id);
store.commit('conference/addRemoteMedia', participant.id);
})
participant.onMediaStarted(()=>{
store.commit('conference/addRemoteMedia', participant.id);
})
participant.onMediaRecovered(()=>{
store.commit('conference/addRemoteMedia', participant.id);
})
participant.onMediaEnded(()=>{
}).onMediaEnded(()=>{
store.commit('conference/removeRemoteMedia', participant.id);
})
});
}).onConferenceParticipantLeft((participant)=>{
store.commit('conference/participantLeft', participant);
}).onConferenceEvent((event)=>{

Loading…
Cancel
Save