Files
Mozilla/mozilla/widget/src/mac/nsSound.cpp
cbiesinger%web.de 3d93c77764 bug 241092, make nsIStreamLoaderObserver usable in javascript, by changing its
argument from a string to an octet array
r=bryner sr=darin


git-svn-id: svn://10.0.0.236/trunk@155585 18797224-902f-48f8-a5cc-f745e15eee43
2004-04-27 16:06:00 +00:00

1114 lines
28 KiB
C++
Raw Blame History

/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* ***** 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) 1998
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Simon Fraser <sfraser@netscape.com>
* Pierre Phaneuf <pp@ludusdesign.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 "nscore.h"
#include "nsIAllocator.h"
#include "plstr.h"
#include "nsVoidArray.h"
#include "nsIURL.h"
#include "nsNetUtil.h"
#include "prmem.h"
#include "nsGfxUtils.h"
#include "nsIStreamLoader.h"
#include "nsICacheService.h"
#include "nsICacheSession.h"
#include "nsICacheEntryDescriptor.h"
#include "nsICachingChannel.h"
#include "nsIInternetConfigService.h"
#include "nsITimer.h"
#include "nsCRT.h"
#include <Gestalt.h>
#include <Sound.h>
#include <Movies.h>
#include <QuickTimeComponents.h>
#include "nsSound.h"
#include "nsString.h"
//#define SOUND_DEBUG
#pragma mark nsSoundRequest
// pure virtual base class for different types of sound requests
class nsSoundRequest : public nsITimerCallback
{
public:
nsSoundRequest();
virtual ~nsSoundRequest();
NS_DECL_ISUPPORTS
// nsITimerCallback
NS_IMETHOD Notify(nsITimer *timer) = 0;
virtual nsresult PlaySound() = 0;
static nsSoundRequest* GetFromISupports(nsISupports* inSupports);
protected:
nsresult Cleanup();
protected:
nsCOMPtr<nsISound> mSound; // back ptr, owned and released when play done
nsCOMPtr<nsITimer> mTimer;
};
// concrete class for playing system sounds asynchronously
class nsSystemSoundRequest : public nsSoundRequest
{
public:
nsSystemSoundRequest();
virtual ~nsSystemSoundRequest();
NS_DECL_ISUPPORTS_INHERITED
// nsITimerCallback
NS_DECL_NSITIMERCALLBACK
nsresult Init(nsISound* aSound, ConstStr255Param aSoundName);
virtual nsresult PlaySound();
protected:
static pascal void SoundCallback(SndChannelPtr chan, SndCommand *theCmd);
void DonePlaying();
protected:
Handle mSoundHandle; // resource handle.
SndChannelPtr mSndChannel;
SndCallBackUPP mSoundCallback;
Boolean mSoundDone;
};
// concrete class for playing URL-based sounds asynchronously
class nsMovieSoundRequest : public nsSoundRequest,
public nsIStreamLoaderObserver
{
public:
nsMovieSoundRequest();
virtual ~nsMovieSoundRequest();
NS_DECL_ISUPPORTS_INHERITED
NS_DECL_NSISTREAMLOADEROBSERVER
// nsITimerCallback
NS_DECL_NSITIMERCALLBACK
nsresult Init(nsISound* aSound, nsIURL *aURL);
virtual nsresult PlaySound();
protected:
OSType GetFileFormat(const char* inData, long inDataSize, const nsACString& contentType);
OSErr ImportMovie(Handle inDataHandle, long inDataSize, const nsACString& contentType);
PRBool HaveQuickTime();
void DisposeMovieData();
PRBool IsAnyMoviePlaying();
OSErr TaskActiveMovies(PRBool *outAllMoviesDone);
static PRBool TaskOneMovie(Movie inMovie); // return true if done
protected:
Movie mMovie; // the original movie, kept around as long as this request is cached
Handle mDataHandle; // data handle, has to persist for the lifetime of any movies
// depending on it
nsVoidArray mMovies; // list of playing movie clones, which are transient.
};
#pragma mark -
static PRUint32
SecondsFromPRTime(PRTime prTime)
{
PRInt64 microSecondsPerSecond, intermediateResult;
PRUint32 seconds;
LL_I2L(microSecondsPerSecond, PR_USEC_PER_SEC);
LL_DIV(intermediateResult, prTime, microSecondsPerSecond);
LL_L2UI(seconds, intermediateResult);
return seconds;
}
static void
CopyCToPascalString(const char* inString, StringPtr outPString)
{
SInt32 nameLen = strlen(inString) & 0xFF; // max 255 chars
::BlockMoveData(inString, &outPString[1], nameLen);
outPString[0] = nameLen;
}
#pragma mark -
nsSound::nsSound()
{
#ifdef SOUND_DEBUG
printf("%%%%%%%% Made nsSound\n");
#endif
}
nsSound::~nsSound()
{
#ifdef SOUND_DEBUG
printf("%%%%%%%% Deleted nsSound\n");
#endif
}
NS_IMPL_ISUPPORTS1(nsSound, nsISound)
NS_METHOD
nsSound::Beep()
{
::SysBeep(1);
return NS_OK;
}
NS_IMETHODIMP
nsSound::Init()
{
return NS_OK;
}
NS_IMETHODIMP
nsSound::PlaySystemSound(const char *aSoundName)
{
nsCOMPtr<nsISupports> requestSupports;
nsSystemSoundRequest* soundRequest;
NS_NEWXPCOM(soundRequest, nsSystemSoundRequest);
if (!soundRequest)
return NS_ERROR_OUT_OF_MEMORY;
requestSupports = NS_STATIC_CAST(nsITimerCallback*, soundRequest);
Str255 soundResource;
nsresult rv = GetSoundResourceName(aSoundName, soundResource);
if (NS_FAILED(rv))
return Beep();
rv = soundRequest->Init(this, soundResource);
if (NS_FAILED(rv))
return Beep();
rv = AddRequest(requestSupports);
if (NS_FAILED(rv))
return Beep();
rv = soundRequest->PlaySound();
if (NS_FAILED(rv))
return Beep();
return NS_OK;
}
// this currently does no caching of the sound buffer. It should
NS_METHOD
nsSound::Play(nsIURL *aURL)
{
NS_ENSURE_ARG(aURL);
nsresult rv;
// try to get from cache
nsCOMPtr<nsISupports> requestSupports;
(void)GetSoundFromCache(NS_STATIC_CAST(nsIURI*,aURL), getter_AddRefs(requestSupports));
if (requestSupports)
{
nsSoundRequest* cachedRequest = nsSoundRequest::GetFromISupports(requestSupports);
nsMovieSoundRequest* movieRequest = NS_STATIC_CAST(nsMovieSoundRequest*, cachedRequest);
// if it was cached, start playing right away
movieRequest->PlaySound();
}
else
{
nsMovieSoundRequest* soundRequest;
NS_NEWXPCOM(soundRequest, nsMovieSoundRequest);
if (!soundRequest)
return NS_ERROR_OUT_OF_MEMORY;
requestSupports = NS_STATIC_CAST(nsITimerCallback*, soundRequest);
nsresult rv = soundRequest->Init(this, aURL);
if (NS_FAILED(rv))
return rv;
}
rv = AddRequest(requestSupports);
if (NS_FAILED(rv))
return rv;
return NS_OK;
}
nsresult
nsSound::AddRequest(nsISupports* aSoundRequest)
{
// only add if not already in the list
PRInt32 index = mSoundRequests.IndexOf(aSoundRequest);
if (index == -1)
{
nsresult appended = mSoundRequests.AppendElement(aSoundRequest);
if (!appended)
return NS_ERROR_FAILURE;
}
return NS_OK;
}
nsresult
nsSound::RemoveRequest(nsISupports* aSoundRequest)
{
nsresult removed = mSoundRequests.RemoveElement(aSoundRequest);
if (!removed)
return NS_ERROR_FAILURE;
return NS_OK;
}
nsresult
nsSound::GetCacheSession(nsICacheSession** outCacheSession)
{
nsresult rv;
nsCOMPtr<nsICacheService> cacheService = do_GetService("@mozilla.org/network/cache-service;1", &rv);
if (NS_FAILED(rv)) return rv;
return cacheService->CreateSession("sound",
nsICache::STORE_IN_MEMORY,
nsICache::NOT_STREAM_BASED, outCacheSession);
}
nsresult
nsSound::GetSoundFromCache(nsIURI* inURI, nsISupports** outSound)
{
nsresult rv;
nsCAutoString uriSpec;
inURI->GetAsciiSpec(uriSpec);
nsCOMPtr<nsICacheSession> cacheSession;
rv = GetCacheSession(getter_AddRefs(cacheSession));
if (NS_FAILED(rv)) return rv;
nsCOMPtr<nsICacheEntryDescriptor> entry;
rv = cacheSession->OpenCacheEntry(uriSpec.get(), nsICache::ACCESS_READ, nsICache::BLOCKING, getter_AddRefs(entry));
#ifdef SOUND_DEBUG
printf("Got sound from cache with rv %ld\n", rv);
#endif
if (NS_FAILED(rv)) return rv;
return entry->GetCacheElement(outSound);
}
nsresult
nsSound::PutSoundInCache(nsIChannel* inChannel, PRUint32 inDataSize, nsISupports* inSound)
{
nsresult rv;
NS_ENSURE_ARG(inChannel && inSound);
nsCOMPtr<nsIURI> uri;
inChannel->GetOriginalURI(getter_AddRefs(uri));
if (!uri) return NS_ERROR_FAILURE;
nsCAutoString uriSpec;
rv = uri->GetAsciiSpec(uriSpec);
if (NS_FAILED(rv)) return rv;
nsCOMPtr<nsICacheSession> cacheSession;
rv = GetCacheSession(getter_AddRefs(cacheSession));
if (NS_FAILED(rv)) return rv;
nsCOMPtr<nsICacheEntryDescriptor> entry;
rv = cacheSession->OpenCacheEntry(uriSpec.get(), nsICache::ACCESS_WRITE, nsICache::BLOCKING, getter_AddRefs(entry));
#ifdef SOUND_DEBUG
printf("Put sound in cache with rv %ld\n", rv);
#endif
if (NS_FAILED(rv)) return rv;
rv = entry->SetCacheElement(inSound);
if (NS_FAILED(rv)) return rv;
rv = entry->SetDataSize(inDataSize);
if (NS_FAILED(rv)) return rv;
PRUint32 expirationTime = 0;
// try to get the expiration time from the URI load
nsCOMPtr<nsICachingChannel> cachingChannel = do_QueryInterface(inChannel);
if (cachingChannel)
{
nsCOMPtr<nsISupports> cacheToken;
cachingChannel->GetCacheToken(getter_AddRefs(cacheToken));
nsCOMPtr<nsICacheEntryInfo> cacheEntryInfo = do_QueryInterface(cacheToken);
if (cacheEntryInfo)
{
cacheEntryInfo->GetExpirationTime(&expirationTime);
}
}
if (expirationTime == PRUint32(-1)) // no expiration time (never expires)
{
// set it to some reasonable default, like now + 24 hours
expirationTime = SecondsFromPRTime(PR_Now()) + 60 * 60 * 24;
}
rv = entry->SetExpirationTime(expirationTime);
if (NS_FAILED(rv)) return rv;
return entry->MarkValid();
}
nsresult
nsSound::GetSoundResourceName(const char* inSoundName, StringPtr outResourceName)
{
nsresult rv = NS_OK;
outResourceName[0] = 0;
// if it's the special mail beep sound, get the real sound name from IC
if (nsCRT::strcmp("_moz_mailbeep", inSoundName) == 0)
{
nsCOMPtr <nsIInternetConfigService> icService = do_GetService(NS_INTERNETCONFIGSERVICE_CONTRACTID, &rv);
if (NS_FAILED(rv))
return rv;
nsCAutoString newMailSound;
rv = icService->GetString(nsIInternetConfigService::eICString_NewMailSoundName, newMailSound);
if (NS_FAILED(rv))
return rv;
CopyCToPascalString(newMailSound.get(), outResourceName);
return NS_OK;
}
// if the name is not "Mailbeep", treat it as the name of a system sound
CopyCToPascalString(inSoundName, outResourceName);
return NS_OK;
}
#pragma mark -
nsSoundRequest::nsSoundRequest()
{
}
nsSoundRequest::~nsSoundRequest()
{
}
NS_IMPL_ISUPPORTS1(nsSoundRequest, nsITimerCallback)
nsSoundRequest*
nsSoundRequest::GetFromISupports(nsISupports* inSupports)
{
if (!inSupports) return nsnull;
// test to see if this is really a nsSoundRequest by trying a QI
nsCOMPtr<nsITimerCallback> timerCallback = do_QueryInterface(inSupports);
if (!timerCallback) return nsnull;
return NS_REINTERPRET_CAST(nsSoundRequest*, inSupports);
}
nsresult
nsSoundRequest::Cleanup()
{
nsresult rv = NS_OK;
#ifdef SOUND_DEBUG
printf("Sound playback done\n");
#endif
// kill the timer
if (mTimer) {
mTimer->Cancel();
mTimer = nsnull;
}
// remove from parent array. Use a deathGrip to ensure that it's OK
// to clear mSound.
nsCOMPtr<nsISupports> deathGrip(this);
if (mSound.get())
{
nsSound* macSound = NS_REINTERPRET_CAST(nsSound*, mSound.get());
rv = macSound->RemoveRequest(NS_STATIC_CAST(nsITimerCallback*, this));
mSound = nsnull;
}
return rv;
}
#pragma mark -
nsSystemSoundRequest::nsSystemSoundRequest()
: mSoundHandle(nsnull)
, mSndChannel(nsnull)
, mSoundCallback(nsnull)
, mSoundDone(false)
{
#ifdef SOUND_DEBUG
printf("%%%%%%%% Made nsSystemSoundRequest\n");
#endif
}
nsSystemSoundRequest::~nsSystemSoundRequest()
{
if (mSoundHandle) {
// unlock the sound resource handle and make it purgeable.
::HUnlock(mSoundHandle);
::HPurge(mSoundHandle);
}
if (mSndChannel)
::SndDisposeChannel(mSndChannel, true);
if (mSoundCallback)
DisposeSndCallBackUPP(mSoundCallback);
#ifdef SOUND_DEBUG
printf("%%%%%%%% Deleted nsSystemSoundRequest\n");
#endif
}
NS_IMPL_ISUPPORTS_INHERITED0(nsSystemSoundRequest, nsSoundRequest)
nsresult
nsSystemSoundRequest::Init(nsISound* aSound, ConstStr255Param aSoundName)
{
mSound = aSound;
mSoundCallback = NewSndCallBackUPP(nsSystemSoundRequest::SoundCallback);
if (!mSoundCallback) return NS_ERROR_OUT_OF_MEMORY;
mSoundHandle = ::GetNamedResource('snd ', aSoundName);
if (!mSoundHandle) return NS_ERROR_FAILURE;
// make sure the resource is loaded
::LoadResource(mSoundHandle);
if (!mSoundHandle || !*mSoundHandle) return NS_ERROR_FAILURE;
// and lock it high
::HLockHi(mSoundHandle);
OSErr err = ::SndNewChannel(&mSndChannel, 0, 0, mSoundCallback);
if (err != noErr) return NS_ERROR_FAILURE;
return NS_OK;
}
nsresult
nsSystemSoundRequest::PlaySound()
{
NS_ASSERTION(mSndChannel && mSoundHandle, "Should have sound channel here");
if (!mSndChannel || !mSoundHandle) {
Cleanup();
return NS_ERROR_NOT_INITIALIZED;
}
nsresult rv;
// set up a timer. This is used to sniff for mSoundDone (which is set by the channel callback).
mTimer = do_CreateInstance("@mozilla.org/timer;1", &rv); // release previous timer, if any
if (NS_FAILED(rv)) {
Cleanup();
return rv;
}
OSErr err = ::SndPlay(mSndChannel, (SndListHandle)mSoundHandle, true /* async */);
if (err != noErr) {
Cleanup();
return NS_ERROR_FAILURE;
}
// now queue up a sound completion command so we get a callback when
// the sound is done.
SndCommand theCmd = { callBackCmd, 0, 0 };
theCmd.param2 = (long)this;
err = ::SndDoCommand(mSndChannel, &theCmd, false); // wait for the channel
if (err != noErr) {
Cleanup();
return NS_ERROR_FAILURE;
}
const PRInt32 kSoundTimerInterval = 250; // 250 milliseconds
rv = mTimer->InitWithCallback(NS_STATIC_CAST(nsITimerCallback*, this), kSoundTimerInterval,
nsITimer::TYPE_REPEATING_PRECISE);
if (NS_FAILED(rv)) {
Cleanup();
return rv;
}
return NS_OK;
}
NS_IMETHODIMP
nsSystemSoundRequest::Notify(nsITimer *timer)
{
if (mSoundDone)
{
Cleanup();
}
return NS_OK;
}
// Note! Called at interrupt time
void
nsSystemSoundRequest::DonePlaying()
{
mSoundDone = true;
}
/* static. Note! Called at interrupt time */
pascal void
nsSystemSoundRequest::SoundCallback(SndChannelPtr chan, SndCommand *theCmd)
{
nsSystemSoundRequest* soundRequest = NS_REINTERPRET_CAST(nsSystemSoundRequest*, theCmd->param2);
if (soundRequest)
soundRequest->DonePlaying();
}
#pragma mark -
// This class should only ever be instantiated once, statically.
// Its job is to create, own and destroy the singleton GWorld.
class nsMoviePortOwner
{
public:
static GWorldPtr GetSingletonMoviePort();
private:
// private ctor and dtor. Only GetSingletonMoviePort() can instantiate us.
nsMoviePortOwner() {}
~nsMoviePortOwner()
{
if (sMoviePort)
{
::DisposeGWorld(sMoviePort);
sMoviePort = nsnull;
}
}
void EnsureMoviePort()
{
if (!sMoviePort)
{
Rect gWorldBounds = {0, 0, 12, 12};
(void)::NewGWorld(&sMoviePort, 8, &gWorldBounds, nil, nil, 0);
}
}
GWorldPtr GetMoviePort()
{
EnsureMoviePort();
return sMoviePort;
}
private:
static GWorldPtr sMoviePort;
};
GWorldPtr nsMoviePortOwner::sMoviePort = nsnull;
GWorldPtr nsMoviePortOwner::GetSingletonMoviePort()
{
static nsMoviePortOwner sMoviePortOwner;
return sMoviePortOwner.GetMoviePort();
}
NS_IMPL_ISUPPORTS_INHERITED1(nsMovieSoundRequest, nsSoundRequest, nsIStreamLoaderObserver)
////////////////////////////////////////////////////////////////////////
nsMovieSoundRequest::nsMovieSoundRequest()
: mMovie(nsnull)
, mDataHandle(nsnull)
{
#ifdef SOUND_DEBUG
printf("%%%%%%%% Made nsMovieSoundRequest\n");
#endif
}
nsMovieSoundRequest::~nsMovieSoundRequest()
{
#ifdef SOUND_DEBUG
printf("%%%%%%%% Deleted nsMovieSoundRequest\n");
#endif
DisposeMovieData();
}
nsresult
nsMovieSoundRequest::Init(nsISound* aSound, nsIURL *aURL)
{
NS_ENSURE_ARG(aURL && aSound);
mSound = aSound;
// if quicktime is not installed, we can't do anything
if (!HaveQuickTime())
return NS_ERROR_NOT_IMPLEMENTED;
#ifdef SOUND_DEBUG
printf("%%%%%%%% Playing nsSound\n");
#endif
NS_ASSERTION(mMovie == nsnull, "nsSound being played twice");
nsCOMPtr<nsIStreamLoader> streamLoader;
return NS_NewStreamLoader(getter_AddRefs(streamLoader), aURL, NS_STATIC_CAST(nsIStreamLoaderObserver*, this));
}
NS_IMETHODIMP
nsMovieSoundRequest::OnStreamComplete(nsIStreamLoader *aLoader,
nsISupports *context,
nsresult aStatus,
PRUint32 dataLen,
const PRUint8 *data)
{
NS_ENSURE_ARG(aLoader);
if (NS_FAILED(aStatus))
return NS_ERROR_FAILURE;
nsCAutoString contentType;
nsCOMPtr<nsIRequest> request;
aLoader->GetRequest(getter_AddRefs(request));
nsCOMPtr<nsIChannel> channel = do_QueryInterface(request);
if (channel)
channel->GetContentType(contentType);
// we could use a Pointer data handler type, and avoid this
// allocation/copy, in QuickTime 5 and above.
OSErr err;
mDataHandle = ::TempNewHandle(dataLen, &err);
if (!mDataHandle) return NS_ERROR_OUT_OF_MEMORY;
::BlockMoveData(data, *mDataHandle, dataLen);
NS_ASSERTION(mMovie == nsnull, "nsMovieSoundRequest has a movie already");
err = ImportMovie(mDataHandle, dataLen, contentType);
if (err != noErr) {
Cleanup();
return NS_ERROR_FAILURE;
}
nsSound* macSound = NS_REINTERPRET_CAST(nsSound*, mSound.get());
NS_ASSERTION(macSound, "Should have nsSound here");
// put it in the cache. Not vital that this succeeds.
// for the data size we just use the string data, since the movie simply wraps this
// (we have to keep the handle around until the movies are done playing)
nsresult rv = macSound->PutSoundInCache(channel, dataLen, NS_STATIC_CAST(nsITimerCallback*, this));
NS_ASSERTION(NS_SUCCEEDED(rv), "Failed to put sound in cache");
return PlaySound();
}
OSType
nsMovieSoundRequest::GetFileFormat(const char* inData, long inDataSize, const nsACString& contentType)
{
OSType fileFormat = kQTFileTypeMovie; // Default to just treating it like a movie.
// Hopefully QuickTime will be able to import it.
if (inDataSize >= 16)
{
// look for WAVE
const char* dataPtr = inData;
if (*(OSType *)dataPtr == 'RIFF')
{
dataPtr += 4; // skip RIFF
dataPtr += 4; // skip length bytes
if (*(OSType *)dataPtr == 'WAVE')
return kQTFileTypeWave;
}
// look for AIFF
dataPtr = inData;
if (*(OSType *)dataPtr == 'FORM')
{
dataPtr += 4; // skip FORM
dataPtr += 4; // skip length bytes
if (*(OSType *)dataPtr == 'AIFF')
return kQTFileTypeAIFF;
if (*(OSType *)dataPtr == 'AIFC')
return kQTFileTypeAIFC;
}
}
if (inDataSize >= 4)
{
// look for midi
if (*(OSType *)inData == 'MThd')
return kQTFileTypeMIDI;
// look for <20>Law/Next-Sun file format (.au)
if (*(OSType *)inData == '.snd')
return kQTFileTypeMuLaw;
}
// MP3 files have a complex format that is not easily sniffed. Just go by
// MIME type.
if (contentType.Equals("audio/mpeg") ||
contentType.Equals("audio/mp3") ||
contentType.Equals("audio/mpeg3") ||
contentType.Equals("audio/x-mpeg3") ||
contentType.Equals("audio/x-mp3") ||
contentType.Equals("audio/x-mpeg3"))
{
fileFormat = 'MP3 '; // not sure why there is no enum for this
}
return fileFormat;
}
nsresult
nsMovieSoundRequest::PlaySound()
{
nsresult rv;
// we'll have a timer already if the sound is still playing from a previous
// request. In that case, we clone the movie into a new one, so we can play it
// again from the start.
if (!mTimer)
{
mTimer = do_CreateInstance("@mozilla.org/timer;1", &rv); // release previous timer, if any
if (NS_FAILED(rv)) {
Cleanup();
return rv;
}
const PRInt32 kMovieTimerInterval = 250; // 250 milliseconds
rv = mTimer->InitWithCallback(NS_STATIC_CAST(nsITimerCallback*, this), kMovieTimerInterval,
nsITimer::TYPE_REPEATING_PRECISE);
if (NS_FAILED(rv)) {
Cleanup();
return rv;
}
}
Movie movieToPlay = mMovie;
if (!::IsMovieDone(mMovie)) // if the current movie is still playing, clone it
{
Movie newMovie = ::NewMovie(0);
if (!newMovie) return NS_ERROR_FAILURE;
// note that this copies refs, not all the data. So it should be fast
OSErr err = ::InsertMovieSegment(mMovie, newMovie, 0, ::GetMovieDuration(mMovie), 0);
if (err != noErr)
{
::DisposeMovie(newMovie);
return NS_ERROR_FAILURE;
}
// append it to the array
PRBool appended = mMovies.AppendElement((void *)newMovie);
if (!appended)
{
::DisposeMovie(newMovie);
return NS_ERROR_FAILURE;
}
movieToPlay = newMovie;
}
::SetMovieVolume(movieToPlay, kFullVolume);
::GoToBeginningOfMovie(movieToPlay);
::StartMovie(movieToPlay);
::MoviesTask(movieToPlay, 0);
#ifdef SOUND_DEBUG
printf("Starting movie playback\n");
#endif
return NS_OK;
}
NS_IMETHODIMP
nsMovieSoundRequest::Notify(nsITimer *timer)
{
if (!mMovie)
{
NS_ASSERTION(0, "nsMovieSoundRequest has no movie in timer callback");
return NS_OK;
}
#ifdef SOUND_DEBUG
printf("In movie timer callback\n");
#endif
PRBool moviesDone;
TaskActiveMovies(&moviesDone);
// we're done for now. Remember that this nsMovieSoundRequest might be in the cache,
// so won't necessarily go away.
if (moviesDone)
Cleanup();
return NS_OK;
}
OSErr
nsMovieSoundRequest::ImportMovie(Handle inDataHandle, long inDataSize, const nsACString& contentType)
{
GWorldPtr moviePort = nsMoviePortOwner::GetSingletonMoviePort();
Handle dataRef = nil;
OSErr err = noErr;
OSType fileFormat;
if (!moviePort)
return memFullErr;
{
StHandleLocker locker(inDataHandle);
fileFormat = GetFileFormat(*inDataHandle, inDataSize, contentType);
}
err = ::PtrToHand(&inDataHandle, &dataRef, sizeof(Handle));
if (err != noErr)
return err;
{
MovieImportComponent miComponent = ::OpenDefaultComponent(MovieImportType, fileFormat);
Track targetTrack = nil;
TimeValue addedDuration = 0;
long outFlags = 0;
ComponentResult compErr = noErr;
// set the port to our singleton GWorld before creating
// the movie. This will ensure that the movie uses this port, and
// not one of our (transient) windows.
StGWorldPortSetter gWorldSetter(moviePort);
if (!miComponent) {
err = paramErr;
goto bail;
}
NS_ASSERTION(mMovie == nsnull, "nsMovieSoundRequest already has movie");
mMovie = ::NewMovie(0);
if (!mMovie) {
err = ::GetMoviesError();
goto bail;
}
compErr = ::MovieImportDataRef(miComponent,
dataRef,
HandleDataHandlerSubType,
mMovie,
nil,
&targetTrack,
nil,
&addedDuration,
movieImportCreateTrack,
&outFlags);
if (compErr != noErr) {
::DisposeMovie(mMovie);
mMovie = nil;
err = compErr;
goto bail;
}
// ensure that the track never draws on screen, otherwise we might be
// suspecptible to spoofing attacks
{
Rect movieRect = {0};
::SetMovieBox(mMovie, &movieRect);
}
::GoToEndOfMovie(mMovie); // simplifies the logic in PlaySound()
bail:
if (miComponent)
::CloseComponent(miComponent);
}
if (dataRef)
::DisposeHandle(dataRef);
return err;
}
void
nsMovieSoundRequest::DisposeMovieData()
{
for (PRInt32 i = 0; i < mMovies.Count(); i ++)
{
Movie thisMovie = (Movie)mMovies.ElementAt(i);
::DisposeMovie(thisMovie);
}
mMovies.Clear();
if (mMovie) {
::DisposeMovie(mMovie);
mMovie = nsnull;
}
if (mDataHandle) {
::DisposeHandle(mDataHandle);
mDataHandle = nsnull;
}
}
PRBool
nsMovieSoundRequest::TaskOneMovie(Movie inMovie) // return true if done
{
PRBool movieDone = PR_FALSE;
ComponentResult status = ::GetMovieStatus(inMovie, nil);
NS_ASSERTION(status == noErr, "Movie bad");
if (status != noErr) {
::StopMovie(inMovie);
movieDone = PR_TRUE;
}
movieDone |= ::IsMovieDone(inMovie);
if (!movieDone)
::MoviesTask(inMovie, 0);
return movieDone;
}
OSErr
nsMovieSoundRequest::TaskActiveMovies(PRBool *outAllMoviesDone)
{
PRBool allMoviesDone = PR_FALSE;
allMoviesDone = TaskOneMovie(mMovie);
PRInt32 curIndex = 0;
while (curIndex < mMovies.Count())
{
Movie thisMovie = (Movie)mMovies.ElementAt(curIndex);
PRBool thisMovieDone = TaskOneMovie(thisMovie);
if (thisMovieDone) // remove finished movies from the array
{
mMovies.RemoveElementAt(curIndex);
::DisposeMovie(thisMovie);
// curIndex doesn't change
}
else
{
curIndex ++;
}
allMoviesDone &= thisMovieDone;
}
*outAllMoviesDone = allMoviesDone;
return noErr;
}
PRBool
nsMovieSoundRequest::IsAnyMoviePlaying()
{
if (!::IsMovieDone(mMovie))
return PR_TRUE;
for (PRInt32 i = 0; i < mMovies.Count(); i ++)
{
Movie thisMovie = (Movie)mMovies.ElementAt(i);
if (!::IsMovieDone(thisMovie))
return PR_TRUE;
}
return PR_FALSE;
}
PRBool
nsMovieSoundRequest::HaveQuickTime()
{
long gestResult;
OSErr err = Gestalt (gestaltQuickTime, &gestResult);
return (err == noErr) && ((long)EnterMovies != kUnresolvedCFragSymbolAddress);
}