mirror of
https://github.com/jambonz/jambonz-feature-server.git
synced 2025-12-20 08:40:38 +00:00
Feat/ambient sounds (#678)
* initial support for coaching mode in conference * wip * wip * add support for answer verb * wip * wip * wip * wip * wip * updates to rename option to dub * wip * wip * wip * update verb-specs * wip * wip * wip * wip * wip * wip * wip * wip * add option to boost audio signal in main channel * wip * wip * wip * wip * wip * wip * for now, bypass use of streaming apis when generating tts audio for dub tracks * add nested dub to dial * wip * add support for filler noise * kill filler noise when gather killed * wip * wip * while using sayOnTrack, we have to enclose the say command in double quotes * disableTtsStreaming = false * allow transcribe of b leg only on dial verb * dub.say can either be text or object like say verb with text and synthesizer * remove loop for sayOnTrack * update speech-utils * fixes for testing transcribe verb and support for dub and boostAudioSignal in lcc commands * add dial.boostAudioSignal * fix bug where session-level recognizer settings incorrectly overwrite verb-level settings * update verb specs * update dial to support array of dub verbs * fix bug setting gain * lint * wip * update speech-utils * use new endpoint methods for mod_dub --------- Co-authored-by: Dave Horton <daveh@beachdognet.com>
This commit is contained in:
@@ -348,8 +348,14 @@ class Conference extends Task {
|
||||
Object.assign(opts, {flags: {
|
||||
...(this.endConferenceOnExit && {endconf: true}),
|
||||
...(this.startConferenceOnEnter && {moderator: true}),
|
||||
...(this.joinMuted && {joinMuted: true}),
|
||||
...((this.joinMuted || this.data.speakOnlyTo) && {joinMuted: true}),
|
||||
}});
|
||||
|
||||
/**
|
||||
* Note on the above: if we are joining in "coaching" mode (ie only going to heard by a subset of participants)
|
||||
* then we join muted temporarily, and then unmute ourselves once we have identified the subset of participants
|
||||
* to whom we will be speaking.
|
||||
*/
|
||||
}
|
||||
|
||||
try {
|
||||
@@ -358,6 +364,11 @@ class Conference extends Task {
|
||||
this.memberId = memberId;
|
||||
this.confUuid = confUuid;
|
||||
|
||||
// set a tag for this member, if provided
|
||||
if (this.data.memberTag) {
|
||||
this.setMemberTag(this.data.memberTag);
|
||||
}
|
||||
|
||||
cs.setConferenceDetails(memberId, this.confName, confUuid);
|
||||
const response = await this.ep.api('conference', [this.confName, 'get', 'count']);
|
||||
if (response.body && /\d+/.test(response.body)) this.participantCount = parseInt(response.body);
|
||||
@@ -384,6 +395,9 @@ class Conference extends Task {
|
||||
.catch((err) => {});
|
||||
}
|
||||
|
||||
if (this.data.speakOnlyTo) {
|
||||
this.setCoachMode(this.data.speakOnlyTo);
|
||||
}
|
||||
} catch (err) {
|
||||
this.logger.error(err, `Failed to join conference ${this.confName}`);
|
||||
throw err;
|
||||
@@ -428,7 +442,15 @@ class Conference extends Task {
|
||||
}
|
||||
}
|
||||
|
||||
async doConferenceHold(cs, opts) {
|
||||
doConferenceMute(cs, opts) {
|
||||
assert (cs.isInConference);
|
||||
|
||||
const mute = opts.conf_mute_status === 'mute';
|
||||
this.ep.api(`conference ${this.confName} ${mute ? 'mute' : 'unmute'} ${this.memberId}`)
|
||||
.catch((err) => this.logger.info({err}, 'Error muting or unmuting participant'));
|
||||
}
|
||||
|
||||
doConferenceHold(cs, opts) {
|
||||
assert (cs.isInConference);
|
||||
|
||||
const {conf_hold_status, wait_hook} = opts;
|
||||
@@ -465,6 +487,40 @@ class Conference extends Task {
|
||||
}
|
||||
}
|
||||
|
||||
async doConferenceParticipantAction(cs, opts) {
|
||||
const {action, tag} = opts;
|
||||
|
||||
switch (action) {
|
||||
case 'tag':
|
||||
await this.setMemberTag(tag);
|
||||
break;
|
||||
case 'untag':
|
||||
await this.clearMemberTag();
|
||||
break;
|
||||
case 'coach':
|
||||
await this.setCoachMode(tag);
|
||||
break;
|
||||
case 'uncoach':
|
||||
await this.clearCoachMode();
|
||||
break;
|
||||
case 'hold':
|
||||
this.doConferenceHold(cs, {conf_hold_status: 'hold'});
|
||||
break;
|
||||
case 'unhold':
|
||||
this.doConferenceHold(cs, {conf_hold_status: 'unhold'});
|
||||
break;
|
||||
case 'mute':
|
||||
this.doConferenceMute(cs, {conf_mute_status: 'mute'});
|
||||
break;
|
||||
case 'unmute':
|
||||
this.doConferenceMute(cs, {conf_mute_status: 'unmute'});
|
||||
break;
|
||||
default:
|
||||
this.logger.info(`Conference:doConferenceParticipantState - unhandled action ${action}`);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
async _doWaitHookWhileOnHold(cs, dlg, wait_hook) {
|
||||
do {
|
||||
try {
|
||||
@@ -642,11 +698,14 @@ class Conference extends Task {
|
||||
}
|
||||
|
||||
// conference event handlers
|
||||
_onAddMember(logger, cs, evt) {
|
||||
logger.debug({evt}, `Conference:_onAddMember - member added to conference ${this.confName}`);
|
||||
}
|
||||
_onDelMember(logger, cs, evt) {
|
||||
const memberId = parseInt(evt.getHeader('Member-ID')) ;
|
||||
this.participantCount = parseInt(evt.getHeader('Conference-Size'));
|
||||
if (memberId === this.memberId) {
|
||||
this.logger.info(`Conference:_onDelMember - I was dropped from conference ${this.confName}, task is complete`);
|
||||
logger.info(`Conference:_onDelMember - I was dropped from conference ${this.confName}, task is complete`);
|
||||
this.replaceEndpointAndEnd(cs);
|
||||
}
|
||||
}
|
||||
@@ -675,6 +734,53 @@ class Conference extends Task {
|
||||
}
|
||||
}
|
||||
|
||||
async setCoachMode(speakOnlyTo) {
|
||||
try {
|
||||
const response = await this.ep.api('conference', [this.confName, 'gettag', speakOnlyTo, 'nomatch']);
|
||||
this.logger.info(`Conference:_setCoachMode: my audio will only be sent to particpants ${response}`);
|
||||
await this.ep.api('conference', [this.confName, 'relate', this.memberId, response, 'nospeak']);
|
||||
this.speakOnlyTo = speakOnlyTo;
|
||||
this.coaching = response;
|
||||
} catch (err) {
|
||||
this.logger.error({err, speakOnlyTo}, '_setCoachMode: Error');
|
||||
}
|
||||
}
|
||||
|
||||
async clearCoachMode() {
|
||||
try {
|
||||
if (!this.coaching) {
|
||||
this.logger.info('Conference:_clearCoachMode: no coaching mode to clear');
|
||||
return;
|
||||
}
|
||||
this.logger.info(`Conference:_clearCoachMode: now sending my audio to all, including ${this.coaching}`);
|
||||
await this.ep.api('conference', [this.confName, 'relate', this.memberId, this.coaching, 'clear']);
|
||||
this.speakOnlyTo = null;
|
||||
this.coaching = null;
|
||||
} catch (err) {
|
||||
this.logger.error({err}, '_clearCoachMode: Error');
|
||||
}
|
||||
}
|
||||
|
||||
async setMemberTag(tag) {
|
||||
try {
|
||||
await this.ep.api('conference', [this.confName, 'tag', this.memberId, tag]);
|
||||
this.logger.info(`Conference:setMemberTag: set tag for ${this.memberId} to ${tag}`);
|
||||
this.memberTag = tag;
|
||||
} catch (err) {
|
||||
this.logger.error({err}, `Error setting tag for ${this.memberId} to ${tag}`);
|
||||
}
|
||||
}
|
||||
|
||||
async clearMemberTag() {
|
||||
try {
|
||||
await this.ep.api('conference', [this.confName, 'tag', this.memberId]);
|
||||
this.logger.info(`Conference:setMemberTag: clearing tag for ${this.memberId}`);
|
||||
this.memberTag = null;
|
||||
} catch (err) {
|
||||
this.logger.error({err}, `Error clearing tag for ${this.memberId}`);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
module.exports = Conference;
|
||||
|
||||
Reference in New Issue
Block a user