FIX: reimplements chat audio into a service (#18983)

This implementation attempts to be more resilient to background tab.

Notes:
- adds support for immediate arg in @debounce decorators
- fixes a bug in discourseDebounce which was not supporting immediate arg in tests
- chat-audio-manager has no tests as audio requires real user interaction and is hard to test reliably
This commit is contained in:
Joffrey JAFFEUX
2022-11-11 13:11:41 +01:00
committed by GitHub
parent bc22fe4fdf
commit c8beefc1ee
7 changed files with 135 additions and 53 deletions

View File

@ -4,7 +4,8 @@ import discourseComputed from "discourse-common/utils/decorators";
import I18n from "I18n";
import { action } from "@ember/object";
import { popupAjaxError } from "discourse/lib/ajax-error";
import { CHAT_SOUNDS } from "discourse/plugins/chat/discourse/initializers/chat-notification-sounds";
import { CHAT_SOUNDS } from "discourse/plugins/chat/discourse/services/chat-audio-manager";
import { inject as service } from "@ember/service";
const CHAT_ATTRS = [
"chat_enabled",
@ -20,6 +21,8 @@ const EMAIL_FREQUENCY_OPTIONS = [
];
export default class PreferencesChatController extends Controller {
@service chatAudioManager;
emailFrequencyOptions = EMAIL_FREQUENCY_OPTIONS;
@discourseComputed
@ -32,8 +35,7 @@ export default class PreferencesChatController extends Controller {
@action
onChangeChatSound(sound) {
if (sound && !isTesting()) {
const audio = new Audio(CHAT_SOUNDS[sound]);
audio.play();
this.chatAudioManager.playImmediately(sound);
}
this.model.set("user_option.chat_sound", sound);
}

View File

@ -0,0 +1,29 @@
import { withPluginApi } from "discourse/lib/plugin-api";
const MENTION = 29;
const MESSAGE = 30;
const CHAT_NOTIFICATION_TYPES = [MENTION, MESSAGE];
export default {
name: "chat-audio",
initialize(container) {
const currentUser = container.lookup("service:current-user");
const chatService = container.lookup("service:chat");
if (!chatService.userCanChat || !currentUser?.chat_sound) {
return;
}
const chatAudioManager = container.lookup("service:chat-audio-manager");
chatAudioManager.setup();
withPluginApi("0.12.1", (api) => {
api.registerDesktopNotificationHandler((data, siteSettings, user) => {
if (CHAT_NOTIFICATION_TYPES.includes(data.notification_type)) {
chatAudioManager.play(user.chat_sound);
}
});
});
},
};

View File

@ -1,47 +0,0 @@
import { withPluginApi } from "discourse/lib/plugin-api";
import discourseDebounce from "discourse-common/lib/debounce";
export const CHAT_SOUNDS = {
bell: "/plugins/chat/audio/bell.mp3",
ding: "/plugins/chat/audio/ding.mp3",
};
const MENTION = 29;
const MESSAGE = 30;
const CHAT_NOTIFICATION_TYPES = [MENTION, MESSAGE];
const AUDIO_DEBOUNCE_TIMEOUT = 3000;
export default {
name: "chat-notification-sounds",
initialize(container) {
const currentUser = container.lookup("service:current-user");
const chatService = container.lookup("service:chat");
if (!chatService.userCanChat || !currentUser?.chat_sound) {
return;
}
function playAudio(user) {
const audio = new Audio(CHAT_SOUNDS[user.chat_sound]);
audio.play().catch(() => {
// eslint-disable-next-line no-console
console.info(
"User needs to interact with DOM before we can play notification sounds"
);
});
}
function playAudioWithDebounce(user) {
discourseDebounce(this, playAudio, user, AUDIO_DEBOUNCE_TIMEOUT, true);
}
withPluginApi("0.12.1", (api) => {
api.registerDesktopNotificationHandler((data, siteSettings, user) => {
if (CHAT_NOTIFICATION_TYPES.includes(data.notification_type)) {
playAudioWithDebounce(user);
}
});
});
},
};

View File

@ -0,0 +1,68 @@
import Service from "@ember/service";
import { debounce } from "discourse-common/utils/decorators";
const AUDIO_DEBOUNCE_DELAY = 3000;
export const CHAT_SOUNDS = {
bell: [{ src: "/plugins/chat/audio/bell.mp3", type: "audio/mpeg" }],
ding: [{ src: "/plugins/chat/audio/ding.mp3", type: "audio/mpeg" }],
};
const DEFAULT_SOUND_NAME = "bell";
const createAudioCache = (sources) => {
const audio = new Audio();
sources.forEach(({ type, src }) => {
const source = document.createElement("source");
source.type = type;
source.src = src;
audio.appendChild(source);
});
return audio;
};
export default class ChatAudioManager extends Service {
_audioCache = {};
setup() {
Object.keys(CHAT_SOUNDS).forEach((soundName) => {
this._audioCache[soundName] = createAudioCache(CHAT_SOUNDS[soundName]);
});
}
willDestroy() {
this._super(...arguments);
this._audioCache = {};
}
playImmediately(soundName) {
return this._play(soundName);
}
@debounce(AUDIO_DEBOUNCE_DELAY, true)
play(soundName) {
return this._play(soundName);
}
_play(soundName) {
const audio =
this._audioCache[soundName] || this._audioCache[DEFAULT_SOUND_NAME];
if (!audio.paused) {
audio.pause();
if (typeof audio.fastSeek === "function") {
audio.fastSeek(0);
} else {
audio.currentTime = 0;
}
}
return audio.play().catch(() => {
// eslint-disable-next-line no-console
console.info(
"[chat] User needs to interact with DOM before we can play notification sounds."
);
});
}
}