[FIX] mail: only play rtc sounds on main tab

Before this commit, rtc join/leave sounds were played on every
tab. This can be due to two issues:
- notifications related to the current user should not play sound
since it is already played by the tab that triggered the action.
- only the main tab should play the sound.

This commit implements this behavior.

Steps to reproduce the issue:
- Open two tabs
- Start a call on the general channel
- Notice that the "join" sound was played twice
- Leave the call
- The same applies for the "leave" sound

task-3355352

closes odoo/odoo#144436

X-original-commit: dfbd95f
Signed-off-by: Matthieu Stockbauer (tsm) <tsm@odoo.com>
Signed-off-by: Alexandre Kühn (aku) <aku@odoo.com>
This commit is contained in:
tsm-odoo
2023-12-01 18:14:19 +00:00
committed by Alexandre Kühn
parent 3f3a654952
commit 018c5c178d
4 changed files with 108 additions and 14 deletions
@@ -1553,7 +1553,15 @@ export class Rtc {
return;
}
const oldCount = channel.rtcSessions.length;
const hadSelfSession = Boolean(this.state.selfSession?.in(channel.rtcSessions));
channel.rtcSessions = sessionsData;
if (
!hadSelfSession ||
hadSelfSession !== Boolean(this.state.selfSession?.in(channel.rtcSessions)) ||
!this.store.env.services["multi_tab"].isOnMainTab()
) {
return;
}
if (channel.rtcSessions.length > oldCount) {
this.soundEffectsService.play("channel-join");
} else if (channel.rtcSessions.length < oldCount) {
@@ -105,16 +105,6 @@ QUnit.test("should disconnect when closing page while in call", async (assert) =
});
QUnit.test("should display invitations", async (assert) => {
patchWithCleanup(browser, {
Audio: class extends Audio {
pause() {
assert.step("pause_sound_effect");
}
play() {
assert.step("play_sound_effect");
}
},
});
const pyEnv = await startServer();
const channelId = pyEnv["discuss.channel"].create({ name: "General" });
const partnerId = pyEnv["res.partner"].create({ name: "InvitationSender" });
@@ -126,7 +116,17 @@ QUnit.test("should display invitations", async (assert) => {
channel_member_id: memberId,
channel_id: channelId,
});
await start();
const { env } = await start();
patchWithCleanup(env.services["mail.sound_effects"], {
play(name) {
assert.step(`play - ${name}`);
super.play(...arguments);
},
stop(name) {
assert.step(`stop - ${name}`);
super.stop(...arguments);
},
});
pyEnv["bus.bus"]._sendone(pyEnv.currentPartner, "mail.record/insert", {
Thread: {
id: channelId,
@@ -135,7 +135,7 @@ QUnit.test("should display invitations", async (assert) => {
},
});
await contains(".o-discuss-CallInvitation");
assert.verifySteps(["play_sound_effect"]);
assert.verifySteps(["play - incoming-call"]);
// Simulate stop receiving call invitation
pyEnv["bus.bus"]._sendone(pyEnv.currentPartner, "mail.record/insert", {
Thread: {
@@ -145,7 +145,7 @@ QUnit.test("should display invitations", async (assert) => {
},
});
await contains(".o-discuss-CallInvitation", { count: 0 });
assert.verifySteps(["pause_sound_effect"]);
assert.verifySteps(["stop - incoming-call"]);
});
QUnit.test("can share screen", async () => {
@@ -223,3 +223,31 @@ QUnit.test("Click on inset card should replace the inset and active stream toget
await contains("video[type='screen'].o-inset");
await contains("video[type='camera']:not(.o-inset)");
});
QUnit.test("join/leave sounds are only played on main tab", async (assert) => {
mockGetMedia();
const pyEnv = await startServer();
const channelId = pyEnv["discuss.channel"].create({ name: "General" });
const tab1 = await start({ asTab: true });
const tab2 = await start({ asTab: true });
patchWithCleanup(tab1.env.services["mail.sound_effects"], {
play(name) {
assert.step(`tab1 - play - ${name}`);
},
});
patchWithCleanup(tab2.env.services["mail.sound_effects"], {
play(name) {
assert.step(`tab2 - play - ${name}`);
},
});
await tab1.openDiscuss(channelId);
await tab2.openDiscuss(channelId);
await click("[title='Start a Call']", { target: tab1.target });
await contains(".o-discuss-Call", { target: tab1.target });
await contains(".o-discuss-Call", { target: tab2.target });
assert.verifySteps(["tab1 - play - channel-join"]);
await click("[title='Disconnect']:not([disabled])", { target: tab1.target });
await contains(".o-discuss-Call", { target: tab1.target, count: 0 });
await contains(".o-discuss-Call", { target: tab2.target, count: 0 });
assert.verifySteps(["tab1 - play - channel-leave"]);
});
@@ -167,7 +167,7 @@ patch(MockServer.prototype, {
return this._mockRouteMailRtcChannelLeaveCall(args.channel_id);
}
if (route === "/mail/rtc/session/update_and_broadcast") {
return;
return this._mockRouteMailRtcSessionUpdateAndBroadcast(args.session_id, args.values);
}
if (route === "/mail/starred/messages") {
const { search_term, after, before, limit } = args;
@@ -602,6 +602,23 @@ patch(MockServer.prototype, {
}
this.pyEnv["bus.bus"]._sendmany(notifications);
},
/**
* Simulates the `/mail/rtc/session/update_and_broadcast` route.
*
* @param {number} session_id
* @param {object} values
*/
async _mockRouteMailRtcSessionUpdateAndBroadcast(session_id, values) {
const [session] = this.pyEnv["discuss.channel.rtc.session"].searchRead([
["id", "=", session_id],
]);
const [currentChannelMember] = this.pyEnv["discuss.channel.member"].searchRead([
["id", "=", session.channel_member_id[0]],
]);
if (session && currentChannelMember.partner_id[0] === this.pyEnv.currentPartnerId) {
this._mockDiscussChannelRtcSession__updateAndBroadcast(session.id, values);
}
},
/**
* Simulates the `/mail/thread/data` route.
*
@@ -4,6 +4,30 @@ import { patch } from "@web/core/utils/patch";
import { MockServer } from "@web/../tests/helpers/mock_server";
patch(MockServer.prototype, {
mockCreate(model) {
if (model !== "discuss.channel.rtc.session") {
return super.mockCreate(...arguments);
}
const sessionIds = super.mockCreate(...arguments);
const channelInfo =
this._mockDiscussChannelRtcSession_DiscussChannelRtcSessionFormatByChannel(sessionIds);
const notifications = [];
for (const [channelId, sessionData] of Object.entries(channelInfo)) {
const [channel] = this.pyEnv["discuss.channel"].searchRead([
["id", "=", Number(channelId)],
]);
notifications.push([
channel,
"discuss.channel/rtc_sessions_update",
{
id: channel.id,
rtcSessions: [["ADD", sessionData]],
},
]);
}
this.pyEnv["bus.bus"]._sendmany(notifications);
return sessionIds;
},
/**
* Simulates `_mail_rtc_session_format` on `discuss.channel.rtc.session`.
*
@@ -54,4 +78,21 @@ patch(MockServer.prototype, {
}
return data;
},
/**
* Simulates `_update_and_broadcast` on `discuss.channel.rtc.session`.
*
* @param {object} values
*/
_mockDiscussChannelRtcSession__updateAndBroadcast(id, values) {
this.pyEnv["discuss.channel.rtc.session"].write([id], values);
const sessionData = this._mockDiscussChannelRtcSession_DiscussChannelRtcSessionFormat(id);
const [channel] = this.pyEnv["discuss.channel"].searchRead([
["id", "=", sessionData.channelMember.thread.id],
]);
this.pyEnv["bus.bus"]._sendone(
channel,
"discuss.channel.rtc.session/update_and_broadcast",
{ data: sessionData, channelId: channel.id }
);
},
});