openbsd-ports/www/fennec/files/nsSound.cpp
landry b715788993 Import fennec 4.0:
Firefox for mobile (codenamed Fennec) is the name of the build of the
Mozilla Firefox web browser for devices such as mobile phones and
personal digital assistants (PDAs).
While it initially targets android and maemo, it also works on desktop
machines, and can be useful on small screens or low-powered devices.

Looks ok to jasper@
2011-04-12 19:48:57 +00:00

466 lines
14 KiB
C++

/* $OpenBSD: nsSound.cpp,v 1.1.1.1 2011/04/12 19:48:57 landry Exp $ */
/*
* Copyright (c) 2009 Martynas Venckus <martynas@openbsd.org>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
/* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is mozilla.org code.
*
* The Initial Developer of the Original Code is
* Netscape Communications Corporation.
* Portions created by the Initial Developer are Copyright (C) 2000
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Stuart Parmenter <pavlov@netscape.com>
*
* Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
#include <string.h>
#include "nscore.h"
#include "plstr.h"
#include "prlink.h"
#include "nsSound.h"
#include "nsIURL.h"
#include "nsIFileURL.h"
#include "nsNetUtil.h"
#include "nsCOMPtr.h"
#include "nsAutoPtr.h"
#include "nsString.h"
#include <prthread.h>
#include <sndio.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <gtk/gtk.h>
#define WAV_MIN_LENGTH 44
typedef struct {
struct sio_hdl *sndio_hdl;
void *audio;
size_t audio_len;
} SioThreadData;
typedef struct _ca_context ca_context;
/* used to find and play common system event sounds */
typedef int (*ca_context_create_fn) (ca_context **);
typedef int (*ca_context_destroy_fn) (ca_context *);
typedef int (*ca_context_play_fn) (ca_context *c, uint32_t id, ...);
typedef int (*ca_context_change_props_fn) (ca_context *c, ...);
static ca_context_create_fn ca_context_create;
static ca_context_destroy_fn ca_context_destroy;
static ca_context_play_fn ca_context_play;
static ca_context_change_props_fn ca_context_change_props;
static PRLibrary *canberra_lib = nsnull;
NS_IMPL_ISUPPORTS2(nsSound, nsISound, nsIStreamLoaderObserver)
////////////////////////////////////////////////////////////////////////
static void
RunSioThread(void *arg)
{
SioThreadData *td;
td = (SioThreadData *)arg;
/* Write stream. */
if (sio_write(td->sndio_hdl, (void *)td->audio,
td->audio_len) == 0 && sio_eof(td->sndio_hdl)) {
NS_WARNING("sio_write: couldn't write the stream");
}
sio_close(td->sndio_hdl);
free(td->audio);
free(td);
}
nsSound::nsSound()
{
mInited = PR_FALSE;
}
nsSound::~nsSound()
{
}
NS_IMETHODIMP
nsSound::Init()
{
/*
* This function is designed so that no library is compulsory, and
* one library missing doesn't cause the other(s) to not be used.
*/
if (mInited)
return NS_OK;
mInited = PR_TRUE;
if (!canberra_lib) {
canberra_lib = PR_LoadLibrary("libcanberra.so");
if (canberra_lib) {
ca_context_create = (ca_context_create_fn) PR_FindFunctionSymbol(
canberra_lib, "ca_context_create");
if (!ca_context_create) {
PR_UnloadLibrary(canberra_lib);
canberra_lib = nsnull;
} else {
ca_context_destroy = (ca_context_destroy_fn)
PR_FindFunctionSymbol(canberra_lib, "ca_context_destroy");
ca_context_play = (ca_context_play_fn) PR_FindFunctionSymbol(
canberra_lib, "ca_context_play");
ca_context_change_props = (ca_context_change_props_fn)
PR_FindFunctionSymbol(canberra_lib,
"ca_context_change_props");
}
}
}
return NS_OK;
}
/* static */ void
nsSound::Shutdown()
{
if (canberra_lib) {
PR_UnloadLibrary(canberra_lib);
canberra_lib = nsnull;
}
}
#define GET_WORD(s, i) (s[i+1] << 8) | s[i]
#define GET_DWORD(s, i) (s[i+3] << 24) | (s[i+2] << 16) | (s[i+1] << 8) | s[i]
NS_IMETHODIMP nsSound::OnStreamComplete(nsIStreamLoader *aLoader,
nsISupports *context,
nsresult aStatus,
PRUint32 dataLen,
const PRUint8 *data)
{
struct sio_hdl *sndio_hdl;
struct sio_par sndio_par;
SioThreadData *td;
PRUint32 samples_per_sec = 0, avg_bytes_per_sec = 0, chunk_len = 0;
PRUint16 format, channels = 1, bits_per_sample = 0;
const PRUint8 *audio = nsnull;
size_t audio_len = 0;
/* Print a load error on bad status, and return. */
if (NS_FAILED(aStatus)) {
return aStatus;
}
if (dataLen < 4) {
NS_WARNING("Sound stream too short to determine its type");
return NS_ERROR_FAILURE;
}
if (memcmp(data, "RIFF", 4)) {
#ifdef DEBUG
printf("We only support WAV files currently.\n");
#endif
return NS_ERROR_FAILURE;
}
if (dataLen <= WAV_MIN_LENGTH) {
NS_WARNING("WAV files should be longer than 44 bytes.");
return NS_ERROR_FAILURE;
}
PRUint32 i = 12;
while (i + 7 < dataLen) {
if (!memcmp(data + i, "fmt ", 4) && !chunk_len) {
i += 4;
/* length of the rest of this subblock (should be 16 for PCM data */
chunk_len = GET_DWORD(data, i);
i += 4;
if (chunk_len < 16 || i + chunk_len >= dataLen) {
NS_WARNING("Invalid WAV file: bad fmt chunk.");
return NS_ERROR_FAILURE;
}
format = GET_WORD(data, i);
i += 2;
channels = GET_WORD(data, i);
i += 2;
samples_per_sec = GET_DWORD(data, i);
i += 4;
avg_bytes_per_sec = GET_DWORD(data, i);
i += 4;
/* block align */
i += 2;
bits_per_sample = GET_WORD(data, i);
i += 2;
/* we don't support WAVs with odd compression codes */
if (chunk_len != 16)
NS_WARNING("Extra format bits found in WAV. Ignoring");
i += chunk_len - 16;
} else if (!memcmp(data + i, "data", 4)) {
i += 4;
if (!chunk_len) {
NS_WARNING("Invalid WAV file: no fmt chunk found");
return NS_ERROR_FAILURE;
}
audio_len = GET_DWORD(data, i);
i += 4;
/* try to play truncated WAVs */
if (i + audio_len > dataLen)
audio_len = dataLen - i;
audio = data + i;
break;
} else {
i += 4;
i += GET_DWORD(data, i);
i += 4;
}
}
if (!audio) {
NS_WARNING("Invalid WAV file: no data chunk found");
return NS_ERROR_FAILURE;
}
/* No audio data? well, at least the WAV was valid. */
if (!audio_len)
return NS_OK;
/* Open up connection to sndio. */
sndio_hdl = sio_open(NULL, SIO_PLAY, 0);
if (sndio_hdl == NULL) {
NS_WARNING("sio_open: couldn't open the stream");
return NS_ERROR_FAILURE;
}
/* Initialize parameters structure. */
sio_initpar(&sndio_par);
sndio_par.bits = bits_per_sample;
sndio_par.le = SIO_LE_NATIVE;
sndio_par.pchan = channels;
sndio_par.rate = samples_per_sec;
sndio_par.sig = (bits_per_sample == 8) ? 0 : 1;
/* Set and get configuration set.
Put the stream into writing state. */
if (!sio_setpar(sndio_hdl, &sndio_par) ||
!sio_getpar(sndio_hdl, &sndio_par) || !sio_start(sndio_hdl)) {
NS_WARNING("sio_setpar: couldn't set configuration");
sio_close(sndio_hdl);
return NS_ERROR_FAILURE;
}
/* Check configuration. */
if (sndio_par.bits != bits_per_sample || sndio_par.pchan != channels ||
sndio_par.rate != samples_per_sec) {
NS_WARNING("configuration is not available");
sio_close(sndio_hdl);
return NS_ERROR_FAILURE;
}
if ((td = (SioThreadData *) malloc(sizeof(SioThreadData))) == NULL ||
(td->audio = malloc(audio_len * sizeof(*audio))) == NULL) {
sio_close(sndio_hdl);
return NS_ERROR_FAILURE;
}
td->sndio_hdl = sndio_hdl;
td->audio_len = audio_len;
memcpy(td->audio, audio, audio_len);
PR_CreateThread(PR_SYSTEM_THREAD, RunSioThread, td, PR_PRIORITY_NORMAL,
PR_GLOBAL_THREAD, PR_UNJOINABLE_THREAD, 0);
return NS_OK;
}
NS_METHOD nsSound::Beep()
{
::gdk_beep();
return NS_OK;
}
NS_METHOD nsSound::Play(nsIURL *aURL)
{
nsresult rv;
if (!mInited)
Init();
nsCOMPtr<nsIStreamLoader> loader;
rv = NS_NewStreamLoader(getter_AddRefs(loader), aURL, this);
return rv;
}
NS_IMETHODIMP nsSound::PlayEventSound(PRUint32 aEventId)
{
if (!mInited)
Init();
if (!canberra_lib)
return NS_OK;
/*
* Do we even want alert sounds?
* If so, what sound theme are we using?
*/
GtkSettings* settings = gtk_settings_get_default();
gchar* sound_theme_name = nsnull;
if (g_object_class_find_property(G_OBJECT_GET_CLASS(settings),
"gtk-sound-theme-name") && g_object_class_find_property(
G_OBJECT_GET_CLASS(settings), "gtk-enable-event-sounds")) {
gboolean enable_sounds = TRUE;
g_object_get(settings, "gtk-enable-event-sounds", &enable_sounds,
"gtk-sound-theme-name", &sound_theme_name, NULL);
if (!enable_sounds) {
g_free(sound_theme_name);
return NS_OK;
}
}
/*
* This allows us to avoid race conditions with freeing the
* context by handing that responsibility to Glib, and still
* use one context at a time.
*/
ca_context* ctx = nsnull;
static GStaticPrivate ctx_static_private = G_STATIC_PRIVATE_INIT;
ctx = (ca_context*) g_static_private_get(&ctx_static_private);
if (!ctx) {
ca_context_create(&ctx);
if (!ctx) {
g_free(sound_theme_name);
return NS_ERROR_OUT_OF_MEMORY;
}
g_static_private_set(&ctx_static_private, ctx, (GDestroyNotify)
ca_context_destroy);
}
if (sound_theme_name) {
ca_context_change_props(ctx, "canberra.xdg-theme.name",
sound_theme_name, NULL);
g_free(sound_theme_name);
}
switch (aEventId) {
case EVENT_ALERT_DIALOG_OPEN:
ca_context_play(ctx, 0, "event.id", "dialog-warning", NULL);
break;
case EVENT_CONFIRM_DIALOG_OPEN:
ca_context_play(ctx, 0, "event.id", "dialog-question", NULL);
break;
case EVENT_NEW_MAIL_RECEIVED:
ca_context_play(ctx, 0, "event.id", "message-new-email", NULL);
break;
case EVENT_MENU_EXECUTE:
ca_context_play(ctx, 0, "event.id", "menu-click", NULL);
break;
case EVENT_MENU_POPUP:
ca_context_play(ctx, 0, "event.id", "menu-popup", NULL);
break;
}
return NS_OK;
}
NS_IMETHODIMP nsSound::PlaySystemSound(const nsAString &aSoundAlias)
{
if (NS_IsMozAliasSound(aSoundAlias)) {
NS_WARNING("nsISound::playSystemSound is called with \"_moz_\" events, "
"they are obsolete, use nsISound::playEventSound instead");
PRUint32 eventId;
if (aSoundAlias.Equals(NS_SYSSOUND_ALERT_DIALOG))
eventId = EVENT_ALERT_DIALOG_OPEN;
else if (aSoundAlias.Equals(NS_SYSSOUND_CONFIRM_DIALOG))
eventId = EVENT_CONFIRM_DIALOG_OPEN;
else if (aSoundAlias.Equals(NS_SYSSOUND_MAIL_BEEP))
eventId = EVENT_NEW_MAIL_RECEIVED;
else if (aSoundAlias.Equals(NS_SYSSOUND_MENU_EXECUTE))
eventId = EVENT_MENU_EXECUTE;
else if (aSoundAlias.Equals(NS_SYSSOUND_MENU_POPUP))
eventId = EVENT_MENU_POPUP;
else
return NS_OK;
return PlayEventSound(eventId);
}
nsresult rv;
nsCOMPtr <nsIURI> fileURI;
/* create a nsILocalFile and then a nsIFileURL from that */
nsCOMPtr <nsILocalFile> soundFile;
rv = NS_NewLocalFile(aSoundAlias, PR_TRUE,
getter_AddRefs(soundFile));
NS_ENSURE_SUCCESS(rv,rv);
rv = NS_NewFileURI(getter_AddRefs(fileURI), soundFile);
NS_ENSURE_SUCCESS(rv,rv);
nsCOMPtr<nsIFileURL> fileURL = do_QueryInterface(fileURI,&rv);
NS_ENSURE_SUCCESS(rv,rv);
rv = Play(fileURL);
return rv;
}