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

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

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

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

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

@ -1,4 +1,3 @@
import Vue from 'vue' import Vue from 'vue'
import { import {
RequestState RequestState
@ -26,7 +25,7 @@ export default {
leaveState: RequestState.initiated, leaveState: RequestState.initiated,
leaveError: null, leaveError: null,
participants: [], participants: [],
remoteMediaStreams: [] remoteMediaStreams: {}
}, },
getters: { getters: {
username(state, getters, rootState, rootGetters) { username(state, getters, rootState, rootGetters) {
@ -88,9 +87,9 @@ export default {
remoteParticipant: () => (participantId) => { remoteParticipant: () => (participantId) => {
return Vue.$conference.getRemoteParticipant(participantId); return Vue.$conference.getRemoteParticipant(participantId);
}, },
remoteMediaStream: (state) => (participantId) => { remoteMediaStream: () => (participantId) => {
if(state.remoteMediaStreams.includes(participantId)){
const participant = Vue.$conference.getRemoteParticipant(participantId); const participant = Vue.$conference.getRemoteParticipant(participantId);
if(participant !== null) {
return participant.mediaStream ? participant.mediaStream.getStream() : null; return participant.mediaStream ? participant.mediaStream.getStream() : null;
} }
return null; return null;
@ -102,10 +101,13 @@ export default {
remoteMediaStreams(state) { remoteMediaStreams(state) {
return state.remoteMediaStreams; return state.remoteMediaStreams;
}, },
hasRemoteMediaStream: (state) => (participantId) => { hasRemoteVideo: () => (participantId) => {
return state.remoteMediaStreams.includes(participantId) const participant = Vue.$conference.getRemoteParticipant(participantId);
if(participant !== null) {
return participant.mediaStream ? participant.mediaStream.hasVideo() : false;
}
return false;
} }
}, },
mutations: { mutations: {
enableConferencing(state) { enableConferencing(state) {
@ -154,18 +156,10 @@ export default {
state.screenEnabled = false; state.screenEnabled = false;
}, },
addRemoteMedia(state, participantId) { addRemoteMedia(state, participantId) {
if(state.remoteMediaStreams.includes(participantId)){ Vue.set(state.remoteMediaStreams, participantId, participantId);
state.remoteMediaStreams = state.remoteMediaStreams.filter(($participant)=>{
return participantId !== $participant;
});
}
state.remoteMediaStreams.push(participantId);
}, },
removeRemoteMedia(state, participant) { removeRemoteMedia(state, participantId) {
state.remoteMediaStreams = state.remoteMediaStreams.filter(($participant)=>{ Vue.delete(state.remoteMediaStreams, participantId);
return participant !== $participant;
});
}, },
joinRequesting(state) { joinRequesting(state) {
state.joinState = RequestState.requesting; state.joinState = RequestState.requesting;
@ -201,12 +195,14 @@ export default {
return participant.getId() !== $participant; return participant.getId() !== $participant;
}); });
} }
state.participants.push(participant.getId()) state.participants.push(participant.getId());
}, },
participantLeft(state, participant) { participantLeft(state, participant) {
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());
} }
}, },
actions: { actions: {

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

Loading…
Cancel
Save