TT#55900 Conferencing: As a Customer, I want to join the conference

Change-Id: I99ae967ed8e2cd9cb5b0035c13901b71ae80faa1
changes/15/28815/5
Hans-Peter Herzog 7 years ago
parent 022b9f27f1
commit fe50cda4ee

File diff suppressed because it is too large Load Diff

@ -4,7 +4,7 @@
minimized
>
<div
class="csc-dialog"
class="csc-dialog csc-share-dialog"
>
<div
class="csc-dialog-title"
@ -98,6 +98,8 @@
<style lang="stylus" rel="stylesheet/stylus">
@import '../themes/quasar.variables.styl'
.csc-share-dialog
min-width 480px
.csc-dialog
max-width 480px
background-color $body-background

@ -0,0 +1,33 @@
<template>
<q-inner-loading
:visible="loading">
<q-spinner-dots
size="32px"
color="white"
/>
</q-inner-loading>
</template>
<script>
import {
QSpinnerDots,
QInnerLoading
} from 'quasar-framework'
export default {
name: 'csc-object-spinner',
data () {
return {}
},
components: {
QSpinnerDots,
QInnerLoading
},
props: [
'loading'
]
}
</script>
<style lang="stylus" rel="stylesheet/stylus">
@import '../themes/quasar.variables.styl'
</style>

@ -24,21 +24,25 @@
<csc-conference-join
id="csc-conf-join"
v-if="!isJoined"
:conferenceId="conferenceId"
:conference-id="conferenceId"
:has-conference-id="hasConferenceId"
:conference-url="conferenceUrl"
:local-media-stream="localMediaStream"
:is-microphone-enabled="isMicrophoneEnabled"
:is-camera-enabled="isCameraEnabled"
:is-screen-enabled="isScreenEnabled"
:is-media-enabled="isMediaEnabled"
:is-joining="isJoining"
:is-joined="isJoined"
@join="join"
/>
<csc-conference-joined
v-else
>
</csc-conference-joined>
v-if="!isJoining && isJoined"
/>
</div>
<div
id="csc-conf-main-media"
v-show="isMediaEnabled && isCameraEnabled"
v-show="isMediaEnabled && (isCameraEnabled || isScreenEnabled)"
>
<csc-media
ref="localMedia"
@ -60,6 +64,7 @@
icon="mic"
round
@click="toggleMicrophone()"
:disable="!hasConferenceId || isJoining"
/>
<q-btn
class="csc-conf-button"
@ -67,6 +72,7 @@
icon="videocam"
round
@click="toggleCamera()"
:disable="!hasConferenceId || isJoining"
/>
<q-btn
class="csc-conf-button"
@ -74,8 +80,10 @@
icon="computer"
round
@click="toggleScreen()"
:disable="!hasConferenceId || isJoining"
/>
</div>
</div>
</q-layout>
</template>
@ -87,6 +95,7 @@
import CscConferenceJoin from '../pages/Conference/CscConferenceJoin'
import CscConferenceJoined from '../pages/Conference/CscConferenceJoined'
import CscMedia from "../CscMedia";
import CscSpinner from "../CscSpinner";
import {
QLayout,
QBtn
@ -99,6 +108,7 @@
this.$store.dispatch('user/initUser');
},
components: {
CscSpinner,
CscMedia,
CscConferenceJoin,
CscConferenceJoined,
@ -106,12 +116,13 @@
QBtn
},
computed: {
...mapGetters([
'conferenceId'
]),
...mapGetters('conference', [
'conferenceId',
'conferenceUrl',
'hasConferenceId',
'isConferencingEnabled',
'isJoined',
'isJoining',
'isMicrophoneEnabled',
'isCameraEnabled',
'isScreenEnabled',
@ -149,13 +160,31 @@
this.$store.commit('conference/disposeLocalMedia');
},
toggleMicrophone() {
this.$store.dispatch('conference/toggleMicrophone');
if(this.hasConferenceId) {
this.$store.dispatch('conference/toggleMicrophone');
}
},
toggleCamera() {
this.$store.dispatch('conference/toggleCamera');
if(this.hasConferenceId) {
this.$store.dispatch('conference/toggleCamera');
}
},
toggleScreen() {
this.$store.dispatch('conference/toggleScreen');
if(this.hasConferenceId) {
this.$store.dispatch('conference/toggleScreen');
}
},
join(conferenceId) {
if(this.hasConferenceId) {
this.$store.dispatch('conference/join', conferenceId);
}
}
},
watch: {
hasConferenceId(value) {
if(!value) {
this.$store.commit('conference/disposeLocalMedia');
}
}
}
}

@ -2,9 +2,10 @@
<div
class="row justify-center items-center csc-conf-full-height"
>
<div
id="csc-conf-join-content"
class="col col-4 text-center"
:class="contentClasses"
>
<p
id="csc-conf-join-text"
@ -13,18 +14,36 @@
ref="conferenceName"
id="csc-conf-link-input"
dark
:value="conferenceId"
align="center"
readonly
:after="conferenceNameButtons"
/>
:value="conferenceIdInput"
placeholder="Conference name"
align="left"
@change="conferenceIdChanged"
:disable="isJoining"
>
<q-btn
:disable="!hasConferenceId || isJoining"
:color="shareButtonColor"
flat
icon="link"
@click="showShareDialog"
>Share</q-btn>
</q-input>
<q-btn
class="csc-button"
color="primary"
:color="joinButtonColor"
:disable="!(isMediaEnabled && hasConferenceId) || isJoining"
icon="call"
round
@click="join"
/>
<csc-object-spinner
:loading="isJoining"
/>
</div>
<csc-share-conference-dialog
ref="shareDialog"
:conference-url="conferenceUrl"
/>
</div>
</template>
@ -34,48 +53,86 @@
QBtn,
QInput
} from 'quasar-framework'
import CscShareConferenceDialog from "./CscShareConferenceDialog";
import CscObjectSpinner from "../../CscObjectSpinner";
export default {
name: 'csc-conference-join',
data () {
return {}
return {
conferenceIdInput: this.conferenceId
}
},
props: [
'conferenceId',
'hasConferenceId',
'conferenceUrl',
'localMediaStream',
'isMicrophoneEnabled',
'isCameraEnabled',
'isScreenEnabled',
'isMediaEnabled'
'isJoining',
'isJoined'
],
components: {
CscObjectSpinner,
CscShareConferenceDialog,
QBtn,
QInput,
CscMedia
},
computed: {
conferenceNameButtons() {
let buttons = [];
let self = this;
buttons.push({
icon: 'link',
error: false,
handler (event) {
event.stopPropagation();
self.copyLinkToClipboard();
}
}
);
return buttons;
contentClasses() {
let classes = ['col', 'col-4', 'text-center'];
if(this.isCameraEnabled) {
classes.push('csc-camera-background');
}
else if (this.isScreenEnabled) {
classes.push('csc-screen-background');
}
return classes;
},
joinButtonColor() {
if(this.isMediaEnabled && this.hasConferenceId) {
return 'primary';
}
else {
return 'grey';
}
},
conferenceLinkValue() {
return window.location.href;
shareButtonColor() {
if(this.hasConferenceId) {
return 'primary';
}
else {
return 'grey';
}
},
isMediaEnabled() {
return this.isCameraEnabled || this.isScreenEnabled || this.isMicrophoneEnabled;
}
},
watch: {
conferenceId(value) {
this.conferenceIdInput = value;
}
},
methods:{
copyLinkToClipboard() {
join() {
this.$emit('join', this.conferenceId);
},
conferenceIdChanged(value) {
try {
this.$router.push({
path: '/conference/' + value
});
this.conferenceIdInput = value;
}
catch(err) {
this.conferenceIdInput = this.conferenceId;
}
},
showShareDialog() {
this.$refs.shareDialog.open();
}
}
}
@ -90,6 +147,11 @@
font-weight bold
font-size 1rem
#csc-conf-join-content
padding $flex-gutter-md
position relative
z-index 2
#csc-conf-join-content.csc-camera-background
background-color alpha($main-menu-background, 0.5)
#csc-conf-join-content.csc-screen-background
background-color alpha($main-menu-background, 0.5)
</style>

@ -0,0 +1,77 @@
<template>
<csc-dialog
ref="dialogComp"
:title="'Share conference'"
:titleIcon="'link'"
>
<div
slot="content"
>
<q-input
ref="conferenceUrlInput"
:value="conferenceUrl"
dark
readonly
@focus="selectConferenceUrl"
/>
</div>
<q-btn
slot="actions"
color="primary"
flat
icon="link"
@click="copy"
>
Copy link
</q-btn>
</csc-dialog>
</template>
<script>
import {
QBtn,
QInput
} from 'quasar-framework'
import CscDialog from '../../CscDialog'
export default {
name: 'csc-share-conference-dialog',
data () {
return {
}
},
props: [
'conferenceUrl'
],
components: {
QBtn,
QInput,
CscDialog
},
mounted() {
this.selectConferenceUrl();
},
methods: {
open() {
this.$refs.dialogComp.open();
this.$nextTick(()=>{
this.$refs.conferenceUrlInput.focus();
});
},
close() {
this.$refs.dialogComp.close();
},
copy() {
this.$refs.conferenceUrlInput.select();
document.execCommand('copy');
this.close();
},
selectConferenceUrl() {
this.$refs.conferenceUrlInput.select();
}
},
computed: {}
}
</script>
<style lang="stylus" rel="stylesheet/stylus">
</style>

@ -8,26 +8,83 @@ export class ConferencePlugin {
constructor() {
this.events = new EventEmitter();
this.rtcEngine = null;
this.conference = null;
this.localMediaStream = null;
}
setRtcEngine(rtcEngine) {
if(this.rtcEngine === null) {
this.rtcEngine = rtcEngine;
this.rtcEngine.onConferenceNetworkConnected(()=>{
this.rtcEngine.onConferenceNetworkConnected((network)=>{
this.events.emit('connected');
network
.onConferenceParticipantJoined((participant)=>{
this.events.emit('participantJoined', participant);
})
.onConferenceParticipantLeft((participant)=>{
this.events.emit('participantLeft', participant);
})
.onConferenceEvent((event)=>{
this.events.emit('conferenceEvent', event);
})
.onConferenceMessage((message)=>{
this.events.emit('conferenceMessage', message);
})
.onConferenceFile((file)=>{
this.events.emit('conferenceFile', file);
});
}).onConferenceNetworkDisconnected(()=>{
this.events.emit('disconnected');
});
}
}
onConnected(listener) {
this.events.on('connected', listener);
getNetwork() {
return this.rtcEngine.getConferenceNetwork();
}
joinConference(options) {
return new Promise((resolve, reject)=>{
this.getNetwork().joinConference(options).then((conference)=>{
resolve(conference);
}).catch((err)=>{
reject(err);
});
});
}
onLeft(listener) {
this.events.on('left', listener);
return this;
}
onConferenceParticipantJoined(listener) {
this.events.on('participantJoined', listener);
return this;
}
onConferenceParticipantLeft(listener) {
this.events.on('participantLeft', listener);
return this;
}
onConferenceEvent(listener) {
this.events.on('conferenceEvent', listener);
return this;
}
onConferenceMessage(listener) {
this.events.on('conferenceMessage', listener);
return this;
}
onConferenceFile(listener) {
this.events.on('conferenceFile', listener);
return this;
}
onDisconnected(listener) {
this.events.on('disconnected', listener);
onError(listener) {
this.events.on('error', listener);
return this;
}
@ -37,6 +94,40 @@ export class ConferencePlugin {
}
return conferencePlugin;
}
setLocalMediaStream(localMediaStream) {
this.removeLocalMediaStream();
this.localMediaStream = localMediaStream;
}
getLocalMediaStream() {
return this.localMediaStream;
}
hasLocalMediaStream() {
return this.localMediaStream !== null;
}
removeLocalMediaStream() {
if(this.hasLocalMediaStream()) {
this.getLocalMediaStream().stop();
}
}
getLocalMediaStreamNative() {
if(this.hasLocalMediaStream()) {
return this.getLocalMediaStream().getStream();
}
return null;
}
getLocalParticipant() {
this.conference.getLocalParticipant();
}
getRemoteParticipant(id) {
this.conference.getRemoteParticipant(id);
}
}
export default {

@ -13,6 +13,10 @@ export class RtcEnginePlugin {
constructor() {
this.script = null;
/**
*
* @type {cdk.Client}
*/
this.client = null;
this.sessionToken = null;
this.ngcpApiJwt = null;
@ -151,6 +155,10 @@ export class RtcEnginePlugin {
return this;
}
getConferenceNetwork() {
return this.client.getNetworkByTag('conference');
}
static getInstance() {
if(rtcEnginePlugin === null) {
rtcEnginePlugin = new RtcEnginePlugin();

@ -21,12 +21,28 @@ export default {
screenEnabled: false,
localMediaState: RequestState.initiated,
localMediaError: null,
localMediaStream: null,
remoteMediaStreams: []
joinState: RequestState.initiated,
joinError: null,
participants: []
},
getters: {
isJoined() {
return false;
username(state, getters, rootState, rootGetters) {
return rootGetters['user/getUsername'];
},
conferenceId(state, getters, rootState, rootGetters) {
return rootGetters['conferenceId'];
},
conferenceUrl(state, getters, rootState, rootGetters) {
return rootGetters['conferenceUrl'];
},
hasConferenceId(state, getters, rootState, rootGetters) {
return rootGetters['hasConferenceId'];
},
isJoined(state) {
return state.joinState === RequestState.succeeded;
},
isJoining(state) {
return state.joinState === RequestState.requesting;
},
isConferencingEnabled(state) {
return state.conferencingEnabled;
@ -41,13 +57,19 @@ export default {
return state.screenEnabled;
},
isMediaEnabled(state) {
return state.localMediaStream !== null;
return (state.localMediaState === RequestState.succeeded ||
state.localMediaState === RequestState.requesting) && Vue.$conference.hasLocalMediaStream();
},
localMediaStream(state) {
if(state.localMediaStream !== null) {
return state.localMediaStream.getStream();
if((state.localMediaState === RequestState.succeeded ||
state.localMediaState === RequestState.requesting) && Vue.$conference.hasLocalMediaStream()) {
return Vue.$conference.getLocalMediaStreamNative();
}
return null;
},
hasLocalMediaStream(state) {
return (state.localMediaState === RequestState.succeeded ||
state.localMediaState === RequestState.requesting) && Vue.$conference.hasLocalMediaStream();
}
},
mutations: {
@ -79,13 +101,8 @@ export default {
state.localMediaState = RequestState.requesting;
state.localMediaError = null;
},
localMediaSucceeded(state, localMediaStream) {
if(state.localMediaStream !== null) {
state.localMediaStream.stop();
state.localMediaStream = null;
}
localMediaSucceeded(state) {
state.localMediaState = RequestState.succeeded;
state.localMediaStream = localMediaStream;
state.localMediaError = null;
},
localMediaFailed(state, error) {
@ -96,13 +113,31 @@ export default {
return state.localMediaState === RequestState.requesting;
},
disposeLocalMedia(state) {
if(state.localMediaStream !== null) {
state.localMediaStream.stop();
state.localMediaStream = null;
state.cameraEnabled = false;
state.microphoneEnabled = false;
state.screenEnabled = false;
}
Vue.$conference.removeLocalMediaStream();
state.cameraEnabled = false;
state.microphoneEnabled = false;
state.screenEnabled = false;
},
joinRequesting(state) {
state.joinState = RequestState.requesting;
state.joinError = null;
},
joinSucceeded(state) {
state.joinState = RequestState.succeeded;
state.joinError = null;
},
joinFailed(state, error) {
state.joinState = RequestState.failed;
state.joinError = error;
},
participantJoined(state, participant) {
state.participants.push(participant.getId());
},
participantLeft(state, participant) {
state.participants = state.participants.filter(($participant)=>{
return participant.getId() !== $participant.getId();
});
}
},
actions: {
@ -130,6 +165,7 @@ export default {
break;
}
media.build().then((localMediaStream)=>{
Vue.$conference.setLocalMediaStream(localMediaStream);
context.commit('localMediaSucceeded', localMediaStream);
switch(type) {
default:
@ -253,6 +289,20 @@ export default {
else {
context.dispatch('disableScreen');
}
},
join(context, conferenceId) {
if(context.getters.hasLocalMediaStream) {
context.commit('joinRequesting');
Vue.$conference.joinConference({
conferenceName: conferenceId,
displayName: context.getters.username,
localMediaStream: context.getters.localMediaStream
}).then(()=>{
context.commit('joinSucceeded');
}).catch((err)=>{
context.commit('joinFailed', err.message);
});
}
}
}
}

@ -45,6 +45,13 @@ export const store = new Vuex.Store({
conferenceId(state) {
return _.get(state, 'route.params.id', null);
},
conferenceUrl(state) {
let id = _.get(state, 'route.params.id', null);id;
return window.location.href;
},
hasConferenceId(state, getters) {
return getters.conferenceId !== null && getters.conferenceId !== void(0);
},
pageTitle(state) {
return _.get(state, 'route.meta.title', 'Not defined');
},
@ -78,6 +85,8 @@ export const store = new Vuex.Store({
}).onConferenceNetworkDisconnected(() => {
store.commit('conference/disableConferencing');
});
},
function call(store) {
Vue.$call.onIncoming(()=>{
store.commit('call/incomingCall', {
number: Vue.call.getNumber()
@ -92,6 +101,21 @@ export const store = new Vuex.Store({
}, errorVisibilityTimeout);
});
},
function conference(store) {
Vue.$conference.onLeft((conference)=>{
store.commit('conference/leftSuccessfully', conference);
}).onConferenceParticipantJoined((participant)=>{
store.commit('conference/participantJoined', participant);
}).onConferenceParticipantLeft((participant)=>{
store.commit('conference/participantLeft', participant);
}).onConferenceEvent((event)=>{
store.commit('conference/event', event);
}).onConferenceMessage(()=>{
store.commit('conference/message', event);
}).onConferenceFile(()=>{
store.commit('conference/file', event);
});
},
function initI18n(store) {
store.commit('user/setLanguageLabels', getLanguageLabels());
store.commit('user/changeSessionLocaleSucceeded', i18n.locale);

Loading…
Cancel
Save