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;