1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
2 *
3 * This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6
7 #include "nscore.h"
8 #include "plstr.h"
9 #include <stdio.h>
10 #include "nsString.h"
11 #include <windows.h>
12
13 // mmsystem.h is needed to build with WIN32_LEAN_AND_MEAN
14 #include <mmsystem.h>
15
16 #include "HeadlessSound.h"
17 #include "nsSound.h"
18 #include "nsIURL.h"
19 #include "nsNetUtil.h"
20 #include "nsIChannel.h"
21 #include "nsContentUtils.h"
22 #include "nsCRT.h"
23 #include "nsIObserverService.h"
24
25 #include "mozilla/Logging.h"
26 #include "prtime.h"
27
28 #include "nsNativeCharsetUtils.h"
29 #include "nsThreadUtils.h"
30 #include "mozilla/ClearOnShutdown.h"
31 #include "gfxPlatform.h"
32
33 using mozilla::LogLevel;
34
35 static mozilla::LazyLogModule gWin32SoundLog("nsSound");
36
37 class nsSoundPlayer : public mozilla::Runnable {
38 public:
nsSoundPlayer(const nsAString & aSoundName)39 explicit nsSoundPlayer(const nsAString &aSoundName)
40 : mozilla::Runnable("nsSoundPlayer"),
41 mSoundName(aSoundName),
42 mSoundData(nullptr) {}
43
nsSoundPlayer(const uint8_t * aData,size_t aSize)44 nsSoundPlayer(const uint8_t *aData, size_t aSize)
45 : mozilla::Runnable("nsSoundPlayer"), mSoundName(EmptyString()) {
46 MOZ_ASSERT(aSize > 0, "Size should not be zero");
47 MOZ_ASSERT(aData, "Data shoud not be null");
48
49 // We will disptach nsSoundPlayer to playerthread, so keep a data copy
50 mSoundData = new uint8_t[aSize];
51 memcpy(mSoundData, aData, aSize);
52 }
53
54 NS_DECL_NSIRUNNABLE
55
56 protected:
57 ~nsSoundPlayer();
58
59 nsString mSoundName;
60 uint8_t *mSoundData;
61 };
62
63 NS_IMETHODIMP
Run()64 nsSoundPlayer::Run() {
65 MOZ_ASSERT(!mSoundName.IsEmpty() || mSoundData,
66 "Sound name or sound data should be specified");
67 DWORD flags = SND_NODEFAULT | SND_ASYNC;
68
69 if (mSoundData) {
70 flags |= SND_MEMORY;
71 ::PlaySoundW(reinterpret_cast<LPCWSTR>(mSoundData), nullptr, flags);
72 } else {
73 flags |= SND_ALIAS;
74 ::PlaySoundW(mSoundName.get(), nullptr, flags);
75 }
76 return NS_OK;
77 }
78
~nsSoundPlayer()79 nsSoundPlayer::~nsSoundPlayer() { delete[] mSoundData; }
80
81 mozilla::StaticRefPtr<nsISound> nsSound::sInstance;
82
GetInstance()83 /* static */ already_AddRefed<nsISound> nsSound::GetInstance() {
84 if (!sInstance) {
85 if (gfxPlatform::IsHeadless()) {
86 sInstance = new mozilla::widget::HeadlessSound();
87 } else {
88 RefPtr<nsSound> sound = new nsSound();
89 nsresult rv = sound->CreatePlayerThread();
90 if (NS_WARN_IF(NS_FAILED(rv))) {
91 return nullptr;
92 }
93 sInstance = sound.forget();
94 }
95 ClearOnShutdown(&sInstance);
96 }
97
98 RefPtr<nsISound> service = sInstance;
99 return service.forget();
100 }
101
102 #ifndef SND_PURGE
103 // Not available on Windows CE, and according to MSDN
104 // doesn't do anything on recent windows either.
105 #define SND_PURGE 0
106 #endif
107
NS_IMPL_ISUPPORTS(nsSound,nsISound,nsIStreamLoaderObserver)108 NS_IMPL_ISUPPORTS(nsSound, nsISound, nsIStreamLoaderObserver)
109
110 nsSound::nsSound() : mInited(false) {}
111
~nsSound()112 nsSound::~nsSound() {}
113
PurgeLastSound()114 void nsSound::PurgeLastSound() {
115 // Halt any currently playing sound.
116 if (mSoundPlayer) {
117 if (mPlayerThread) {
118 mPlayerThread->Dispatch(NS_NewRunnableFunction(
119 "nsSound::PurgeLastSound", [player = std::move(mSoundPlayer)]() {
120 // Capture move mSoundPlayer to lambda then
121 // PlaySoundW(nullptr, nullptr, SND_PURGE) will be called before
122 // freeing the nsSoundPlayer.
123 ::PlaySoundW(nullptr, nullptr, SND_PURGE);
124 }), NS_DISPATCH_NORMAL);
125 }
126 }
127 }
128
Beep()129 NS_IMETHODIMP nsSound::Beep() {
130 ::MessageBeep(0);
131
132 return NS_OK;
133 }
134
OnStreamComplete(nsIStreamLoader * aLoader,nsISupports * context,nsresult aStatus,uint32_t dataLen,const uint8_t * data)135 NS_IMETHODIMP nsSound::OnStreamComplete(nsIStreamLoader *aLoader,
136 nsISupports *context, nsresult aStatus,
137 uint32_t dataLen, const uint8_t *data) {
138 MOZ_ASSERT(mPlayerThread, "player thread should not be null ");
139 // print a load error on bad status
140 if (NS_FAILED(aStatus)) {
141 #ifdef DEBUG
142 if (aLoader) {
143 nsCOMPtr<nsIRequest> request;
144 nsCOMPtr<nsIChannel> channel;
145 aLoader->GetRequest(getter_AddRefs(request));
146 if (request) channel = do_QueryInterface(request);
147 if (channel) {
148 nsCOMPtr<nsIURI> uri;
149 channel->GetURI(getter_AddRefs(uri));
150 if (uri) {
151 nsAutoCString uriSpec;
152 uri->GetSpec(uriSpec);
153 MOZ_LOG(gWin32SoundLog, LogLevel::Info,
154 ("Failed to load %s\n", uriSpec.get()));
155 }
156 }
157 }
158 #endif
159 return aStatus;
160 }
161
162 PurgeLastSound();
163
164 if (data && dataLen > 0) {
165 MOZ_ASSERT(!mSoundPlayer, "mSoundPlayer should be null");
166 mSoundPlayer = new nsSoundPlayer(data, dataLen);
167 MOZ_ASSERT(mSoundPlayer, "Could not create player");
168
169 nsresult rv = mPlayerThread->Dispatch(mSoundPlayer, NS_DISPATCH_NORMAL);
170 if (NS_WARN_IF(FAILED(rv))) {
171 return rv;
172 }
173 }
174
175 return NS_OK;
176 }
177
Play(nsIURL * aURL)178 NS_IMETHODIMP nsSound::Play(nsIURL *aURL) {
179 nsresult rv;
180
181 #ifdef DEBUG_SOUND
182 char *url;
183 aURL->GetSpec(&url);
184 MOZ_LOG(gWin32SoundLog, LogLevel::Info, ("%s\n", url));
185 #endif
186
187 nsCOMPtr<nsIStreamLoader> loader;
188 rv = NS_NewStreamLoader(getter_AddRefs(loader), aURL,
189 this, // aObserver
190 nsContentUtils::GetSystemPrincipal(),
191 nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL,
192 nsIContentPolicy::TYPE_OTHER);
193 return rv;
194 }
195
CreatePlayerThread()196 nsresult nsSound::CreatePlayerThread() {
197 if (mPlayerThread) {
198 return NS_OK;
199 }
200 if (NS_WARN_IF(NS_FAILED(NS_NewNamedThread("PlayEventSound",
201 getter_AddRefs(mPlayerThread))))) {
202 return NS_ERROR_FAILURE;
203 }
204
205 // Add an observer for shutdown event to release the thread at that time
206 nsCOMPtr<nsIObserverService> observerService =
207 mozilla::services::GetObserverService();
208 if (!observerService) {
209 return NS_ERROR_FAILURE;
210 }
211
212 observerService->AddObserver(this, "xpcom-shutdown-threads", false);
213 return NS_OK;
214 }
215
216 NS_IMETHODIMP
Observe(nsISupports * aSubject,const char * aTopic,const char16_t * aData)217 nsSound::Observe(nsISupports *aSubject, const char *aTopic,
218 const char16_t *aData) {
219 if (!strcmp(aTopic, "xpcom-shutdown-threads")) {
220 PurgeLastSound();
221
222 if (mPlayerThread) {
223 mPlayerThread->Shutdown();
224 mPlayerThread = nullptr;
225 }
226 }
227
228 return NS_OK;
229 }
230
Init()231 NS_IMETHODIMP nsSound::Init() {
232 if (mInited) {
233 return NS_OK;
234 }
235
236 MOZ_ASSERT(mPlayerThread, "player thread should not be null ");
237 // This call halts a sound if it was still playing.
238 // We have to use the sound library for something to make sure
239 // it is initialized.
240 // If we wait until the first sound is played, there will
241 // be a time lag as the library gets loaded.
242 // This should be done in player thread otherwise it will block main thread
243 // at the first time loading sound library.
244 mPlayerThread->Dispatch(
245 NS_NewRunnableFunction(
246 "nsSound::Init", []() { ::PlaySoundW(nullptr, nullptr, SND_PURGE); }),
247 NS_DISPATCH_NORMAL);
248
249 mInited = true;
250
251 return NS_OK;
252 }
253
PlaySystemSound(const nsAString & aSoundAlias)254 NS_IMETHODIMP nsSound::PlaySystemSound(const nsAString &aSoundAlias) {
255 MOZ_ASSERT(mPlayerThread, "player thread should not be null ");
256 PurgeLastSound();
257
258 if (!NS_IsMozAliasSound(aSoundAlias)) {
259 if (aSoundAlias.IsEmpty()) return NS_OK;
260 nsCOMPtr<nsIRunnable> player = new nsSoundPlayer(aSoundAlias);
261 MOZ_ASSERT(player, "Could not create player");
262 nsresult rv = mPlayerThread->Dispatch(player, NS_DISPATCH_NORMAL);
263 if (NS_WARN_IF(NS_FAILED(rv))) {
264 return rv;
265 }
266 return NS_OK;
267 }
268
269 NS_WARNING(
270 "nsISound::playSystemSound is called with \"_moz_\" events, they are "
271 "obsolete, use nsISound::playEventSound instead");
272
273 uint32_t eventId;
274 if (aSoundAlias.Equals(NS_SYSSOUND_MAIL_BEEP))
275 eventId = EVENT_NEW_MAIL_RECEIVED;
276 else if (aSoundAlias.Equals(NS_SYSSOUND_CONFIRM_DIALOG))
277 eventId = EVENT_CONFIRM_DIALOG_OPEN;
278 else if (aSoundAlias.Equals(NS_SYSSOUND_ALERT_DIALOG))
279 eventId = EVENT_ALERT_DIALOG_OPEN;
280 else if (aSoundAlias.Equals(NS_SYSSOUND_MENU_EXECUTE))
281 eventId = EVENT_MENU_EXECUTE;
282 else if (aSoundAlias.Equals(NS_SYSSOUND_MENU_POPUP))
283 eventId = EVENT_MENU_POPUP;
284 else
285 return NS_OK;
286
287 return PlayEventSound(eventId);
288 }
289
PlayEventSound(uint32_t aEventId)290 NS_IMETHODIMP nsSound::PlayEventSound(uint32_t aEventId) {
291 MOZ_ASSERT(mPlayerThread, "player thread should not be null ");
292 PurgeLastSound();
293
294 const wchar_t *sound = nullptr;
295 switch (aEventId) {
296 case EVENT_NEW_MAIL_RECEIVED:
297 sound = L"MailBeep";
298 break;
299 case EVENT_ALERT_DIALOG_OPEN:
300 sound = L"SystemExclamation";
301 break;
302 case EVENT_CONFIRM_DIALOG_OPEN:
303 sound = L"SystemQuestion";
304 break;
305 case EVENT_MENU_EXECUTE:
306 sound = L"MenuCommand";
307 break;
308 case EVENT_MENU_POPUP:
309 sound = L"MenuPopup";
310 break;
311 case EVENT_EDITOR_MAX_LEN:
312 sound = L".Default";
313 break;
314 default:
315 // Win32 plays no sounds at NS_SYSSOUND_PROMPT_DIALOG and
316 // NS_SYSSOUND_SELECT_DIALOG.
317 return NS_OK;
318 }
319 NS_ASSERTION(sound, "sound is null");
320 MOZ_ASSERT(!mSoundPlayer, "mSoundPlayer should be null");
321 mSoundPlayer = new nsSoundPlayer(nsDependentString(sound));
322 MOZ_ASSERT(mSoundPlayer, "Could not create player");
323 nsresult rv = mPlayerThread->Dispatch(mSoundPlayer, NS_DISPATCH_NORMAL);
324 if (NS_WARN_IF(NS_FAILED(rv))) {
325 return rv;
326 }
327 return NS_OK;
328 }
329