diff --git a/modules/UI/side_pannels/chat/Chat.js b/modules/UI/side_pannels/chat/Chat.js
index f68f82f19..e335771ee 100644
--- a/modules/UI/side_pannels/chat/Chat.js
+++ b/modules/UI/side_pannels/chat/Chat.js
@@ -25,8 +25,6 @@ const htmlStr = `
-
@@ -285,7 +283,6 @@ const Chat = {
if (!Chat.isVisible()) {
unreadMessages++;
- UIUtil.playSoundNotification('chatNotification');
updateVisualNotification();
}
}
diff --git a/modules/UI/util/UIUtil.js b/modules/UI/util/UIUtil.js
index edb2567ce..d6cf7412f 100644
--- a/modules/UI/util/UIUtil.js
+++ b/modules/UI/util/UIUtil.js
@@ -66,15 +66,6 @@ const UIUtil = {
return el.clientHeight + 1;
},
- /**
- * Plays the sound given by id.
- *
- * @param id the identifier of the audio element.
- */
- playSoundNotification(id) {
- document.getElementById(id).play();
- },
-
/**
* Escapes the given text.
*/
diff --git a/react/features/app/components/App.web.js b/react/features/app/components/App.web.js
index 615564cb2..aaa8c18ef 100644
--- a/react/features/app/components/App.web.js
+++ b/react/features/app/components/App.web.js
@@ -3,6 +3,7 @@ import React from 'react';
import '../../base/responsive-ui';
import { getLocationContextRoot } from '../../base/util';
+import '../../chat';
import '../../room-lock';
import { AbstractApp } from './AbstractApp';
diff --git a/react/features/chat/constants.js b/react/features/chat/constants.js
new file mode 100644
index 000000000..699a4a61f
--- /dev/null
+++ b/react/features/chat/constants.js
@@ -0,0 +1,9 @@
+/* eslint-disable no-unused-vars */
+import { playAudio } from '../base/media';
+
+/**
+ * The audio ID of the audio element for which the {@link playAudio} action is
+ * triggered when new chat message is received.
+ * @type {string}
+ */
+export const INCOMING_MSG_SOUND_ID = 'INCOMING_MSG_SOUND';
diff --git a/react/features/chat/index.js b/react/features/chat/index.js
new file mode 100644
index 000000000..5d43fae60
--- /dev/null
+++ b/react/features/chat/index.js
@@ -0,0 +1,3 @@
+export * from './constants';
+
+import './middleware';
diff --git a/react/features/chat/middleware.js b/react/features/chat/middleware.js
new file mode 100644
index 000000000..f8b0440b6
--- /dev/null
+++ b/react/features/chat/middleware.js
@@ -0,0 +1,66 @@
+// @flow
+
+import { APP_WILL_MOUNT, APP_WILL_UNMOUNT } from '../app';
+import { CONFERENCE_JOINED } from '../base/conference';
+import { JitsiConferenceEvents } from '../base/lib-jitsi-meet';
+import { MiddlewareRegistry } from '../base/redux';
+import { playSound, registerSound, unregisterSound } from '../base/sounds';
+
+import { INCOMING_MSG_SOUND_ID } from './constants';
+import { INCOMING_MSG_SOUND_SRC } from './sounds';
+
+declare var APP: Object;
+
+/**
+ * Implements the middleware of the chat feature.
+ *
+ * @param {Store} store - The redux store.
+ * @returns {Function}
+ */
+MiddlewareRegistry.register(store => next => action => {
+ switch (action.type) {
+ case APP_WILL_MOUNT: {
+ // Register chat msg sound only on web
+ typeof APP !== 'undefined'
+ && store.dispatch(
+ registerSound(INCOMING_MSG_SOUND_ID, INCOMING_MSG_SOUND_SRC));
+ break;
+ }
+ case APP_WILL_UNMOUNT: {
+ // Register chat msg sound only on web
+ typeof APP !== 'undefined'
+ && store.dispatch(unregisterSound(INCOMING_MSG_SOUND_ID));
+ break;
+ }
+ case CONFERENCE_JOINED:
+ typeof APP !== 'undefined'
+ && _addChatMsgListener(action.conference, store);
+ break;
+ }
+
+ return next(action);
+});
+
+/**
+ * Registers listener for {@link JitsiConferenceEvents.MESSAGE_RECEIVED} which
+ * will play a sound on the event, given that the chat is not currently visible.
+ *
+ * @param {JitsiConference} conference - The conference instance on which the
+ * new event listener will be registered.
+ * @param {Dispatch} next - The redux dispatch function to dispatch the
+ * specified action to the specified store.
+ * @returns {void}
+ * @private
+ */
+function _addChatMsgListener(conference, { dispatch }) {
+ // XXX Currently there's no need to remove the listener, because
+ // conference instance can not be re-used. Listener will be gone with
+ // the conference instance.
+ conference.on(
+ JitsiConferenceEvents.MESSAGE_RECEIVED,
+ () => {
+ if (!APP.UI.isChatVisible()) {
+ dispatch(playSound(INCOMING_MSG_SOUND_ID));
+ }
+ });
+}
diff --git a/react/features/chat/sounds.web.js b/react/features/chat/sounds.web.js
new file mode 100644
index 000000000..980276081
--- /dev/null
+++ b/react/features/chat/sounds.web.js
@@ -0,0 +1,5 @@
+/**
+ * The audio source for the incoming chat message sound.
+ * @type {string}
+ */
+export const INCOMING_MSG_SOUND_SRC = 'sounds/incomingMessage.wav';