TT#44662 Customer wants to play busy greeting

What is done:
- TT#45397, Implement greeting sound player methods and state handling
- TT#45389, Implement UI for playing "busy greeting sound", stop/play/pause
- TT#45390, Implement API method for fetching the playable "busy greeting sound"
- TT#45385, Implement API method for fetching the playable "unavailable greeting
sound"
- TT#45384, Implement UI for playing "unavailable greeting sound",
stop/play/pause
- TT#45398, Implement greeting sound player methods and state handling

Change-Id: I866bda3931c67cc79ad34d4be9e784f0705c221e
changes/70/24470/13
raxelsen 7 years ago committed by Hans-Peter Herzog
parent 8e1a96066c
commit 5a152e3967

@ -141,6 +141,21 @@ export function uploadGreeting(options) {
} }
export function abortPreviousRequest(name) { export function abortPreviousRequest(name) {
return new Promise((resolve) => {
let requestKey = `previous${_.capitalize(name)}Request`; let requestKey = `previous${_.capitalize(name)}Request`;
Vue[requestKey].abort(); Vue[requestKey].abort();
resolve();
});
}
export function playGreeting(options) {
return new Promise((resolve, reject)=>{
let params = { format: options.format };
Vue.http.get(`api/voicemailgreetings/${options.id}`, { params: params, responseType: 'blob' })
.then((res) => {
resolve(URL.createObjectURL(res.body));
}).catch((err) => {
reject(err);
});
});
} }

@ -1,13 +1,49 @@
<template> <template>
<div class="voicemail-player"> <div class="audio-player">
<audio :src="fileUrl" ref="audio" preload="auto" @timeupdate="timeupdate($event)"/> <audio
:src="fileUrl"
ref="audio"
preload="auto"
@timeupdate="timeUpdate"
/>
<div class="control-btns"> <div class="control-btns">
<q-btn class="play-pause-btn" round flat small color="primary" <q-btn
:icon="playPauseIcon" @click="toggle()" /> v-if="pausable"
:disable="disable"
<q-btn class="stop-btn" round flat small color="primary" icon="stop" @click="stop()"/> round
flat
small
color="primary"
:icon="playPauseIcon"
@click="toggle()"
/>
<q-btn
v-else
:disable="disable || playing"
round
flat
small
color="primary"
icon="play_arrow"
@click="playLoad()"
/>
<q-btn
:disable="disable || !pausable && !playing"
round
flat
small
color="primary"
icon="stop"
@click="stop()"
/>
</div> </div>
<q-progress class="progress-bar" :percentage="progressPercentage" stripe animate color="primary"/> <q-progress
class="progress-bar"
:percentage="progressPercentage"
stripe
animate
color="primary"
/>
</div> </div>
</template> </template>
@ -18,21 +54,23 @@
name: 'csc-audio-player', name: 'csc-audio-player',
props: [ props: [
'fileUrl', 'fileUrl',
'loaded' 'loaded',
'disable',
'pausable'
], ],
mounted() { mounted() {
this.$refs.audio.addEventListener('play', ()=>{ this.$refs.audio.addEventListener('play', ()=> {
this.playing = true; this.playing = true;
}); });
this.$refs.audio.addEventListener('playing', ()=>{ this.$refs.audio.addEventListener('playing', ()=> {
this.playing = true; this.playing = true;
}); });
this.$refs.audio.addEventListener('ended', ()=>{ this.$refs.audio.addEventListener('ended', ()=> {
this.playing = false; this.playing = false;
this.stop(); this.stop();
}); });
this.$refs.audio.addEventListener('canplay', ()=>{ this.$refs.audio.addEventListener('canplay', ()=> {
if(!this.paused && this.playing) { if (!this.paused && this.playing) {
this.$refs.audio.play(); this.$refs.audio.play();
} }
}); });
@ -58,6 +96,7 @@
this.$refs.audio.play(); this.$refs.audio.play();
this.playing = true; this.playing = true;
this.paused = false; this.paused = false;
this.$emit('playing');
}, },
pause() { pause() {
this.$refs.audio.pause(); this.$refs.audio.pause();
@ -67,6 +106,7 @@
stop() { stop() {
this.$refs.audio.currentTime = 0; this.$refs.audio.currentTime = 0;
this.pause(); this.pause();
this.$emit('stopped');
}, },
setPlayingTrue() { setPlayingTrue() {
this.playing = true; this.playing = true;
@ -74,9 +114,8 @@
setPausedFalse() { setPausedFalse() {
this.paused = false; this.paused = false;
}, },
timeupdate(e) { timeUpdate() {
let newPercentage = Math.floor((e.target.currentTime / e.target.duration) * 100); this.progressPercentage = this.$refs.audio.currentTime * 100 / this.$refs.audio.duration;
this.progressPercentage = newPercentage;
}, },
load() { load() {
this.$emit('load'); this.$emit('load');
@ -91,6 +130,14 @@
else { else {
this.pause(); this.pause();
} }
},
playLoad() {
if (!this.loaded) {
this.load();
}
else if (this.$refs.audio.paused) {
this.play();
}
} }
} }
} }
@ -99,15 +146,17 @@
<style lang="stylus" rel="stylesheet/stylus"> <style lang="stylus" rel="stylesheet/stylus">
@import '../themes/app.common' @import '../themes/app.common'
.voicemail-player .audio-player
width 100% width 100%
height 56px height 56px
display flex display flex
justify-content space-around justify-content space-around
align-items center align-items center
.control-btns .control-btns
display flex display flex
justify-content space-between justify-content space-between
.progress-bar .progress-bar
margin-left 16px margin-left 16px
margin-right 16px margin-right 16px

@ -4,6 +4,7 @@
:icon="icon" :icon="icon"
> >
<q-input <q-input
:disable="isPlaying"
dark dark
readonly readonly
:float-label="label" :float-label="label"
@ -36,6 +37,16 @@
class="upload-progress" class="upload-progress"
/> />
</div> </div>
<csc-audio-player
ref="audioPlayer"
:file-url="fileUrl"
:loaded="loaded"
class="csc-greeting-player"
@load="init"
:disable="disablePlayer"
@playing="audioPlayerPlaying"
@stopped="audioPlayerStopped"
/>
<div <div
class="csc-file-upload-actions" class="csc-file-upload-actions"
> >
@ -58,6 +69,7 @@
{{ $t('buttons.upload') }} {{ $t('buttons.upload') }}
</q-btn> </q-btn>
<q-btn <q-btn
:disable="isPlaying"
flat flat
v-if="uploaded && selectedFile == null" v-if="uploaded && selectedFile == null"
color="primary" color="primary"
@ -71,6 +83,7 @@
</template> </template>
<script> <script>
import CscAudioPlayer from '../CscAudioPlayer'
import { import {
QInput, QInput,
QField, QField,
@ -81,6 +94,7 @@
export default { export default {
name: 'csc-sound-file-upload', name: 'csc-sound-file-upload',
components: { components: {
CscAudioPlayer,
QInput, QInput,
QField, QField,
QBtn, QBtn,
@ -94,14 +108,20 @@
'uploading', 'uploading',
'uploaded', 'uploaded',
'progress', 'progress',
'fileTypes' 'fileTypes',
'fileUrl',
'loaded'
], ],
data () { data () {
return { return {
selectedFile: null selectedFile: null,
isPlaying: false
} }
}, },
computed: { computed: {
disablePlayer() {
return (this.selectedFile ? true : false) || !this.uploaded;
},
inputValue() { inputValue() {
if(this.selectedFile === null) { if(this.selectedFile === null) {
return this.value; return this.value;
@ -113,6 +133,17 @@
inputButtons() { inputButtons() {
let buttons = []; let buttons = [];
let self = this; let self = this;
if (this.isPlaying) {
buttons.push({
icon: 'folder',
error: false,
handler (event) {
event.stopPropagation();
}
}
);
}
else {
buttons.push({ buttons.push({
icon: 'folder', icon: 'folder',
error: false, error: false,
@ -122,10 +153,24 @@
} }
} }
); );
}
return buttons; return buttons;
} }
}, },
methods: { methods: {
setPlayingTrue() {
this.$refs.audioPlayer.setPlayingTrue();
this.isPlaying = true;
},
setPausedFalse() {
this.$refs.audioPlayer.setPausedFalse();
},
audioPlayerPlaying() {
this.isPlaying = true;
},
audioPlayerStopped() {
this.isPlaying = false;
},
inputChange(event) { inputChange(event) {
this.selectedFile = event.target.files[0]; this.selectedFile = event.target.files[0];
}, },
@ -147,6 +192,9 @@
}, },
undo() { undo() {
this.$emit('reset'); this.$emit('reset');
},
init() {
this.$emit('init');
} }
} }
} }
@ -155,15 +203,15 @@
<style lang="stylus" rel="stylesheet/stylus"> <style lang="stylus" rel="stylesheet/stylus">
@import '../../themes/quasar.variables'; @import '../../themes/quasar.variables';
.csc-file-upload-actions
padding-top $flex-gutter-xs
.csc-upload-field .csc-upload-field
margin-bottom 40px margin-bottom 40px
.q-field-icon .q-field-icon
color $primary color $primary
.csc-upload-field.csc-player-margin
margin-bottom 0
.csc-upload-progress-field .csc-upload-progress-field
margin 10px 0 5px 0 margin 10px 0 5px 0
@ -177,4 +225,7 @@
.upload-progress .upload-progress
height 20px height 20px
.csc-greeting-player
padding 0
</style> </style>

@ -39,6 +39,7 @@
:loaded="voiceMailLoaded" :loaded="voiceMailLoaded"
class="csc-voice-mail-player" class="csc-voice-mail-player"
@load="load" @load="load"
:pausable="true"
/> />
</q-item-tile> </q-item-tile>
</q-item-main> </q-item-main>

@ -20,7 +20,7 @@
:attachLabel="attachLabel" :attachLabel="attachLabel"
/> />
<csc-sound-file-upload <csc-sound-file-upload
ref="uploadBusyGreeting" ref="uploadBusy"
icon="music_note" icon="music_note"
file-types=".wav,.mp3" file-types=".wav,.mp3"
:label="$t('voicebox.label.busyGreeting')" :label="$t('voicebox.label.busyGreeting')"
@ -31,9 +31,12 @@
@upload="uploadBusyGreeting" @upload="uploadBusyGreeting"
@abort="abortBusy" @abort="abortBusy"
@reset="deleteBusy" @reset="deleteBusy"
:file-url="playBusyGreetingUrl"
:loaded="playBusyGreetingLoaded"
@init="initBusyGreetingAudio"
/> />
<csc-sound-file-upload <csc-sound-file-upload
ref="uploadUnavailGreeting" ref="uploadUnavail"
icon="music_note" icon="music_note"
file-types=".wav,.mp3" file-types=".wav,.mp3"
:label="$t('voicebox.label.unavailGreeting')" :label="$t('voicebox.label.unavailGreeting')"
@ -44,6 +47,9 @@
@upload="uploadUnavailGreeting" @upload="uploadUnavailGreeting"
@abort="abortUnavail" @abort="abortUnavail"
@reset="deleteUnavail" @reset="deleteUnavail"
:file-url="playUnavailGreetingUrl"
:loaded="playUnavailGreetingLoaded"
@init="initUnavailGreetingAudio"
/> />
</div> </div>
</div> </div>
@ -68,6 +74,7 @@
export default { export default {
data () { data () {
return { return {
platform: this.$q.platform.is
} }
}, },
components: { components: {
@ -117,16 +124,23 @@
'deleteGreetingState', 'deleteGreetingState',
'deleteGreetingError', 'deleteGreetingError',
'busyGreetingLabel', 'busyGreetingLabel',
'unavailGreetingLabel' 'unavailGreetingLabel',
]) 'playBusyGreetingLoaded',
'playBusyGreetingUrl',
'playUnavailGreetingLoaded',
'playUnavailGreetingUrl'
]),
soundFileFormat() {
return this.platform.mozilla ? 'ogg' : 'mp3';
}
}, },
methods: { methods: {
resetBusyFile() { resetBusyFile() {
this.$refs.uploadBusyGreeting.reset(); this.$refs.uploadBusy.reset();
this.$store.commit('voicebox/resetBusyProgress'); this.$store.commit('voicebox/resetBusyProgress');
}, },
resetUnavailFile() { resetUnavailFile() {
this.$refs.uploadUnavailGreeting.reset(); this.$refs.uploadUnavail.reset();
this.$store.commit('voicebox/resetUnavailProgress'); this.$store.commit('voicebox/resetUnavailProgress');
}, },
uploadBusyGreeting(file) { uploadBusyGreeting(file) {
@ -140,10 +154,10 @@
}); });
}, },
abortBusy() { abortBusy() {
this.$store.dispatch('voicebox/abortPreviousRequest', 'busy'); this.$store.dispatch('voicebox/abortUploadBusyGreeting');
}, },
abortUnavail() { abortUnavail() {
this.$store.dispatch('voicebox/abortPreviousRequest', 'unavail'); this.$store.dispatch('voicebox/abortUploadUnavailGreeting');
}, },
deleteBusy() { deleteBusy() {
let self = this; let self = this;
@ -202,6 +216,22 @@
}, },
loadUnavailGreeting() { loadUnavailGreeting() {
this.$store.dispatch('voicebox/loadUnavailGreeting'); this.$store.dispatch('voicebox/loadUnavailGreeting');
},
playBusyGreeting() {
this.$store.dispatch('voicebox/playBusyGreeting', this.soundFileFormat);
},
playUnavailGreeting() {
this.$store.dispatch('voicebox/playUnavailGreeting', this.soundFileFormat);
},
initBusyGreetingAudio() {
this.playBusyGreeting();
this.$refs.uploadBusy.setPlayingTrue();
this.$refs.uploadBusy.setPausedFalse();
},
initUnavailGreetingAudio() {
this.playUnavailGreeting();
this.$refs.uploadUnavail.setPlayingTrue();
this.$refs.uploadUnavail.setPausedFalse();
} }
}, },
watch: { watch: {
@ -286,7 +316,4 @@
<style lang="stylus" rel="stylesheet/stylus"> <style lang="stylus" rel="stylesheet/stylus">
@import '../../../themes/quasar.variables'; @import '../../../themes/quasar.variables';
.csc-upload-file-label
margin-bottom $flex-gutter-sm
</style> </style>

@ -226,15 +226,15 @@ export default {
}, },
downloadVoiceMail(context, id) { downloadVoiceMail(context, id) {
context.commit('downloadVoiceMailRequesting'); context.commit('downloadVoiceMailRequesting');
downloadVoiceMail(id).then(()=>{ downloadVoiceMail(id).then(() => {
context.commit('downloadVoiceMailSucceeded'); context.commit('downloadVoiceMailSucceeded');
}).catch((err)=>{ }).catch((err) => {
context.commit('downloadVoiceMailFailed', err.body.message); context.commit('downloadVoiceMailFailed', err.body.message);
}); });
}, },
downloadFax(context, id) { downloadFax(context, id) {
context.commit('downloadFaxRequesting'); context.commit('downloadFaxRequesting');
downloadFax(id).then(()=>{ downloadFax(id).then(() => {
context.commit('downloadFaxSucceeded'); context.commit('downloadFaxSucceeded');
}).catch((err)=>{ }).catch((err)=>{
context.commit('downloadFaxFailed', err.body.message); context.commit('downloadFaxFailed', err.body.message);
@ -242,12 +242,12 @@ export default {
}, },
playVoiceMail(context, options) { playVoiceMail(context, options) {
context.commit('playVoiceMailRequesting', options.id); context.commit('playVoiceMailRequesting', options.id);
playVoiceMail(options).then((url)=>{ playVoiceMail(options).then((url) => {
context.commit('playVoiceMailSucceeded', { context.commit('playVoiceMailSucceeded', {
id: options.id, id: options.id,
url: url url: url
}); });
}).catch((err)=>{ }).catch((err) => {
context.commit('playVoiceMailFailed', options.id, err.mesage); context.commit('playVoiceMailFailed', options.id, err.mesage);
}); });
}, },

@ -3,15 +3,15 @@
import { RequestState } from './common' import { RequestState } from './common'
import { import {
getVoiceboxSettings, getVoiceboxSettings, setVoiceboxDelete,
setVoiceboxDelete,
setVoiceboxAttach, setVoiceboxAttach,
setVoiceboxPin, setVoiceboxPin,
setVoiceboxEmail, setVoiceboxEmail,
uploadGreeting, uploadGreeting,
abortPreviousRequest, abortPreviousRequest,
getVoiceboxGreetingByType, getVoiceboxGreetingByType,
deleteVoiceboxGreetingById deleteVoiceboxGreetingById,
playGreeting
} from '../api/voicebox'; } from '../api/voicebox';
import { i18n } from '../i18n'; import { i18n } from '../i18n';
@ -45,7 +45,13 @@ export default {
loadUnavailGreetingState: RequestState.initial, loadUnavailGreetingState: RequestState.initial,
loadUnavailGreetingError: null, loadUnavailGreetingError: null,
deleteGreetingState: RequestState.initial, deleteGreetingState: RequestState.initial,
deleteGreetingError: null deleteGreetingError: null,
playBusyGreetingUrl: null,
playBusyGreetingState: RequestState.initial,
playBusyGreetingError: null,
playUnavailGreetingUrl: null,
playUnavailGreetingState: RequestState.initial,
playUnavailGreetingError: null
}, },
getters: { getters: {
subscriberId(state, getters, rootState, rootGetters) { subscriberId(state, getters, rootState, rootGetters) {
@ -176,6 +182,18 @@ export default {
unavailGreetingLabel(state) { unavailGreetingLabel(state) {
return state.unavailGreetingId ? i18n.t('voicebox.label.customSoundActive') : return state.unavailGreetingId ? i18n.t('voicebox.label.customSoundActive') :
i18n.t('voicebox.label.defaultSoundActive') i18n.t('voicebox.label.defaultSoundActive')
},
playBusyGreetingLoaded(state) {
return state.playBusyGreetingState === 'succeeded';
},
playBusyGreetingUrl(state) {
return state.playBusyGreetingUrl;
},
playUnavailGreetingLoaded(state) {
return state.playUnavailGreetingState === 'succeeded';
},
playUnavailGreetingUrl(state) {
return state.playUnavailGreetingUrl;
} }
}, },
mutations: { mutations: {
@ -292,6 +310,7 @@ export default {
state.loadBusyGreetingError = null; state.loadBusyGreetingError = null;
}, },
loadBusyGreetingSucceeded(state, greetings) { loadBusyGreetingSucceeded(state, greetings) {
state.playBusyGreetingState = RequestState.initial;
if (greetings.length > 0) { if (greetings.length > 0) {
state.busyGreetingId = greetings[0].id; state.busyGreetingId = greetings[0].id;
} }
@ -308,6 +327,7 @@ export default {
state.loadUnavailGreetingError = null; state.loadUnavailGreetingError = null;
}, },
loadUnavailGreetingSucceeded(state, greetings) { loadUnavailGreetingSucceeded(state, greetings) {
state.playUnavailGreetingState = RequestState.initial;
if (greetings.length > 0) { if (greetings.length > 0) {
state.unavailGreetingId = greetings[0].id; state.unavailGreetingId = greetings[0].id;
} }
@ -329,6 +349,34 @@ export default {
deleteGreetingFailed(state, error) { deleteGreetingFailed(state, error) {
state.deleteGreetingState = RequestState.failed; state.deleteGreetingState = RequestState.failed;
state.deleteGreetingError = error; state.deleteGreetingError = error;
},
playBusyGreetingRequesting(state) {
state.playBusyGreetingState = RequestState.requesting;
state.playBusyGreetingError = null;
},
playBusyGreetingSucceeded(state, url) {
state.playBusyGreetingUrl = url;
state.playBusyGreetingState = RequestState.succeeded;
state.playBusyGreetingError = null;
},
playBusyGreetingFailed(state, err) {
state.playBusyGreetingUrl = null;
state.playBusyGreetingState = RequestState.failed;
state.playBusyGreetingError = err;
},
playUnavailGreetingRequesting(state) {
state.playUnavailGreetingState = RequestState.requesting;
state.playUnavailGreetingError = null;
},
playUnavailGreetingSucceeded(state, url) {
state.playUnavailGreetingUrl = url;
state.playUnavailGreetingState = RequestState.succeeded;
state.playUnavailGreetingError = null;
},
playUnavailGreetingFailed(state, err) {
state.playUnavailGreetingUrl = null;
state.playUnavailGreetingState = RequestState.failed;
state.playUnavailGreetingError = err;
} }
}, },
actions: { actions: {
@ -480,6 +528,38 @@ export default {
}).catch((err) => { }).catch((err) => {
context.commit('deleteGreetingFailed', err.message); context.commit('deleteGreetingFailed', err.message);
}); });
},
playBusyGreeting(context, format) {
context.commit('playBusyGreetingRequesting');
playGreeting({
id: context.getters.busyGreetingId,
format: format
}).then((url) => {
context.commit('playBusyGreetingSucceeded', url);
}).catch((err) => {
context.commit('playBusyGreetingFailed', err.mesage);
});
},
playUnavailGreeting(context, format) {
context.commit('playUnavailGreetingRequesting');
playGreeting({
id: context.getters.unavailGreetingId,
format: format
}).then((url) => {
context.commit('playUnavailGreetingSucceeded', url);
}).catch((err) => {
context.commit('playUnavailGreetingFailed', err.mesage);
});
},
abortUploadBusyGreeting(context) {
abortPreviousRequest('busy').then(() => {
context.dispatch('loadBusyGreeting');
});
},
abortUploadUnavailGreeting(context) {
abortPreviousRequest('unavail').then(() => {
context.dispatch('loadUnavailGreeting');
});
} }
} }
}; };

@ -70,6 +70,24 @@ describe('Voicebox', function(){
assert.deepEqual(state.unavailGreetingId, greetings[0].id); assert.deepEqual(state.unavailGreetingId, greetings[0].id);
}); });
it('should load busy greeting url into store', function(){
let state = {
playBusyGreetingUrl: null
};
let url = "blob:https://1.2.3.4/6341147c-3ed2-4112-876b-331e834a4821";
VoiceboxModule.mutations.playBusyGreetingSucceeded(state, url);
assert.deepEqual(state.playBusyGreetingUrl, url);
});
it('should load unavailable greeting id into store', function(){
let state = {
playUnavailGreetingUrl: null
};
let url = "blob:https://1.2.3.4/6341147c-3ed2-4112-876b-331e834a4821";
VoiceboxModule.mutations.playUnavailGreetingSucceeded(state, url);
assert.deepEqual(state.playUnavailGreetingUrl, url);
});
it('should get right label for busy greeting to indicate if it\'s custom or default', function(){ it('should get right label for busy greeting to indicate if it\'s custom or default', function(){
let state = { let state = {
busyGreetingId: null busyGreetingId: null

Loading…
Cancel
Save