diff --git a/addons/mail/static/src/rtc/call_context_menu.xml b/addons/mail/static/src/rtc/call_context_menu.xml
index b68b46c5db6..5174419a2af 100644
--- a/addons/mail/static/src/rtc/call_context_menu.xml
+++ b/addons/mail/static/src/rtc/call_context_menu.xml
@@ -9,6 +9,8 @@
ICE:
DTLS:
Data channel:
+ Audio player:
+ Video player:
ICE gathering:
From peer:
diff --git a/addons/mail/static/src/rtc/call_participant_card.js b/addons/mail/static/src/rtc/call_participant_card.js
index d075e539a71..722cc666859 100644
--- a/addons/mail/static/src/rtc/call_participant_card.js
+++ b/addons/mail/static/src/rtc/call_participant_card.js
@@ -8,6 +8,7 @@ import { useService } from "@web/core/utils/hooks";
import { isEventHandled, markEventHandled } from "@mail/utils/misc";
import { usePopover } from "@web/core/popover/popover_hook";
import { useStore } from "@mail/core/messaging_hook";
+import { useHover } from "@mail/utils/hooks";
const HIDDEN_CONNECTION_STATES = new Set(["connected", "completed"]);
@@ -24,6 +25,7 @@ export class CallParticipantCard extends Component {
this.rpc = useService("rpc");
this.rtc = useRtc();
this.store = useStore();
+ this.rootHover = useHover("root");
this.threadService = useService("mail.thread");
onMounted(() => {
if (!this.rtcSession) {
@@ -74,6 +76,13 @@ export class CallParticipantCard extends Component {
return this.channelMember?.persona.name;
}
+ get hasMediaError() {
+ return (
+ this.isOfActiveCall &&
+ Boolean(this.rtcSession?.videoError || this.rtcSession?.audioError)
+ );
+ }
+
get hasVideo() {
return Boolean(this.rtcSession?.videoStream);
}
@@ -106,6 +115,10 @@ export class CallParticipantCard extends Component {
});
}
+ async onClickReplay() {
+ this.env.bus.trigger("RTC-SERVICE:PLAY_MEDIA");
+ }
+
/**
* @param {Event} ev
*/
diff --git a/addons/mail/static/src/rtc/call_participant_card.scss b/addons/mail/static/src/rtc/call_participant_card.scss
index ba2d45a2c39..33d4bc48836 100644
--- a/addons/mail/static/src/rtc/call_participant_card.scss
+++ b/addons/mail/static/src/rtc/call_participant_card.scss
@@ -35,7 +35,6 @@
}
.o-mail-CallParticipantCard-overlay {
- pointer-events: none;
margin: Min(5%, map-get($spacers, 2));
}
@@ -56,3 +55,13 @@
.o-mail-CallParticipantCard-avatar:not(.o-minimized) {
background-color: var(--o-mail-CallParticipantCard-avatarBgColor, #{$o-gray-700});
}
+
+.o-mail-CallParticipantCard-overlay-replayButton {
+ background-color: $o-gray-900;
+ &:hover {
+ background-color: $o-gray-700;
+ }
+ &:active {
+ background-color: $o-gray-800;
+ }
+}
diff --git a/addons/mail/static/src/rtc/call_participant_card.xml b/addons/mail/static/src/rtc/call_participant_card.xml
index d68a55b0c65..4181584bd7c 100644
--- a/addons/mail/static/src/rtc/call_participant_card.xml
+++ b/addons/mail/static/src/rtc/call_participant_card.xml
@@ -29,7 +29,7 @@
-
+
LIVE
@@ -45,8 +45,9 @@
-
-
+
+
+
diff --git a/addons/mail/static/src/rtc/call_participant_video.js b/addons/mail/static/src/rtc/call_participant_video.js
index 7958a454d18..eb91faa54c6 100644
--- a/addons/mail/static/src/rtc/call_participant_video.js
+++ b/addons/mail/static/src/rtc/call_participant_video.js
@@ -1,6 +1,6 @@
/* @odoo-module */
-import { Component, onMounted, onPatched, useRef } from "@odoo/owl";
+import { Component, onMounted, onPatched, useExternalListener, useRef } from "@odoo/owl";
import { useRtc } from "@mail/rtc/rtc_hook";
export class CallParticipantVideo extends Component {
@@ -12,6 +12,9 @@ export class CallParticipantVideo extends Component {
this.root = useRef("root");
onMounted(() => this._update());
onPatched(() => this._update());
+ useExternalListener(this.env.bus, "RTC-SERVICE:PLAY_MEDIA", async () => {
+ await this.play();
+ });
}
_update() {
@@ -26,16 +29,16 @@ export class CallParticipantVideo extends Component {
this.root.el.load();
}
- async onVideoLoadedMetaData(ev) {
+ async play() {
try {
- await ev.target.play();
+ await this.root.el?.play?.();
+ this.props.session.videoError = undefined;
} catch (error) {
- if (typeof error === "object" && error.name === "NotAllowedError") {
- // Ignored as some browsers may reject play() calls that do not
- // originate from a user input.
- return;
- }
- throw error;
+ this.props.session.videoError = error.name;
}
}
+
+ async onVideoLoadedMetaData() {
+ await this.play();
+ }
}
diff --git a/addons/mail/static/src/rtc/rtc_service.js b/addons/mail/static/src/rtc/rtc_service.js
index c76da6b4e18..bc3a13cfb95 100644
--- a/addons/mail/static/src/rtc/rtc_service.js
+++ b/addons/mail/static/src/rtc/rtc_service.js
@@ -194,7 +194,11 @@ export class Rtc {
}
}
);
-
+ this.env.bus.addEventListener("RTC-SERVICE:PLAY_MEDIA", () => {
+ for (const session of Object.values(this.state.channel.rtcSessions)) {
+ session.playAudio();
+ }
+ });
browser.addEventListener("keydown", (ev) => {
if (
!this.state.channel ||
@@ -926,7 +930,8 @@ export class Rtc {
delete session.dtlsState;
delete session.iceState;
delete session.logStep;
- session.isAudioInError = false;
+ delete session.audioError;
+ delete session.videoError;
session.isRaisingHand = false;
session.isTalking = false;
this.removeVideoFromSession(session);
@@ -1417,11 +1422,7 @@ export class Rtc {
stream.addTrack(track);
if (track.kind === "audio") {
const audioElement = session.audioElement || new window.Audio();
- try {
- audioElement.srcObject = stream;
- } catch {
- session.isAudioInError = true;
- }
+ audioElement.srcObject = stream;
audioElement.load();
audioElement.muted = mute;
audioElement.volume = this.userSettingsService.getVolume(session);
@@ -1432,17 +1433,7 @@ export class Rtc {
session.audioStream = stream;
session.isSelfMuted = false;
session.isTalking = false;
- try {
- await audioElement.play();
- session.isAudioInError = false;
- } catch (error) {
- if (typeof error === "object" && error.name === "NotAllowedError") {
- // Ignored as some browsers may reject play() calls that do not
- // originate from a user input.
- return;
- }
- session.isAudioInError = true;
- }
+ await session.playAudio();
}
if (track.kind === "video") {
session.videoStream = stream;
diff --git a/addons/mail/static/src/rtc/rtc_session_model.js b/addons/mail/static/src/rtc/rtc_session_model.js
index 47e12eda5c8..ed4e237483d 100644
--- a/addons/mail/static/src/rtc/rtc_session_model.js
+++ b/addons/mail/static/src/rtc/rtc_session_model.js
@@ -18,7 +18,8 @@ export class RtcSession {
audioStream;
/** @type {RTCDataChannel} */
dataChannel;
- isAudioInError;
+ audioError;
+ videoError;
isRaisingHand;
isTalking;
localVolume;
@@ -84,6 +85,18 @@ export class RtcSession {
this.localVolume = value;
}
+ async playAudio() {
+ if (!this.audioElement) {
+ return;
+ }
+ try {
+ await this.audioElement.play();
+ this.audioError = undefined;
+ } catch (error) {
+ this.audioError = error.name;
+ }
+ }
+
async updateStats() {
delete this.localCandidateType;
delete this.remoteCandidateType;