michael@0: /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- michael@0: * michael@0: * This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #include "nscore.h" michael@0: #include "plstr.h" michael@0: #include michael@0: #include "nsString.h" michael@0: #include michael@0: michael@0: // mmsystem.h is needed to build with WIN32_LEAN_AND_MEAN michael@0: #include michael@0: michael@0: #include "nsSound.h" michael@0: #include "nsIURL.h" michael@0: #include "nsNetUtil.h" michael@0: #include "nsCRT.h" michael@0: michael@0: #include "prlog.h" michael@0: #include "prtime.h" michael@0: #include "prprf.h" michael@0: #include "prmem.h" michael@0: michael@0: #include "nsNativeCharsetUtils.h" michael@0: #include "nsThreadUtils.h" michael@0: michael@0: #ifdef PR_LOGGING michael@0: PRLogModuleInfo* gWin32SoundLog = nullptr; michael@0: #endif michael@0: michael@0: class nsSoundPlayer: public nsRunnable { michael@0: public: michael@0: nsSoundPlayer(nsSound *aSound, const wchar_t* aSoundName) : michael@0: mSoundName(aSoundName), mSound(aSound) michael@0: { michael@0: Init(); michael@0: } michael@0: michael@0: nsSoundPlayer(nsSound *aSound, const nsAString& aSoundName) : michael@0: mSoundName(aSoundName), mSound(aSound) michael@0: { michael@0: Init(); michael@0: } michael@0: michael@0: NS_DECL_NSIRUNNABLE michael@0: michael@0: protected: michael@0: nsString mSoundName; michael@0: nsSound *mSound; // Strong, but this will be released from SoundReleaser. michael@0: nsCOMPtr mThread; michael@0: michael@0: void Init() michael@0: { michael@0: NS_GetCurrentThread(getter_AddRefs(mThread)); michael@0: NS_ASSERTION(mThread, "failed to get current thread"); michael@0: NS_IF_ADDREF(mSound); michael@0: } michael@0: michael@0: class SoundReleaser: public nsRunnable { michael@0: public: michael@0: SoundReleaser(nsSound* aSound) : michael@0: mSound(aSound) michael@0: { michael@0: } michael@0: michael@0: NS_DECL_NSIRUNNABLE michael@0: michael@0: protected: michael@0: nsSound *mSound; michael@0: }; michael@0: }; michael@0: michael@0: NS_IMETHODIMP michael@0: nsSoundPlayer::Run() michael@0: { michael@0: PR_SetCurrentThreadName("Play Sound"); michael@0: michael@0: NS_PRECONDITION(!mSoundName.IsEmpty(), "Sound name should not be empty"); michael@0: ::PlaySoundW(mSoundName.get(), nullptr, michael@0: SND_NODEFAULT | SND_ALIAS | SND_ASYNC); michael@0: nsCOMPtr releaser = new SoundReleaser(mSound); michael@0: // Don't release nsSound from here, because here is not an owning thread of michael@0: // the nsSound. nsSound must be released in its owning thread. michael@0: mThread->Dispatch(releaser, NS_DISPATCH_NORMAL); michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsSoundPlayer::SoundReleaser::Run() michael@0: { michael@0: mSound->ShutdownOldPlayerThread(); michael@0: NS_IF_RELEASE(mSound); michael@0: return NS_OK; michael@0: } michael@0: michael@0: michael@0: #ifndef SND_PURGE michael@0: // Not available on Windows CE, and according to MSDN michael@0: // doesn't do anything on recent windows either. michael@0: #define SND_PURGE 0 michael@0: #endif michael@0: michael@0: NS_IMPL_ISUPPORTS(nsSound, nsISound, nsIStreamLoaderObserver) michael@0: michael@0: michael@0: nsSound::nsSound() michael@0: { michael@0: #ifdef PR_LOGGING michael@0: if (!gWin32SoundLog) { michael@0: gWin32SoundLog = PR_NewLogModule("nsSound"); michael@0: } michael@0: #endif michael@0: michael@0: mLastSound = nullptr; michael@0: } michael@0: michael@0: nsSound::~nsSound() michael@0: { michael@0: NS_ASSERTION(!mPlayerThread, "player thread is not null but should be"); michael@0: PurgeLastSound(); michael@0: } michael@0: michael@0: void nsSound::ShutdownOldPlayerThread() michael@0: { michael@0: if (mPlayerThread) { michael@0: mPlayerThread->Shutdown(); michael@0: mPlayerThread = nullptr; michael@0: } michael@0: } michael@0: michael@0: void nsSound::PurgeLastSound() michael@0: { michael@0: if (mLastSound) { michael@0: // Halt any currently playing sound. michael@0: ::PlaySound(nullptr, nullptr, SND_PURGE); michael@0: michael@0: // Now delete the buffer. michael@0: free(mLastSound); michael@0: mLastSound = nullptr; michael@0: } michael@0: } michael@0: michael@0: NS_IMETHODIMP nsSound::Beep() michael@0: { michael@0: ::MessageBeep(0); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP nsSound::OnStreamComplete(nsIStreamLoader *aLoader, michael@0: nsISupports *context, michael@0: nsresult aStatus, michael@0: uint32_t dataLen, michael@0: const uint8_t *data) michael@0: { michael@0: // print a load error on bad status michael@0: if (NS_FAILED(aStatus)) { michael@0: #ifdef DEBUG michael@0: if (aLoader) { michael@0: nsCOMPtr request; michael@0: nsCOMPtr channel; michael@0: aLoader->GetRequest(getter_AddRefs(request)); michael@0: if (request) michael@0: channel = do_QueryInterface(request); michael@0: if (channel) { michael@0: nsCOMPtr uri; michael@0: channel->GetURI(getter_AddRefs(uri)); michael@0: if (uri) { michael@0: nsAutoCString uriSpec; michael@0: uri->GetSpec(uriSpec); michael@0: PR_LOG(gWin32SoundLog, PR_LOG_ALWAYS, michael@0: ("Failed to load %s\n", uriSpec.get())); michael@0: } michael@0: } michael@0: } michael@0: #endif michael@0: return aStatus; michael@0: } michael@0: michael@0: ShutdownOldPlayerThread(); michael@0: PurgeLastSound(); michael@0: michael@0: if (data && dataLen > 0) { michael@0: DWORD flags = SND_MEMORY | SND_NODEFAULT; michael@0: // We try to make a copy so we can play it async. michael@0: mLastSound = (uint8_t *) malloc(dataLen); michael@0: if (mLastSound) { michael@0: memcpy(mLastSound, data, dataLen); michael@0: data = mLastSound; michael@0: flags |= SND_ASYNC; michael@0: } michael@0: ::PlaySoundW(reinterpret_cast(data), 0, flags); michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP nsSound::Play(nsIURL *aURL) michael@0: { michael@0: nsresult rv; michael@0: michael@0: #ifdef DEBUG_SOUND michael@0: char *url; michael@0: aURL->GetSpec(&url); michael@0: PR_LOG(gWin32SoundLog, PR_LOG_ALWAYS, michael@0: ("%s\n", url)); michael@0: #endif michael@0: michael@0: nsCOMPtr loader; michael@0: rv = NS_NewStreamLoader(getter_AddRefs(loader), aURL, this); michael@0: michael@0: return rv; michael@0: } michael@0: michael@0: michael@0: NS_IMETHODIMP nsSound::Init() michael@0: { michael@0: // This call halts a sound if it was still playing. michael@0: // We have to use the sound library for something to make sure michael@0: // it is initialized. michael@0: // If we wait until the first sound is played, there will michael@0: // be a time lag as the library gets loaded. michael@0: ::PlaySound(nullptr, nullptr, SND_PURGE); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: michael@0: NS_IMETHODIMP nsSound::PlaySystemSound(const nsAString &aSoundAlias) michael@0: { michael@0: ShutdownOldPlayerThread(); michael@0: PurgeLastSound(); michael@0: michael@0: if (!NS_IsMozAliasSound(aSoundAlias)) { michael@0: if (aSoundAlias.IsEmpty()) michael@0: return NS_OK; michael@0: nsCOMPtr player = new nsSoundPlayer(this, aSoundAlias); michael@0: NS_ENSURE_TRUE(player, NS_ERROR_OUT_OF_MEMORY); michael@0: nsresult rv = NS_NewThread(getter_AddRefs(mPlayerThread), player); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_WARNING("nsISound::playSystemSound is called with \"_moz_\" events, they are obsolete, use nsISound::playEventSound instead"); michael@0: michael@0: uint32_t eventId; michael@0: if (aSoundAlias.Equals(NS_SYSSOUND_MAIL_BEEP)) michael@0: eventId = EVENT_NEW_MAIL_RECEIVED; michael@0: else if (aSoundAlias.Equals(NS_SYSSOUND_CONFIRM_DIALOG)) michael@0: eventId = EVENT_CONFIRM_DIALOG_OPEN; michael@0: else if (aSoundAlias.Equals(NS_SYSSOUND_ALERT_DIALOG)) michael@0: eventId = EVENT_ALERT_DIALOG_OPEN; michael@0: else if (aSoundAlias.Equals(NS_SYSSOUND_MENU_EXECUTE)) michael@0: eventId = EVENT_MENU_EXECUTE; michael@0: else if (aSoundAlias.Equals(NS_SYSSOUND_MENU_POPUP)) michael@0: eventId = EVENT_MENU_POPUP; michael@0: else michael@0: return NS_OK; michael@0: michael@0: return PlayEventSound(eventId); michael@0: } michael@0: michael@0: NS_IMETHODIMP nsSound::PlayEventSound(uint32_t aEventId) michael@0: { michael@0: ShutdownOldPlayerThread(); michael@0: PurgeLastSound(); michael@0: michael@0: const wchar_t *sound = nullptr; michael@0: switch (aEventId) { michael@0: case EVENT_NEW_MAIL_RECEIVED: michael@0: sound = L"MailBeep"; michael@0: break; michael@0: case EVENT_ALERT_DIALOG_OPEN: michael@0: sound = L"SystemExclamation"; michael@0: break; michael@0: case EVENT_CONFIRM_DIALOG_OPEN: michael@0: sound = L"SystemQuestion"; michael@0: break; michael@0: case EVENT_MENU_EXECUTE: michael@0: sound = L"MenuCommand"; michael@0: break; michael@0: case EVENT_MENU_POPUP: michael@0: sound = L"MenuPopup"; michael@0: break; michael@0: case EVENT_EDITOR_MAX_LEN: michael@0: sound = L".Default"; michael@0: break; michael@0: default: michael@0: // Win32 plays no sounds at NS_SYSSOUND_PROMPT_DIALOG and michael@0: // NS_SYSSOUND_SELECT_DIALOG. michael@0: return NS_OK; michael@0: } michael@0: NS_ASSERTION(sound, "sound is null"); michael@0: michael@0: nsCOMPtr player = new nsSoundPlayer(this, sound); michael@0: NS_ENSURE_TRUE(player, NS_ERROR_OUT_OF_MEMORY); michael@0: nsresult rv = NS_NewThread(getter_AddRefs(mPlayerThread), player); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: return NS_OK; michael@0: }