Back to index

lightning-sunbird  0.9+nobinonly
nsSound.cpp
Go to the documentation of this file.
00001 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
00002 /* ***** BEGIN LICENSE BLOCK *****
00003  * Version: MPL 1.1/GPL 2.0/LGPL 2.1
00004  *
00005  * The contents of this file are subject to the Mozilla Public License Version
00006  * 1.1 (the "License"); you may not use this file except in compliance with
00007  * the License. You may obtain a copy of the License at
00008  * http://www.mozilla.org/MPL/
00009  *
00010  * Software distributed under the License is distributed on an "AS IS" basis,
00011  * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
00012  * for the specific language governing rights and limitations under the
00013  * License.
00014  *
00015  * The Original Code is mozilla.org code.
00016  *
00017  * The Initial Developer of the Original Code is
00018  * Netscape Communications Corporation.
00019  * Portions created by the Initial Developer are Copyright (C) 1998
00020  * the Initial Developer. All Rights Reserved.
00021  *
00022  * Contributor(s):
00023  *   Simon Fraser   <sfraser@netscape.com>
00024  *   Pierre Phaneuf <pp@ludusdesign.com>
00025  *
00026  * Alternatively, the contents of this file may be used under the terms of
00027  * either the GNU General Public License Version 2 or later (the "GPL"), or
00028  * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
00029  * in which case the provisions of the GPL or the LGPL are applicable instead
00030  * of those above. If you wish to allow use of your version of this file only
00031  * under the terms of either the GPL or the LGPL, and not to allow others to
00032  * use your version of this file under the terms of the MPL, indicate your
00033  * decision by deleting the provisions above and replace them with the notice
00034  * and other provisions required by the GPL or the LGPL. If you do not delete
00035  * the provisions above, a recipient may use your version of this file under
00036  * the terms of any one of the MPL, the GPL or the LGPL.
00037  *
00038  * ***** END LICENSE BLOCK ***** */
00039 
00040 #include "nscore.h"
00041 #include "nsIAllocator.h"
00042 #include "plstr.h"
00043 #include "nsVoidArray.h"
00044 #include "prnetdb.h"
00045 
00046 #include "nsIURL.h"
00047 #include "nsNetUtil.h"
00048 #include "prmem.h"
00049 #include "nsGfxUtils.h"
00050 
00051 #include "nsIStreamLoader.h"
00052 #include "nsICacheService.h"
00053 #include "nsICacheSession.h"
00054 #include "nsICacheEntryDescriptor.h"
00055 #include "nsICachingChannel.h"
00056 
00057 #include "nsIInternetConfigService.h"
00058 
00059 #include "nsITimer.h"
00060 #include "nsCRT.h"
00061 
00062 #include <Gestalt.h>
00063 #include <Sound.h>
00064 #include <Movies.h>
00065 #include <QuickTimeComponents.h>
00066 
00067 #include "nsSound.h"
00068 
00069 #include "nsString.h"
00070 
00071 //#define SOUND_DEBUG
00072 
00073 #pragma mark nsSoundRequest
00074 
00075 // pure virtual base class for different types of sound requests
00076 class nsSoundRequest : public nsITimerCallback
00077 {
00078 public:
00079 
00080                     nsSoundRequest();
00081   virtual           ~nsSoundRequest();
00082 
00083   NS_DECL_ISUPPORTS
00084 
00085   // nsITimerCallback
00086   NS_IMETHOD Notify(nsITimer *timer) = 0;
00087 
00088   virtual nsresult  PlaySound() = 0;
00089 
00090   static nsSoundRequest*  GetFromISupports(nsISupports* inSupports);
00091 
00092 protected:
00093 
00094   nsresult          Cleanup();
00095 
00096 protected:
00097 
00098   nsCOMPtr<nsISound>  mSound;       // back ptr, owned and released when play done  
00099   nsCOMPtr<nsITimer>  mTimer;
00100 };
00101 
00102 
00103 // concrete class for playing system sounds asynchronously
00104 class nsSystemSoundRequest : public nsSoundRequest
00105 {
00106 public:
00107 
00108                     nsSystemSoundRequest();
00109   virtual           ~nsSystemSoundRequest();
00110 
00111   NS_DECL_ISUPPORTS_INHERITED
00112 
00113   // nsITimerCallback
00114   NS_DECL_NSITIMERCALLBACK
00115 
00116   nsresult          Init(nsISound* aSound, ConstStr255Param aSoundName);
00117   virtual nsresult  PlaySound();
00118   
00119 protected:
00120 
00121   static pascal void SoundCallback(SndChannelPtr chan, SndCommand *theCmd);
00122 
00123   void               DonePlaying();
00124 
00125 protected:
00126   
00127   Handle            mSoundHandle;     // resource handle.
00128   SndChannelPtr     mSndChannel;
00129   SndCallBackUPP    mSoundCallback;
00130   
00131   Boolean           mSoundDone;
00132 };
00133 
00134 
00135 // concrete class for playing URL-based sounds asynchronously
00136 class nsMovieSoundRequest :   public nsSoundRequest,
00137                               public nsIStreamLoaderObserver
00138 {
00139 public:
00140 
00141                     nsMovieSoundRequest();
00142   virtual           ~nsMovieSoundRequest();
00143 
00144   NS_DECL_ISUPPORTS_INHERITED
00145   NS_DECL_NSISTREAMLOADEROBSERVER
00146 
00147   // nsITimerCallback
00148   NS_DECL_NSITIMERCALLBACK
00149 
00150   nsresult          Init(nsISound* aSound, nsIURL *aURL);
00151   virtual nsresult  PlaySound();
00152   
00153 protected:
00154 
00155   OSType            GetFileFormat(const char* inData, long inDataSize, const nsACString& contentType);
00156   
00157   OSErr             ImportMovie(Handle inDataHandle, long inDataSize, const nsACString& contentType);
00158   PRBool            HaveQuickTime();
00159 
00160   void              DisposeMovieData();
00161   
00162   PRBool            IsAnyMoviePlaying();
00163   
00164   OSErr             TaskActiveMovies(PRBool *outAllMoviesDone);
00165 
00166   static PRBool     TaskOneMovie(Movie inMovie);    // return true if done
00167   
00168 protected:
00169   
00170   Movie                     mMovie;       // the original movie, kept around as long as this request is cached
00171   Handle                    mDataHandle;  // data handle, has to persist for the lifetime of any movies
00172                                           // depending on it
00173   
00174   nsVoidArray               mMovies;      // list of playing movie clones, which are transient.
00175 
00176 };
00177 
00178 #pragma mark -
00179 
00180 
00181 static PRUint32
00182 SecondsFromPRTime(PRTime prTime)
00183 {
00184   PRInt64 microSecondsPerSecond, intermediateResult;
00185   PRUint32 seconds;
00186   
00187   LL_I2L(microSecondsPerSecond, PR_USEC_PER_SEC);
00188   LL_DIV(intermediateResult, prTime, microSecondsPerSecond);
00189   LL_L2UI(seconds, intermediateResult);
00190   return seconds;
00191 }
00192 
00193 static void
00194 CopyCToPascalString(const char* inString, StringPtr outPString)
00195 {
00196   SInt32   nameLen = strlen(inString) & 0xFF;    // max 255 chars
00197   ::BlockMoveData(inString, &outPString[1], nameLen);
00198   outPString[0] = nameLen;
00199 }
00200 
00201 #pragma mark -
00202 
00203 nsSound::nsSound()
00204 {
00205 #ifdef SOUND_DEBUG
00206   printf("%%%%%%%% Made nsSound\n");
00207 #endif
00208 }
00209 
00210 nsSound::~nsSound()
00211 {
00212 #ifdef SOUND_DEBUG
00213   printf("%%%%%%%% Deleted nsSound\n");
00214 #endif
00215 }
00216 
00217 NS_IMPL_ISUPPORTS1(nsSound, nsISound)
00218 
00219 NS_METHOD
00220 nsSound::Beep()
00221 {
00222   ::SysBeep(1);
00223   return NS_OK;
00224 }
00225 
00226 NS_IMETHODIMP
00227 nsSound::Init()
00228 {
00229   return NS_OK;
00230 }
00231 
00232 NS_IMETHODIMP
00233 nsSound::PlaySystemSound(const char *aSoundName)
00234 {
00235   nsCOMPtr<nsISupports> requestSupports;
00236   
00237   nsSystemSoundRequest* soundRequest;
00238   NS_NEWXPCOM(soundRequest, nsSystemSoundRequest);
00239   if (!soundRequest)
00240     return NS_ERROR_OUT_OF_MEMORY;
00241 
00242   requestSupports = NS_STATIC_CAST(nsITimerCallback*, soundRequest);
00243   
00244   Str255  soundResource;
00245   nsresult rv = GetSoundResourceName(aSoundName, soundResource);
00246   if (NS_FAILED(rv))
00247     return Beep();
00248   
00249   rv = soundRequest->Init(this, soundResource);
00250   if (NS_FAILED(rv))
00251     return Beep();
00252 
00253   rv = AddRequest(requestSupports);
00254   if (NS_FAILED(rv))
00255     return Beep();
00256   
00257   rv = soundRequest->PlaySound();
00258   if (NS_FAILED(rv))
00259     return Beep();
00260   
00261   return NS_OK;
00262 }
00263 
00264 // this currently does no caching of the sound buffer. It should
00265 NS_METHOD
00266 nsSound::Play(nsIURL *aURL)
00267 {
00268   NS_ENSURE_ARG(aURL);
00269 
00270   nsresult rv;
00271 
00272   // try to get from cache
00273   nsCOMPtr<nsISupports>   requestSupports;
00274   (void)GetSoundFromCache(NS_STATIC_CAST(nsIURI*,aURL), getter_AddRefs(requestSupports));
00275   if (requestSupports)
00276   {
00277     nsSoundRequest* cachedRequest = nsSoundRequest::GetFromISupports(requestSupports);
00278     nsMovieSoundRequest* movieRequest = NS_STATIC_CAST(nsMovieSoundRequest*, cachedRequest);
00279     // if it was cached, start playing right away
00280     movieRequest->PlaySound();
00281   }
00282   else
00283   {
00284     nsMovieSoundRequest* soundRequest;
00285     NS_NEWXPCOM(soundRequest, nsMovieSoundRequest);
00286     if (!soundRequest)
00287       return NS_ERROR_OUT_OF_MEMORY;
00288 
00289     requestSupports = NS_STATIC_CAST(nsITimerCallback*, soundRequest);
00290     nsresult  rv = soundRequest->Init(this, aURL);
00291     if (NS_FAILED(rv))
00292       return rv;
00293   }
00294   
00295   rv = AddRequest(requestSupports);
00296   if (NS_FAILED(rv))
00297     return rv;
00298 
00299   return NS_OK;
00300 }
00301 
00302 
00303 nsresult
00304 nsSound::AddRequest(nsISupports* aSoundRequest)
00305 {
00306   // only add if not already in the list
00307   PRInt32   index = mSoundRequests.IndexOf(aSoundRequest);
00308   if (index == -1)
00309   {
00310     nsresult  appended = mSoundRequests.AppendElement(aSoundRequest);
00311     if (!appended)
00312       return NS_ERROR_FAILURE;
00313   }
00314   
00315   return NS_OK;
00316 }
00317 
00318 
00319 nsresult
00320 nsSound::RemoveRequest(nsISupports* aSoundRequest)
00321 {
00322   nsresult  removed = mSoundRequests.RemoveElement(aSoundRequest);
00323   if (!removed)
00324     return NS_ERROR_FAILURE;
00325 
00326   return NS_OK;
00327 }
00328 
00329 
00330 nsresult
00331 nsSound::GetCacheSession(nsICacheSession** outCacheSession)
00332 {
00333   nsresult rv;
00334 
00335   nsCOMPtr<nsICacheService> cacheService = do_GetService("@mozilla.org/network/cache-service;1", &rv);
00336   if (NS_FAILED(rv)) return rv;
00337   
00338   return cacheService->CreateSession("sound",
00339                               nsICache::STORE_IN_MEMORY,
00340                               nsICache::NOT_STREAM_BASED, outCacheSession);
00341 }
00342 
00343 
00344 nsresult
00345 nsSound::GetSoundFromCache(nsIURI* inURI, nsISupports** outSound)
00346 {
00347   nsresult rv;
00348   
00349   nsCAutoString uriSpec;
00350   inURI->GetAsciiSpec(uriSpec);
00351 
00352   nsCOMPtr<nsICacheSession> cacheSession;
00353   rv = GetCacheSession(getter_AddRefs(cacheSession));
00354   if (NS_FAILED(rv)) return rv;
00355   
00356   nsCOMPtr<nsICacheEntryDescriptor> entry;
00357   rv = cacheSession->OpenCacheEntry(uriSpec, nsICache::ACCESS_READ, nsICache::BLOCKING, getter_AddRefs(entry));
00358 
00359 #ifdef SOUND_DEBUG
00360   printf("Got sound from cache with rv %ld\n", rv);
00361 #endif
00362 
00363   if (NS_FAILED(rv)) return rv;
00364   
00365   return entry->GetCacheElement(outSound);
00366 }
00367 
00368 
00369 nsresult
00370 nsSound::PutSoundInCache(nsIChannel* inChannel, PRUint32 inDataSize, nsISupports* inSound)
00371 {
00372   nsresult rv;
00373   
00374   NS_ENSURE_ARG(inChannel && inSound);
00375   
00376   nsCOMPtr<nsIURI>  uri;
00377   inChannel->GetOriginalURI(getter_AddRefs(uri));
00378   if (!uri) return NS_ERROR_FAILURE;
00379   
00380   nsCAutoString uriSpec;
00381   rv = uri->GetAsciiSpec(uriSpec);
00382   if (NS_FAILED(rv)) return rv;
00383 
00384   nsCOMPtr<nsICacheSession> cacheSession;
00385   rv = GetCacheSession(getter_AddRefs(cacheSession));
00386   if (NS_FAILED(rv)) return rv;
00387 
00388   nsCOMPtr<nsICacheEntryDescriptor> entry;
00389   rv = cacheSession->OpenCacheEntry(uriSpec, nsICache::ACCESS_WRITE, nsICache::BLOCKING, getter_AddRefs(entry));
00390 #ifdef SOUND_DEBUG
00391   printf("Put sound in cache with rv %ld\n", rv);
00392 #endif
00393 
00394   if (NS_FAILED(rv)) return rv;
00395 
00396   rv = entry->SetCacheElement(inSound);
00397   if (NS_FAILED(rv)) return rv;
00398 
00399   rv = entry->SetDataSize(inDataSize);
00400   if (NS_FAILED(rv)) return rv;
00401   
00402   PRUint32    expirationTime = 0;
00403 
00404   // try to get the expiration time from the URI load
00405   nsCOMPtr<nsICachingChannel> cachingChannel = do_QueryInterface(inChannel);
00406   if (cachingChannel)
00407   {
00408     nsCOMPtr<nsISupports> cacheToken;
00409     cachingChannel->GetCacheToken(getter_AddRefs(cacheToken));
00410     nsCOMPtr<nsICacheEntryInfo> cacheEntryInfo = do_QueryInterface(cacheToken);
00411     if (cacheEntryInfo)
00412     {
00413       cacheEntryInfo->GetExpirationTime(&expirationTime);
00414     }
00415   }
00416   
00417   if (expirationTime == PRUint32(-1))   // no expiration time (never expires)
00418   {
00419     // set it to some reasonable default, like now + 24 hours
00420     expirationTime = SecondsFromPRTime(PR_Now()) + 60 * 60 * 24;
00421   }
00422   
00423   rv = entry->SetExpirationTime(expirationTime);
00424   if (NS_FAILED(rv)) return rv;
00425   
00426   return entry->MarkValid();
00427 }
00428 
00429 
00430 nsresult
00431 nsSound::GetSoundResourceName(const char* inSoundName, StringPtr outResourceName)
00432 {
00433   nsresult rv = NS_OK;
00434   
00435   outResourceName[0] = 0;
00436   
00437   // if it's the special mail beep sound, get the real sound name from IC
00438   if (nsCRT::strcmp("_moz_mailbeep", inSoundName) == 0)
00439   {
00440     nsCOMPtr <nsIInternetConfigService> icService = do_GetService(NS_INTERNETCONFIGSERVICE_CONTRACTID, &rv);
00441     if (NS_FAILED(rv))
00442       return rv;
00443     
00444     nsCAutoString  newMailSound;
00445     rv = icService->GetString(nsIInternetConfigService::eICString_NewMailSoundName, newMailSound);
00446     if (NS_FAILED(rv))
00447       return rv;
00448       
00449     CopyCToPascalString(newMailSound.get(), outResourceName);
00450     return NS_OK;
00451   }
00452 
00453   // if the name is not "Mailbeep", treat it as the name of a system sound
00454   CopyCToPascalString(inSoundName, outResourceName);
00455   return NS_OK;
00456 }
00457 
00458 
00459 #pragma mark -
00460 
00461 nsSoundRequest::nsSoundRequest()
00462 {
00463 }
00464 
00465 nsSoundRequest::~nsSoundRequest()
00466 {
00467 }
00468 
00469 NS_IMPL_ISUPPORTS1(nsSoundRequest, nsITimerCallback)
00470 
00471 nsSoundRequest*
00472 nsSoundRequest::GetFromISupports(nsISupports* inSupports)
00473 {
00474   if (!inSupports) return nsnull;
00475   
00476   // test to see if this is really a nsSoundRequest by trying a QI
00477   nsCOMPtr<nsITimerCallback>  timerCallback = do_QueryInterface(inSupports);
00478   if (!timerCallback) return nsnull;
00479   
00480   return NS_REINTERPRET_CAST(nsSoundRequest*, inSupports);
00481 }
00482 
00483 
00484 nsresult
00485 nsSoundRequest::Cleanup()
00486 {
00487   nsresult rv = NS_OK;
00488   
00489 #ifdef SOUND_DEBUG
00490   printf("Sound playback done\n");
00491 #endif
00492   
00493   // kill the timer
00494   if (mTimer) {
00495     mTimer->Cancel();
00496     mTimer = nsnull;
00497   }
00498   
00499   // remove from parent array. Use a deathGrip to ensure that it's OK
00500   // to clear mSound.
00501   nsCOMPtr<nsISupports>   deathGrip(this);
00502   if (mSound.get())
00503   {
00504     nsSound*    macSound = NS_REINTERPRET_CAST(nsSound*, mSound.get());
00505     rv = macSound->RemoveRequest(NS_STATIC_CAST(nsITimerCallback*, this));
00506     mSound = nsnull;
00507   }
00508   
00509   return rv;
00510 }
00511 
00512 
00513 #pragma mark -
00514 
00515 
00516 nsSystemSoundRequest::nsSystemSoundRequest()
00517 : mSoundHandle(nsnull)
00518 , mSndChannel(nsnull)
00519 , mSoundCallback(nsnull)
00520 , mSoundDone(false)
00521 {
00522 #ifdef SOUND_DEBUG
00523   printf("%%%%%%%% Made nsSystemSoundRequest\n");
00524 #endif
00525 }
00526 
00527 nsSystemSoundRequest::~nsSystemSoundRequest()
00528 {
00529   if (mSoundHandle) {
00530     // unlock the sound resource handle and make it purgeable.
00531     ::HUnlock(mSoundHandle);
00532     ::HPurge(mSoundHandle);
00533   }
00534 
00535   if (mSndChannel)
00536     ::SndDisposeChannel(mSndChannel, true);
00537 
00538   if (mSoundCallback)
00539     DisposeSndCallBackUPP(mSoundCallback);
00540     
00541 #ifdef SOUND_DEBUG
00542   printf("%%%%%%%% Deleted nsSystemSoundRequest\n");
00543 #endif
00544 }
00545 
00546 NS_IMPL_ISUPPORTS_INHERITED0(nsSystemSoundRequest, nsSoundRequest)
00547 
00548 nsresult
00549 nsSystemSoundRequest::Init(nsISound* aSound, ConstStr255Param aSoundName)
00550 {
00551   mSound = aSound;
00552 
00553   mSoundCallback = NewSndCallBackUPP(nsSystemSoundRequest::SoundCallback);
00554   if (!mSoundCallback) return NS_ERROR_OUT_OF_MEMORY;
00555   
00556   mSoundHandle = ::GetNamedResource('snd ', aSoundName);
00557   if (!mSoundHandle) return NS_ERROR_FAILURE;
00558   
00559   // make sure the resource is loaded
00560   ::LoadResource(mSoundHandle);
00561   if (!mSoundHandle || !*mSoundHandle) return NS_ERROR_FAILURE;
00562   
00563   // and lock it high
00564   ::HLockHi(mSoundHandle);
00565   
00566   OSErr err = ::SndNewChannel(&mSndChannel, 0, 0, mSoundCallback);
00567   if (err != noErr) return NS_ERROR_FAILURE;
00568 
00569   return NS_OK;
00570 }
00571 
00572 nsresult
00573 nsSystemSoundRequest::PlaySound()
00574 {
00575   NS_ASSERTION(mSndChannel && mSoundHandle, "Should have sound channel here");
00576   if (!mSndChannel || !mSoundHandle) {
00577     Cleanup();
00578     return NS_ERROR_NOT_INITIALIZED;
00579   }
00580   
00581   nsresult rv;
00582   // set up a timer. This is used to sniff for mSoundDone (which is set by the channel callback).
00583   mTimer = do_CreateInstance("@mozilla.org/timer;1", &rv);    // release previous timer, if any
00584   if (NS_FAILED(rv)) {
00585     Cleanup();
00586     return rv;
00587   }
00588 
00589   OSErr err = ::SndPlay(mSndChannel, (SndListHandle)mSoundHandle, true /* async */);
00590   if (err != noErr) {
00591      Cleanup();
00592      return NS_ERROR_FAILURE;
00593   }
00594 
00595   // now queue up a sound completion command so we get a callback when
00596   // the sound is done.
00597   SndCommand    theCmd = { callBackCmd, 0, 0 };
00598   theCmd.param2 = (long)this;
00599   
00600   err = ::SndDoCommand(mSndChannel, &theCmd, false);   // wait for the channel
00601   if (err != noErr) {
00602     Cleanup();
00603     return NS_ERROR_FAILURE;
00604   }
00605   
00606   const PRInt32   kSoundTimerInterval = 250;      // 250 milliseconds
00607   rv = mTimer->InitWithCallback(NS_STATIC_CAST(nsITimerCallback*, this), kSoundTimerInterval,
00608                                 nsITimer::TYPE_REPEATING_PRECISE);
00609   if (NS_FAILED(rv)) {
00610     Cleanup();
00611     return rv;
00612   }
00613 
00614   return NS_OK;
00615 }
00616 
00617 NS_IMETHODIMP
00618 nsSystemSoundRequest::Notify(nsITimer *timer)
00619 {
00620   if (mSoundDone)
00621   {
00622     Cleanup();
00623   }
00624   return NS_OK;
00625 }
00626 
00627 
00628 // Note! Called at interrupt time
00629 void
00630 nsSystemSoundRequest::DonePlaying()
00631 {
00632   mSoundDone = true;
00633 }
00634 
00635 /* static. Note! Called at interrupt time */
00636 pascal void
00637 nsSystemSoundRequest::SoundCallback(SndChannelPtr chan, SndCommand *theCmd)
00638 {
00639   nsSystemSoundRequest*   soundRequest = NS_REINTERPRET_CAST(nsSystemSoundRequest*, theCmd->param2);
00640   if (soundRequest)
00641     soundRequest->DonePlaying();
00642 }
00643 
00644 
00645 #pragma mark -
00646 
00647 // This class should only ever be instantiated once, statically.
00648 // Its job is to create, own and destroy the singleton GWorld.
00649 class nsMoviePortOwner
00650 {
00651 public:
00652 
00653   static GWorldPtr   GetSingletonMoviePort();
00654   
00655 private:
00656 
00657               // private ctor and dtor. Only GetSingletonMoviePort() can instantiate us.
00658               nsMoviePortOwner() {}
00659               
00660               ~nsMoviePortOwner()
00661               {
00662                 if (sMoviePort)
00663                 {
00664                   ::DisposeGWorld(sMoviePort);
00665                   sMoviePort = nsnull;
00666                 }
00667               }
00668 
00669   void        EnsureMoviePort()
00670               {
00671                 if (!sMoviePort)
00672                 {
00673                   Rect        gWorldBounds = {0, 0, 12, 12};
00674                   (void)::NewGWorld(&sMoviePort, 8, &gWorldBounds, nil, nil, 0);
00675                 }
00676               }
00677 
00678   GWorldPtr   GetMoviePort()
00679               {
00680                 EnsureMoviePort();
00681                 return sMoviePort;
00682               }
00683 
00684 private:
00685 
00686   static GWorldPtr    sMoviePort;
00687 
00688 };
00689 
00690 GWorldPtr nsMoviePortOwner::sMoviePort = nsnull;
00691 
00692 GWorldPtr nsMoviePortOwner::GetSingletonMoviePort()
00693 {
00694   static nsMoviePortOwner   sMoviePortOwner;
00695   return sMoviePortOwner.GetMoviePort();
00696 }
00697 
00698 
00699 NS_IMPL_ISUPPORTS_INHERITED1(nsMovieSoundRequest, nsSoundRequest, nsIStreamLoaderObserver)
00700 
00701 
00702 nsMovieSoundRequest::nsMovieSoundRequest()
00703 : mMovie(nsnull)
00704 , mDataHandle(nsnull)
00705 {
00706 #ifdef SOUND_DEBUG
00707   printf("%%%%%%%% Made nsMovieSoundRequest\n");
00708 #endif
00709 }
00710 
00711 nsMovieSoundRequest::~nsMovieSoundRequest()
00712 {
00713 #ifdef SOUND_DEBUG
00714   printf("%%%%%%%% Deleted nsMovieSoundRequest\n");
00715 #endif
00716   DisposeMovieData();
00717 }
00718 
00719 
00720 nsresult
00721 nsMovieSoundRequest::Init(nsISound* aSound, nsIURL *aURL)
00722 {
00723   NS_ENSURE_ARG(aURL && aSound);
00724 
00725   mSound = aSound;
00726   
00727   // if quicktime is not installed, we can't do anything
00728   if (!HaveQuickTime())
00729     return NS_ERROR_NOT_IMPLEMENTED;
00730 
00731 #ifdef SOUND_DEBUG
00732   printf("%%%%%%%% Playing nsSound\n");
00733 #endif
00734   NS_ASSERTION(mMovie == nsnull, "nsSound being played twice");
00735   
00736   nsCOMPtr<nsIStreamLoader> streamLoader;
00737   return NS_NewStreamLoader(getter_AddRefs(streamLoader), aURL, NS_STATIC_CAST(nsIStreamLoaderObserver*, this));
00738 }
00739 
00740 NS_IMETHODIMP
00741 nsMovieSoundRequest::OnStreamComplete(nsIStreamLoader *aLoader,
00742                                         nsISupports *context,
00743                                         nsresult aStatus,
00744                                         PRUint32 dataLen,
00745                                         const PRUint8 *data)
00746 {
00747   NS_ENSURE_ARG(aLoader);
00748   
00749   if (NS_FAILED(aStatus))
00750     return NS_ERROR_FAILURE;
00751 
00752   nsCAutoString contentType;
00753 
00754   nsCOMPtr<nsIRequest>    request;
00755   aLoader->GetRequest(getter_AddRefs(request));
00756   nsCOMPtr<nsIChannel>  channel = do_QueryInterface(request);
00757   if (channel)
00758     channel->GetContentType(contentType);
00759   
00760   // we could use a Pointer data handler type, and avoid this
00761   // allocation/copy, in QuickTime 5 and above.
00762   OSErr     err;
00763   mDataHandle = ::TempNewHandle(dataLen, &err);
00764   if (!mDataHandle) return NS_ERROR_OUT_OF_MEMORY;
00765 
00766   ::BlockMoveData(data, *mDataHandle, dataLen);
00767 
00768   NS_ASSERTION(mMovie == nsnull, "nsMovieSoundRequest has a movie already");
00769   
00770   err = ImportMovie(mDataHandle, dataLen, contentType);
00771   if (err != noErr) {
00772     Cleanup();
00773     return NS_ERROR_FAILURE;
00774   }
00775 
00776   nsSound*    macSound = NS_REINTERPRET_CAST(nsSound*, mSound.get());
00777   NS_ASSERTION(macSound, "Should have nsSound here");
00778   
00779   // put it in the cache. Not vital that this succeeds.
00780   // for the data size we just use the string data, since the movie simply wraps this
00781   // (we have to keep the handle around until the movies are done playing)
00782   nsresult rv = macSound->PutSoundInCache(channel, dataLen, NS_STATIC_CAST(nsITimerCallback*, this));
00783   NS_ASSERTION(NS_SUCCEEDED(rv), "Failed to put sound in cache");
00784   
00785   return PlaySound();
00786 }
00787 
00788 
00789 OSType
00790 nsMovieSoundRequest::GetFileFormat(const char* inData, long inDataSize, const nsACString& contentType)
00791 {
00792   OSType    fileFormat = kQTFileTypeMovie;    // Default to just treating it like a movie.
00793                                               // Hopefully QuickTime will be able to import it.
00794   
00795   if (inDataSize >= 16)
00796   {
00797     OSType firstFourBytes = PR_ntohl(*(OSType *)inData);
00798 
00799     // look for WAVE
00800     if (firstFourBytes == 'RIFF')
00801     {
00802       const char* dataPtr = inData + 8; // skip RIFF and length bytes  
00803 
00804       if (PR_ntohl(*(OSType *)dataPtr) == 'WAVE')
00805         return kQTFileTypeWave;
00806     }
00807 
00808     // look for AIFF
00809     if (firstFourBytes == 'FORM')
00810     {
00811       const char* dataPtr = inData + 8; // skip FORM and length bytes
00812       OSType bytesEightThroughEleven = PR_ntohl(*(OSType *)dataPtr);
00813 
00814       if (bytesEightThroughEleven == 'AIFF')
00815         return kQTFileTypeAIFF;
00816 
00817       if (bytesEightThroughEleven == 'AIFC')
00818         return kQTFileTypeAIFC;
00819     }
00820   }
00821 
00822   if (inDataSize >= 4)
00823   {
00824     OSType firstFourBytes = PR_ntohl(*(OSType *)inData);
00825 
00826     // look for midi
00827     if (firstFourBytes == 'MThd')
00828       return kQTFileTypeMIDI;
00829 
00830     // look for µLaw/Next-Sun file format (.au)
00831     if (firstFourBytes == '.snd')
00832       return kQTFileTypeMuLaw;
00833   }
00834 
00835   // MP3 files have a complex format that is not easily sniffed. Just go by
00836   // MIME type.
00837   if (contentType.Equals("audio/mpeg")    ||
00838       contentType.Equals("audio/mp3")     ||
00839       contentType.Equals("audio/mpeg3")   ||
00840       contentType.Equals("audio/x-mpeg3") ||
00841       contentType.Equals("audio/x-mp3")   ||
00842       contentType.Equals("audio/x-mpeg3"))
00843   {
00844     fileFormat = 'MP3 ';      // not sure why there is no enum for this
00845   }
00846 
00847   return fileFormat;
00848 }
00849 
00850 nsresult
00851 nsMovieSoundRequest::PlaySound()
00852 {
00853   nsresult rv;
00854 
00855   // we'll have a timer already if the sound is still playing from a previous
00856   // request. In that case, we clone the movie into a new one, so we can play it
00857   // again from the start.
00858   if (!mTimer)
00859   {  
00860     mTimer = do_CreateInstance("@mozilla.org/timer;1", &rv);    // release previous timer, if any
00861     if (NS_FAILED(rv)) {
00862       Cleanup();
00863       return rv;
00864     }
00865     
00866     const PRInt32   kMovieTimerInterval = 250;      // 250 milliseconds
00867     rv = mTimer->InitWithCallback(NS_STATIC_CAST(nsITimerCallback*, this), kMovieTimerInterval,
00868                                   nsITimer::TYPE_REPEATING_PRECISE);
00869     if (NS_FAILED(rv)) {
00870       Cleanup();
00871       return rv;
00872     }
00873   }
00874   
00875   Movie   movieToPlay = mMovie;
00876   
00877   if (!::IsMovieDone(mMovie))     // if the current movie is still playing, clone it
00878   {
00879     Movie   newMovie = ::NewMovie(0);
00880     if (!newMovie) return NS_ERROR_FAILURE;
00881     
00882     // note that this copies refs, not all the data. So it should be fast
00883     OSErr err = ::InsertMovieSegment(mMovie, newMovie, 0, ::GetMovieDuration(mMovie), 0);
00884     if (err != noErr)
00885     {
00886       ::DisposeMovie(newMovie);
00887       return NS_ERROR_FAILURE;
00888     }
00889     
00890     // append it to the array
00891     PRBool  appended = mMovies.AppendElement((void *)newMovie);
00892     if (!appended) 
00893     {
00894       ::DisposeMovie(newMovie);
00895       return NS_ERROR_FAILURE;
00896     }
00897 
00898     movieToPlay = newMovie;
00899   }
00900   
00901   ::SetMovieVolume(movieToPlay, kFullVolume);
00902   ::GoToBeginningOfMovie(movieToPlay);
00903   ::StartMovie(movieToPlay);
00904   ::MoviesTask(movieToPlay, 0);
00905   
00906 #ifdef SOUND_DEBUG
00907   printf("Starting movie playback\n");
00908 #endif
00909   return NS_OK;
00910 }
00911 
00912 NS_IMETHODIMP
00913 nsMovieSoundRequest::Notify(nsITimer *timer)
00914 {
00915   if (!mMovie)
00916   {
00917     NS_ASSERTION(0, "nsMovieSoundRequest has no movie in timer callback");
00918     return NS_OK;
00919   }
00920   
00921 #ifdef SOUND_DEBUG
00922   printf("In movie timer callback\n");
00923 #endif
00924 
00925   PRBool  moviesDone;
00926 
00927   TaskActiveMovies(&moviesDone);
00928   
00929   // we're done for now. Remember that this nsMovieSoundRequest might be in the cache,
00930   // so won't necessarily go away.
00931   if (moviesDone)
00932     Cleanup();
00933   return NS_OK;
00934 }
00935 
00936 OSErr
00937 nsMovieSoundRequest::ImportMovie(Handle inDataHandle, long inDataSize, const nsACString& contentType)
00938 {
00939   GWorldPtr               moviePort = nsMoviePortOwner::GetSingletonMoviePort();
00940   Handle                  dataRef = nil;
00941   OSErr                   err = noErr;
00942   OSType                  fileFormat;
00943   
00944   if (!moviePort)
00945     return memFullErr;
00946   
00947   {
00948     StHandleLocker  locker(inDataHandle);
00949     fileFormat = GetFileFormat(*inDataHandle, inDataSize, contentType);
00950   }
00951 
00952   err = ::PtrToHand(&inDataHandle, &dataRef, sizeof(Handle));
00953   if (err != noErr)
00954     return err;
00955 
00956   {
00957     MovieImportComponent  miComponent = ::OpenDefaultComponent(MovieImportType, fileFormat);
00958     Track                 targetTrack = nil;
00959     TimeValue             addedDuration = 0;
00960     long                  outFlags = 0;
00961     ComponentResult       compErr = noErr;
00962 
00963     // set the port to our singleton GWorld before creating
00964     // the movie. This will ensure that the movie uses this port, and
00965     // not one of our (transient) windows.
00966     StGWorldPortSetter    gWorldSetter(moviePort);
00967 
00968     if (!miComponent) {
00969       err = paramErr;
00970       goto bail;
00971     }
00972     
00973     NS_ASSERTION(mMovie == nsnull, "nsMovieSoundRequest already has movie");
00974     mMovie = ::NewMovie(0);
00975     if (!mMovie) {
00976       err = ::GetMoviesError();
00977       goto bail;
00978     }
00979 
00980     compErr = ::MovieImportDataRef(miComponent,
00981                                 dataRef,
00982                                 HandleDataHandlerSubType,
00983                                 mMovie,
00984                                 nil,
00985                                 &targetTrack,
00986                                 nil,
00987                                 &addedDuration,
00988                                 movieImportCreateTrack,
00989                                 &outFlags);
00990 
00991     if (compErr != noErr) {
00992       ::DisposeMovie(mMovie);
00993       mMovie = nil;
00994       err = compErr;
00995       goto bail;
00996     }
00997 
00998     // ensure that the track never draws on screen, otherwise we might be
00999     // suspecptible to spoofing attacks
01000     {
01001       Rect   movieRect = {0};
01002       ::SetMovieBox(mMovie, &movieRect);
01003     }
01004     
01005     ::GoToEndOfMovie(mMovie);   // simplifies the logic in PlaySound()
01006         
01007   bail:
01008     if (miComponent)
01009       ::CloseComponent(miComponent);
01010   }
01011 
01012   if (dataRef)
01013     ::DisposeHandle(dataRef);
01014   
01015   return err;
01016 }
01017 
01018 void
01019 nsMovieSoundRequest::DisposeMovieData()
01020 {
01021   for (PRInt32 i = 0; i < mMovies.Count(); i ++)
01022   {
01023     Movie   thisMovie = (Movie)mMovies.ElementAt(i);
01024     ::DisposeMovie(thisMovie);
01025   }
01026   
01027   mMovies.Clear();
01028   
01029   if (mMovie) {
01030     ::DisposeMovie(mMovie);
01031     mMovie = nsnull;
01032   }
01033 
01034   if (mDataHandle) {
01035     ::DisposeHandle(mDataHandle);
01036     mDataHandle = nsnull;
01037   } 
01038 }
01039 
01040 
01041 PRBool
01042 nsMovieSoundRequest::TaskOneMovie(Movie inMovie)    // return true if done
01043 {
01044   PRBool    movieDone = PR_FALSE;
01045   
01046   ComponentResult status = ::GetMovieStatus(inMovie, nil);
01047   NS_ASSERTION(status == noErr, "Movie bad");
01048   if (status != noErr) {
01049     ::StopMovie(inMovie);
01050     movieDone = PR_TRUE;
01051   }
01052 
01053   movieDone |= ::IsMovieDone(inMovie);
01054   
01055   if (!movieDone)
01056     ::MoviesTask(inMovie, 0);
01057 
01058   return movieDone;
01059 }
01060 
01061 OSErr
01062 nsMovieSoundRequest::TaskActiveMovies(PRBool *outAllMoviesDone)
01063 {
01064   PRBool    allMoviesDone = PR_FALSE;
01065 
01066   allMoviesDone = TaskOneMovie(mMovie);
01067 
01068   PRInt32 curIndex = 0;
01069 
01070   while (curIndex < mMovies.Count())
01071   {
01072     Movie   thisMovie     = (Movie)mMovies.ElementAt(curIndex);
01073     PRBool  thisMovieDone = TaskOneMovie(thisMovie);
01074     
01075     if (thisMovieDone)    // remove finished movies from the array
01076     {
01077       mMovies.RemoveElementAt(curIndex);
01078       ::DisposeMovie(thisMovie);
01079       // curIndex doesn't change
01080     }
01081     else
01082     {
01083       curIndex ++;
01084     } 
01085     allMoviesDone &= thisMovieDone;
01086   }
01087   
01088   *outAllMoviesDone = allMoviesDone;
01089   return noErr;  
01090 }
01091 
01092 
01093 PRBool
01094 nsMovieSoundRequest::IsAnyMoviePlaying()
01095 {
01096   if (!::IsMovieDone(mMovie))
01097     return PR_TRUE;
01098   
01099   for (PRInt32 i = 0; i < mMovies.Count(); i ++)
01100   {
01101     Movie   thisMovie = (Movie)mMovies.ElementAt(i);
01102     if (!::IsMovieDone(thisMovie))
01103       return PR_TRUE;
01104   }
01105     
01106   return PR_FALSE;
01107 }
01108 
01109 PRBool
01110 nsMovieSoundRequest::HaveQuickTime()
01111 {
01112   long  gestResult;
01113   OSErr err = Gestalt (gestaltQuickTime, &gestResult);
01114   return (err == noErr) && ((long)EnterMovies != kUnresolvedCFragSymbolAddress);
01115 }
01116 
01117