|
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 "nsSound.h" |
|
17 #include "nsIURL.h" |
|
18 #include "nsNetUtil.h" |
|
19 #include "nsCRT.h" |
|
20 |
|
21 #include "prlog.h" |
|
22 #include "prtime.h" |
|
23 #include "prprf.h" |
|
24 #include "prmem.h" |
|
25 |
|
26 #include "nsNativeCharsetUtils.h" |
|
27 #include "nsThreadUtils.h" |
|
28 |
|
29 #ifdef PR_LOGGING |
|
30 PRLogModuleInfo* gWin32SoundLog = nullptr; |
|
31 #endif |
|
32 |
|
33 class nsSoundPlayer: public nsRunnable { |
|
34 public: |
|
35 nsSoundPlayer(nsSound *aSound, const wchar_t* aSoundName) : |
|
36 mSoundName(aSoundName), mSound(aSound) |
|
37 { |
|
38 Init(); |
|
39 } |
|
40 |
|
41 nsSoundPlayer(nsSound *aSound, const nsAString& aSoundName) : |
|
42 mSoundName(aSoundName), mSound(aSound) |
|
43 { |
|
44 Init(); |
|
45 } |
|
46 |
|
47 NS_DECL_NSIRUNNABLE |
|
48 |
|
49 protected: |
|
50 nsString mSoundName; |
|
51 nsSound *mSound; // Strong, but this will be released from SoundReleaser. |
|
52 nsCOMPtr<nsIThread> mThread; |
|
53 |
|
54 void Init() |
|
55 { |
|
56 NS_GetCurrentThread(getter_AddRefs(mThread)); |
|
57 NS_ASSERTION(mThread, "failed to get current thread"); |
|
58 NS_IF_ADDREF(mSound); |
|
59 } |
|
60 |
|
61 class SoundReleaser: public nsRunnable { |
|
62 public: |
|
63 SoundReleaser(nsSound* aSound) : |
|
64 mSound(aSound) |
|
65 { |
|
66 } |
|
67 |
|
68 NS_DECL_NSIRUNNABLE |
|
69 |
|
70 protected: |
|
71 nsSound *mSound; |
|
72 }; |
|
73 }; |
|
74 |
|
75 NS_IMETHODIMP |
|
76 nsSoundPlayer::Run() |
|
77 { |
|
78 PR_SetCurrentThreadName("Play Sound"); |
|
79 |
|
80 NS_PRECONDITION(!mSoundName.IsEmpty(), "Sound name should not be empty"); |
|
81 ::PlaySoundW(mSoundName.get(), nullptr, |
|
82 SND_NODEFAULT | SND_ALIAS | SND_ASYNC); |
|
83 nsCOMPtr<nsIRunnable> releaser = new SoundReleaser(mSound); |
|
84 // Don't release nsSound from here, because here is not an owning thread of |
|
85 // the nsSound. nsSound must be released in its owning thread. |
|
86 mThread->Dispatch(releaser, NS_DISPATCH_NORMAL); |
|
87 return NS_OK; |
|
88 } |
|
89 |
|
90 NS_IMETHODIMP |
|
91 nsSoundPlayer::SoundReleaser::Run() |
|
92 { |
|
93 mSound->ShutdownOldPlayerThread(); |
|
94 NS_IF_RELEASE(mSound); |
|
95 return NS_OK; |
|
96 } |
|
97 |
|
98 |
|
99 #ifndef SND_PURGE |
|
100 // Not available on Windows CE, and according to MSDN |
|
101 // doesn't do anything on recent windows either. |
|
102 #define SND_PURGE 0 |
|
103 #endif |
|
104 |
|
105 NS_IMPL_ISUPPORTS(nsSound, nsISound, nsIStreamLoaderObserver) |
|
106 |
|
107 |
|
108 nsSound::nsSound() |
|
109 { |
|
110 #ifdef PR_LOGGING |
|
111 if (!gWin32SoundLog) { |
|
112 gWin32SoundLog = PR_NewLogModule("nsSound"); |
|
113 } |
|
114 #endif |
|
115 |
|
116 mLastSound = nullptr; |
|
117 } |
|
118 |
|
119 nsSound::~nsSound() |
|
120 { |
|
121 NS_ASSERTION(!mPlayerThread, "player thread is not null but should be"); |
|
122 PurgeLastSound(); |
|
123 } |
|
124 |
|
125 void nsSound::ShutdownOldPlayerThread() |
|
126 { |
|
127 if (mPlayerThread) { |
|
128 mPlayerThread->Shutdown(); |
|
129 mPlayerThread = nullptr; |
|
130 } |
|
131 } |
|
132 |
|
133 void nsSound::PurgeLastSound() |
|
134 { |
|
135 if (mLastSound) { |
|
136 // Halt any currently playing sound. |
|
137 ::PlaySound(nullptr, nullptr, SND_PURGE); |
|
138 |
|
139 // Now delete the buffer. |
|
140 free(mLastSound); |
|
141 mLastSound = nullptr; |
|
142 } |
|
143 } |
|
144 |
|
145 NS_IMETHODIMP nsSound::Beep() |
|
146 { |
|
147 ::MessageBeep(0); |
|
148 |
|
149 return NS_OK; |
|
150 } |
|
151 |
|
152 NS_IMETHODIMP nsSound::OnStreamComplete(nsIStreamLoader *aLoader, |
|
153 nsISupports *context, |
|
154 nsresult aStatus, |
|
155 uint32_t dataLen, |
|
156 const uint8_t *data) |
|
157 { |
|
158 // print a load error on bad status |
|
159 if (NS_FAILED(aStatus)) { |
|
160 #ifdef DEBUG |
|
161 if (aLoader) { |
|
162 nsCOMPtr<nsIRequest> request; |
|
163 nsCOMPtr<nsIChannel> channel; |
|
164 aLoader->GetRequest(getter_AddRefs(request)); |
|
165 if (request) |
|
166 channel = do_QueryInterface(request); |
|
167 if (channel) { |
|
168 nsCOMPtr<nsIURI> uri; |
|
169 channel->GetURI(getter_AddRefs(uri)); |
|
170 if (uri) { |
|
171 nsAutoCString uriSpec; |
|
172 uri->GetSpec(uriSpec); |
|
173 PR_LOG(gWin32SoundLog, PR_LOG_ALWAYS, |
|
174 ("Failed to load %s\n", uriSpec.get())); |
|
175 } |
|
176 } |
|
177 } |
|
178 #endif |
|
179 return aStatus; |
|
180 } |
|
181 |
|
182 ShutdownOldPlayerThread(); |
|
183 PurgeLastSound(); |
|
184 |
|
185 if (data && dataLen > 0) { |
|
186 DWORD flags = SND_MEMORY | SND_NODEFAULT; |
|
187 // We try to make a copy so we can play it async. |
|
188 mLastSound = (uint8_t *) malloc(dataLen); |
|
189 if (mLastSound) { |
|
190 memcpy(mLastSound, data, dataLen); |
|
191 data = mLastSound; |
|
192 flags |= SND_ASYNC; |
|
193 } |
|
194 ::PlaySoundW(reinterpret_cast<LPCWSTR>(data), 0, flags); |
|
195 } |
|
196 |
|
197 return NS_OK; |
|
198 } |
|
199 |
|
200 NS_IMETHODIMP nsSound::Play(nsIURL *aURL) |
|
201 { |
|
202 nsresult rv; |
|
203 |
|
204 #ifdef DEBUG_SOUND |
|
205 char *url; |
|
206 aURL->GetSpec(&url); |
|
207 PR_LOG(gWin32SoundLog, PR_LOG_ALWAYS, |
|
208 ("%s\n", url)); |
|
209 #endif |
|
210 |
|
211 nsCOMPtr<nsIStreamLoader> loader; |
|
212 rv = NS_NewStreamLoader(getter_AddRefs(loader), aURL, this); |
|
213 |
|
214 return rv; |
|
215 } |
|
216 |
|
217 |
|
218 NS_IMETHODIMP nsSound::Init() |
|
219 { |
|
220 // This call halts a sound if it was still playing. |
|
221 // We have to use the sound library for something to make sure |
|
222 // it is initialized. |
|
223 // If we wait until the first sound is played, there will |
|
224 // be a time lag as the library gets loaded. |
|
225 ::PlaySound(nullptr, nullptr, SND_PURGE); |
|
226 |
|
227 return NS_OK; |
|
228 } |
|
229 |
|
230 |
|
231 NS_IMETHODIMP nsSound::PlaySystemSound(const nsAString &aSoundAlias) |
|
232 { |
|
233 ShutdownOldPlayerThread(); |
|
234 PurgeLastSound(); |
|
235 |
|
236 if (!NS_IsMozAliasSound(aSoundAlias)) { |
|
237 if (aSoundAlias.IsEmpty()) |
|
238 return NS_OK; |
|
239 nsCOMPtr<nsIRunnable> player = new nsSoundPlayer(this, aSoundAlias); |
|
240 NS_ENSURE_TRUE(player, NS_ERROR_OUT_OF_MEMORY); |
|
241 nsresult rv = NS_NewThread(getter_AddRefs(mPlayerThread), player); |
|
242 NS_ENSURE_SUCCESS(rv, rv); |
|
243 return NS_OK; |
|
244 } |
|
245 |
|
246 NS_WARNING("nsISound::playSystemSound is called with \"_moz_\" events, they are obsolete, use nsISound::playEventSound instead"); |
|
247 |
|
248 uint32_t eventId; |
|
249 if (aSoundAlias.Equals(NS_SYSSOUND_MAIL_BEEP)) |
|
250 eventId = EVENT_NEW_MAIL_RECEIVED; |
|
251 else if (aSoundAlias.Equals(NS_SYSSOUND_CONFIRM_DIALOG)) |
|
252 eventId = EVENT_CONFIRM_DIALOG_OPEN; |
|
253 else if (aSoundAlias.Equals(NS_SYSSOUND_ALERT_DIALOG)) |
|
254 eventId = EVENT_ALERT_DIALOG_OPEN; |
|
255 else if (aSoundAlias.Equals(NS_SYSSOUND_MENU_EXECUTE)) |
|
256 eventId = EVENT_MENU_EXECUTE; |
|
257 else if (aSoundAlias.Equals(NS_SYSSOUND_MENU_POPUP)) |
|
258 eventId = EVENT_MENU_POPUP; |
|
259 else |
|
260 return NS_OK; |
|
261 |
|
262 return PlayEventSound(eventId); |
|
263 } |
|
264 |
|
265 NS_IMETHODIMP nsSound::PlayEventSound(uint32_t aEventId) |
|
266 { |
|
267 ShutdownOldPlayerThread(); |
|
268 PurgeLastSound(); |
|
269 |
|
270 const wchar_t *sound = nullptr; |
|
271 switch (aEventId) { |
|
272 case EVENT_NEW_MAIL_RECEIVED: |
|
273 sound = L"MailBeep"; |
|
274 break; |
|
275 case EVENT_ALERT_DIALOG_OPEN: |
|
276 sound = L"SystemExclamation"; |
|
277 break; |
|
278 case EVENT_CONFIRM_DIALOG_OPEN: |
|
279 sound = L"SystemQuestion"; |
|
280 break; |
|
281 case EVENT_MENU_EXECUTE: |
|
282 sound = L"MenuCommand"; |
|
283 break; |
|
284 case EVENT_MENU_POPUP: |
|
285 sound = L"MenuPopup"; |
|
286 break; |
|
287 case EVENT_EDITOR_MAX_LEN: |
|
288 sound = L".Default"; |
|
289 break; |
|
290 default: |
|
291 // Win32 plays no sounds at NS_SYSSOUND_PROMPT_DIALOG and |
|
292 // NS_SYSSOUND_SELECT_DIALOG. |
|
293 return NS_OK; |
|
294 } |
|
295 NS_ASSERTION(sound, "sound is null"); |
|
296 |
|
297 nsCOMPtr<nsIRunnable> player = new nsSoundPlayer(this, sound); |
|
298 NS_ENSURE_TRUE(player, NS_ERROR_OUT_OF_MEMORY); |
|
299 nsresult rv = NS_NewThread(getter_AddRefs(mPlayerThread), player); |
|
300 NS_ENSURE_SUCCESS(rv, rv); |
|
301 return NS_OK; |
|
302 } |