soundmanager2.js 141 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427242824292430243124322433243424352436243724382439244024412442244324442445244624472448244924502451245224532454245524562457245824592460246124622463246424652466246724682469247024712472247324742475247624772478247924802481248224832484248524862487248824892490249124922493249424952496249724982499250025012502250325042505250625072508250925102511251225132514251525162517251825192520252125222523252425252526252725282529253025312532253325342535253625372538253925402541254225432544254525462547254825492550255125522553255425552556255725582559256025612562256325642565256625672568256925702571257225732574257525762577257825792580258125822583258425852586258725882589259025912592259325942595259625972598259926002601260226032604260526062607260826092610261126122613261426152616261726182619262026212622262326242625262626272628262926302631263226332634263526362637263826392640264126422643264426452646264726482649265026512652265326542655265626572658265926602661266226632664266526662667266826692670267126722673267426752676267726782679268026812682268326842685268626872688268926902691269226932694269526962697269826992700270127022703270427052706270727082709271027112712271327142715271627172718271927202721272227232724272527262727272827292730273127322733273427352736273727382739274027412742274327442745274627472748274927502751275227532754275527562757275827592760276127622763276427652766276727682769277027712772277327742775277627772778277927802781278227832784278527862787278827892790279127922793279427952796279727982799280028012802280328042805280628072808280928102811281228132814281528162817281828192820282128222823282428252826282728282829283028312832283328342835283628372838283928402841284228432844284528462847284828492850285128522853285428552856285728582859286028612862286328642865286628672868286928702871287228732874287528762877287828792880288128822883288428852886288728882889289028912892289328942895289628972898289929002901290229032904290529062907290829092910291129122913291429152916291729182919292029212922292329242925292629272928292929302931293229332934293529362937293829392940294129422943294429452946294729482949295029512952295329542955295629572958295929602961296229632964296529662967296829692970297129722973297429752976297729782979298029812982298329842985298629872988298929902991299229932994299529962997299829993000300130023003300430053006300730083009301030113012301330143015301630173018301930203021302230233024302530263027302830293030303130323033303430353036303730383039304030413042304330443045304630473048304930503051305230533054305530563057305830593060306130623063306430653066306730683069307030713072307330743075307630773078307930803081308230833084308530863087308830893090309130923093309430953096309730983099310031013102310331043105310631073108310931103111311231133114311531163117311831193120312131223123312431253126312731283129313031313132313331343135313631373138313931403141314231433144314531463147314831493150315131523153315431553156315731583159316031613162316331643165316631673168316931703171317231733174317531763177317831793180318131823183318431853186318731883189319031913192319331943195319631973198319932003201320232033204320532063207320832093210321132123213321432153216321732183219322032213222322332243225322632273228322932303231323232333234323532363237323832393240324132423243324432453246324732483249325032513252325332543255325632573258325932603261326232633264326532663267326832693270327132723273327432753276327732783279328032813282328332843285328632873288328932903291329232933294329532963297329832993300330133023303330433053306330733083309331033113312331333143315331633173318331933203321332233233324332533263327332833293330333133323333333433353336333733383339334033413342334333443345334633473348334933503351335233533354335533563357335833593360336133623363336433653366336733683369337033713372337333743375337633773378337933803381338233833384338533863387338833893390339133923393339433953396339733983399340034013402340334043405340634073408340934103411341234133414341534163417341834193420342134223423342434253426342734283429343034313432343334343435343634373438343934403441344234433444344534463447344834493450345134523453345434553456345734583459346034613462346334643465346634673468346934703471347234733474347534763477347834793480348134823483348434853486348734883489349034913492349334943495349634973498349935003501350235033504350535063507350835093510351135123513351435153516351735183519352035213522352335243525352635273528352935303531353235333534353535363537353835393540354135423543354435453546354735483549355035513552355335543555355635573558355935603561356235633564356535663567356835693570357135723573357435753576357735783579358035813582358335843585358635873588358935903591359235933594359535963597359835993600360136023603360436053606360736083609361036113612361336143615361636173618361936203621362236233624362536263627362836293630363136323633363436353636363736383639364036413642364336443645364636473648364936503651365236533654365536563657365836593660366136623663366436653666366736683669367036713672367336743675367636773678367936803681368236833684368536863687368836893690369136923693369436953696369736983699370037013702370337043705370637073708370937103711371237133714371537163717371837193720372137223723372437253726372737283729373037313732373337343735373637373738373937403741374237433744374537463747374837493750375137523753375437553756375737583759376037613762376337643765376637673768376937703771377237733774377537763777377837793780378137823783378437853786378737883789379037913792379337943795379637973798379938003801380238033804380538063807380838093810381138123813381438153816381738183819382038213822382338243825382638273828382938303831383238333834383538363837383838393840384138423843384438453846384738483849385038513852385338543855385638573858385938603861386238633864386538663867386838693870387138723873387438753876387738783879388038813882388338843885388638873888388938903891389238933894389538963897389838993900390139023903390439053906390739083909391039113912391339143915391639173918391939203921392239233924392539263927392839293930393139323933393439353936393739383939394039413942394339443945394639473948394939503951395239533954395539563957395839593960396139623963396439653966396739683969397039713972397339743975397639773978397939803981398239833984398539863987398839893990399139923993399439953996399739983999400040014002400340044005400640074008400940104011401240134014401540164017401840194020402140224023402440254026402740284029403040314032403340344035403640374038403940404041404240434044404540464047404840494050405140524053405440554056405740584059406040614062406340644065406640674068406940704071407240734074407540764077407840794080408140824083408440854086408740884089409040914092409340944095409640974098409941004101410241034104410541064107410841094110411141124113411441154116411741184119412041214122412341244125412641274128412941304131413241334134413541364137413841394140414141424143414441454146414741484149415041514152415341544155415641574158415941604161416241634164416541664167416841694170417141724173417441754176417741784179418041814182418341844185418641874188418941904191419241934194419541964197419841994200420142024203420442054206420742084209421042114212421342144215421642174218421942204221422242234224422542264227422842294230423142324233423442354236423742384239424042414242424342444245424642474248424942504251425242534254425542564257425842594260426142624263426442654266426742684269427042714272427342744275427642774278427942804281428242834284428542864287428842894290429142924293429442954296429742984299430043014302430343044305430643074308430943104311431243134314431543164317431843194320432143224323432443254326432743284329433043314332433343344335433643374338433943404341434243434344434543464347434843494350435143524353435443554356435743584359436043614362436343644365436643674368436943704371437243734374437543764377437843794380438143824383438443854386438743884389439043914392439343944395439643974398439944004401440244034404440544064407440844094410441144124413441444154416441744184419442044214422442344244425442644274428442944304431443244334434443544364437443844394440444144424443444444454446444744484449445044514452445344544455445644574458445944604461446244634464446544664467446844694470447144724473447444754476447744784479448044814482448344844485448644874488448944904491449244934494449544964497449844994500450145024503450445054506450745084509451045114512451345144515451645174518451945204521452245234524452545264527452845294530453145324533453445354536453745384539454045414542454345444545454645474548454945504551455245534554455545564557455845594560456145624563456445654566456745684569457045714572457345744575457645774578457945804581458245834584458545864587458845894590459145924593459445954596459745984599460046014602460346044605460646074608460946104611461246134614461546164617461846194620462146224623462446254626462746284629463046314632463346344635463646374638463946404641464246434644464546464647464846494650465146524653465446554656465746584659466046614662466346644665466646674668466946704671467246734674467546764677467846794680468146824683468446854686468746884689469046914692469346944695469646974698469947004701470247034704470547064707470847094710471147124713471447154716471747184719472047214722472347244725472647274728472947304731473247334734473547364737473847394740474147424743474447454746474747484749475047514752475347544755475647574758475947604761476247634764476547664767476847694770477147724773477447754776477747784779478047814782478347844785478647874788478947904791479247934794479547964797479847994800480148024803480448054806480748084809481048114812481348144815481648174818481948204821482248234824482548264827482848294830483148324833483448354836483748384839484048414842484348444845484648474848484948504851485248534854485548564857485848594860486148624863486448654866486748684869487048714872487348744875487648774878487948804881488248834884488548864887488848894890489148924893489448954896489748984899490049014902490349044905490649074908490949104911491249134914491549164917491849194920492149224923492449254926492749284929493049314932493349344935493649374938493949404941494249434944494549464947494849494950495149524953495449554956495749584959496049614962496349644965496649674968496949704971497249734974497549764977497849794980498149824983498449854986498749884989499049914992499349944995499649974998499950005001500250035004500550065007500850095010501150125013501450155016501750185019502050215022502350245025502650275028502950305031503250335034503550365037503850395040504150425043504450455046504750485049505050515052505350545055505650575058505950605061506250635064506550665067506850695070507150725073507450755076507750785079508050815082508350845085508650875088508950905091509250935094509550965097509850995100510151025103510451055106510751085109511051115112511351145115511651175118511951205121512251235124512551265127512851295130513151325133513451355136513751385139514051415142514351445145514651475148514951505151515251535154515551565157515851595160516151625163516451655166516751685169517051715172517351745175517651775178517951805181518251835184518551865187518851895190519151925193519451955196519751985199520052015202520352045205520652075208520952105211521252135214521552165217521852195220522152225223522452255226522752285229523052315232523352345235523652375238523952405241524252435244524552465247524852495250525152525253525452555256525752585259526052615262526352645265526652675268526952705271527252735274527552765277527852795280528152825283528452855286528752885289529052915292529352945295529652975298529953005301530253035304530553065307530853095310531153125313531453155316531753185319532053215322532353245325532653275328532953305331533253335334533553365337533853395340534153425343534453455346534753485349535053515352535353545355535653575358535953605361536253635364536553665367536853695370537153725373537453755376537753785379538053815382538353845385538653875388538953905391539253935394539553965397539853995400540154025403540454055406540754085409541054115412541354145415541654175418541954205421542254235424542554265427542854295430543154325433543454355436543754385439544054415442544354445445544654475448544954505451545254535454545554565457545854595460546154625463546454655466546754685469547054715472547354745475547654775478547954805481548254835484548554865487548854895490549154925493549454955496549754985499550055015502550355045505550655075508550955105511551255135514551555165517551855195520552155225523552455255526552755285529553055315532553355345535553655375538553955405541554255435544554555465547554855495550555155525553555455555556555755585559556055615562556355645565556655675568556955705571557255735574557555765577557855795580558155825583558455855586558755885589559055915592559355945595559655975598559956005601560256035604560556065607560856095610561156125613561456155616561756185619562056215622562356245625562656275628562956305631563256335634563556365637563856395640564156425643564456455646564756485649565056515652565356545655565656575658565956605661566256635664566556665667566856695670567156725673567456755676567756785679568056815682568356845685568656875688568956905691569256935694569556965697569856995700570157025703570457055706570757085709571057115712571357145715571657175718571957205721572257235724
  1. /** @license
  2. *
  3. * SoundManager 2: JavaScript Sound for the Web
  4. * ----------------------------------------------
  5. * http://schillmania.com/projects/soundmanager2/
  6. *
  7. * Copyright (c) 2007, Scott Schiller. All rights reserved.
  8. * Code provided under the BSD License:
  9. * http://schillmania.com/projects/soundmanager2/license.txt
  10. *
  11. * V2.97a.20130324 ("Mahalo" Edition)
  12. */
  13. /*global window, SM2_DEFER, sm2Debugger, console, document, navigator, setTimeout, setInterval, clearInterval, Audio, opera */
  14. /*jslint regexp: true, sloppy: true, white: true, nomen: true, plusplus: true, todo: true */
  15. /**
  16. * About this file
  17. * -------------------------------------------------------------------------------------
  18. * This is the fully-commented source version of the SoundManager 2 API,
  19. * recommended for use during development and testing.
  20. *
  21. * See soundmanager2-nodebug-jsmin.js for an optimized build (~11KB with gzip.)
  22. * http://schillmania.com/projects/soundmanager2/doc/getstarted/#basic-inclusion
  23. * Alternately, serve this file with gzip for 75% compression savings (~30KB over HTTP.)
  24. *
  25. * You may notice <d> and </d> comments in this source; these are delimiters for
  26. * debug blocks which are removed in the -nodebug builds, further optimizing code size.
  27. *
  28. * Also, as you may note: Whoa, reliable cross-platform/device audio support is hard! ;)
  29. */
  30. (function(window, _undefined) {
  31. "use strict";
  32. var soundManager = null;
  33. /**
  34. * The SoundManager constructor.
  35. *
  36. * @constructor
  37. * @param {string} smURL Optional: Path to SWF files
  38. * @param {string} smID Optional: The ID to use for the SWF container element
  39. * @this {SoundManager}
  40. * @return {SoundManager} The new SoundManager instance
  41. */
  42. function SoundManager(smURL, smID) {
  43. /**
  44. * soundManager configuration options list
  45. * defines top-level configuration properties to be applied to the soundManager instance (eg. soundManager.flashVersion)
  46. * to set these properties, use the setup() method - eg., soundManager.setup({url: '/swf/', flashVersion: 9})
  47. */
  48. this.setupOptions = {
  49. 'url': (smURL || null), // path (directory) where SoundManager 2 SWFs exist, eg., /path/to/swfs/
  50. 'flashVersion': 8, // flash build to use (8 or 9.) Some API features require 9.
  51. 'debugMode': true, // enable debugging output (console.log() with HTML fallback)
  52. 'debugFlash': false, // enable debugging output inside SWF, troubleshoot Flash/browser issues
  53. 'useConsole': true, // use console.log() if available (otherwise, writes to #soundmanager-debug element)
  54. 'consoleOnly': true, // if console is being used, do not create/write to #soundmanager-debug
  55. 'waitForWindowLoad': false, // force SM2 to wait for window.onload() before trying to call soundManager.onload()
  56. 'bgColor': '#ffffff', // SWF background color. N/A when wmode = 'transparent'
  57. 'useHighPerformance': false, // position:fixed flash movie can help increase js/flash speed, minimize lag
  58. 'flashPollingInterval': null, // msec affecting whileplaying/loading callback frequency. If null, default of 50 msec is used.
  59. 'html5PollingInterval': null, // msec affecting whileplaying() for HTML5 audio, excluding mobile devices. If null, native HTML5 update events are used.
  60. 'flashLoadTimeout': 1000, // msec to wait for flash movie to load before failing (0 = infinity)
  61. 'wmode': null, // flash rendering mode - null, 'transparent', or 'opaque' (last two allow z-index to work)
  62. 'allowScriptAccess': 'always', // for scripting the SWF (object/embed property), 'always' or 'sameDomain'
  63. 'useFlashBlock': false, // *requires flashblock.css, see demos* - allow recovery from flash blockers. Wait indefinitely and apply timeout CSS to SWF, if applicable.
  64. 'useHTML5Audio': true, // use HTML5 Audio() where API is supported (most Safari, Chrome versions), Firefox (no MP3/MP4.) Ideally, transparent vs. Flash API where possible.
  65. 'html5Test': /^(probably|maybe)$/i, // HTML5 Audio() format support test. Use /^probably$/i; if you want to be more conservative.
  66. 'preferFlash': true, // overrides useHTML5audio. if true and flash support present, will try to use flash for MP3/MP4 as needed since HTML5 audio support is still quirky in browsers.
  67. 'noSWFCache': false // if true, appends ?ts={date} to break aggressive SWF caching.
  68. };
  69. this.defaultOptions = {
  70. /**
  71. * the default configuration for sound objects made with createSound() and related methods
  72. * eg., volume, auto-load behaviour and so forth
  73. */
  74. 'autoLoad': false, // enable automatic loading (otherwise .load() will be called on demand with .play(), the latter being nicer on bandwidth - if you want to .load yourself, you also can)
  75. 'autoPlay': false, // enable playing of file as soon as possible (much faster if "stream" is true)
  76. 'from': null, // position to start playback within a sound (msec), default = beginning
  77. 'loops': 1, // how many times to repeat the sound (position will wrap around to 0, setPosition() will break out of loop when >0)
  78. 'onid3': null, // callback function for "ID3 data is added/available"
  79. 'onload': null, // callback function for "load finished"
  80. 'whileloading': null, // callback function for "download progress update" (X of Y bytes received)
  81. 'onplay': null, // callback for "play" start
  82. 'onpause': null, // callback for "pause"
  83. 'onresume': null, // callback for "resume" (pause toggle)
  84. 'whileplaying': null, // callback during play (position update)
  85. 'onposition': null, // object containing times and function callbacks for positions of interest
  86. 'onstop': null, // callback for "user stop"
  87. 'onfailure': null, // callback function for when playing fails
  88. 'onfinish': null, // callback function for "sound finished playing"
  89. 'multiShot': true, // let sounds "restart" or layer on top of each other when played multiple times, rather than one-shot/one at a time
  90. 'multiShotEvents': false, // fire multiple sound events (currently onfinish() only) when multiShot is enabled
  91. 'position': null, // offset (milliseconds) to seek to within loaded sound data.
  92. 'pan': 0, // "pan" settings, left-to-right, -100 to 100
  93. 'stream': true, // allows playing before entire file has loaded (recommended)
  94. 'to': null, // position to end playback within a sound (msec), default = end
  95. 'type': null, // MIME-like hint for file pattern / canPlay() tests, eg. audio/mp3
  96. 'usePolicyFile': false, // enable crossdomain.xml request for audio on remote domains (for ID3/waveform access)
  97. 'volume': 100 // self-explanatory. 0-100, the latter being the max.
  98. };
  99. this.flash9Options = {
  100. /**
  101. * flash 9-only options,
  102. * merged into defaultOptions if flash 9 is being used
  103. */
  104. 'isMovieStar': null, // "MovieStar" MPEG4 audio mode. Null (default) = auto detect MP4, AAC etc. based on URL. true = force on, ignore URL
  105. 'usePeakData': false, // enable left/right channel peak (level) data
  106. 'useWaveformData': false, // enable sound spectrum (raw waveform data) - NOTE: May increase CPU load.
  107. 'useEQData': false, // enable sound EQ (frequency spectrum data) - NOTE: May increase CPU load.
  108. 'onbufferchange': null, // callback for "isBuffering" property change
  109. 'ondataerror': null // callback for waveform/eq data access error (flash playing audio in other tabs/domains)
  110. };
  111. this.movieStarOptions = {
  112. /**
  113. * flash 9.0r115+ MPEG4 audio options,
  114. * merged into defaultOptions if flash 9+movieStar mode is enabled
  115. */
  116. 'bufferTime': 3, // seconds of data to buffer before playback begins (null = flash default of 0.1 seconds - if AAC playback is gappy, try increasing.)
  117. 'serverURL': null, // rtmp: FMS or FMIS server to connect to, required when requesting media via RTMP or one of its variants
  118. 'onconnect': null, // rtmp: callback for connection to flash media server
  119. 'duration': null // rtmp: song duration (msec)
  120. };
  121. this.audioFormats = {
  122. /**
  123. * determines HTML5 support + flash requirements.
  124. * if no support (via flash and/or HTML5) for a "required" format, SM2 will fail to start.
  125. * flash fallback is used for MP3 or MP4 if HTML5 can't play it (or if preferFlash = true)
  126. */
  127. 'mp3': {
  128. 'type': ['audio/mpeg; codecs="mp3"', 'audio/mpeg', 'audio/mp3', 'audio/MPA', 'audio/mpa-robust'],
  129. 'required': true
  130. },
  131. 'mp4': {
  132. 'related': ['aac','m4a','m4b'], // additional formats under the MP4 container
  133. 'type': ['audio/mp4; codecs="mp4a.40.2"', 'audio/aac', 'audio/x-m4a', 'audio/MP4A-LATM', 'audio/mpeg4-generic'],
  134. 'required': false
  135. },
  136. 'ogg': {
  137. 'type': ['audio/ogg; codecs=vorbis'],
  138. 'required': false
  139. },
  140. 'opus': {
  141. 'type': ['audio/ogg; codecs=opus', 'audio/opus'],
  142. 'required': false
  143. },
  144. 'wav': {
  145. 'type': ['audio/wav; codecs="1"', 'audio/wav', 'audio/wave', 'audio/x-wav'],
  146. 'required': false
  147. }
  148. };
  149. // HTML attributes (id + class names) for the SWF container
  150. this.movieID = 'sm2-container';
  151. this.id = (smID || 'sm2movie');
  152. this.debugID = 'soundmanager-debug';
  153. this.debugURLParam = /([#?&])debug=1/i;
  154. // dynamic attributes
  155. this.versionNumber = 'V2.97a.20130324';
  156. this.version = null;
  157. this.movieURL = null;
  158. this.altURL = null;
  159. this.swfLoaded = false;
  160. this.enabled = false;
  161. this.oMC = null;
  162. this.sounds = {};
  163. this.soundIDs = [];
  164. this.muted = false;
  165. this.didFlashBlock = false;
  166. this.filePattern = null;
  167. this.filePatterns = {
  168. 'flash8': /\.mp3(\?.*)?$/i,
  169. 'flash9': /\.mp3(\?.*)?$/i
  170. };
  171. // support indicators, set at init
  172. this.features = {
  173. 'buffering': false,
  174. 'peakData': false,
  175. 'waveformData': false,
  176. 'eqData': false,
  177. 'movieStar': false
  178. };
  179. // flash sandbox info, used primarily in troubleshooting
  180. this.sandbox = {
  181. // <d>
  182. 'type': null,
  183. 'types': {
  184. 'remote': 'remote (domain-based) rules',
  185. 'localWithFile': 'local with file access (no internet access)',
  186. 'localWithNetwork': 'local with network (internet access only, no local access)',
  187. 'localTrusted': 'local, trusted (local+internet access)'
  188. },
  189. 'description': null,
  190. 'noRemote': null,
  191. 'noLocal': null
  192. // </d>
  193. };
  194. /**
  195. * format support (html5/flash)
  196. * stores canPlayType() results based on audioFormats.
  197. * eg. { mp3: boolean, mp4: boolean }
  198. * treat as read-only.
  199. */
  200. this.html5 = {
  201. 'usingFlash': null // set if/when flash fallback is needed
  202. };
  203. // file type support hash
  204. this.flash = {};
  205. // determined at init time
  206. this.html5Only = false;
  207. // used for special cases (eg. iPad/iPhone/palm OS?)
  208. this.ignoreFlash = false;
  209. /**
  210. * a few private internals (OK, a lot. :D)
  211. */
  212. var SMSound,
  213. sm2 = this, globalHTML5Audio = null, flash = null, sm = 'soundManager', smc = sm + ': ', h5 = 'HTML5::', id, ua = navigator.userAgent, wl = window.location.href.toString(), doc = document, doNothing, setProperties, init, fV, on_queue = [], debugOpen = true, debugTS, didAppend = false, appendSuccess = false, didInit = false, disabled = false, windowLoaded = false, _wDS, wdCount = 0, initComplete, mixin, assign, extraOptions, addOnEvent, processOnEvents, initUserOnload, delayWaitForEI, waitForEI, setVersionInfo, handleFocus, strings, initMovie, preInit, domContentLoaded, winOnLoad, didDCLoaded, getDocument, createMovie, catchError, setPolling, initDebug, debugLevels = ['log', 'info', 'warn', 'error'], defaultFlashVersion = 8, disableObject, failSafely, normalizeMovieURL, oRemoved = null, oRemovedHTML = null, str, flashBlockHandler, getSWFCSS, swfCSS, toggleDebug, loopFix, policyFix, complain, idCheck, waitingForEI = false, initPending = false, startTimer, stopTimer, timerExecute, h5TimerCount = 0, h5IntervalTimer = null, parseURL, messages = [],
  214. needsFlash = null, featureCheck, html5OK, html5CanPlay, html5Ext, html5Unload, domContentLoadedIE, testHTML5, event, slice = Array.prototype.slice, useGlobalHTML5Audio = false, lastGlobalHTML5URL, hasFlash, detectFlash, badSafariFix, html5_events, showSupport, flushMessages, wrapCallback,
  215. is_iDevice = ua.match(/(ipad|iphone|ipod)/i), isAndroid = ua.match(/android/i), isIE = ua.match(/msie/i), isWebkit = ua.match(/webkit/i), isSafari = (ua.match(/safari/i) && !ua.match(/chrome/i)), isOpera = (ua.match(/opera/i)),
  216. mobileHTML5 = (ua.match(/(mobile|pre\/|xoom)/i) || is_iDevice || isAndroid),
  217. isBadSafari = (!wl.match(/usehtml5audio/i) && !wl.match(/sm2\-ignorebadua/i) && isSafari && !ua.match(/silk/i) && ua.match(/OS X 10_6_([3-7])/i)), // Safari 4 and 5 (excluding Kindle Fire, "Silk") occasionally fail to load/play HTML5 audio on Snow Leopard 10.6.3 through 10.6.7 due to bug(s) in QuickTime X and/or other underlying frameworks. :/ Confirmed bug. https://bugs.webkit.org/show_bug.cgi?id=32159
  218. hasConsole = (window.console !== _undefined && console.log !== _undefined), isFocused = (doc.hasFocus !== _undefined?doc.hasFocus():null), tryInitOnFocus = (isSafari && (doc.hasFocus === _undefined || !doc.hasFocus())), okToDisable = !tryInitOnFocus, flashMIME = /(mp3|mp4|mpa|m4a|m4b)/i,
  219. emptyURL = 'about:blank', // safe URL to unload, or load nothing from (flash 8 + most HTML5 UAs)
  220. overHTTP = (doc.location?doc.location.protocol.match(/http/i):null),
  221. http = (!overHTTP ? 'http:/'+'/' : ''),
  222. // mp3, mp4, aac etc.
  223. netStreamMimeTypes = /^\s*audio\/(?:x-)?(?:mpeg4|aac|flv|mov|mp4||m4v|m4a|m4b|mp4v|3gp|3g2)\s*(?:$|;)/i,
  224. // Flash v9.0r115+ "moviestar" formats
  225. netStreamTypes = ['mpeg4', 'aac', 'flv', 'mov', 'mp4', 'm4v', 'f4v', 'm4a', 'm4b', 'mp4v', '3gp', '3g2'],
  226. netStreamPattern = new RegExp('\\.(' + netStreamTypes.join('|') + ')(\\?.*)?$', 'i');
  227. this.mimePattern = /^\s*audio\/(?:x-)?(?:mp(?:eg|3))\s*(?:$|;)/i; // default mp3 set
  228. // use altURL if not "online"
  229. this.useAltURL = !overHTTP;
  230. swfCSS = {
  231. 'swfBox': 'sm2-object-box',
  232. 'swfDefault': 'movieContainer',
  233. 'swfError': 'swf_error', // SWF loaded, but SM2 couldn't start (other error)
  234. 'swfTimedout': 'swf_timedout',
  235. 'swfLoaded': 'swf_loaded',
  236. 'swfUnblocked': 'swf_unblocked', // or loaded OK
  237. 'sm2Debug': 'sm2_debug',
  238. 'highPerf': 'high_performance',
  239. 'flashDebug': 'flash_debug'
  240. };
  241. /**
  242. * basic HTML5 Audio() support test
  243. * try...catch because of IE 9 "not implemented" nonsense
  244. * https://github.com/Modernizr/Modernizr/issues/224
  245. */
  246. this.hasHTML5 = (function() {
  247. try {
  248. // new Audio(null) for stupid Opera 9.64 case, which throws not_enough_arguments exception otherwise.
  249. return (Audio !== _undefined && (isOpera && opera !== _undefined && opera.version() < 10 ? new Audio(null) : new Audio()).canPlayType !== _undefined);
  250. } catch(e) {
  251. return false;
  252. }
  253. }());
  254. /**
  255. * Public SoundManager API
  256. * -----------------------
  257. */
  258. /**
  259. * Configures top-level soundManager properties.
  260. *
  261. * @param {object} options Option parameters, eg. { flashVersion: 9, url: '/path/to/swfs/' }
  262. * onready and ontimeout are also accepted parameters. call soundManager.setup() to see the full list.
  263. */
  264. this.setup = function(options) {
  265. var noURL = (!sm2.url);
  266. // warn if flash options have already been applied
  267. if (options !== _undefined && didInit && needsFlash && sm2.ok() && (options.flashVersion !== _undefined || options.url !== _undefined || options.html5Test !== _undefined)) {
  268. complain(str('setupLate'));
  269. }
  270. // TODO: defer: true?
  271. assign(options);
  272. // special case 1: "Late setup". SM2 loaded normally, but user didn't assign flash URL eg., setup({url:...}) before SM2 init. Treat as delayed init.
  273. if (noURL && didDCLoaded && options.url !== _undefined) {
  274. sm2.beginDelayedInit();
  275. }
  276. // special case 2: If lazy-loading SM2 (DOMContentLoaded has already happened) and user calls setup() with url: parameter, try to init ASAP.
  277. if (!didDCLoaded && options.url !== _undefined && doc.readyState === 'complete') {
  278. setTimeout(domContentLoaded, 1);
  279. }
  280. return sm2;
  281. };
  282. this.ok = function() {
  283. return (needsFlash?(didInit && !disabled):(sm2.useHTML5Audio && sm2.hasHTML5));
  284. };
  285. this.supported = this.ok; // legacy
  286. this.getMovie = function(smID) {
  287. // safety net: some old browsers differ on SWF references, possibly related to ExternalInterface / flash version
  288. return id(smID) || doc[smID] || window[smID];
  289. };
  290. /**
  291. * Creates a SMSound sound object instance.
  292. *
  293. * @param {object} oOptions Sound options (at minimum, id and url parameters are required.)
  294. * @return {object} SMSound The new SMSound object.
  295. */
  296. this.createSound = function(oOptions, _url) {
  297. var cs, cs_string, options, oSound = null;
  298. // <d>
  299. cs = sm + '.createSound(): ';
  300. cs_string = cs + str(!didInit?'notReady':'notOK');
  301. // </d>
  302. if (!didInit || !sm2.ok()) {
  303. complain(cs_string);
  304. return false;
  305. }
  306. if (_url !== _undefined) {
  307. // function overloading in JS! :) ..assume simple createSound(id,url) use case
  308. oOptions = {
  309. 'id': oOptions,
  310. 'url': _url
  311. };
  312. }
  313. // inherit from defaultOptions
  314. options = mixin(oOptions);
  315. options.url = parseURL(options.url);
  316. // <d>
  317. if (options.id.toString().charAt(0).match(/^[0-9]$/)) {
  318. sm2._wD(cs + str('badID', options.id), 2);
  319. }
  320. sm2._wD(cs + options.id + ' (' + options.url + ')', 1);
  321. // </d>
  322. if (idCheck(options.id, true)) {
  323. sm2._wD(cs + options.id + ' exists', 1);
  324. return sm2.sounds[options.id];
  325. }
  326. function make() {
  327. options = loopFix(options);
  328. sm2.sounds[options.id] = new SMSound(options);
  329. sm2.soundIDs.push(options.id);
  330. return sm2.sounds[options.id];
  331. }
  332. if (html5OK(options)) {
  333. oSound = make();
  334. sm2._wD(options.id + ': Using HTML5');
  335. oSound._setup_html5(options);
  336. } else {
  337. if (fV > 8) {
  338. if (options.isMovieStar === null) {
  339. // attempt to detect MPEG-4 formats
  340. options.isMovieStar = !!(options.serverURL || (options.type ? options.type.match(netStreamMimeTypes) : false) || options.url.match(netStreamPattern));
  341. }
  342. // <d>
  343. if (options.isMovieStar) {
  344. sm2._wD(cs + 'using MovieStar handling');
  345. if (options.loops > 1) {
  346. _wDS('noNSLoop');
  347. }
  348. }
  349. // </d>
  350. }
  351. options = policyFix(options, cs);
  352. oSound = make();
  353. if (fV === 8) {
  354. flash._createSound(options.id, options.loops||1, options.usePolicyFile);
  355. } else {
  356. flash._createSound(options.id, options.url, options.usePeakData, options.useWaveformData, options.useEQData, options.isMovieStar, (options.isMovieStar?options.bufferTime:false), options.loops||1, options.serverURL, options.duration||null, options.autoPlay, true, options.autoLoad, options.usePolicyFile);
  357. if (!options.serverURL) {
  358. // We are connected immediately
  359. oSound.connected = true;
  360. if (options.onconnect) {
  361. options.onconnect.apply(oSound);
  362. }
  363. }
  364. }
  365. if (!options.serverURL && (options.autoLoad || options.autoPlay)) {
  366. // call load for non-rtmp streams
  367. oSound.load(options);
  368. }
  369. }
  370. // rtmp will play in onconnect
  371. if (!options.serverURL && options.autoPlay) {
  372. oSound.play();
  373. }
  374. return oSound;
  375. };
  376. /**
  377. * Destroys a SMSound sound object instance.
  378. *
  379. * @param {string} sID The ID of the sound to destroy
  380. */
  381. this.destroySound = function(sID, _bFromSound) {
  382. // explicitly destroy a sound before normal page unload, etc.
  383. if (!idCheck(sID)) {
  384. return false;
  385. }
  386. var oS = sm2.sounds[sID], i;
  387. // Disable all callbacks while the sound is being destroyed
  388. oS._iO = {};
  389. oS.stop();
  390. oS.unload();
  391. for (i = 0; i < sm2.soundIDs.length; i++) {
  392. if (sm2.soundIDs[i] === sID) {
  393. sm2.soundIDs.splice(i, 1);
  394. break;
  395. }
  396. }
  397. if (!_bFromSound) {
  398. // ignore if being called from SMSound instance
  399. oS.destruct(true);
  400. }
  401. oS = null;
  402. delete sm2.sounds[sID];
  403. return true;
  404. };
  405. /**
  406. * Calls the load() method of a SMSound object by ID.
  407. *
  408. * @param {string} sID The ID of the sound
  409. * @param {object} oOptions Optional: Sound options
  410. */
  411. this.load = function(sID, oOptions) {
  412. if (!idCheck(sID)) {
  413. return false;
  414. }
  415. return sm2.sounds[sID].load(oOptions);
  416. };
  417. /**
  418. * Calls the unload() method of a SMSound object by ID.
  419. *
  420. * @param {string} sID The ID of the sound
  421. */
  422. this.unload = function(sID) {
  423. if (!idCheck(sID)) {
  424. return false;
  425. }
  426. return sm2.sounds[sID].unload();
  427. };
  428. /**
  429. * Calls the onPosition() method of a SMSound object by ID.
  430. *
  431. * @param {string} sID The ID of the sound
  432. * @param {number} nPosition The position to watch for
  433. * @param {function} oMethod The relevant callback to fire
  434. * @param {object} oScope Optional: The scope to apply the callback to
  435. * @return {SMSound} The SMSound object
  436. */
  437. this.onPosition = function(sID, nPosition, oMethod, oScope) {
  438. if (!idCheck(sID)) {
  439. return false;
  440. }
  441. return sm2.sounds[sID].onposition(nPosition, oMethod, oScope);
  442. };
  443. // legacy/backwards-compability: lower-case method name
  444. this.onposition = this.onPosition;
  445. /**
  446. * Calls the clearOnPosition() method of a SMSound object by ID.
  447. *
  448. * @param {string} sID The ID of the sound
  449. * @param {number} nPosition The position to watch for
  450. * @param {function} oMethod Optional: The relevant callback to fire
  451. * @return {SMSound} The SMSound object
  452. */
  453. this.clearOnPosition = function(sID, nPosition, oMethod) {
  454. if (!idCheck(sID)) {
  455. return false;
  456. }
  457. return sm2.sounds[sID].clearOnPosition(nPosition, oMethod);
  458. };
  459. /**
  460. * Calls the play() method of a SMSound object by ID.
  461. *
  462. * @param {string} sID The ID of the sound
  463. * @param {object} oOptions Optional: Sound options
  464. * @return {SMSound} The SMSound object
  465. */
  466. this.play = function(sID, oOptions) {
  467. var result = false;
  468. if (!didInit || !sm2.ok()) {
  469. complain(sm + '.play(): ' + str(!didInit?'notReady':'notOK'));
  470. return result;
  471. }
  472. if (!idCheck(sID)) {
  473. if (!(oOptions instanceof Object)) {
  474. // overloading use case: play('mySound','/path/to/some.mp3');
  475. oOptions = {
  476. url: oOptions
  477. };
  478. }
  479. if (oOptions && oOptions.url) {
  480. // overloading use case, create+play: .play('someID',{url:'/path/to.mp3'});
  481. sm2._wD(sm + '.play(): attempting to create "' + sID + '"', 1);
  482. oOptions.id = sID;
  483. result = sm2.createSound(oOptions).play();
  484. }
  485. return result;
  486. }
  487. return sm2.sounds[sID].play(oOptions);
  488. };
  489. this.start = this.play; // just for convenience
  490. /**
  491. * Calls the setPosition() method of a SMSound object by ID.
  492. *
  493. * @param {string} sID The ID of the sound
  494. * @param {number} nMsecOffset Position (milliseconds)
  495. * @return {SMSound} The SMSound object
  496. */
  497. this.setPosition = function(sID, nMsecOffset) {
  498. if (!idCheck(sID)) {
  499. return false;
  500. }
  501. return sm2.sounds[sID].setPosition(nMsecOffset);
  502. };
  503. /**
  504. * Calls the stop() method of a SMSound object by ID.
  505. *
  506. * @param {string} sID The ID of the sound
  507. * @return {SMSound} The SMSound object
  508. */
  509. this.stop = function(sID) {
  510. if (!idCheck(sID)) {
  511. return false;
  512. }
  513. sm2._wD(sm + '.stop(' + sID + ')', 1);
  514. return sm2.sounds[sID].stop();
  515. };
  516. /**
  517. * Stops all currently-playing sounds.
  518. */
  519. this.stopAll = function() {
  520. var oSound;
  521. sm2._wD(sm + '.stopAll()', 1);
  522. for (oSound in sm2.sounds) {
  523. if (sm2.sounds.hasOwnProperty(oSound)) {
  524. // apply only to sound objects
  525. sm2.sounds[oSound].stop();
  526. }
  527. }
  528. };
  529. /**
  530. * Calls the pause() method of a SMSound object by ID.
  531. *
  532. * @param {string} sID The ID of the sound
  533. * @return {SMSound} The SMSound object
  534. */
  535. this.pause = function(sID) {
  536. if (!idCheck(sID)) {
  537. return false;
  538. }
  539. return sm2.sounds[sID].pause();
  540. };
  541. /**
  542. * Pauses all currently-playing sounds.
  543. */
  544. this.pauseAll = function() {
  545. var i;
  546. for (i = sm2.soundIDs.length-1; i >= 0; i--) {
  547. sm2.sounds[sm2.soundIDs[i]].pause();
  548. }
  549. };
  550. /**
  551. * Calls the resume() method of a SMSound object by ID.
  552. *
  553. * @param {string} sID The ID of the sound
  554. * @return {SMSound} The SMSound object
  555. */
  556. this.resume = function(sID) {
  557. if (!idCheck(sID)) {
  558. return false;
  559. }
  560. return sm2.sounds[sID].resume();
  561. };
  562. /**
  563. * Resumes all currently-paused sounds.
  564. */
  565. this.resumeAll = function() {
  566. var i;
  567. for (i = sm2.soundIDs.length-1; i >= 0; i--) {
  568. sm2.sounds[sm2.soundIDs[i]].resume();
  569. }
  570. };
  571. /**
  572. * Calls the togglePause() method of a SMSound object by ID.
  573. *
  574. * @param {string} sID The ID of the sound
  575. * @return {SMSound} The SMSound object
  576. */
  577. this.togglePause = function(sID) {
  578. if (!idCheck(sID)) {
  579. return false;
  580. }
  581. return sm2.sounds[sID].togglePause();
  582. };
  583. /**
  584. * Calls the setPan() method of a SMSound object by ID.
  585. *
  586. * @param {string} sID The ID of the sound
  587. * @param {number} nPan The pan value (-100 to 100)
  588. * @return {SMSound} The SMSound object
  589. */
  590. this.setPan = function(sID, nPan) {
  591. if (!idCheck(sID)) {
  592. return false;
  593. }
  594. return sm2.sounds[sID].setPan(nPan);
  595. };
  596. /**
  597. * Calls the setVolume() method of a SMSound object by ID.
  598. *
  599. * @param {string} sID The ID of the sound
  600. * @param {number} nVol The volume value (0 to 100)
  601. * @return {SMSound} The SMSound object
  602. */
  603. this.setVolume = function(sID, nVol) {
  604. if (!idCheck(sID)) {
  605. return false;
  606. }
  607. return sm2.sounds[sID].setVolume(nVol);
  608. };
  609. /**
  610. * Calls the mute() method of either a single SMSound object by ID, or all sound objects.
  611. *
  612. * @param {string} sID Optional: The ID of the sound (if omitted, all sounds will be used.)
  613. */
  614. this.mute = function(sID) {
  615. var i = 0;
  616. if (sID instanceof String) {
  617. sID = null;
  618. }
  619. if (!sID) {
  620. sm2._wD(sm + '.mute(): Muting all sounds');
  621. for (i = sm2.soundIDs.length-1; i >= 0; i--) {
  622. sm2.sounds[sm2.soundIDs[i]].mute();
  623. }
  624. sm2.muted = true;
  625. } else {
  626. if (!idCheck(sID)) {
  627. return false;
  628. }
  629. sm2._wD(sm + '.mute(): Muting "' + sID + '"');
  630. return sm2.sounds[sID].mute();
  631. }
  632. return true;
  633. };
  634. /**
  635. * Mutes all sounds.
  636. */
  637. this.muteAll = function() {
  638. sm2.mute();
  639. };
  640. /**
  641. * Calls the unmute() method of either a single SMSound object by ID, or all sound objects.
  642. *
  643. * @param {string} sID Optional: The ID of the sound (if omitted, all sounds will be used.)
  644. */
  645. this.unmute = function(sID) {
  646. var i;
  647. if (sID instanceof String) {
  648. sID = null;
  649. }
  650. if (!sID) {
  651. sm2._wD(sm + '.unmute(): Unmuting all sounds');
  652. for (i = sm2.soundIDs.length-1; i >= 0; i--) {
  653. sm2.sounds[sm2.soundIDs[i]].unmute();
  654. }
  655. sm2.muted = false;
  656. } else {
  657. if (!idCheck(sID)) {
  658. return false;
  659. }
  660. sm2._wD(sm + '.unmute(): Unmuting "' + sID + '"');
  661. return sm2.sounds[sID].unmute();
  662. }
  663. return true;
  664. };
  665. /**
  666. * Unmutes all sounds.
  667. */
  668. this.unmuteAll = function() {
  669. sm2.unmute();
  670. };
  671. /**
  672. * Calls the toggleMute() method of a SMSound object by ID.
  673. *
  674. * @param {string} sID The ID of the sound
  675. * @return {SMSound} The SMSound object
  676. */
  677. this.toggleMute = function(sID) {
  678. if (!idCheck(sID)) {
  679. return false;
  680. }
  681. return sm2.sounds[sID].toggleMute();
  682. };
  683. /**
  684. * Retrieves the memory used by the flash plugin.
  685. *
  686. * @return {number} The amount of memory in use
  687. */
  688. this.getMemoryUse = function() {
  689. // flash-only
  690. var ram = 0;
  691. if (flash && fV !== 8) {
  692. ram = parseInt(flash._getMemoryUse(), 10);
  693. }
  694. return ram;
  695. };
  696. /**
  697. * Undocumented: NOPs soundManager and all SMSound objects.
  698. */
  699. this.disable = function(bNoDisable) {
  700. // destroy all functions
  701. var i;
  702. if (bNoDisable === _undefined) {
  703. bNoDisable = false;
  704. }
  705. if (disabled) {
  706. return false;
  707. }
  708. disabled = true;
  709. _wDS('shutdown', 1);
  710. for (i = sm2.soundIDs.length-1; i >= 0; i--) {
  711. disableObject(sm2.sounds[sm2.soundIDs[i]]);
  712. }
  713. // fire "complete", despite fail
  714. initComplete(bNoDisable);
  715. event.remove(window, 'load', initUserOnload);
  716. return true;
  717. };
  718. /**
  719. * Determines playability of a MIME type, eg. 'audio/mp3'.
  720. */
  721. this.canPlayMIME = function(sMIME) {
  722. var result;
  723. if (sm2.hasHTML5) {
  724. result = html5CanPlay({type:sMIME});
  725. }
  726. if (!result && needsFlash) {
  727. // if flash 9, test netStream (movieStar) types as well.
  728. result = (sMIME && sm2.ok() ? !!((fV > 8 ? sMIME.match(netStreamMimeTypes) : null) || sMIME.match(sm2.mimePattern)) : null);
  729. }
  730. return result;
  731. };
  732. /**
  733. * Determines playability of a URL based on audio support.
  734. *
  735. * @param {string} sURL The URL to test
  736. * @return {boolean} URL playability
  737. */
  738. this.canPlayURL = function(sURL) {
  739. var result;
  740. if (sm2.hasHTML5) {
  741. result = html5CanPlay({url: sURL});
  742. }
  743. if (!result && needsFlash) {
  744. result = (sURL && sm2.ok() ? !!(sURL.match(sm2.filePattern)) : null);
  745. }
  746. return result;
  747. };
  748. /**
  749. * Determines playability of an HTML DOM &lt;a&gt; object (or similar object literal) based on audio support.
  750. *
  751. * @param {object} oLink an HTML DOM &lt;a&gt; object or object literal including href and/or type attributes
  752. * @return {boolean} URL playability
  753. */
  754. this.canPlayLink = function(oLink) {
  755. if (oLink.type !== _undefined && oLink.type) {
  756. if (sm2.canPlayMIME(oLink.type)) {
  757. return true;
  758. }
  759. }
  760. return sm2.canPlayURL(oLink.href);
  761. };
  762. /**
  763. * Retrieves a SMSound object by ID.
  764. *
  765. * @param {string} sID The ID of the sound
  766. * @return {SMSound} The SMSound object
  767. */
  768. this.getSoundById = function(sID, _suppressDebug) {
  769. if (!sID) {
  770. throw new Error(sm + '.getSoundById(): sID is null/_undefined');
  771. }
  772. var result = sm2.sounds[sID];
  773. // <d>
  774. if (!result && !_suppressDebug) {
  775. sm2._wD('"' + sID + '" is an invalid sound ID.', 2);
  776. }
  777. // </d>
  778. return result;
  779. };
  780. /**
  781. * Queues a callback for execution when SoundManager has successfully initialized.
  782. *
  783. * @param {function} oMethod The callback method to fire
  784. * @param {object} oScope Optional: The scope to apply to the callback
  785. */
  786. this.onready = function(oMethod, oScope) {
  787. var sType = 'onready',
  788. result = false;
  789. if (typeof oMethod === 'function') {
  790. // <d>
  791. if (didInit) {
  792. sm2._wD(str('queue', sType));
  793. }
  794. // </d>
  795. if (!oScope) {
  796. oScope = window;
  797. }
  798. addOnEvent(sType, oMethod, oScope);
  799. processOnEvents();
  800. result = true;
  801. } else {
  802. throw str('needFunction', sType);
  803. }
  804. return result;
  805. };
  806. /**
  807. * Queues a callback for execution when SoundManager has failed to initialize.
  808. *
  809. * @param {function} oMethod The callback method to fire
  810. * @param {object} oScope Optional: The scope to apply to the callback
  811. */
  812. this.ontimeout = function(oMethod, oScope) {
  813. var sType = 'ontimeout',
  814. result = false;
  815. if (typeof oMethod === 'function') {
  816. // <d>
  817. if (didInit) {
  818. sm2._wD(str('queue', sType));
  819. }
  820. // </d>
  821. if (!oScope) {
  822. oScope = window;
  823. }
  824. addOnEvent(sType, oMethod, oScope);
  825. processOnEvents({type:sType});
  826. result = true;
  827. } else {
  828. throw str('needFunction', sType);
  829. }
  830. return result;
  831. };
  832. /**
  833. * Writes console.log()-style debug output to a console or in-browser element.
  834. * Applies when debugMode = true
  835. *
  836. * @param {string} sText The console message
  837. * @param {object} nType Optional log level (number), or object. Number case: Log type/style where 0 = 'info', 1 = 'warn', 2 = 'error'. Object case: Object to be dumped.
  838. */
  839. this._writeDebug = function(sText, sTypeOrObject) {
  840. // pseudo-private console.log()-style output
  841. // <d>
  842. var sDID = 'soundmanager-debug', o, oItem;
  843. if (!sm2.debugMode) {
  844. return false;
  845. }
  846. if (hasConsole && sm2.useConsole) {
  847. if (sTypeOrObject && typeof sTypeOrObject === 'object') {
  848. // object passed; dump to console.
  849. console.log(sText, sTypeOrObject);
  850. } else if (debugLevels[sTypeOrObject] !== _undefined) {
  851. console[debugLevels[sTypeOrObject]](sText);
  852. } else {
  853. console.log(sText);
  854. }
  855. if (sm2.consoleOnly) {
  856. return true;
  857. }
  858. }
  859. o = id(sDID);
  860. if (!o) {
  861. return false;
  862. }
  863. oItem = doc.createElement('div');
  864. if (++wdCount % 2 === 0) {
  865. oItem.className = 'sm2-alt';
  866. }
  867. if (sTypeOrObject === _undefined) {
  868. sTypeOrObject = 0;
  869. } else {
  870. sTypeOrObject = parseInt(sTypeOrObject, 10);
  871. }
  872. oItem.appendChild(doc.createTextNode(sText));
  873. if (sTypeOrObject) {
  874. if (sTypeOrObject >= 2) {
  875. oItem.style.fontWeight = 'bold';
  876. }
  877. if (sTypeOrObject === 3) {
  878. oItem.style.color = '#ff3333';
  879. }
  880. }
  881. // top-to-bottom
  882. // o.appendChild(oItem);
  883. // bottom-to-top
  884. o.insertBefore(oItem, o.firstChild);
  885. o = null;
  886. // </d>
  887. return true;
  888. };
  889. // <d>
  890. // last-resort debugging option
  891. if (wl.indexOf('sm2-debug=alert') !== -1) {
  892. this._writeDebug = function(sText) {
  893. window.alert(sText);
  894. };
  895. }
  896. // </d>
  897. // alias
  898. this._wD = this._writeDebug;
  899. /**
  900. * Provides debug / state information on all SMSound objects.
  901. */
  902. this._debug = function() {
  903. // <d>
  904. var i, j;
  905. _wDS('currentObj', 1);
  906. for (i = 0, j = sm2.soundIDs.length; i < j; i++) {
  907. sm2.sounds[sm2.soundIDs[i]]._debug();
  908. }
  909. // </d>
  910. };
  911. /**
  912. * Restarts and re-initializes the SoundManager instance.
  913. *
  914. * @param {boolean} resetEvents Optional: When true, removes all registered onready and ontimeout event callbacks.
  915. * @param {boolean} excludeInit Options: When true, does not call beginDelayedInit() (which would restart SM2).
  916. * @return {object} soundManager The soundManager instance.
  917. */
  918. this.reboot = function(resetEvents, excludeInit) {
  919. // reset some (or all) state, and re-init unless otherwise specified.
  920. // <d>
  921. if (sm2.soundIDs.length) {
  922. sm2._wD('Destroying ' + sm2.soundIDs.length + ' SMSound objects...');
  923. }
  924. // </d>
  925. var i, j, k;
  926. for (i = sm2.soundIDs.length-1; i >= 0; i--) {
  927. sm2.sounds[sm2.soundIDs[i]].destruct();
  928. }
  929. // trash ze flash
  930. if (flash) {
  931. try {
  932. if (isIE) {
  933. oRemovedHTML = flash.innerHTML;
  934. }
  935. oRemoved = flash.parentNode.removeChild(flash);
  936. _wDS('flRemoved');
  937. } catch(e) {
  938. // Remove failed? May be due to flash blockers silently removing the SWF object/embed node from the DOM. Warn and continue.
  939. _wDS('badRemove', 2);
  940. }
  941. }
  942. // actually, force recreate of movie.
  943. oRemovedHTML = oRemoved = needsFlash = flash = null;
  944. sm2.enabled = didDCLoaded = didInit = waitingForEI = initPending = didAppend = appendSuccess = disabled = useGlobalHTML5Audio = sm2.swfLoaded = false;
  945. sm2.soundIDs = [];
  946. sm2.sounds = {};
  947. if (!resetEvents) {
  948. // reset callbacks for onready, ontimeout etc. so that they will fire again on re-init
  949. for (i in on_queue) {
  950. if (on_queue.hasOwnProperty(i)) {
  951. for (j = 0, k = on_queue[i].length; j < k; j++) {
  952. on_queue[i][j].fired = false;
  953. }
  954. }
  955. }
  956. } else {
  957. // remove all callbacks entirely
  958. on_queue = [];
  959. }
  960. // <d>
  961. if (!excludeInit) {
  962. sm2._wD(sm + ': Rebooting...');
  963. }
  964. // </d>
  965. // reset HTML5 and flash canPlay test results
  966. sm2.html5 = {
  967. 'usingFlash': null
  968. };
  969. sm2.flash = {};
  970. // reset device-specific HTML/flash mode switches
  971. sm2.html5Only = false;
  972. sm2.ignoreFlash = false;
  973. window.setTimeout(function() {
  974. preInit();
  975. // by default, re-init
  976. if (!excludeInit) {
  977. sm2.beginDelayedInit();
  978. }
  979. }, 20);
  980. return sm2;
  981. };
  982. this.reset = function() {
  983. /**
  984. * Shuts down and restores the SoundManager instance to its original loaded state, without an explicit reboot. All onready/ontimeout handlers are removed.
  985. * After this call, SM2 may be re-initialized via soundManager.beginDelayedInit().
  986. * @return {object} soundManager The soundManager instance.
  987. */
  988. _wDS('reset');
  989. return sm2.reboot(true, true);
  990. };
  991. /**
  992. * Undocumented: Determines the SM2 flash movie's load progress.
  993. *
  994. * @return {number or null} Percent loaded, or if invalid/unsupported, null.
  995. */
  996. this.getMoviePercent = function() {
  997. /**
  998. * Interesting syntax notes...
  999. * Flash/ExternalInterface (ActiveX/NPAPI) bridge methods are not typeof "function" nor instanceof Function, but are still valid.
  1000. * Additionally, JSLint dislikes ('PercentLoaded' in flash)-style syntax and recommends hasOwnProperty(), which does not work in this case.
  1001. * Furthermore, using (flash && flash.PercentLoaded) causes IE to throw "object doesn't support this property or method".
  1002. * Thus, 'in' syntax must be used.
  1003. */
  1004. return (flash && 'PercentLoaded' in flash ? flash.PercentLoaded() : null); // Yes, JSLint. See nearby comment in source for explanation.
  1005. };
  1006. /**
  1007. * Additional helper for manually invoking SM2's init process after DOM Ready / window.onload().
  1008. */
  1009. this.beginDelayedInit = function() {
  1010. windowLoaded = true;
  1011. domContentLoaded();
  1012. setTimeout(function() {
  1013. if (initPending) {
  1014. return false;
  1015. }
  1016. createMovie();
  1017. initMovie();
  1018. initPending = true;
  1019. return true;
  1020. }, 20);
  1021. delayWaitForEI();
  1022. };
  1023. /**
  1024. * Destroys the SoundManager instance and all SMSound instances.
  1025. */
  1026. this.destruct = function() {
  1027. sm2._wD(sm + '.destruct()');
  1028. sm2.disable(true);
  1029. };
  1030. /**
  1031. * SMSound() (sound object) constructor
  1032. * ------------------------------------
  1033. *
  1034. * @param {object} oOptions Sound options (id and url are required attributes)
  1035. * @return {SMSound} The new SMSound object
  1036. */
  1037. SMSound = function(oOptions) {
  1038. var s = this, resetProperties, add_html5_events, remove_html5_events, stop_html5_timer, start_html5_timer, attachOnPosition, onplay_called = false, onPositionItems = [], onPositionFired = 0, detachOnPosition, applyFromTo, lastURL = null, lastHTML5State;
  1039. lastHTML5State = {
  1040. // tracks duration + position (time)
  1041. duration: null,
  1042. time: null
  1043. };
  1044. this.id = oOptions.id;
  1045. // legacy
  1046. this.sID = this.id;
  1047. this.url = oOptions.url;
  1048. this.options = mixin(oOptions);
  1049. // per-play-instance-specific options
  1050. this.instanceOptions = this.options;
  1051. // short alias
  1052. this._iO = this.instanceOptions;
  1053. // assign property defaults
  1054. this.pan = this.options.pan;
  1055. this.volume = this.options.volume;
  1056. // whether or not this object is using HTML5
  1057. this.isHTML5 = false;
  1058. // internal HTML5 Audio() object reference
  1059. this._a = null;
  1060. /**
  1061. * SMSound() public methods
  1062. * ------------------------
  1063. */
  1064. this.id3 = {};
  1065. /**
  1066. * Writes SMSound object parameters to debug console
  1067. */
  1068. this._debug = function() {
  1069. // <d>
  1070. sm2._wD(s.id + ': Merged options:', s.options);
  1071. // </d>
  1072. };
  1073. /**
  1074. * Begins loading a sound per its *url*.
  1075. *
  1076. * @param {object} oOptions Optional: Sound options
  1077. * @return {SMSound} The SMSound object
  1078. */
  1079. this.load = function(oOptions) {
  1080. var oSound = null, instanceOptions;
  1081. if (oOptions !== _undefined) {
  1082. s._iO = mixin(oOptions, s.options);
  1083. } else {
  1084. oOptions = s.options;
  1085. s._iO = oOptions;
  1086. if (lastURL && lastURL !== s.url) {
  1087. _wDS('manURL');
  1088. s._iO.url = s.url;
  1089. s.url = null;
  1090. }
  1091. }
  1092. if (!s._iO.url) {
  1093. s._iO.url = s.url;
  1094. }
  1095. s._iO.url = parseURL(s._iO.url);
  1096. // ensure we're in sync
  1097. s.instanceOptions = s._iO;
  1098. // local shortcut
  1099. instanceOptions = s._iO;
  1100. sm2._wD(s.id + ': load (' + instanceOptions.url + ')');
  1101. if (instanceOptions.url === s.url && s.readyState !== 0 && s.readyState !== 2) {
  1102. _wDS('onURL', 1);
  1103. // if loaded and an onload() exists, fire immediately.
  1104. if (s.readyState === 3 && instanceOptions.onload) {
  1105. // assume success based on truthy duration.
  1106. wrapCallback(s, function() {
  1107. instanceOptions.onload.apply(s, [(!!s.duration)]);
  1108. });
  1109. }
  1110. return s;
  1111. }
  1112. // reset a few state properties
  1113. s.loaded = false;
  1114. s.readyState = 1;
  1115. s.playState = 0;
  1116. s.id3 = {};
  1117. // TODO: If switching from HTML5 -> flash (or vice versa), stop currently-playing audio.
  1118. if (html5OK(instanceOptions)) {
  1119. oSound = s._setup_html5(instanceOptions);
  1120. if (!oSound._called_load) {
  1121. s._html5_canplay = false;
  1122. // TODO: review called_load / html5_canplay logic
  1123. // if url provided directly to load(), assign it here.
  1124. if (s.url !== instanceOptions.url) {
  1125. sm2._wD(_wDS('manURL') + ': ' + instanceOptions.url);
  1126. s._a.src = instanceOptions.url;
  1127. // TODO: review / re-apply all relevant options (volume, loop, onposition etc.)
  1128. // reset position for new URL
  1129. s.setPosition(0);
  1130. }
  1131. // given explicit load call, try to preload.
  1132. // early HTML5 implementation (non-standard)
  1133. s._a.autobuffer = 'auto';
  1134. // standard
  1135. s._a.preload = 'auto';
  1136. s._a._called_load = true;
  1137. if (instanceOptions.autoPlay) {
  1138. s.play();
  1139. }
  1140. } else {
  1141. sm2._wD(s.id + ': Ignoring request to load again');
  1142. }
  1143. } else {
  1144. try {
  1145. s.isHTML5 = false;
  1146. s._iO = policyFix(loopFix(instanceOptions));
  1147. // re-assign local shortcut
  1148. instanceOptions = s._iO;
  1149. if (fV === 8) {
  1150. flash._load(s.id, instanceOptions.url, instanceOptions.stream, instanceOptions.autoPlay, instanceOptions.usePolicyFile);
  1151. } else {
  1152. flash._load(s.id, instanceOptions.url, !!(instanceOptions.stream), !!(instanceOptions.autoPlay), instanceOptions.loops||1, !!(instanceOptions.autoLoad), instanceOptions.usePolicyFile);
  1153. }
  1154. } catch(e) {
  1155. _wDS('smError', 2);
  1156. debugTS('onload', false);
  1157. catchError({type:'SMSOUND_LOAD_JS_EXCEPTION', fatal:true});
  1158. }
  1159. }
  1160. // after all of this, ensure sound url is up to date.
  1161. s.url = instanceOptions.url;
  1162. return s;
  1163. };
  1164. /**
  1165. * Unloads a sound, canceling any open HTTP requests.
  1166. *
  1167. * @return {SMSound} The SMSound object
  1168. */
  1169. this.unload = function() {
  1170. // Flash 8/AS2 can't "close" a stream - fake it by loading an empty URL
  1171. // Flash 9/AS3: Close stream, preventing further load
  1172. // HTML5: Most UAs will use empty URL
  1173. if (s.readyState !== 0) {
  1174. sm2._wD(s.id + ': unload()');
  1175. if (!s.isHTML5) {
  1176. if (fV === 8) {
  1177. flash._unload(s.id, emptyURL);
  1178. } else {
  1179. flash._unload(s.id);
  1180. }
  1181. } else {
  1182. stop_html5_timer();
  1183. if (s._a) {
  1184. s._a.pause();
  1185. html5Unload(s._a, emptyURL);
  1186. // update empty URL, too
  1187. lastURL = emptyURL;
  1188. }
  1189. }
  1190. // reset load/status flags
  1191. resetProperties();
  1192. }
  1193. return s;
  1194. };
  1195. /**
  1196. * Unloads and destroys a sound.
  1197. */
  1198. this.destruct = function(_bFromSM) {
  1199. sm2._wD(s.id + ': Destruct');
  1200. if (!s.isHTML5) {
  1201. // kill sound within Flash
  1202. // Disable the onfailure handler
  1203. s._iO.onfailure = null;
  1204. flash._destroySound(s.id);
  1205. } else {
  1206. stop_html5_timer();
  1207. if (s._a) {
  1208. s._a.pause();
  1209. html5Unload(s._a);
  1210. if (!useGlobalHTML5Audio) {
  1211. remove_html5_events();
  1212. }
  1213. // break obvious circular reference
  1214. s._a._s = null;
  1215. s._a = null;
  1216. }
  1217. }
  1218. if (!_bFromSM) {
  1219. // ensure deletion from controller
  1220. sm2.destroySound(s.id, true);
  1221. }
  1222. };
  1223. /**
  1224. * Begins playing a sound.
  1225. *
  1226. * @param {object} oOptions Optional: Sound options
  1227. * @return {SMSound} The SMSound object
  1228. */
  1229. this.play = function(oOptions, _updatePlayState) {
  1230. var fN, allowMulti, a, onready, startOK = true,
  1231. exit = null;
  1232. // <d>
  1233. fN = s.id + ': play(): ';
  1234. // </d>
  1235. // default to true
  1236. _updatePlayState = (_updatePlayState === _undefined ? true : _updatePlayState);
  1237. if (!oOptions) {
  1238. oOptions = {};
  1239. }
  1240. // first, use local URL (if specified)
  1241. if (s.url) {
  1242. s._iO.url = s.url;
  1243. }
  1244. // mix in any options defined at createSound()
  1245. s._iO = mixin(s._iO, s.options);
  1246. // mix in any options specific to this method
  1247. s._iO = mixin(oOptions, s._iO);
  1248. s._iO.url = parseURL(s._iO.url);
  1249. s.instanceOptions = s._iO;
  1250. // RTMP-only
  1251. if (s._iO.serverURL && !s.connected) {
  1252. if (!s.getAutoPlay()) {
  1253. sm2._wD(fN +' Netstream not connected yet - setting autoPlay');
  1254. s.setAutoPlay(true);
  1255. }
  1256. // play will be called in onconnect()
  1257. return s;
  1258. }
  1259. if (html5OK(s._iO)) {
  1260. s._setup_html5(s._iO);
  1261. start_html5_timer();
  1262. }
  1263. if (s.playState === 1 && !s.paused) {
  1264. allowMulti = s._iO.multiShot;
  1265. if (!allowMulti) {
  1266. sm2._wD(fN + 'Already playing (one-shot)', 1);
  1267. exit = s;
  1268. } else {
  1269. sm2._wD(fN + 'Already playing (multi-shot)', 1);
  1270. }
  1271. }
  1272. if (exit !== null) {
  1273. return exit;
  1274. }
  1275. // edge case: play() with explicit URL parameter
  1276. if (oOptions.url && oOptions.url !== s.url) {
  1277. // load using merged options
  1278. s.load(s._iO);
  1279. }
  1280. if (!s.loaded) {
  1281. if (s.readyState === 0) {
  1282. sm2._wD(fN + 'Attempting to load');
  1283. // try to get this sound playing ASAP
  1284. if (!s.isHTML5) {
  1285. // assign directly because setAutoPlay() increments the instanceCount
  1286. s._iO.autoPlay = true;
  1287. s.load(s._iO);
  1288. } else {
  1289. // iOS needs this when recycling sounds, loading a new URL on an existing object.
  1290. s.load(s._iO);
  1291. }
  1292. // HTML5 hack - re-set instanceOptions?
  1293. s.instanceOptions = s._iO;
  1294. } else if (s.readyState === 2) {
  1295. sm2._wD(fN + 'Could not load - exiting', 2);
  1296. exit = s;
  1297. } else {
  1298. sm2._wD(fN + 'Loading - attempting to play...');
  1299. }
  1300. } else {
  1301. // "play()"
  1302. sm2._wD(fN.substr(0, fN.lastIndexOf(':')));
  1303. }
  1304. if (exit !== null) {
  1305. return exit;
  1306. }
  1307. if (!s.isHTML5 && fV === 9 && s.position > 0 && s.position === s.duration) {
  1308. // flash 9 needs a position reset if play() is called while at the end of a sound.
  1309. sm2._wD(fN + 'Sound at end, resetting to position:0');
  1310. oOptions.position = 0;
  1311. }
  1312. /**
  1313. * Streams will pause when their buffer is full if they are being loaded.
  1314. * In this case paused is true, but the song hasn't started playing yet.
  1315. * If we just call resume() the onplay() callback will never be called.
  1316. * So only call resume() if the position is > 0.
  1317. * Another reason is because options like volume won't have been applied yet.
  1318. * For normal sounds, just resume.
  1319. */
  1320. if (s.paused && s.position >= 0 && (!s._iO.serverURL || s.position > 0)) {
  1321. // https://gist.github.com/37b17df75cc4d7a90bf6
  1322. sm2._wD(fN + 'Resuming from paused state', 1);
  1323. s.resume();
  1324. } else {
  1325. s._iO = mixin(oOptions, s._iO);
  1326. // apply from/to parameters, if they exist (and not using RTMP)
  1327. if (s._iO.from !== null && s._iO.to !== null && s.instanceCount === 0 && s.playState === 0 && !s._iO.serverURL) {
  1328. onready = function() {
  1329. // sound "canplay" or onload()
  1330. // re-apply from/to to instance options, and start playback
  1331. s._iO = mixin(oOptions, s._iO);
  1332. s.play(s._iO);
  1333. };
  1334. // HTML5 needs to at least have "canplay" fired before seeking.
  1335. if (s.isHTML5 && !s._html5_canplay) {
  1336. // this hasn't been loaded yet. load it first, and then do this again.
  1337. sm2._wD(fN + 'Beginning load for from/to case');
  1338. s.load({
  1339. // TODO: was _oncanplay. Sounds wrong.
  1340. oncanplay: onready
  1341. });
  1342. exit = false;
  1343. } else if (!s.isHTML5 && !s.loaded && (!s.readyState || s.readyState !== 2)) {
  1344. // to be safe, preload the whole thing in Flash.
  1345. sm2._wD(fN + 'Preloading for from/to case');
  1346. s.load({
  1347. onload: onready
  1348. });
  1349. exit = false;
  1350. }
  1351. if (exit !== null) {
  1352. return exit;
  1353. }
  1354. // otherwise, we're ready to go. re-apply local options, and continue
  1355. s._iO = applyFromTo();
  1356. }
  1357. sm2._wD(fN + 'Starting to play');
  1358. if (!s.instanceCount || s._iO.multiShotEvents || (!s.isHTML5 && fV > 8 && !s.getAutoPlay())) {
  1359. s.instanceCount++;
  1360. }
  1361. // if first play and onposition parameters exist, apply them now
  1362. if (s._iO.onposition && s.playState === 0) {
  1363. attachOnPosition(s);
  1364. }
  1365. s.playState = 1;
  1366. s.paused = false;
  1367. s.position = (s._iO.position !== _undefined && !isNaN(s._iO.position) ? s._iO.position : 0);
  1368. if (!s.isHTML5) {
  1369. s._iO = policyFix(loopFix(s._iO));
  1370. }
  1371. if (s._iO.onplay && _updatePlayState) {
  1372. s._iO.onplay.apply(s);
  1373. onplay_called = true;
  1374. }
  1375. s.setVolume(s._iO.volume, true);
  1376. s.setPan(s._iO.pan, true);
  1377. if (!s.isHTML5) {
  1378. startOK = flash._start(s.id, s._iO.loops || 1, (fV === 9 ? s.position : s.position / 1000), s._iO.multiShot || false);
  1379. if (fV === 9 && !startOK) {
  1380. // edge case: no sound hardware, or 32-channel flash ceiling hit.
  1381. // applies only to Flash 9, non-NetStream/MovieStar sounds.
  1382. // http://help.adobe.com/en_US/FlashPlatform/reference/actionscript/3/flash/media/Sound.html#play%28%29
  1383. sm2._wD(fN + 'No sound hardware, or 32-sound ceiling hit');
  1384. if (s._iO.onplayerror) {
  1385. s._iO.onplayerror.apply(s);
  1386. }
  1387. }
  1388. } else {
  1389. start_html5_timer();
  1390. a = s._setup_html5();
  1391. s.setPosition(s._iO.position);
  1392. a.play();
  1393. }
  1394. }
  1395. return s;
  1396. };
  1397. // just for convenience
  1398. this.start = this.play;
  1399. /**
  1400. * Stops playing a sound (and optionally, all sounds)
  1401. *
  1402. * @param {boolean} bAll Optional: Whether to stop all sounds
  1403. * @return {SMSound} The SMSound object
  1404. */
  1405. this.stop = function(bAll) {
  1406. var instanceOptions = s._iO,
  1407. originalPosition;
  1408. if (s.playState === 1) {
  1409. sm2._wD(s.id + ': stop()');
  1410. s._onbufferchange(0);
  1411. s._resetOnPosition(0);
  1412. s.paused = false;
  1413. if (!s.isHTML5) {
  1414. s.playState = 0;
  1415. }
  1416. // remove onPosition listeners, if any
  1417. detachOnPosition();
  1418. // and "to" position, if set
  1419. if (instanceOptions.to) {
  1420. s.clearOnPosition(instanceOptions.to);
  1421. }
  1422. if (!s.isHTML5) {
  1423. flash._stop(s.id, bAll);
  1424. // hack for netStream: just unload
  1425. if (instanceOptions.serverURL) {
  1426. s.unload();
  1427. }
  1428. } else {
  1429. if (s._a) {
  1430. originalPosition = s.position;
  1431. // act like Flash, though
  1432. s.setPosition(0);
  1433. // hack: reflect old position for onstop() (also like Flash)
  1434. s.position = originalPosition;
  1435. // html5 has no stop()
  1436. // NOTE: pausing means iOS requires interaction to resume.
  1437. s._a.pause();
  1438. s.playState = 0;
  1439. // and update UI
  1440. s._onTimer();
  1441. stop_html5_timer();
  1442. }
  1443. }
  1444. s.instanceCount = 0;
  1445. s._iO = {};
  1446. if (instanceOptions.onstop) {
  1447. instanceOptions.onstop.apply(s);
  1448. }
  1449. }
  1450. return s;
  1451. };
  1452. this.getSpectrum = function(pos) {
  1453. if (!s.isHTML5) {
  1454. return flash._getSpectrum(s.id, pos).split(',');
  1455. }
  1456. };
  1457. /**
  1458. * Undocumented/internal: Sets autoPlay for RTMP.
  1459. *
  1460. * @param {boolean} autoPlay state
  1461. */
  1462. this.setAutoPlay = function(autoPlay) {
  1463. sm2._wD(s.id + ': Autoplay turned ' + (autoPlay ? 'on' : 'off'));
  1464. s._iO.autoPlay = autoPlay;
  1465. if (!s.isHTML5) {
  1466. flash._setAutoPlay(s.id, autoPlay);
  1467. if (autoPlay) {
  1468. // only increment the instanceCount if the sound isn't loaded (TODO: verify RTMP)
  1469. if (!s.instanceCount && s.readyState === 1) {
  1470. s.instanceCount++;
  1471. sm2._wD(s.id + ': Incremented instance count to '+s.instanceCount);
  1472. }
  1473. }
  1474. }
  1475. };
  1476. /**
  1477. * Undocumented/internal: Returns the autoPlay boolean.
  1478. *
  1479. * @return {boolean} The current autoPlay value
  1480. */
  1481. this.getAutoPlay = function() {
  1482. return s._iO.autoPlay;
  1483. };
  1484. /**
  1485. * Sets the position of a sound.
  1486. *
  1487. * @param {number} nMsecOffset Position (milliseconds)
  1488. * @return {SMSound} The SMSound object
  1489. */
  1490. this.setPosition = function(nMsecOffset) {
  1491. if (nMsecOffset === _undefined) {
  1492. nMsecOffset = 0;
  1493. }
  1494. var original_pos,
  1495. position, position1K,
  1496. // Use the duration from the instance options, if we don't have a track duration yet.
  1497. // position >= 0 and <= current available (loaded) duration
  1498. offset = (s.isHTML5 ? Math.max(nMsecOffset, 0) : Math.min(s.duration || s._iO.duration, Math.max(nMsecOffset, 0)));
  1499. original_pos = s.position;
  1500. s.position = offset;
  1501. position1K = s.position/1000;
  1502. s._resetOnPosition(s.position);
  1503. s._iO.position = offset;
  1504. if (!s.isHTML5) {
  1505. position = (fV === 9 ? s.position : position1K);
  1506. if (s.readyState && s.readyState !== 2) {
  1507. // if paused or not playing, will not resume (by playing)
  1508. flash._setPosition(s.id, position, (s.paused || !s.playState), s._iO.multiShot);
  1509. }
  1510. } else if (s._a) {
  1511. // Set the position in the canplay handler if the sound is not ready yet
  1512. if (s._html5_canplay) {
  1513. if (s._a.currentTime !== position1K) {
  1514. /**
  1515. * DOM/JS errors/exceptions to watch out for:
  1516. * if seek is beyond (loaded?) position, "DOM exception 11"
  1517. * "INDEX_SIZE_ERR": DOM exception 1
  1518. */
  1519. sm2._wD(s.id + ': setPosition('+position1K+')');
  1520. try {
  1521. s._a.currentTime = position1K;
  1522. if (s.playState === 0 || s.paused) {
  1523. // allow seek without auto-play/resume
  1524. s._a.pause();
  1525. }
  1526. } catch(e) {
  1527. sm2._wD(s.id + ': setPosition(' + position1K + ') failed: ' + e.message, 2);
  1528. }
  1529. }
  1530. } else {
  1531. sm2._wD(s.id + ': setPosition(' + position1K + '): Cannot seek yet, sound not ready');
  1532. }
  1533. }
  1534. if (s.isHTML5) {
  1535. if (s.paused) {
  1536. // if paused, refresh UI right away
  1537. // force update
  1538. s._onTimer(true);
  1539. }
  1540. }
  1541. return s;
  1542. };
  1543. /**
  1544. * Pauses sound playback.
  1545. *
  1546. * @return {SMSound} The SMSound object
  1547. */
  1548. this.pause = function(_bCallFlash) {
  1549. if (s.paused || (s.playState === 0 && s.readyState !== 1)) {
  1550. return s;
  1551. }
  1552. sm2._wD(s.id + ': pause()');
  1553. s.paused = true;
  1554. if (!s.isHTML5) {
  1555. if (_bCallFlash || _bCallFlash === _undefined) {
  1556. flash._pause(s.id, s._iO.multiShot);
  1557. }
  1558. } else {
  1559. s._setup_html5().pause();
  1560. stop_html5_timer();
  1561. }
  1562. if (s._iO.onpause) {
  1563. s._iO.onpause.apply(s);
  1564. }
  1565. return s;
  1566. };
  1567. /**
  1568. * Resumes sound playback.
  1569. *
  1570. * @return {SMSound} The SMSound object
  1571. */
  1572. /**
  1573. * When auto-loaded streams pause on buffer full they have a playState of 0.
  1574. * We need to make sure that the playState is set to 1 when these streams "resume".
  1575. * When a paused stream is resumed, we need to trigger the onplay() callback if it
  1576. * hasn't been called already. In this case since the sound is being played for the
  1577. * first time, I think it's more appropriate to call onplay() rather than onresume().
  1578. */
  1579. this.resume = function() {
  1580. var instanceOptions = s._iO;
  1581. if (!s.paused) {
  1582. return s;
  1583. }
  1584. sm2._wD(s.id + ': resume()');
  1585. s.paused = false;
  1586. s.playState = 1;
  1587. if (!s.isHTML5) {
  1588. if (instanceOptions.isMovieStar && !instanceOptions.serverURL) {
  1589. // Bizarre Webkit bug (Chrome reported via 8tracks.com dudes): AAC content paused for 30+ seconds(?) will not resume without a reposition.
  1590. s.setPosition(s.position);
  1591. }
  1592. // flash method is toggle-based (pause/resume)
  1593. flash._pause(s.id, instanceOptions.multiShot);
  1594. } else {
  1595. s._setup_html5().play();
  1596. start_html5_timer();
  1597. }
  1598. if (!onplay_called && instanceOptions.onplay) {
  1599. instanceOptions.onplay.apply(s);
  1600. onplay_called = true;
  1601. } else if (instanceOptions.onresume) {
  1602. instanceOptions.onresume.apply(s);
  1603. }
  1604. return s;
  1605. };
  1606. /**
  1607. * Toggles sound playback.
  1608. *
  1609. * @return {SMSound} The SMSound object
  1610. */
  1611. this.togglePause = function() {
  1612. sm2._wD(s.id + ': togglePause()');
  1613. if (s.playState === 0) {
  1614. s.play({
  1615. position: (fV === 9 && !s.isHTML5 ? s.position : s.position / 1000)
  1616. });
  1617. return s;
  1618. }
  1619. if (s.paused) {
  1620. s.resume();
  1621. } else {
  1622. s.pause();
  1623. }
  1624. return s;
  1625. };
  1626. /**
  1627. * Sets the panning (L-R) effect.
  1628. *
  1629. * @param {number} nPan The pan value (-100 to 100)
  1630. * @return {SMSound} The SMSound object
  1631. */
  1632. this.setPan = function(nPan, bInstanceOnly) {
  1633. if (nPan === _undefined) {
  1634. nPan = 0;
  1635. }
  1636. if (bInstanceOnly === _undefined) {
  1637. bInstanceOnly = false;
  1638. }
  1639. if (!s.isHTML5) {
  1640. flash._setPan(s.id, nPan);
  1641. } // else { no HTML5 pan? }
  1642. s._iO.pan = nPan;
  1643. if (!bInstanceOnly) {
  1644. s.pan = nPan;
  1645. s.options.pan = nPan;
  1646. }
  1647. return s;
  1648. };
  1649. /**
  1650. * Sets the volume.
  1651. *
  1652. * @param {number} nVol The volume value (0 to 100)
  1653. * @return {SMSound} The SMSound object
  1654. */
  1655. this.setVolume = function(nVol, _bInstanceOnly) {
  1656. /**
  1657. * Note: Setting volume has no effect on iOS "special snowflake" devices.
  1658. * Hardware volume control overrides software, and volume
  1659. * will always return 1 per Apple docs. (iOS 4 + 5.)
  1660. * http://developer.apple.com/library/safari/documentation/AudioVideo/Conceptual/HTML-canvas-guide/AddingSoundtoCanvasAnimations/AddingSoundtoCanvasAnimations.html
  1661. */
  1662. if (nVol === _undefined) {
  1663. nVol = 100;
  1664. }
  1665. if (_bInstanceOnly === _undefined) {
  1666. _bInstanceOnly = false;
  1667. }
  1668. if (!s.isHTML5) {
  1669. flash._setVolume(s.id, (sm2.muted && !s.muted) || s.muted?0:nVol);
  1670. } else if (s._a) {
  1671. // valid range: 0-1
  1672. s._a.volume = Math.max(0, Math.min(1, nVol/100));
  1673. }
  1674. s._iO.volume = nVol;
  1675. if (!_bInstanceOnly) {
  1676. s.volume = nVol;
  1677. s.options.volume = nVol;
  1678. }
  1679. return s;
  1680. };
  1681. /**
  1682. * Mutes the sound.
  1683. *
  1684. * @return {SMSound} The SMSound object
  1685. */
  1686. this.mute = function() {
  1687. s.muted = true;
  1688. if (!s.isHTML5) {
  1689. flash._setVolume(s.id, 0);
  1690. } else if (s._a) {
  1691. s._a.muted = true;
  1692. }
  1693. return s;
  1694. };
  1695. /**
  1696. * Unmutes the sound.
  1697. *
  1698. * @return {SMSound} The SMSound object
  1699. */
  1700. this.unmute = function() {
  1701. s.muted = false;
  1702. var hasIO = (s._iO.volume !== _undefined);
  1703. if (!s.isHTML5) {
  1704. flash._setVolume(s.id, hasIO?s._iO.volume:s.options.volume);
  1705. } else if (s._a) {
  1706. s._a.muted = false;
  1707. }
  1708. return s;
  1709. };
  1710. /**
  1711. * Toggles the muted state of a sound.
  1712. *
  1713. * @return {SMSound} The SMSound object
  1714. */
  1715. this.toggleMute = function() {
  1716. return (s.muted?s.unmute():s.mute());
  1717. };
  1718. /**
  1719. * Registers a callback to be fired when a sound reaches a given position during playback.
  1720. *
  1721. * @param {number} nPosition The position to watch for
  1722. * @param {function} oMethod The relevant callback to fire
  1723. * @param {object} oScope Optional: The scope to apply the callback to
  1724. * @return {SMSound} The SMSound object
  1725. */
  1726. this.onPosition = function(nPosition, oMethod, oScope) {
  1727. // TODO: basic dupe checking?
  1728. onPositionItems.push({
  1729. position: parseInt(nPosition, 10),
  1730. method: oMethod,
  1731. scope: (oScope !== _undefined ? oScope : s),
  1732. fired: false
  1733. });
  1734. return s;
  1735. };
  1736. // legacy/backwards-compability: lower-case method name
  1737. this.onposition = this.onPosition;
  1738. /**
  1739. * Removes registered callback(s) from a sound, by position and/or callback.
  1740. *
  1741. * @param {number} nPosition The position to clear callback(s) for
  1742. * @param {function} oMethod Optional: Identify one callback to be removed when multiple listeners exist for one position
  1743. * @return {SMSound} The SMSound object
  1744. */
  1745. this.clearOnPosition = function(nPosition, oMethod) {
  1746. var i;
  1747. nPosition = parseInt(nPosition, 10);
  1748. if (isNaN(nPosition)) {
  1749. // safety check
  1750. return false;
  1751. }
  1752. for (i=0; i < onPositionItems.length; i++) {
  1753. if (nPosition === onPositionItems[i].position) {
  1754. // remove this item if no method was specified, or, if the method matches
  1755. if (!oMethod || (oMethod === onPositionItems[i].method)) {
  1756. if (onPositionItems[i].fired) {
  1757. // decrement "fired" counter, too
  1758. onPositionFired--;
  1759. }
  1760. onPositionItems.splice(i, 1);
  1761. }
  1762. }
  1763. }
  1764. };
  1765. this._processOnPosition = function() {
  1766. var i, item, j = onPositionItems.length;
  1767. if (!j || !s.playState || onPositionFired >= j) {
  1768. return false;
  1769. }
  1770. for (i=j-1; i >= 0; i--) {
  1771. item = onPositionItems[i];
  1772. if (!item.fired && s.position >= item.position) {
  1773. item.fired = true;
  1774. onPositionFired++;
  1775. item.method.apply(item.scope, [item.position]);
  1776. }
  1777. }
  1778. return true;
  1779. };
  1780. this._resetOnPosition = function(nPosition) {
  1781. // reset "fired" for items interested in this position
  1782. var i, item, j = onPositionItems.length;
  1783. if (!j) {
  1784. return false;
  1785. }
  1786. for (i=j-1; i >= 0; i--) {
  1787. item = onPositionItems[i];
  1788. if (item.fired && nPosition <= item.position) {
  1789. item.fired = false;
  1790. onPositionFired--;
  1791. }
  1792. }
  1793. return true;
  1794. };
  1795. /**
  1796. * SMSound() private internals
  1797. * --------------------------------
  1798. */
  1799. applyFromTo = function() {
  1800. var instanceOptions = s._iO,
  1801. f = instanceOptions.from,
  1802. t = instanceOptions.to,
  1803. start, end;
  1804. end = function() {
  1805. // end has been reached.
  1806. sm2._wD(s.id + ': "To" time of ' + t + ' reached.');
  1807. // detach listener
  1808. s.clearOnPosition(t, end);
  1809. // stop should clear this, too
  1810. s.stop();
  1811. };
  1812. start = function() {
  1813. sm2._wD(s.id + ': Playing "from" ' + f);
  1814. // add listener for end
  1815. if (t !== null && !isNaN(t)) {
  1816. s.onPosition(t, end);
  1817. }
  1818. };
  1819. if (f !== null && !isNaN(f)) {
  1820. // apply to instance options, guaranteeing correct start position.
  1821. instanceOptions.position = f;
  1822. // multiShot timing can't be tracked, so prevent that.
  1823. instanceOptions.multiShot = false;
  1824. start();
  1825. }
  1826. // return updated instanceOptions including starting position
  1827. return instanceOptions;
  1828. };
  1829. attachOnPosition = function() {
  1830. var item,
  1831. op = s._iO.onposition;
  1832. // attach onposition things, if any, now.
  1833. if (op) {
  1834. for (item in op) {
  1835. if (op.hasOwnProperty(item)) {
  1836. s.onPosition(parseInt(item, 10), op[item]);
  1837. }
  1838. }
  1839. }
  1840. };
  1841. detachOnPosition = function() {
  1842. var item,
  1843. op = s._iO.onposition;
  1844. // detach any onposition()-style listeners.
  1845. if (op) {
  1846. for (item in op) {
  1847. if (op.hasOwnProperty(item)) {
  1848. s.clearOnPosition(parseInt(item, 10));
  1849. }
  1850. }
  1851. }
  1852. };
  1853. start_html5_timer = function() {
  1854. if (s.isHTML5) {
  1855. startTimer(s);
  1856. }
  1857. };
  1858. stop_html5_timer = function() {
  1859. if (s.isHTML5) {
  1860. stopTimer(s);
  1861. }
  1862. };
  1863. resetProperties = function(retainPosition) {
  1864. if (!retainPosition) {
  1865. onPositionItems = [];
  1866. onPositionFired = 0;
  1867. }
  1868. onplay_called = false;
  1869. s._hasTimer = null;
  1870. s._a = null;
  1871. s._html5_canplay = false;
  1872. s.bytesLoaded = null;
  1873. s.bytesTotal = null;
  1874. s.duration = (s._iO && s._iO.duration ? s._iO.duration : null);
  1875. s.durationEstimate = null;
  1876. s.buffered = [];
  1877. // legacy: 1D array
  1878. s.eqData = [];
  1879. s.eqData.left = [];
  1880. s.eqData.right = [];
  1881. s.failures = 0;
  1882. s.isBuffering = false;
  1883. s.instanceOptions = {};
  1884. s.instanceCount = 0;
  1885. s.loaded = false;
  1886. s.metadata = {};
  1887. // 0 = uninitialised, 1 = loading, 2 = failed/error, 3 = loaded/success
  1888. s.readyState = 0;
  1889. s.muted = false;
  1890. s.paused = false;
  1891. s.peakData = {
  1892. left: 0,
  1893. right: 0
  1894. };
  1895. s.waveformData = {
  1896. left: [],
  1897. right: []
  1898. };
  1899. s.playState = 0;
  1900. s.position = null;
  1901. s.id3 = {};
  1902. };
  1903. resetProperties();
  1904. /**
  1905. * Pseudo-private SMSound internals
  1906. * --------------------------------
  1907. */
  1908. this._onTimer = function(bForce) {
  1909. /**
  1910. * HTML5-only _whileplaying() etc.
  1911. * called from both HTML5 native events, and polling/interval-based timers
  1912. * mimics flash and fires only when time/duration change, so as to be polling-friendly
  1913. */
  1914. var duration, isNew = false, time, x = {};
  1915. if (s._hasTimer || bForce) {
  1916. // TODO: May not need to track readyState (1 = loading)
  1917. if (s._a && (bForce || ((s.playState > 0 || s.readyState === 1) && !s.paused))) {
  1918. duration = s._get_html5_duration();
  1919. if (duration !== lastHTML5State.duration) {
  1920. lastHTML5State.duration = duration;
  1921. s.duration = duration;
  1922. isNew = true;
  1923. }
  1924. // TODO: investigate why this goes wack if not set/re-set each time.
  1925. s.durationEstimate = s.duration;
  1926. time = (s._a.currentTime * 1000 || 0);
  1927. if (time !== lastHTML5State.time) {
  1928. lastHTML5State.time = time;
  1929. isNew = true;
  1930. }
  1931. if (isNew || bForce) {
  1932. s._whileplaying(time,x,x,x,x);
  1933. }
  1934. }/* else {
  1935. // sm2._wD('_onTimer: Warn for "'+s.id+'": '+(!s._a?'Could not find element. ':'')+(s.playState === 0?'playState bad, 0?':'playState = '+s.playState+', OK'));
  1936. return false;
  1937. }*/
  1938. return isNew;
  1939. }
  1940. };
  1941. this._get_html5_duration = function() {
  1942. var instanceOptions = s._iO,
  1943. // if audio object exists, use its duration - else, instance option duration (if provided - it's a hack, really, and should be retired) OR null
  1944. d = (s._a && s._a.duration ? s._a.duration*1000 : (instanceOptions && instanceOptions.duration ? instanceOptions.duration : null)),
  1945. result = (d && !isNaN(d) && d !== Infinity ? d : null);
  1946. return result;
  1947. };
  1948. this._apply_loop = function(a, nLoops) {
  1949. /**
  1950. * boolean instead of "loop", for webkit? - spec says string. http://www.w3.org/TR/html-markup/audio.html#audio.attrs.loop
  1951. * note that loop is either off or infinite under HTML5, unlike Flash which allows arbitrary loop counts to be specified.
  1952. */
  1953. // <d>
  1954. if (!a.loop && nLoops > 1) {
  1955. sm2._wD('Note: Native HTML5 looping is infinite.', 1);
  1956. }
  1957. // </d>
  1958. a.loop = (nLoops > 1 ? 'loop' : '');
  1959. };
  1960. this._setup_html5 = function(oOptions) {
  1961. var instanceOptions = mixin(s._iO, oOptions),
  1962. a = useGlobalHTML5Audio ? globalHTML5Audio : s._a,
  1963. dURL = decodeURI(instanceOptions.url),
  1964. sameURL;
  1965. /**
  1966. * "First things first, I, Poppa..." (reset the previous state of the old sound, if playing)
  1967. * Fixes case with devices that can only play one sound at a time
  1968. * Otherwise, other sounds in mid-play will be terminated without warning and in a stuck state
  1969. */
  1970. if (useGlobalHTML5Audio) {
  1971. if (dURL === decodeURI(lastGlobalHTML5URL)) {
  1972. // global HTML5 audio: re-use of URL
  1973. sameURL = true;
  1974. }
  1975. } else if (dURL === decodeURI(lastURL)) {
  1976. // options URL is the same as the "last" URL, and we used (loaded) it
  1977. sameURL = true;
  1978. }
  1979. if (a) {
  1980. if (a._s) {
  1981. if (useGlobalHTML5Audio) {
  1982. if (a._s && a._s.playState && !sameURL) {
  1983. // global HTML5 audio case, and loading a new URL. stop the currently-playing one.
  1984. a._s.stop();
  1985. }
  1986. } else if (!useGlobalHTML5Audio && dURL === decodeURI(lastURL)) {
  1987. // non-global HTML5 reuse case: same url, ignore request
  1988. s._apply_loop(a, instanceOptions.loops);
  1989. return a;
  1990. }
  1991. }
  1992. if (!sameURL) {
  1993. // don't retain onPosition() stuff with new URL.
  1994. resetProperties(false);
  1995. // assign new HTML5 URL
  1996. a.src = instanceOptions.url;
  1997. s.url = instanceOptions.url;
  1998. lastURL = instanceOptions.url;
  1999. lastGlobalHTML5URL = instanceOptions.url;
  2000. a._called_load = false;
  2001. }
  2002. } else {
  2003. if (instanceOptions.autoLoad || instanceOptions.autoPlay) {
  2004. s._a = new Audio(instanceOptions.url);
  2005. } else {
  2006. // null for stupid Opera 9.64 case
  2007. s._a = (isOpera && opera.version() < 10 ? new Audio(null) : new Audio());
  2008. }
  2009. // assign local reference
  2010. a = s._a;
  2011. a._called_load = false;
  2012. if (useGlobalHTML5Audio) {
  2013. globalHTML5Audio = a;
  2014. }
  2015. }
  2016. s.isHTML5 = true;
  2017. // store a ref on the track
  2018. s._a = a;
  2019. // store a ref on the audio
  2020. a._s = s;
  2021. add_html5_events();
  2022. s._apply_loop(a, instanceOptions.loops);
  2023. if (instanceOptions.autoLoad || instanceOptions.autoPlay) {
  2024. s.load();
  2025. } else {
  2026. // early HTML5 implementation (non-standard)
  2027. a.autobuffer = false;
  2028. // standard ('none' is also an option.)
  2029. a.preload = 'auto';
  2030. }
  2031. return a;
  2032. };
  2033. add_html5_events = function() {
  2034. if (s._a._added_events) {
  2035. return false;
  2036. }
  2037. var f;
  2038. function add(oEvt, oFn, bCapture) {
  2039. return s._a ? s._a.addEventListener(oEvt, oFn, bCapture||false) : null;
  2040. }
  2041. s._a._added_events = true;
  2042. for (f in html5_events) {
  2043. if (html5_events.hasOwnProperty(f)) {
  2044. add(f, html5_events[f]);
  2045. }
  2046. }
  2047. return true;
  2048. };
  2049. remove_html5_events = function() {
  2050. // Remove event listeners
  2051. var f;
  2052. function remove(oEvt, oFn, bCapture) {
  2053. return (s._a ? s._a.removeEventListener(oEvt, oFn, bCapture||false) : null);
  2054. }
  2055. sm2._wD(s.id + ': Removing event listeners');
  2056. s._a._added_events = false;
  2057. for (f in html5_events) {
  2058. if (html5_events.hasOwnProperty(f)) {
  2059. remove(f, html5_events[f]);
  2060. }
  2061. }
  2062. };
  2063. /**
  2064. * Pseudo-private event internals
  2065. * ------------------------------
  2066. */
  2067. this._onload = function(nSuccess) {
  2068. var fN,
  2069. // check for duration to prevent false positives from flash 8 when loading from cache.
  2070. loadOK = !!nSuccess || (!s.isHTML5 && fV === 8 && s.duration);
  2071. // <d>
  2072. fN = s.id + ': ';
  2073. sm2._wD(fN + (loadOK ? 'onload()' : 'Failed to load? - ' + s.url), (loadOK ? 1 : 2));
  2074. if (!loadOK && !s.isHTML5) {
  2075. if (sm2.sandbox.noRemote === true) {
  2076. sm2._wD(fN + str('noNet'), 1);
  2077. }
  2078. if (sm2.sandbox.noLocal === true) {
  2079. sm2._wD(fN + str('noLocal'), 1);
  2080. }
  2081. }
  2082. // </d>
  2083. s.loaded = loadOK;
  2084. s.readyState = loadOK?3:2;
  2085. s._onbufferchange(0);
  2086. if (s._iO.onload) {
  2087. wrapCallback(s, function() {
  2088. s._iO.onload.apply(s, [loadOK]);
  2089. });
  2090. }
  2091. return true;
  2092. };
  2093. this._onbufferchange = function(nIsBuffering) {
  2094. if (s.playState === 0) {
  2095. // ignore if not playing
  2096. return false;
  2097. }
  2098. if ((nIsBuffering && s.isBuffering) || (!nIsBuffering && !s.isBuffering)) {
  2099. return false;
  2100. }
  2101. s.isBuffering = (nIsBuffering === 1);
  2102. if (s._iO.onbufferchange) {
  2103. sm2._wD(s.id + ': Buffer state change: ' + nIsBuffering);
  2104. s._iO.onbufferchange.apply(s);
  2105. }
  2106. return true;
  2107. };
  2108. /**
  2109. * Playback may have stopped due to buffering, or related reason.
  2110. * This state can be encountered on iOS < 6 when auto-play is blocked.
  2111. */
  2112. this._onsuspend = function() {
  2113. if (s._iO.onsuspend) {
  2114. sm2._wD(s.id + ': Playback suspended');
  2115. s._iO.onsuspend.apply(s);
  2116. }
  2117. return true;
  2118. };
  2119. /**
  2120. * flash 9/movieStar + RTMP-only method, should fire only once at most
  2121. * at this point we just recreate failed sounds rather than trying to reconnect
  2122. */
  2123. this._onfailure = function(msg, level, code) {
  2124. s.failures++;
  2125. sm2._wD(s.id + ': Failures = ' + s.failures);
  2126. if (s._iO.onfailure && s.failures === 1) {
  2127. s._iO.onfailure(s, msg, level, code);
  2128. } else {
  2129. sm2._wD(s.id + ': Ignoring failure');
  2130. }
  2131. };
  2132. this._onfinish = function() {
  2133. // store local copy before it gets trashed...
  2134. var io_onfinish = s._iO.onfinish;
  2135. s._onbufferchange(0);
  2136. s._resetOnPosition(0);
  2137. // reset some state items
  2138. if (s.instanceCount) {
  2139. s.instanceCount--;
  2140. if (!s.instanceCount) {
  2141. // remove onPosition listeners, if any
  2142. detachOnPosition();
  2143. // reset instance options
  2144. s.playState = 0;
  2145. s.paused = false;
  2146. s.instanceCount = 0;
  2147. s.instanceOptions = {};
  2148. s._iO = {};
  2149. stop_html5_timer();
  2150. // reset position, too
  2151. if (s.isHTML5) {
  2152. s.position = 0;
  2153. }
  2154. }
  2155. if (!s.instanceCount || s._iO.multiShotEvents) {
  2156. // fire onfinish for last, or every instance
  2157. if (io_onfinish) {
  2158. sm2._wD(s.id + ': onfinish()');
  2159. wrapCallback(s, function() {
  2160. io_onfinish.apply(s);
  2161. });
  2162. }
  2163. }
  2164. }
  2165. };
  2166. this._whileloading = function(nBytesLoaded, nBytesTotal, nDuration, nBufferLength) {
  2167. var instanceOptions = s._iO;
  2168. s.bytesLoaded = nBytesLoaded;
  2169. s.bytesTotal = nBytesTotal;
  2170. s.duration = Math.floor(nDuration);
  2171. s.bufferLength = nBufferLength;
  2172. if (!s.isHTML5 && !instanceOptions.isMovieStar) {
  2173. if (instanceOptions.duration) {
  2174. // use duration from options, if specified and larger. nobody should be specifying duration in options, actually, and it should be retired.
  2175. s.durationEstimate = (s.duration > instanceOptions.duration) ? s.duration : instanceOptions.duration;
  2176. } else {
  2177. s.durationEstimate = parseInt((s.bytesTotal / s.bytesLoaded) * s.duration, 10);
  2178. }
  2179. } else {
  2180. s.durationEstimate = s.duration;
  2181. }
  2182. // for flash, reflect sequential-load-style buffering
  2183. if (!s.isHTML5) {
  2184. s.buffered = [{
  2185. 'start': 0,
  2186. 'end': s.duration
  2187. }];
  2188. }
  2189. // allow whileloading to fire even if "load" fired under HTML5, due to HTTP range/partials
  2190. if ((s.readyState !== 3 || s.isHTML5) && instanceOptions.whileloading) {
  2191. instanceOptions.whileloading.apply(s);
  2192. }
  2193. };
  2194. this._whileplaying = function(nPosition, oPeakData, oWaveformDataLeft, oWaveformDataRight, oEQData) {
  2195. var instanceOptions = s._iO,
  2196. eqLeft;
  2197. if (isNaN(nPosition) || nPosition === null) {
  2198. // flash safety net
  2199. return false;
  2200. }
  2201. // Safari HTML5 play() may return small -ve values when starting from position: 0, eg. -50.120396875. Unexpected/invalid per W3, I think. Normalize to 0.
  2202. s.position = Math.max(0, nPosition);
  2203. s._processOnPosition();
  2204. if (!s.isHTML5 && fV > 8) {
  2205. if (instanceOptions.usePeakData && oPeakData !== _undefined && oPeakData) {
  2206. s.peakData = {
  2207. left: oPeakData.leftPeak,
  2208. right: oPeakData.rightPeak
  2209. };
  2210. }
  2211. if (instanceOptions.useWaveformData && oWaveformDataLeft !== _undefined && oWaveformDataLeft) {
  2212. s.waveformData = {
  2213. left: oWaveformDataLeft.split(','),
  2214. right: oWaveformDataRight.split(',')
  2215. };
  2216. }
  2217. if (instanceOptions.useEQData) {
  2218. if (oEQData !== _undefined && oEQData && oEQData.leftEQ) {
  2219. eqLeft = oEQData.leftEQ.split(',');
  2220. s.eqData = eqLeft;
  2221. s.eqData.left = eqLeft;
  2222. if (oEQData.rightEQ !== _undefined && oEQData.rightEQ) {
  2223. s.eqData.right = oEQData.rightEQ.split(',');
  2224. }
  2225. }
  2226. }
  2227. }
  2228. if (s.playState === 1) {
  2229. // special case/hack: ensure buffering is false if loading from cache (and not yet started)
  2230. if (!s.isHTML5 && fV === 8 && !s.position && s.isBuffering) {
  2231. s._onbufferchange(0);
  2232. }
  2233. if (instanceOptions.whileplaying) {
  2234. // flash may call after actual finish
  2235. instanceOptions.whileplaying.apply(s);
  2236. }
  2237. }
  2238. return true;
  2239. };
  2240. this._oncaptiondata = function(oData) {
  2241. /**
  2242. * internal: flash 9 + NetStream (MovieStar/RTMP-only) feature
  2243. *
  2244. * @param {object} oData
  2245. */
  2246. sm2._wD(s.id + ': Caption data received.');
  2247. s.captiondata = oData;
  2248. if (s._iO.oncaptiondata) {
  2249. s._iO.oncaptiondata.apply(s, [oData]);
  2250. }
  2251. };
  2252. this._onmetadata = function(oMDProps, oMDData) {
  2253. /**
  2254. * internal: flash 9 + NetStream (MovieStar/RTMP-only) feature
  2255. * RTMP may include song title, MovieStar content may include encoding info
  2256. *
  2257. * @param {array} oMDProps (names)
  2258. * @param {array} oMDData (values)
  2259. */
  2260. sm2._wD(s.id + ': Metadata received.');
  2261. var oData = {}, i, j;
  2262. for (i = 0, j = oMDProps.length; i < j; i++) {
  2263. oData[oMDProps[i]] = oMDData[i];
  2264. }
  2265. s.metadata = oData;
  2266. if (s._iO.onmetadata) {
  2267. s._iO.onmetadata.apply(s);
  2268. }
  2269. };
  2270. this._onid3 = function(oID3Props, oID3Data) {
  2271. /**
  2272. * internal: flash 8 + flash 9 ID3 feature
  2273. * may include artist, song title etc.
  2274. *
  2275. * @param {array} oID3Props (names)
  2276. * @param {array} oID3Data (values)
  2277. */
  2278. sm2._wD(s.id + ': ID3 data received.');
  2279. var oData = [], i, j;
  2280. for (i = 0, j = oID3Props.length; i < j; i++) {
  2281. oData[oID3Props[i]] = oID3Data[i];
  2282. }
  2283. s.id3 = mixin(s.id3, oData);
  2284. if (s._iO.onid3) {
  2285. s._iO.onid3.apply(s);
  2286. }
  2287. };
  2288. // flash/RTMP-only
  2289. this._onconnect = function(bSuccess) {
  2290. bSuccess = (bSuccess === 1);
  2291. sm2._wD(s.id + ': ' + (bSuccess ? 'Connected.' : 'Failed to connect? - ' + s.url), (bSuccess ? 1 : 2));
  2292. s.connected = bSuccess;
  2293. if (bSuccess) {
  2294. s.failures = 0;
  2295. if (idCheck(s.id)) {
  2296. if (s.getAutoPlay()) {
  2297. // only update the play state if auto playing
  2298. s.play(_undefined, s.getAutoPlay());
  2299. } else if (s._iO.autoLoad) {
  2300. s.load();
  2301. }
  2302. }
  2303. if (s._iO.onconnect) {
  2304. s._iO.onconnect.apply(s, [bSuccess]);
  2305. }
  2306. }
  2307. };
  2308. this._ondataerror = function(sError) {
  2309. // flash 9 wave/eq data handler
  2310. // hack: called at start, and end from flash at/after onfinish()
  2311. if (s.playState > 0) {
  2312. sm2._wD(s.id + ': Data error: ' + sError);
  2313. if (s._iO.ondataerror) {
  2314. s._iO.ondataerror.apply(s);
  2315. }
  2316. }
  2317. };
  2318. // <d>
  2319. this._debug();
  2320. // </d>
  2321. }; // SMSound()
  2322. /**
  2323. * Private SoundManager internals
  2324. * ------------------------------
  2325. */
  2326. getDocument = function() {
  2327. return (doc.body || doc._docElement || doc.getElementsByTagName('div')[0]);
  2328. };
  2329. id = function(sID) {
  2330. return doc.getElementById(sID);
  2331. };
  2332. mixin = function(oMain, oAdd) {
  2333. // non-destructive merge
  2334. var o1 = (oMain || {}), o2, o;
  2335. // if unspecified, o2 is the default options object
  2336. o2 = (oAdd === _undefined ? sm2.defaultOptions : oAdd);
  2337. for (o in o2) {
  2338. if (o2.hasOwnProperty(o) && o1[o] === _undefined) {
  2339. if (typeof o2[o] !== 'object' || o2[o] === null) {
  2340. // assign directly
  2341. o1[o] = o2[o];
  2342. } else {
  2343. // recurse through o2
  2344. o1[o] = mixin(o1[o], o2[o]);
  2345. }
  2346. }
  2347. }
  2348. return o1;
  2349. };
  2350. wrapCallback = function(oSound, callback) {
  2351. /**
  2352. * 03/03/2013: Fix for Flash Player 11.6.602.171 + Flash 8 (flashVersion = 8) SWF issue
  2353. * setTimeout() fix for certain SMSound callbacks like onload() and onfinish(), where subsequent calls like play() and load() fail when Flash Player 11.6.602.171 is installed, and using soundManager with flashVersion = 8 (which is the default).
  2354. * Not sure of exact cause. Suspect race condition and/or invalid (NaN-style) position argument trickling down to the next JS -> Flash _start() call, in the play() case.
  2355. * Fix: setTimeout() to yield, plus safer null / NaN checking on position argument provided to Flash.
  2356. * https://getsatisfaction.com/schillmania/topics/recent_chrome_update_seems_to_have_broken_my_sm2_audio_player
  2357. */
  2358. if (!oSound.isHTML5 && fV === 8) {
  2359. window.setTimeout(callback, 0);
  2360. } else {
  2361. callback();
  2362. }
  2363. };
  2364. // additional soundManager properties that soundManager.setup() will accept
  2365. extraOptions = {
  2366. 'onready': 1,
  2367. 'ontimeout': 1,
  2368. 'defaultOptions': 1,
  2369. 'flash9Options': 1,
  2370. 'movieStarOptions': 1
  2371. };
  2372. assign = function(o, oParent) {
  2373. /**
  2374. * recursive assignment of properties, soundManager.setup() helper
  2375. * allows property assignment based on whitelist
  2376. */
  2377. var i,
  2378. result = true,
  2379. hasParent = (oParent !== _undefined),
  2380. setupOptions = sm2.setupOptions,
  2381. bonusOptions = extraOptions;
  2382. // <d>
  2383. // if soundManager.setup() called, show accepted parameters.
  2384. if (o === _undefined) {
  2385. result = [];
  2386. for (i in setupOptions) {
  2387. if (setupOptions.hasOwnProperty(i)) {
  2388. result.push(i);
  2389. }
  2390. }
  2391. for (i in bonusOptions) {
  2392. if (bonusOptions.hasOwnProperty(i)) {
  2393. if (typeof sm2[i] === 'object') {
  2394. result.push(i+': {...}');
  2395. } else if (sm2[i] instanceof Function) {
  2396. result.push(i+': function() {...}');
  2397. } else {
  2398. result.push(i);
  2399. }
  2400. }
  2401. }
  2402. sm2._wD(str('setup', result.join(', ')));
  2403. return false;
  2404. }
  2405. // </d>
  2406. for (i in o) {
  2407. if (o.hasOwnProperty(i)) {
  2408. // if not an {object} we want to recurse through...
  2409. if (typeof o[i] !== 'object' || o[i] === null || o[i] instanceof Array || o[i] instanceof RegExp) {
  2410. // check "allowed" options
  2411. if (hasParent && bonusOptions[oParent] !== _undefined) {
  2412. // valid recursive / nested object option, eg., { defaultOptions: { volume: 50 } }
  2413. sm2[oParent][i] = o[i];
  2414. } else if (setupOptions[i] !== _undefined) {
  2415. // special case: assign to setupOptions object, which soundManager property references
  2416. sm2.setupOptions[i] = o[i];
  2417. // assign directly to soundManager, too
  2418. sm2[i] = o[i];
  2419. } else if (bonusOptions[i] === _undefined) {
  2420. // invalid or disallowed parameter. complain.
  2421. complain(str((sm2[i] === _undefined ? 'setupUndef' : 'setupError'), i), 2);
  2422. result = false;
  2423. } else {
  2424. /**
  2425. * valid extraOptions (bonusOptions) parameter.
  2426. * is it a method, like onready/ontimeout? call it.
  2427. * multiple parameters should be in an array, eg. soundManager.setup({onready: [myHandler, myScope]});
  2428. */
  2429. if (sm2[i] instanceof Function) {
  2430. sm2[i].apply(sm2, (o[i] instanceof Array? o[i] : [o[i]]));
  2431. } else {
  2432. // good old-fashioned direct assignment
  2433. sm2[i] = o[i];
  2434. }
  2435. }
  2436. } else {
  2437. // recursion case, eg., { defaultOptions: { ... } }
  2438. if (bonusOptions[i] === _undefined) {
  2439. // invalid or disallowed parameter. complain.
  2440. complain(str((sm2[i] === _undefined ? 'setupUndef' : 'setupError'), i), 2);
  2441. result = false;
  2442. } else {
  2443. // recurse through object
  2444. return assign(o[i], i);
  2445. }
  2446. }
  2447. }
  2448. }
  2449. return result;
  2450. };
  2451. function preferFlashCheck(kind) {
  2452. // whether flash should play a given type
  2453. return (sm2.preferFlash && hasFlash && !sm2.ignoreFlash && (sm2.flash[kind] !== _undefined && sm2.flash[kind]));
  2454. }
  2455. /**
  2456. * Internal DOM2-level event helpers
  2457. * ---------------------------------
  2458. */
  2459. event = (function() {
  2460. // normalize event methods
  2461. var old = (window.attachEvent),
  2462. evt = {
  2463. add: (old?'attachEvent':'addEventListener'),
  2464. remove: (old?'detachEvent':'removeEventListener')
  2465. };
  2466. // normalize "on" event prefix, optional capture argument
  2467. function getArgs(oArgs) {
  2468. var args = slice.call(oArgs),
  2469. len = args.length;
  2470. if (old) {
  2471. // prefix
  2472. args[1] = 'on' + args[1];
  2473. if (len > 3) {
  2474. // no capture
  2475. args.pop();
  2476. }
  2477. } else if (len === 3) {
  2478. args.push(false);
  2479. }
  2480. return args;
  2481. }
  2482. function apply(args, sType) {
  2483. // normalize and call the event method, with the proper arguments
  2484. var element = args.shift(),
  2485. method = [evt[sType]];
  2486. if (old) {
  2487. // old IE can't do apply().
  2488. element[method](args[0], args[1]);
  2489. } else {
  2490. element[method].apply(element, args);
  2491. }
  2492. }
  2493. function add() {
  2494. apply(getArgs(arguments), 'add');
  2495. }
  2496. function remove() {
  2497. apply(getArgs(arguments), 'remove');
  2498. }
  2499. return {
  2500. 'add': add,
  2501. 'remove': remove
  2502. };
  2503. }());
  2504. /**
  2505. * Internal HTML5 event handling
  2506. * -----------------------------
  2507. */
  2508. function html5_event(oFn) {
  2509. // wrap html5 event handlers so we don't call them on destroyed and/or unloaded sounds
  2510. return function(e) {
  2511. var s = this._s,
  2512. result;
  2513. if (!s || !s._a) {
  2514. // <d>
  2515. if (s && s.id) {
  2516. sm2._wD(s.id + ': Ignoring ' + e.type);
  2517. } else {
  2518. sm2._wD(h5 + 'Ignoring ' + e.type);
  2519. }
  2520. // </d>
  2521. result = null;
  2522. } else {
  2523. result = oFn.call(this, e);
  2524. }
  2525. return result;
  2526. };
  2527. }
  2528. html5_events = {
  2529. // HTML5 event-name-to-handler map
  2530. abort: html5_event(function() {
  2531. sm2._wD(this._s.id + ': abort');
  2532. }),
  2533. // enough has loaded to play
  2534. canplay: html5_event(function() {
  2535. var s = this._s,
  2536. position1K;
  2537. if (s._html5_canplay) {
  2538. // this event has already fired. ignore.
  2539. return true;
  2540. }
  2541. s._html5_canplay = true;
  2542. sm2._wD(s.id + ': canplay');
  2543. s._onbufferchange(0);
  2544. // position according to instance options
  2545. position1K = (s._iO.position !== _undefined && !isNaN(s._iO.position)?s._iO.position/1000:null);
  2546. // set the position if position was set before the sound loaded
  2547. if (s.position && this.currentTime !== position1K) {
  2548. sm2._wD(s.id + ': canplay: Setting position to ' + position1K);
  2549. try {
  2550. this.currentTime = position1K;
  2551. } catch(ee) {
  2552. sm2._wD(s.id + ': canplay: Setting position of ' + position1K + ' failed: ' + ee.message, 2);
  2553. }
  2554. }
  2555. // hack for HTML5 from/to case
  2556. if (s._iO._oncanplay) {
  2557. s._iO._oncanplay();
  2558. }
  2559. }),
  2560. canplaythrough: html5_event(function() {
  2561. var s = this._s;
  2562. if (!s.loaded) {
  2563. s._onbufferchange(0);
  2564. s._whileloading(s.bytesLoaded, s.bytesTotal, s._get_html5_duration());
  2565. s._onload(true);
  2566. }
  2567. }),
  2568. // TODO: Reserved for potential use
  2569. /*
  2570. emptied: html5_event(function() {
  2571. sm2._wD(this._s.id + ': emptied');
  2572. }),
  2573. */
  2574. ended: html5_event(function() {
  2575. var s = this._s;
  2576. sm2._wD(s.id + ': ended');
  2577. s._onfinish();
  2578. }),
  2579. error: html5_event(function() {
  2580. sm2._wD(this._s.id + ': HTML5 error, code ' + this.error.code);
  2581. // call load with error state?
  2582. this._s._onload(false);
  2583. }),
  2584. loadeddata: html5_event(function() {
  2585. var s = this._s;
  2586. sm2._wD(s.id + ': loadeddata');
  2587. // safari seems to nicely report progress events, eventually totalling 100%
  2588. if (!s._loaded && !isSafari) {
  2589. s.duration = s._get_html5_duration();
  2590. }
  2591. }),
  2592. loadedmetadata: html5_event(function() {
  2593. sm2._wD(this._s.id + ': loadedmetadata');
  2594. }),
  2595. loadstart: html5_event(function() {
  2596. sm2._wD(this._s.id + ': loadstart');
  2597. // assume buffering at first
  2598. this._s._onbufferchange(1);
  2599. }),
  2600. play: html5_event(function() {
  2601. sm2._wD(this._s.id + ': play()');
  2602. // once play starts, no buffering
  2603. this._s._onbufferchange(0);
  2604. }),
  2605. playing: html5_event(function() {
  2606. sm2._wD(this._s.id + ': playing');
  2607. // once play starts, no buffering
  2608. this._s._onbufferchange(0);
  2609. }),
  2610. progress: html5_event(function(e) {
  2611. // note: can fire repeatedly after "loaded" event, due to use of HTTP range/partials
  2612. var s = this._s,
  2613. i, j, str, buffered = 0,
  2614. isProgress = (e.type === 'progress'),
  2615. ranges = e.target.buffered,
  2616. // firefox 3.6 implements e.loaded/total (bytes)
  2617. loaded = (e.loaded||0),
  2618. total = (e.total||1),
  2619. // HTML5 returns msec. SM2 API uses seconds for setPosition() etc., whether Flash or HTML5.
  2620. scale = 1000;
  2621. // reset the "buffered" (loaded byte ranges) array
  2622. s.buffered = [];
  2623. if (ranges && ranges.length) {
  2624. // if loaded is 0, try TimeRanges implementation as % of load
  2625. // https://developer.mozilla.org/en/DOM/TimeRanges
  2626. // re-build "buffered" array
  2627. for (i=0, j=ranges.length; i<j; i++) {
  2628. s.buffered.push({
  2629. 'start': ranges.start(i) * scale,
  2630. 'end': ranges.end(i) * scale
  2631. });
  2632. }
  2633. // use the last value locally
  2634. buffered = (ranges.end(0) - ranges.start(0)) * scale;
  2635. // linear case, buffer sum; does not account for seeking and HTTP partials / byte ranges
  2636. loaded = buffered/(e.target.duration*scale);
  2637. // <d>
  2638. if (isProgress && ranges.length > 1) {
  2639. str = [];
  2640. j = ranges.length;
  2641. for (i=0; i<j; i++) {
  2642. str.push(e.target.buffered.start(i)*scale +'-'+ e.target.buffered.end(i)*scale);
  2643. }
  2644. sm2._wD(this._s.id + ': progress, timeRanges: ' + str.join(', '));
  2645. }
  2646. if (isProgress && !isNaN(loaded)) {
  2647. sm2._wD(this._s.id + ': progress, ' + Math.floor(loaded*100) + '% loaded');
  2648. }
  2649. // </d>
  2650. }
  2651. if (!isNaN(loaded)) {
  2652. // if progress, likely not buffering
  2653. s._onbufferchange(0);
  2654. // TODO: prevent calls with duplicate values.
  2655. s._whileloading(loaded, total, s._get_html5_duration());
  2656. if (loaded && total && loaded === total) {
  2657. // in case "onload" doesn't fire (eg. gecko 1.9.2)
  2658. html5_events.canplaythrough.call(this, e);
  2659. }
  2660. }
  2661. }),
  2662. ratechange: html5_event(function() {
  2663. sm2._wD(this._s.id + ': ratechange');
  2664. }),
  2665. suspend: html5_event(function(e) {
  2666. // download paused/stopped, may have finished (eg. onload)
  2667. var s = this._s;
  2668. sm2._wD(this._s.id + ': suspend');
  2669. html5_events.progress.call(this, e);
  2670. s._onsuspend();
  2671. }),
  2672. stalled: html5_event(function() {
  2673. sm2._wD(this._s.id + ': stalled');
  2674. }),
  2675. timeupdate: html5_event(function() {
  2676. this._s._onTimer();
  2677. }),
  2678. waiting: html5_event(function() {
  2679. var s = this._s;
  2680. // see also: seeking
  2681. sm2._wD(this._s.id + ': waiting');
  2682. // playback faster than download rate, etc.
  2683. s._onbufferchange(1);
  2684. })
  2685. };
  2686. html5OK = function(iO) {
  2687. // playability test based on URL or MIME type
  2688. var result;
  2689. if (iO.serverURL || (iO.type && preferFlashCheck(iO.type))) {
  2690. // RTMP, or preferring flash
  2691. result = false;
  2692. } else {
  2693. // Use type, if specified. If HTML5-only mode, no other options, so just give 'er
  2694. result = ((iO.type ? html5CanPlay({type:iO.type}) : html5CanPlay({url:iO.url}) || sm2.html5Only));
  2695. }
  2696. return result;
  2697. };
  2698. html5Unload = function(oAudio, url) {
  2699. /**
  2700. * Internal method: Unload media, and cancel any current/pending network requests.
  2701. * Firefox can load an empty URL, which allegedly destroys the decoder and stops the download.
  2702. * https://developer.mozilla.org/En/Using_audio_and_video_in_Firefox#Stopping_the_download_of_media
  2703. * However, Firefox has been seen loading a relative URL from '' and thus requesting the hosting page on unload.
  2704. * Other UA behaviour is unclear, so everyone else gets an about:blank-style URL.
  2705. */
  2706. if (oAudio) {
  2707. // Firefox likes '' for unload (used to work?) - however, may request hosting page URL (bad.) Most other UAs dislike '' and fail to unload.
  2708. oAudio.src = url;
  2709. // reset some state, too
  2710. oAudio._called_load = false;
  2711. }
  2712. if (useGlobalHTML5Audio) {
  2713. // ensure URL state is trashed, also
  2714. lastGlobalHTML5URL = null;
  2715. }
  2716. };
  2717. html5CanPlay = function(o) {
  2718. /**
  2719. * Try to find MIME, test and return truthiness
  2720. * o = {
  2721. * url: '/path/to/an.mp3',
  2722. * type: 'audio/mp3'
  2723. * }
  2724. */
  2725. if (!sm2.useHTML5Audio || !sm2.hasHTML5) {
  2726. return false;
  2727. }
  2728. var url = (o.url || null),
  2729. mime = (o.type || null),
  2730. aF = sm2.audioFormats,
  2731. result,
  2732. offset,
  2733. fileExt,
  2734. item;
  2735. // account for known cases like audio/mp3
  2736. if (mime && sm2.html5[mime] !== _undefined) {
  2737. return (sm2.html5[mime] && !preferFlashCheck(mime));
  2738. }
  2739. if (!html5Ext) {
  2740. html5Ext = [];
  2741. for (item in aF) {
  2742. if (aF.hasOwnProperty(item)) {
  2743. html5Ext.push(item);
  2744. if (aF[item].related) {
  2745. html5Ext = html5Ext.concat(aF[item].related);
  2746. }
  2747. }
  2748. }
  2749. html5Ext = new RegExp('\\.('+html5Ext.join('|')+')(\\?.*)?$','i');
  2750. }
  2751. // TODO: Strip URL queries, etc.
  2752. fileExt = (url ? url.toLowerCase().match(html5Ext) : null);
  2753. if (!fileExt || !fileExt.length) {
  2754. if (!mime) {
  2755. result = false;
  2756. } else {
  2757. // audio/mp3 -> mp3, result should be known
  2758. offset = mime.indexOf(';');
  2759. // strip "audio/X; codecs..."
  2760. fileExt = (offset !== -1?mime.substr(0,offset):mime).substr(6);
  2761. }
  2762. } else {
  2763. // match the raw extension name - "mp3", for example
  2764. fileExt = fileExt[1];
  2765. }
  2766. if (fileExt && sm2.html5[fileExt] !== _undefined) {
  2767. // result known
  2768. result = (sm2.html5[fileExt] && !preferFlashCheck(fileExt));
  2769. } else {
  2770. mime = 'audio/'+fileExt;
  2771. result = sm2.html5.canPlayType({type:mime});
  2772. sm2.html5[fileExt] = result;
  2773. // sm2._wD('canPlayType, found result: ' + result);
  2774. result = (result && sm2.html5[mime] && !preferFlashCheck(mime));
  2775. }
  2776. return result;
  2777. };
  2778. testHTML5 = function() {
  2779. /**
  2780. * Internal: Iterates over audioFormats, determining support eg. audio/mp3, audio/mpeg and so on
  2781. * assigns results to html5[] and flash[].
  2782. */
  2783. if (!sm2.useHTML5Audio || !sm2.hasHTML5) {
  2784. return false;
  2785. }
  2786. // double-whammy: Opera 9.64 throws WRONG_ARGUMENTS_ERR if no parameter passed to Audio(), and Webkit + iOS happily tries to load "null" as a URL. :/
  2787. var a = (Audio !== _undefined ? (isOpera && opera.version() < 10 ? new Audio(null) : new Audio()) : null),
  2788. item, lookup, support = {}, aF, i;
  2789. function cp(m) {
  2790. var canPlay, i, j,
  2791. result = false,
  2792. isOK = false;
  2793. if (!a || typeof a.canPlayType !== 'function') {
  2794. return result;
  2795. }
  2796. if (m instanceof Array) {
  2797. // iterate through all mime types, return any successes
  2798. for (i=0, j=m.length; i<j; i++) {
  2799. if (sm2.html5[m[i]] || a.canPlayType(m[i]).match(sm2.html5Test)) {
  2800. isOK = true;
  2801. sm2.html5[m[i]] = true;
  2802. // note flash support, too
  2803. sm2.flash[m[i]] = !!(m[i].match(flashMIME));
  2804. }
  2805. }
  2806. result = isOK;
  2807. } else {
  2808. canPlay = (a && typeof a.canPlayType === 'function' ? a.canPlayType(m) : false);
  2809. result = !!(canPlay && (canPlay.match(sm2.html5Test)));
  2810. }
  2811. return result;
  2812. }
  2813. // test all registered formats + codecs
  2814. aF = sm2.audioFormats;
  2815. for (item in aF) {
  2816. if (aF.hasOwnProperty(item)) {
  2817. lookup = 'audio/' + item;
  2818. support[item] = cp(aF[item].type);
  2819. // write back generic type too, eg. audio/mp3
  2820. support[lookup] = support[item];
  2821. // assign flash
  2822. if (item.match(flashMIME)) {
  2823. sm2.flash[item] = true;
  2824. sm2.flash[lookup] = true;
  2825. } else {
  2826. sm2.flash[item] = false;
  2827. sm2.flash[lookup] = false;
  2828. }
  2829. // assign result to related formats, too
  2830. if (aF[item] && aF[item].related) {
  2831. for (i=aF[item].related.length-1; i >= 0; i--) {
  2832. // eg. audio/m4a
  2833. support['audio/'+aF[item].related[i]] = support[item];
  2834. sm2.html5[aF[item].related[i]] = support[item];
  2835. sm2.flash[aF[item].related[i]] = support[item];
  2836. }
  2837. }
  2838. }
  2839. }
  2840. support.canPlayType = (a?cp:null);
  2841. sm2.html5 = mixin(sm2.html5, support);
  2842. return true;
  2843. };
  2844. strings = {
  2845. // <d>
  2846. notReady: 'Unavailable - wait until onready() has fired.',
  2847. notOK: 'Audio support is not available.',
  2848. domError: sm + 'exception caught while appending SWF to DOM.',
  2849. spcWmode: 'Removing wmode, preventing known SWF loading issue(s)',
  2850. swf404: smc + 'Verify that %s is a valid path.',
  2851. tryDebug: 'Try ' + sm + '.debugFlash = true for more security details (output goes to SWF.)',
  2852. checkSWF: 'See SWF output for more debug info.',
  2853. localFail: smc + 'Non-HTTP page (' + doc.location.protocol + ' URL?) Review Flash player security settings for this special case:\nhttp://www.macromedia.com/support/documentation/en/flashplayer/help/settings_manager04.html\nMay need to add/allow path, eg. c:/sm2/ or /users/me/sm2/',
  2854. waitFocus: smc + 'Special case: Waiting for SWF to load with window focus...',
  2855. waitForever: smc + 'Waiting indefinitely for Flash (will recover if unblocked)...',
  2856. waitSWF: smc + 'Waiting for 100% SWF load...',
  2857. needFunction: smc + 'Function object expected for %s',
  2858. badID: 'Warning: Sound ID "%s" should be a string, starting with a non-numeric character',
  2859. currentObj: smc + '_debug(): Current sound objects',
  2860. waitOnload: smc + 'Waiting for window.onload()',
  2861. docLoaded: smc + 'Document already loaded',
  2862. onload: smc + 'initComplete(): calling soundManager.onload()',
  2863. onloadOK: sm + '.onload() complete',
  2864. didInit: smc + 'init(): Already called?',
  2865. secNote: 'Flash security note: Network/internet URLs will not load due to security restrictions. Access can be configured via Flash Player Global Security Settings Page: http://www.macromedia.com/support/documentation/en/flashplayer/help/settings_manager04.html',
  2866. badRemove: smc + 'Failed to remove Flash node.',
  2867. shutdown: sm + '.disable(): Shutting down',
  2868. queue: smc + 'Queueing %s handler',
  2869. smError: 'SMSound.load(): Exception: JS-Flash communication failed, or JS error.',
  2870. fbTimeout: 'No flash response, applying .'+swfCSS.swfTimedout+' CSS...',
  2871. fbLoaded: 'Flash loaded',
  2872. flRemoved: smc + 'Flash movie removed.',
  2873. fbHandler: smc + 'flashBlockHandler()',
  2874. manURL: 'SMSound.load(): Using manually-assigned URL',
  2875. onURL: sm + '.load(): current URL already assigned.',
  2876. badFV: sm + '.flashVersion must be 8 or 9. "%s" is invalid. Reverting to %s.',
  2877. as2loop: 'Note: Setting stream:false so looping can work (flash 8 limitation)',
  2878. noNSLoop: 'Note: Looping not implemented for MovieStar formats',
  2879. needfl9: 'Note: Switching to flash 9, required for MP4 formats.',
  2880. mfTimeout: 'Setting flashLoadTimeout = 0 (infinite) for off-screen, mobile flash case',
  2881. needFlash: smc + 'Fatal error: Flash is needed to play some required formats, but is not available.',
  2882. gotFocus: smc + 'Got window focus.',
  2883. policy: 'Enabling usePolicyFile for data access',
  2884. setup: sm + '.setup(): allowed parameters: %s',
  2885. setupError: sm + '.setup(): "%s" cannot be assigned with this method.',
  2886. setupUndef: sm + '.setup(): Could not find option "%s"',
  2887. setupLate: sm + '.setup(): url, flashVersion and html5Test property changes will not take effect until reboot().',
  2888. noURL: smc + 'Flash URL required. Call soundManager.setup({url:...}) to get started.',
  2889. sm2Loaded: 'SoundManager 2: Ready.',
  2890. reset: sm + '.reset(): Removing event callbacks',
  2891. mobileUA: 'Mobile UA detected, preferring HTML5 by default.',
  2892. globalHTML5: 'Using singleton HTML5 Audio() pattern for this device.'
  2893. // </d>
  2894. };
  2895. str = function() {
  2896. // internal string replace helper.
  2897. // arguments: o [,items to replace]
  2898. // <d>
  2899. // real array, please
  2900. var args = slice.call(arguments),
  2901. // first arg
  2902. o = args.shift(),
  2903. str = (strings && strings[o]?strings[o]:''), i, j;
  2904. if (str && args && args.length) {
  2905. for (i = 0, j = args.length; i < j; i++) {
  2906. str = str.replace('%s', args[i]);
  2907. }
  2908. }
  2909. return str;
  2910. // </d>
  2911. };
  2912. loopFix = function(sOpt) {
  2913. // flash 8 requires stream = false for looping to work
  2914. if (fV === 8 && sOpt.loops > 1 && sOpt.stream) {
  2915. _wDS('as2loop');
  2916. sOpt.stream = false;
  2917. }
  2918. return sOpt;
  2919. };
  2920. policyFix = function(sOpt, sPre) {
  2921. if (sOpt && !sOpt.usePolicyFile && (sOpt.onid3 || sOpt.usePeakData || sOpt.useWaveformData || sOpt.useEQData)) {
  2922. sm2._wD((sPre || '') + str('policy'));
  2923. sOpt.usePolicyFile = true;
  2924. }
  2925. return sOpt;
  2926. };
  2927. complain = function(sMsg) {
  2928. // <d>
  2929. if (console !== _undefined && console.warn !== _undefined) {
  2930. console.warn(sMsg);
  2931. } else {
  2932. sm2._wD(sMsg);
  2933. }
  2934. // </d>
  2935. };
  2936. doNothing = function() {
  2937. return false;
  2938. };
  2939. disableObject = function(o) {
  2940. var oProp;
  2941. for (oProp in o) {
  2942. if (o.hasOwnProperty(oProp) && typeof o[oProp] === 'function') {
  2943. o[oProp] = doNothing;
  2944. }
  2945. }
  2946. oProp = null;
  2947. };
  2948. failSafely = function(bNoDisable) {
  2949. // general failure exception handler
  2950. if (bNoDisable === _undefined) {
  2951. bNoDisable = false;
  2952. }
  2953. if (disabled || bNoDisable) {
  2954. sm2.disable(bNoDisable);
  2955. }
  2956. };
  2957. normalizeMovieURL = function(smURL) {
  2958. var urlParams = null, url;
  2959. if (smURL) {
  2960. if (smURL.match(/\.swf(\?.*)?$/i)) {
  2961. urlParams = smURL.substr(smURL.toLowerCase().lastIndexOf('.swf?') + 4);
  2962. if (urlParams) {
  2963. // assume user knows what they're doing
  2964. return smURL;
  2965. }
  2966. } else if (smURL.lastIndexOf('/') !== smURL.length - 1) {
  2967. // append trailing slash, if needed
  2968. smURL += '/';
  2969. }
  2970. }
  2971. url = (smURL && smURL.lastIndexOf('/') !== - 1 ? smURL.substr(0, smURL.lastIndexOf('/') + 1) : './') + sm2.movieURL;
  2972. if (sm2.noSWFCache) {
  2973. url += ('?ts=' + new Date().getTime());
  2974. }
  2975. return url;
  2976. };
  2977. setVersionInfo = function() {
  2978. // short-hand for internal use
  2979. fV = parseInt(sm2.flashVersion, 10);
  2980. if (fV !== 8 && fV !== 9) {
  2981. sm2._wD(str('badFV', fV, defaultFlashVersion));
  2982. sm2.flashVersion = fV = defaultFlashVersion;
  2983. }
  2984. // debug flash movie, if applicable
  2985. var isDebug = (sm2.debugMode || sm2.debugFlash?'_debug.swf':'.swf');
  2986. if (sm2.useHTML5Audio && !sm2.html5Only && sm2.audioFormats.mp4.required && fV < 9) {
  2987. sm2._wD(str('needfl9'));
  2988. sm2.flashVersion = fV = 9;
  2989. }
  2990. sm2.version = sm2.versionNumber + (sm2.html5Only?' (HTML5-only mode)':(fV === 9?' (AS3/Flash 9)':' (AS2/Flash 8)'));
  2991. // set up default options
  2992. if (fV > 8) {
  2993. // +flash 9 base options
  2994. sm2.defaultOptions = mixin(sm2.defaultOptions, sm2.flash9Options);
  2995. sm2.features.buffering = true;
  2996. // +moviestar support
  2997. sm2.defaultOptions = mixin(sm2.defaultOptions, sm2.movieStarOptions);
  2998. sm2.filePatterns.flash9 = new RegExp('\\.(mp3|' + netStreamTypes.join('|') + ')(\\?.*)?$', 'i');
  2999. sm2.features.movieStar = true;
  3000. } else {
  3001. sm2.features.movieStar = false;
  3002. }
  3003. // regExp for flash canPlay(), etc.
  3004. sm2.filePattern = sm2.filePatterns[(fV !== 8?'flash9':'flash8')];
  3005. // if applicable, use _debug versions of SWFs
  3006. sm2.movieURL = (fV === 8?'soundmanager2.swf':'soundmanager2_flash9.swf').replace('.swf', isDebug);
  3007. sm2.features.peakData = sm2.features.waveformData = sm2.features.eqData = (fV > 8);
  3008. };
  3009. setPolling = function(bPolling, bHighPerformance) {
  3010. if (!flash) {
  3011. return false;
  3012. }
  3013. flash._setPolling(bPolling, bHighPerformance);
  3014. };
  3015. initDebug = function() {
  3016. // starts debug mode, creating output <div> for UAs without console object
  3017. // allow force of debug mode via URL
  3018. if (sm2.debugURLParam.test(wl)) {
  3019. sm2.debugMode = true;
  3020. }
  3021. // <d>
  3022. if (id(sm2.debugID)) {
  3023. return false;
  3024. }
  3025. var oD, oDebug, oTarget, oToggle, tmp;
  3026. if (sm2.debugMode && !id(sm2.debugID) && (!hasConsole || !sm2.useConsole || !sm2.consoleOnly)) {
  3027. oD = doc.createElement('div');
  3028. oD.id = sm2.debugID + '-toggle';
  3029. oToggle = {
  3030. 'position': 'fixed',
  3031. 'bottom': '0px',
  3032. 'right': '0px',
  3033. 'width': '1.2em',
  3034. 'height': '1.2em',
  3035. 'lineHeight': '1.2em',
  3036. 'margin': '2px',
  3037. 'textAlign': 'center',
  3038. 'border': '1px solid #999',
  3039. 'cursor': 'pointer',
  3040. 'background': '#fff',
  3041. 'color': '#333',
  3042. 'zIndex': 10001
  3043. };
  3044. oD.appendChild(doc.createTextNode('-'));
  3045. oD.onclick = toggleDebug;
  3046. oD.title = 'Toggle SM2 debug console';
  3047. if (ua.match(/msie 6/i)) {
  3048. oD.style.position = 'absolute';
  3049. oD.style.cursor = 'hand';
  3050. }
  3051. for (tmp in oToggle) {
  3052. if (oToggle.hasOwnProperty(tmp)) {
  3053. oD.style[tmp] = oToggle[tmp];
  3054. }
  3055. }
  3056. oDebug = doc.createElement('div');
  3057. oDebug.id = sm2.debugID;
  3058. oDebug.style.display = (sm2.debugMode?'block':'none');
  3059. if (sm2.debugMode && !id(oD.id)) {
  3060. try {
  3061. oTarget = getDocument();
  3062. oTarget.appendChild(oD);
  3063. } catch(e2) {
  3064. throw new Error(str('domError')+' \n'+e2.toString());
  3065. }
  3066. oTarget.appendChild(oDebug);
  3067. }
  3068. }
  3069. oTarget = null;
  3070. // </d>
  3071. };
  3072. idCheck = this.getSoundById;
  3073. // <d>
  3074. _wDS = function(o, errorLevel) {
  3075. return (!o ? '' : sm2._wD(str(o), errorLevel));
  3076. };
  3077. toggleDebug = function() {
  3078. var o = id(sm2.debugID),
  3079. oT = id(sm2.debugID + '-toggle');
  3080. if (!o) {
  3081. return false;
  3082. }
  3083. if (debugOpen) {
  3084. // minimize
  3085. oT.innerHTML = '+';
  3086. o.style.display = 'none';
  3087. } else {
  3088. oT.innerHTML = '-';
  3089. o.style.display = 'block';
  3090. }
  3091. debugOpen = !debugOpen;
  3092. };
  3093. debugTS = function(sEventType, bSuccess, sMessage) {
  3094. // troubleshooter debug hooks
  3095. if (window.sm2Debugger !== _undefined) {
  3096. try {
  3097. sm2Debugger.handleEvent(sEventType, bSuccess, sMessage);
  3098. } catch(e) {
  3099. // oh well
  3100. }
  3101. }
  3102. return true;
  3103. };
  3104. // </d>
  3105. getSWFCSS = function() {
  3106. var css = [];
  3107. if (sm2.debugMode) {
  3108. css.push(swfCSS.sm2Debug);
  3109. }
  3110. if (sm2.debugFlash) {
  3111. css.push(swfCSS.flashDebug);
  3112. }
  3113. if (sm2.useHighPerformance) {
  3114. css.push(swfCSS.highPerf);
  3115. }
  3116. return css.join(' ');
  3117. };
  3118. flashBlockHandler = function() {
  3119. // *possible* flash block situation.
  3120. var name = str('fbHandler'),
  3121. p = sm2.getMoviePercent(),
  3122. css = swfCSS,
  3123. error = {type:'FLASHBLOCK'};
  3124. if (sm2.html5Only) {
  3125. return false;
  3126. }
  3127. if (!sm2.ok()) {
  3128. if (needsFlash) {
  3129. // make the movie more visible, so user can fix
  3130. sm2.oMC.className = getSWFCSS() + ' ' + css.swfDefault + ' ' + (p === null?css.swfTimedout:css.swfError);
  3131. sm2._wD(name + ': ' + str('fbTimeout') + (p ? ' (' + str('fbLoaded') + ')' : ''));
  3132. }
  3133. sm2.didFlashBlock = true;
  3134. // fire onready(), complain lightly
  3135. processOnEvents({type:'ontimeout', ignoreInit:true, error:error});
  3136. catchError(error);
  3137. } else {
  3138. // SM2 loaded OK (or recovered)
  3139. // <d>
  3140. if (sm2.didFlashBlock) {
  3141. sm2._wD(name + ': Unblocked');
  3142. }
  3143. // </d>
  3144. if (sm2.oMC) {
  3145. sm2.oMC.className = [getSWFCSS(), css.swfDefault, css.swfLoaded + (sm2.didFlashBlock?' '+css.swfUnblocked:'')].join(' ');
  3146. }
  3147. }
  3148. };
  3149. addOnEvent = function(sType, oMethod, oScope) {
  3150. if (on_queue[sType] === _undefined) {
  3151. on_queue[sType] = [];
  3152. }
  3153. on_queue[sType].push({
  3154. 'method': oMethod,
  3155. 'scope': (oScope || null),
  3156. 'fired': false
  3157. });
  3158. };
  3159. processOnEvents = function(oOptions) {
  3160. // if unspecified, assume OK/error
  3161. if (!oOptions) {
  3162. oOptions = {
  3163. type: (sm2.ok() ? 'onready' : 'ontimeout')
  3164. };
  3165. }
  3166. if (!didInit && oOptions && !oOptions.ignoreInit) {
  3167. // not ready yet.
  3168. return false;
  3169. }
  3170. if (oOptions.type === 'ontimeout' && (sm2.ok() || (disabled && !oOptions.ignoreInit))) {
  3171. // invalid case
  3172. return false;
  3173. }
  3174. var status = {
  3175. success: (oOptions && oOptions.ignoreInit?sm2.ok():!disabled)
  3176. },
  3177. // queue specified by type, or none
  3178. srcQueue = (oOptions && oOptions.type?on_queue[oOptions.type]||[]:[]),
  3179. queue = [], i, j,
  3180. args = [status],
  3181. canRetry = (needsFlash && !sm2.ok());
  3182. if (oOptions.error) {
  3183. args[0].error = oOptions.error;
  3184. }
  3185. for (i = 0, j = srcQueue.length; i < j; i++) {
  3186. if (srcQueue[i].fired !== true) {
  3187. queue.push(srcQueue[i]);
  3188. }
  3189. }
  3190. if (queue.length) {
  3191. // sm2._wD(sm + ': Firing ' + queue.length + ' ' + oOptions.type + '() item' + (queue.length === 1 ? '' : 's'));
  3192. for (i = 0, j = queue.length; i < j; i++) {
  3193. if (queue[i].scope) {
  3194. queue[i].method.apply(queue[i].scope, args);
  3195. } else {
  3196. queue[i].method.apply(this, args);
  3197. }
  3198. if (!canRetry) {
  3199. // useFlashBlock and SWF timeout case doesn't count here.
  3200. queue[i].fired = true;
  3201. }
  3202. }
  3203. }
  3204. return true;
  3205. };
  3206. initUserOnload = function() {
  3207. window.setTimeout(function() {
  3208. if (sm2.useFlashBlock) {
  3209. flashBlockHandler();
  3210. }
  3211. processOnEvents();
  3212. // call user-defined "onload", scoped to window
  3213. if (typeof sm2.onload === 'function') {
  3214. _wDS('onload', 1);
  3215. sm2.onload.apply(window);
  3216. _wDS('onloadOK', 1);
  3217. }
  3218. if (sm2.waitForWindowLoad) {
  3219. event.add(window, 'load', initUserOnload);
  3220. }
  3221. },1);
  3222. };
  3223. detectFlash = function() {
  3224. // hat tip: Flash Detect library (BSD, (C) 2007) by Carl "DocYes" S. Yestrau - http://featureblend.com/javascript-flash-detection-library.html / http://featureblend.com/license.txt
  3225. if (hasFlash !== _undefined) {
  3226. // this work has already been done.
  3227. return hasFlash;
  3228. }
  3229. var hasPlugin = false, n = navigator, nP = n.plugins, obj, type, types, AX = window.ActiveXObject;
  3230. if (nP && nP.length) {
  3231. type = 'application/x-shockwave-flash';
  3232. types = n.mimeTypes;
  3233. if (types && types[type] && types[type].enabledPlugin && types[type].enabledPlugin.description) {
  3234. hasPlugin = true;
  3235. }
  3236. } else if (AX !== _undefined && !ua.match(/MSAppHost/i)) {
  3237. // Windows 8 Store Apps (MSAppHost) are weird (compatibility?) and won't complain here, but will barf if Flash/ActiveX object is appended to the DOM.
  3238. try {
  3239. obj = new AX('ShockwaveFlash.ShockwaveFlash');
  3240. } catch(e) {
  3241. // oh well
  3242. }
  3243. hasPlugin = (!!obj);
  3244. // cleanup, because it is ActiveX after all
  3245. obj = null;
  3246. }
  3247. hasFlash = hasPlugin;
  3248. return hasPlugin;
  3249. };
  3250. featureCheck = function() {
  3251. var needsFlash,
  3252. item,
  3253. result = true,
  3254. formats = sm2.audioFormats,
  3255. // iPhone <= 3.1 has broken HTML5 audio(), but firmware 3.2 (original iPad) + iOS4 works.
  3256. isSpecial = (is_iDevice && !!(ua.match(/os (1|2|3_0|3_1)/i)));
  3257. if (isSpecial) {
  3258. // has Audio(), but is broken; let it load links directly.
  3259. sm2.hasHTML5 = false;
  3260. // ignore flash case, however
  3261. sm2.html5Only = true;
  3262. if (sm2.oMC) {
  3263. sm2.oMC.style.display = 'none';
  3264. }
  3265. result = false;
  3266. } else {
  3267. if (sm2.useHTML5Audio) {
  3268. if (!sm2.html5 || !sm2.html5.canPlayType) {
  3269. sm2._wD('SoundManager: No HTML5 Audio() support detected.');
  3270. sm2.hasHTML5 = false;
  3271. }
  3272. // <d>
  3273. if (isBadSafari) {
  3274. sm2._wD(smc + 'Note: Buggy HTML5 Audio in Safari on this OS X release, see https://bugs.webkit.org/show_bug.cgi?id=32159 - ' + (!hasFlash ?' would use flash fallback for MP3/MP4, but none detected.' : 'will use flash fallback for MP3/MP4, if available'), 1);
  3275. }
  3276. // </d>
  3277. }
  3278. }
  3279. if (sm2.useHTML5Audio && sm2.hasHTML5) {
  3280. for (item in formats) {
  3281. if (formats.hasOwnProperty(item)) {
  3282. if ((formats[item].required && !sm2.html5.canPlayType(formats[item].type)) || (sm2.preferFlash && (sm2.flash[item] || sm2.flash[formats[item].type]))) {
  3283. // flash may be required, or preferred for this format
  3284. needsFlash = true;
  3285. }
  3286. }
  3287. }
  3288. }
  3289. // sanity check...
  3290. if (sm2.ignoreFlash) {
  3291. needsFlash = false;
  3292. }
  3293. sm2.html5Only = (sm2.hasHTML5 && sm2.useHTML5Audio && !needsFlash);
  3294. return (!sm2.html5Only);
  3295. };
  3296. parseURL = function(url) {
  3297. /**
  3298. * Internal: Finds and returns the first playable URL (or failing that, the first URL.)
  3299. * @param {string or array} url A single URL string, OR, an array of URL strings or {url:'/path/to/resource', type:'audio/mp3'} objects.
  3300. */
  3301. var i, j, urlResult = 0, result;
  3302. if (url instanceof Array) {
  3303. // find the first good one
  3304. for (i=0, j=url.length; i<j; i++) {
  3305. if (url[i] instanceof Object) {
  3306. // MIME check
  3307. if (sm2.canPlayMIME(url[i].type)) {
  3308. urlResult = i;
  3309. break;
  3310. }
  3311. } else if (sm2.canPlayURL(url[i])) {
  3312. // URL string check
  3313. urlResult = i;
  3314. break;
  3315. }
  3316. }
  3317. // normalize to string
  3318. if (url[urlResult].url) {
  3319. url[urlResult] = url[urlResult].url;
  3320. }
  3321. result = url[urlResult];
  3322. } else {
  3323. // single URL case
  3324. result = url;
  3325. }
  3326. return result;
  3327. };
  3328. startTimer = function(oSound) {
  3329. /**
  3330. * attach a timer to this sound, and start an interval if needed
  3331. */
  3332. if (!oSound._hasTimer) {
  3333. oSound._hasTimer = true;
  3334. if (!mobileHTML5 && sm2.html5PollingInterval) {
  3335. if (h5IntervalTimer === null && h5TimerCount === 0) {
  3336. h5IntervalTimer = setInterval(timerExecute, sm2.html5PollingInterval);
  3337. }
  3338. h5TimerCount++;
  3339. }
  3340. }
  3341. };
  3342. stopTimer = function(oSound) {
  3343. /**
  3344. * detach a timer
  3345. */
  3346. if (oSound._hasTimer) {
  3347. oSound._hasTimer = false;
  3348. if (!mobileHTML5 && sm2.html5PollingInterval) {
  3349. // interval will stop itself at next execution.
  3350. h5TimerCount--;
  3351. }
  3352. }
  3353. };
  3354. timerExecute = function() {
  3355. /**
  3356. * manual polling for HTML5 progress events, ie., whileplaying() (can achieve greater precision than conservative default HTML5 interval)
  3357. */
  3358. var i;
  3359. if (h5IntervalTimer !== null && !h5TimerCount) {
  3360. // no active timers, stop polling interval.
  3361. clearInterval(h5IntervalTimer);
  3362. h5IntervalTimer = null;
  3363. return false;
  3364. }
  3365. // check all HTML5 sounds with timers
  3366. for (i = sm2.soundIDs.length-1; i >= 0; i--) {
  3367. if (sm2.sounds[sm2.soundIDs[i]].isHTML5 && sm2.sounds[sm2.soundIDs[i]]._hasTimer) {
  3368. sm2.sounds[sm2.soundIDs[i]]._onTimer();
  3369. }
  3370. }
  3371. };
  3372. catchError = function(options) {
  3373. options = (options !== _undefined ? options : {});
  3374. if (typeof sm2.onerror === 'function') {
  3375. sm2.onerror.apply(window, [{type:(options.type !== _undefined ? options.type : null)}]);
  3376. }
  3377. if (options.fatal !== _undefined && options.fatal) {
  3378. sm2.disable();
  3379. }
  3380. };
  3381. badSafariFix = function() {
  3382. // special case: "bad" Safari (OS X 10.3 - 10.7) must fall back to flash for MP3/MP4
  3383. if (!isBadSafari || !detectFlash()) {
  3384. // doesn't apply
  3385. return false;
  3386. }
  3387. var aF = sm2.audioFormats, i, item;
  3388. for (item in aF) {
  3389. if (aF.hasOwnProperty(item)) {
  3390. if (item === 'mp3' || item === 'mp4') {
  3391. sm2._wD(sm + ': Using flash fallback for ' + item + ' format');
  3392. sm2.html5[item] = false;
  3393. // assign result to related formats, too
  3394. if (aF[item] && aF[item].related) {
  3395. for (i = aF[item].related.length-1; i >= 0; i--) {
  3396. sm2.html5[aF[item].related[i]] = false;
  3397. }
  3398. }
  3399. }
  3400. }
  3401. }
  3402. };
  3403. /**
  3404. * Pseudo-private flash/ExternalInterface methods
  3405. * ----------------------------------------------
  3406. */
  3407. this._setSandboxType = function(sandboxType) {
  3408. // <d>
  3409. var sb = sm2.sandbox;
  3410. sb.type = sandboxType;
  3411. sb.description = sb.types[(sb.types[sandboxType] !== _undefined?sandboxType:'unknown')];
  3412. if (sb.type === 'localWithFile') {
  3413. sb.noRemote = true;
  3414. sb.noLocal = false;
  3415. _wDS('secNote', 2);
  3416. } else if (sb.type === 'localWithNetwork') {
  3417. sb.noRemote = false;
  3418. sb.noLocal = true;
  3419. } else if (sb.type === 'localTrusted') {
  3420. sb.noRemote = false;
  3421. sb.noLocal = false;
  3422. }
  3423. // </d>
  3424. };
  3425. this._externalInterfaceOK = function(flashDate, swfVersion) {
  3426. // flash callback confirming flash loaded, EI working etc.
  3427. // flashDate = approx. timing/delay info for JS/flash bridge
  3428. // swfVersion: SWF build string
  3429. if (sm2.swfLoaded) {
  3430. return false;
  3431. }
  3432. var e;
  3433. debugTS('swf', true);
  3434. debugTS('flashtojs', true);
  3435. sm2.swfLoaded = true;
  3436. tryInitOnFocus = false;
  3437. if (isBadSafari) {
  3438. badSafariFix();
  3439. }
  3440. // complain if JS + SWF build/version strings don't match, excluding +DEV builds
  3441. // <d>
  3442. if (!swfVersion || swfVersion.replace(/\+dev/i,'') !== sm2.versionNumber.replace(/\+dev/i, '')) {
  3443. e = sm + ': Fatal: JavaScript file build "' + sm2.versionNumber + '" does not match Flash SWF build "' + swfVersion + '" at ' + sm2.url + '. Ensure both are up-to-date.';
  3444. // escape flash -> JS stack so this error fires in window.
  3445. setTimeout(function versionMismatch() {
  3446. throw new Error(e);
  3447. }, 0);
  3448. // exit, init will fail with timeout
  3449. return false;
  3450. }
  3451. // </d>
  3452. // slight delay before init
  3453. setTimeout(init, isIE ? 100 : 1);
  3454. };
  3455. /**
  3456. * Private initialization helpers
  3457. * ------------------------------
  3458. */
  3459. createMovie = function(smID, smURL) {
  3460. if (didAppend && appendSuccess) {
  3461. // ignore if already succeeded
  3462. return false;
  3463. }
  3464. function initMsg() {
  3465. // <d>
  3466. var options = [], title, str = [], delimiter = ' + ';
  3467. title = 'SoundManager ' + sm2.version + (!sm2.html5Only && sm2.useHTML5Audio ? (sm2.hasHTML5 ? ' + HTML5 audio' : ', no HTML5 audio support') : '');
  3468. if (!sm2.html5Only) {
  3469. if (sm2.preferFlash) {
  3470. options.push('preferFlash');
  3471. }
  3472. if (sm2.useHighPerformance) {
  3473. options.push('useHighPerformance');
  3474. }
  3475. if (sm2.flashPollingInterval) {
  3476. options.push('flashPollingInterval (' + sm2.flashPollingInterval + 'ms)');
  3477. }
  3478. if (sm2.html5PollingInterval) {
  3479. options.push('html5PollingInterval (' + sm2.html5PollingInterval + 'ms)');
  3480. }
  3481. if (sm2.wmode) {
  3482. options.push('wmode (' + sm2.wmode + ')');
  3483. }
  3484. if (sm2.debugFlash) {
  3485. options.push('debugFlash');
  3486. }
  3487. if (sm2.useFlashBlock) {
  3488. options.push('flashBlock');
  3489. }
  3490. } else {
  3491. if (sm2.html5PollingInterval) {
  3492. options.push('html5PollingInterval (' + sm2.html5PollingInterval + 'ms)');
  3493. }
  3494. }
  3495. if (options.length) {
  3496. str = str.concat([options.join(delimiter)]);
  3497. }
  3498. sm2._wD(title + (str.length ? delimiter + str.join(', ') : ''), 1);
  3499. showSupport();
  3500. // </d>
  3501. }
  3502. if (sm2.html5Only) {
  3503. // 100% HTML5 mode
  3504. setVersionInfo();
  3505. initMsg();
  3506. sm2.oMC = id(sm2.movieID);
  3507. init();
  3508. // prevent multiple init attempts
  3509. didAppend = true;
  3510. appendSuccess = true;
  3511. return false;
  3512. }
  3513. // flash path
  3514. var remoteURL = (smURL || sm2.url),
  3515. localURL = (sm2.altURL || remoteURL),
  3516. swfTitle = 'JS/Flash audio component (SoundManager 2)',
  3517. oTarget = getDocument(),
  3518. extraClass = getSWFCSS(),
  3519. isRTL = null,
  3520. html = doc.getElementsByTagName('html')[0],
  3521. oEmbed, oMovie, tmp, movieHTML, oEl, s, x, sClass;
  3522. isRTL = (html && html.dir && html.dir.match(/rtl/i));
  3523. smID = (smID === _undefined?sm2.id:smID);
  3524. function param(name, value) {
  3525. return '<param name="'+name+'" value="'+value+'" />';
  3526. }
  3527. // safety check for legacy (change to Flash 9 URL)
  3528. setVersionInfo();
  3529. sm2.url = normalizeMovieURL(overHTTP?remoteURL:localURL);
  3530. smURL = sm2.url;
  3531. sm2.wmode = (!sm2.wmode && sm2.useHighPerformance ? 'transparent' : sm2.wmode);
  3532. if (sm2.wmode !== null && (ua.match(/msie 8/i) || (!isIE && !sm2.useHighPerformance)) && navigator.platform.match(/win32|win64/i)) {
  3533. /**
  3534. * extra-special case: movie doesn't load until scrolled into view when using wmode = anything but 'window' here
  3535. * does not apply when using high performance (position:fixed means on-screen), OR infinite flash load timeout
  3536. * wmode breaks IE 8 on Vista + Win7 too in some cases, as of January 2011 (?)
  3537. */
  3538. messages.push(strings.spcWmode);
  3539. sm2.wmode = null;
  3540. }
  3541. oEmbed = {
  3542. 'name': smID,
  3543. 'id': smID,
  3544. 'src': smURL,
  3545. 'quality': 'high',
  3546. 'allowScriptAccess': sm2.allowScriptAccess,
  3547. 'bgcolor': sm2.bgColor,
  3548. 'pluginspage': http+'www.macromedia.com/go/getflashplayer',
  3549. 'title': swfTitle,
  3550. 'type': 'application/x-shockwave-flash',
  3551. 'wmode': sm2.wmode,
  3552. // http://help.adobe.com/en_US/as3/mobile/WS4bebcd66a74275c36cfb8137124318eebc6-7ffd.html
  3553. 'hasPriority': 'true'
  3554. };
  3555. if (sm2.debugFlash) {
  3556. oEmbed.FlashVars = 'debug=1';
  3557. }
  3558. if (!sm2.wmode) {
  3559. // don't write empty attribute
  3560. delete oEmbed.wmode;
  3561. }
  3562. if (isIE) {
  3563. // IE is "special".
  3564. oMovie = doc.createElement('div');
  3565. movieHTML = [
  3566. '<object id="' + smID + '" data="' + smURL + '" type="' + oEmbed.type + '" title="' + oEmbed.title +'" classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000" codebase="' + http+'download.macromedia.com/pub/shockwave/cabs/flash/swflash.cab#version=6,0,40,0">',
  3567. param('movie', smURL),
  3568. param('AllowScriptAccess', sm2.allowScriptAccess),
  3569. param('quality', oEmbed.quality),
  3570. (sm2.wmode? param('wmode', sm2.wmode): ''),
  3571. param('bgcolor', sm2.bgColor),
  3572. param('hasPriority', 'true'),
  3573. (sm2.debugFlash ? param('FlashVars', oEmbed.FlashVars) : ''),
  3574. '</object>'
  3575. ].join('');
  3576. } else {
  3577. oMovie = doc.createElement('embed');
  3578. for (tmp in oEmbed) {
  3579. if (oEmbed.hasOwnProperty(tmp)) {
  3580. oMovie.setAttribute(tmp, oEmbed[tmp]);
  3581. }
  3582. }
  3583. }
  3584. initDebug();
  3585. extraClass = getSWFCSS();
  3586. oTarget = getDocument();
  3587. if (oTarget) {
  3588. sm2.oMC = (id(sm2.movieID) || doc.createElement('div'));
  3589. if (!sm2.oMC.id) {
  3590. sm2.oMC.id = sm2.movieID;
  3591. sm2.oMC.className = swfCSS.swfDefault + ' ' + extraClass;
  3592. s = null;
  3593. oEl = null;
  3594. if (!sm2.useFlashBlock) {
  3595. if (sm2.useHighPerformance) {
  3596. // on-screen at all times
  3597. s = {
  3598. 'position': 'fixed',
  3599. 'width': '8px',
  3600. 'height': '8px',
  3601. // >= 6px for flash to run fast, >= 8px to start up under Firefox/win32 in some cases. odd? yes.
  3602. 'bottom': '0px',
  3603. 'left': '0px',
  3604. 'overflow': 'hidden'
  3605. };
  3606. } else {
  3607. // hide off-screen, lower priority
  3608. s = {
  3609. 'position': 'absolute',
  3610. 'width': '6px',
  3611. 'height': '6px',
  3612. 'top': '-9999px',
  3613. 'left': '-9999px'
  3614. };
  3615. if (isRTL) {
  3616. s.left = Math.abs(parseInt(s.left,10))+'px';
  3617. }
  3618. }
  3619. }
  3620. if (isWebkit) {
  3621. // soundcloud-reported render/crash fix, safari 5
  3622. sm2.oMC.style.zIndex = 10000;
  3623. }
  3624. if (!sm2.debugFlash) {
  3625. for (x in s) {
  3626. if (s.hasOwnProperty(x)) {
  3627. sm2.oMC.style[x] = s[x];
  3628. }
  3629. }
  3630. }
  3631. try {
  3632. if (!isIE) {
  3633. sm2.oMC.appendChild(oMovie);
  3634. }
  3635. oTarget.appendChild(sm2.oMC);
  3636. if (isIE) {
  3637. oEl = sm2.oMC.appendChild(doc.createElement('div'));
  3638. oEl.className = swfCSS.swfBox;
  3639. oEl.innerHTML = movieHTML;
  3640. }
  3641. appendSuccess = true;
  3642. } catch(e) {
  3643. throw new Error(str('domError')+' \n'+e.toString());
  3644. }
  3645. } else {
  3646. // SM2 container is already in the document (eg. flashblock use case)
  3647. sClass = sm2.oMC.className;
  3648. sm2.oMC.className = (sClass?sClass+' ':swfCSS.swfDefault) + (extraClass?' '+extraClass:'');
  3649. sm2.oMC.appendChild(oMovie);
  3650. if (isIE) {
  3651. oEl = sm2.oMC.appendChild(doc.createElement('div'));
  3652. oEl.className = swfCSS.swfBox;
  3653. oEl.innerHTML = movieHTML;
  3654. }
  3655. appendSuccess = true;
  3656. }
  3657. }
  3658. didAppend = true;
  3659. initMsg();
  3660. // sm2._wD(sm + ': Trying to load ' + smURL + (!overHTTP && sm2.altURL ? ' (alternate URL)' : ''), 1);
  3661. return true;
  3662. };
  3663. initMovie = function() {
  3664. if (sm2.html5Only) {
  3665. createMovie();
  3666. return false;
  3667. }
  3668. // attempt to get, or create, movie (may already exist)
  3669. if (flash) {
  3670. return false;
  3671. }
  3672. if (!sm2.url) {
  3673. /**
  3674. * Something isn't right - we've reached init, but the soundManager url property has not been set.
  3675. * User has not called setup({url: ...}), or has not set soundManager.url (legacy use case) directly before init time.
  3676. * Notify and exit. If user calls setup() with a url: property, init will be restarted as in the deferred loading case.
  3677. */
  3678. _wDS('noURL');
  3679. return false;
  3680. }
  3681. // inline markup case
  3682. flash = sm2.getMovie(sm2.id);
  3683. if (!flash) {
  3684. if (!oRemoved) {
  3685. // try to create
  3686. createMovie(sm2.id, sm2.url);
  3687. } else {
  3688. // try to re-append removed movie after reboot()
  3689. if (!isIE) {
  3690. sm2.oMC.appendChild(oRemoved);
  3691. } else {
  3692. sm2.oMC.innerHTML = oRemovedHTML;
  3693. }
  3694. oRemoved = null;
  3695. didAppend = true;
  3696. }
  3697. flash = sm2.getMovie(sm2.id);
  3698. }
  3699. if (typeof sm2.oninitmovie === 'function') {
  3700. setTimeout(sm2.oninitmovie, 1);
  3701. }
  3702. // <d>
  3703. flushMessages();
  3704. // </d>
  3705. return true;
  3706. };
  3707. delayWaitForEI = function() {
  3708. setTimeout(waitForEI, 1000);
  3709. };
  3710. waitForEI = function() {
  3711. var p,
  3712. loadIncomplete = false;
  3713. if (!sm2.url) {
  3714. // No SWF url to load (noURL case) - exit for now. Will be retried when url is set.
  3715. return false;
  3716. }
  3717. if (waitingForEI) {
  3718. return false;
  3719. }
  3720. waitingForEI = true;
  3721. event.remove(window, 'load', delayWaitForEI);
  3722. if (tryInitOnFocus && !isFocused) {
  3723. // Safari won't load flash in background tabs, only when focused.
  3724. _wDS('waitFocus');
  3725. return false;
  3726. }
  3727. if (!didInit) {
  3728. p = sm2.getMoviePercent();
  3729. if (p > 0 && p < 100) {
  3730. loadIncomplete = true;
  3731. }
  3732. }
  3733. setTimeout(function() {
  3734. p = sm2.getMoviePercent();
  3735. if (loadIncomplete) {
  3736. // special case: if movie *partially* loaded, retry until it's 100% before assuming failure.
  3737. waitingForEI = false;
  3738. sm2._wD(str('waitSWF'));
  3739. window.setTimeout(delayWaitForEI, 1);
  3740. return false;
  3741. }
  3742. // <d>
  3743. if (!didInit) {
  3744. sm2._wD(sm + ': No Flash response within expected time. Likely causes: ' + (p === 0 ? 'SWF load failed, ':'') + 'Flash blocked or JS-Flash security error.' + (sm2.debugFlash?' ' + str('checkSWF'):''), 2);
  3745. if (!overHTTP && p) {
  3746. _wDS('localFail', 2);
  3747. if (!sm2.debugFlash) {
  3748. _wDS('tryDebug', 2);
  3749. }
  3750. }
  3751. if (p === 0) {
  3752. // if 0 (not null), probably a 404.
  3753. sm2._wD(str('swf404', sm2.url), 1);
  3754. }
  3755. debugTS('flashtojs', false, ': Timed out' + overHTTP?' (Check flash security or flash blockers)':' (No plugin/missing SWF?)');
  3756. }
  3757. // </d>
  3758. // give up / time-out, depending
  3759. if (!didInit && okToDisable) {
  3760. if (p === null) {
  3761. // SWF failed. Maybe blocked.
  3762. if (sm2.useFlashBlock || sm2.flashLoadTimeout === 0) {
  3763. if (sm2.useFlashBlock) {
  3764. flashBlockHandler();
  3765. }
  3766. _wDS('waitForever');
  3767. } else {
  3768. // no custom flash block handling, but SWF has timed out. Will recover if user unblocks / allows SWF load.
  3769. _wDS('waitForever');
  3770. // fire any regular registered ontimeout() listeners.
  3771. processOnEvents({type:'ontimeout', ignoreInit: true});
  3772. }
  3773. } else {
  3774. // flash loaded? Shouldn't be a blocking issue, then.
  3775. if (sm2.flashLoadTimeout === 0) {
  3776. _wDS('waitForever');
  3777. } else {
  3778. failSafely(true);
  3779. }
  3780. }
  3781. }
  3782. }, sm2.flashLoadTimeout);
  3783. };
  3784. handleFocus = function() {
  3785. function cleanup() {
  3786. event.remove(window, 'focus', handleFocus);
  3787. }
  3788. if (isFocused || !tryInitOnFocus) {
  3789. // already focused, or not special Safari background tab case
  3790. cleanup();
  3791. return true;
  3792. }
  3793. okToDisable = true;
  3794. isFocused = true;
  3795. _wDS('gotFocus');
  3796. // allow init to restart
  3797. waitingForEI = false;
  3798. // kick off ExternalInterface timeout, now that the SWF has started
  3799. delayWaitForEI();
  3800. cleanup();
  3801. return true;
  3802. };
  3803. flushMessages = function() {
  3804. // <d>
  3805. // SM2 pre-init debug messages
  3806. if (messages.length) {
  3807. sm2._wD('SoundManager 2: ' + messages.join(' '), 1);
  3808. messages = [];
  3809. }
  3810. // </d>
  3811. };
  3812. showSupport = function() {
  3813. // <d>
  3814. flushMessages();
  3815. var item, tests = [];
  3816. if (sm2.useHTML5Audio && sm2.hasHTML5) {
  3817. for (item in sm2.audioFormats) {
  3818. if (sm2.audioFormats.hasOwnProperty(item)) {
  3819. tests.push(item + ' = ' + sm2.html5[item] + (!sm2.html5[item] && hasFlash && sm2.flash[item] ? ' (using flash)' : (sm2.preferFlash && sm2.flash[item] && hasFlash ? ' (preferring flash)': (!sm2.html5[item] ? ' (' + (sm2.audioFormats[item].required ? 'required, ':'') + 'and no flash support)' : ''))));
  3820. }
  3821. }
  3822. sm2._wD('SoundManager 2 HTML5 support: ' + tests.join(', '), 1);
  3823. }
  3824. // </d>
  3825. };
  3826. initComplete = function(bNoDisable) {
  3827. if (didInit) {
  3828. return false;
  3829. }
  3830. if (sm2.html5Only) {
  3831. // all good.
  3832. _wDS('sm2Loaded');
  3833. didInit = true;
  3834. initUserOnload();
  3835. debugTS('onload', true);
  3836. return true;
  3837. }
  3838. var wasTimeout = (sm2.useFlashBlock && sm2.flashLoadTimeout && !sm2.getMoviePercent()),
  3839. result = true,
  3840. error;
  3841. if (!wasTimeout) {
  3842. didInit = true;
  3843. if (disabled) {
  3844. error = {type: (!hasFlash && needsFlash ? 'NO_FLASH' : 'INIT_TIMEOUT')};
  3845. }
  3846. }
  3847. sm2._wD('SoundManager 2 ' + (disabled ? 'failed to load' : 'loaded') + ' (' + (disabled ? 'Flash security/load error' : 'OK') + ')', disabled ? 2: 1);
  3848. if (disabled || bNoDisable) {
  3849. if (sm2.useFlashBlock && sm2.oMC) {
  3850. sm2.oMC.className = getSWFCSS() + ' ' + (sm2.getMoviePercent() === null?swfCSS.swfTimedout:swfCSS.swfError);
  3851. }
  3852. processOnEvents({type:'ontimeout', error:error, ignoreInit: true});
  3853. debugTS('onload', false);
  3854. catchError(error);
  3855. result = false;
  3856. } else {
  3857. debugTS('onload', true);
  3858. }
  3859. if (!disabled) {
  3860. if (sm2.waitForWindowLoad && !windowLoaded) {
  3861. _wDS('waitOnload');
  3862. event.add(window, 'load', initUserOnload);
  3863. } else {
  3864. // <d>
  3865. if (sm2.waitForWindowLoad && windowLoaded) {
  3866. _wDS('docLoaded');
  3867. }
  3868. // </d>
  3869. initUserOnload();
  3870. }
  3871. }
  3872. return result;
  3873. };
  3874. /**
  3875. * apply top-level setupOptions object as local properties, eg., this.setupOptions.flashVersion -> this.flashVersion (soundManager.flashVersion)
  3876. * this maintains backward compatibility, and allows properties to be defined separately for use by soundManager.setup().
  3877. */
  3878. setProperties = function() {
  3879. var i,
  3880. o = sm2.setupOptions;
  3881. for (i in o) {
  3882. if (o.hasOwnProperty(i)) {
  3883. // assign local property if not already defined
  3884. if (sm2[i] === _undefined) {
  3885. sm2[i] = o[i];
  3886. } else if (sm2[i] !== o[i]) {
  3887. // legacy support: write manually-assigned property (eg., soundManager.url) back to setupOptions to keep things in sync
  3888. sm2.setupOptions[i] = sm2[i];
  3889. }
  3890. }
  3891. }
  3892. };
  3893. init = function() {
  3894. // called after onload()
  3895. if (didInit) {
  3896. _wDS('didInit');
  3897. return false;
  3898. }
  3899. function cleanup() {
  3900. event.remove(window, 'load', sm2.beginDelayedInit);
  3901. }
  3902. if (sm2.html5Only) {
  3903. if (!didInit) {
  3904. // we don't need no steenking flash!
  3905. cleanup();
  3906. sm2.enabled = true;
  3907. initComplete();
  3908. }
  3909. return true;
  3910. }
  3911. // flash path
  3912. initMovie();
  3913. try {
  3914. // attempt to talk to Flash
  3915. flash._externalInterfaceTest(false);
  3916. // apply user-specified polling interval, OR, if "high performance" set, faster vs. default polling
  3917. // (determines frequency of whileloading/whileplaying callbacks, effectively driving UI framerates)
  3918. setPolling(true, (sm2.flashPollingInterval || (sm2.useHighPerformance ? 10 : 50)));
  3919. if (!sm2.debugMode) {
  3920. // stop the SWF from making debug output calls to JS
  3921. flash._disableDebug();
  3922. }
  3923. sm2.enabled = true;
  3924. debugTS('jstoflash', true);
  3925. if (!sm2.html5Only) {
  3926. // prevent browser from showing cached page state (or rather, restoring "suspended" page state) via back button, because flash may be dead
  3927. // http://www.webkit.org/blog/516/webkit-page-cache-ii-the-unload-event/
  3928. event.add(window, 'unload', doNothing);
  3929. }
  3930. } catch(e) {
  3931. sm2._wD('js/flash exception: ' + e.toString());
  3932. debugTS('jstoflash', false);
  3933. catchError({type:'JS_TO_FLASH_EXCEPTION', fatal:true});
  3934. // don't disable, for reboot()
  3935. failSafely(true);
  3936. initComplete();
  3937. return false;
  3938. }
  3939. initComplete();
  3940. // disconnect events
  3941. cleanup();
  3942. return true;
  3943. };
  3944. domContentLoaded = function() {
  3945. if (didDCLoaded) {
  3946. return false;
  3947. }
  3948. didDCLoaded = true;
  3949. // assign top-level soundManager properties eg. soundManager.url
  3950. setProperties();
  3951. initDebug();
  3952. /**
  3953. * Temporary feature: allow force of HTML5 via URL params: sm2-usehtml5audio=0 or 1
  3954. * Ditto for sm2-preferFlash, too.
  3955. */
  3956. // <d>
  3957. (function(){
  3958. var a = 'sm2-usehtml5audio=',
  3959. a2 = 'sm2-preferflash=',
  3960. b = null,
  3961. b2 = null,
  3962. hasCon = (window.console !== _undefined && typeof console.log === 'function'),
  3963. l = wl.toLowerCase();
  3964. if (l.indexOf(a) !== -1) {
  3965. b = (l.charAt(l.indexOf(a)+a.length) === '1');
  3966. if (hasCon) {
  3967. console.log((b?'Enabling ':'Disabling ')+'useHTML5Audio via URL parameter');
  3968. }
  3969. sm2.setup({
  3970. 'useHTML5Audio': b
  3971. });
  3972. }
  3973. if (l.indexOf(a2) !== -1) {
  3974. b2 = (l.charAt(l.indexOf(a2)+a2.length) === '1');
  3975. if (hasCon) {
  3976. console.log((b2?'Enabling ':'Disabling ')+'preferFlash via URL parameter');
  3977. }
  3978. sm2.setup({
  3979. 'preferFlash': b2
  3980. });
  3981. }
  3982. }());
  3983. // </d>
  3984. if (!hasFlash && sm2.hasHTML5) {
  3985. sm2._wD('SoundManager: No Flash detected' + (!sm2.useHTML5Audio ? ', enabling HTML5.' : '. Trying HTML5-only mode.'), 1);
  3986. sm2.setup({
  3987. 'useHTML5Audio': true,
  3988. // make sure we aren't preferring flash, either
  3989. // TODO: preferFlash should not matter if flash is not installed. Currently, stuff breaks without the below tweak.
  3990. 'preferFlash': false
  3991. });
  3992. }
  3993. testHTML5();
  3994. sm2.html5.usingFlash = featureCheck();
  3995. needsFlash = sm2.html5.usingFlash;
  3996. if (!hasFlash && needsFlash) {
  3997. messages.push(strings.needFlash);
  3998. // TODO: Fatal here vs. timeout approach, etc.
  3999. // hack: fail sooner.
  4000. sm2.setup({
  4001. 'flashLoadTimeout': 1
  4002. });
  4003. }
  4004. if (doc.removeEventListener) {
  4005. doc.removeEventListener('DOMContentLoaded', domContentLoaded, false);
  4006. }
  4007. initMovie();
  4008. return true;
  4009. };
  4010. domContentLoadedIE = function() {
  4011. if (doc.readyState === 'complete') {
  4012. domContentLoaded();
  4013. doc.detachEvent('onreadystatechange', domContentLoadedIE);
  4014. }
  4015. return true;
  4016. };
  4017. winOnLoad = function() {
  4018. // catch edge case of initComplete() firing after window.load()
  4019. windowLoaded = true;
  4020. event.remove(window, 'load', winOnLoad);
  4021. };
  4022. /**
  4023. * miscellaneous run-time, pre-init stuff
  4024. */
  4025. preInit = function() {
  4026. if (mobileHTML5) {
  4027. // prefer HTML5 for mobile + tablet-like devices, probably more reliable vs. flash at this point.
  4028. // <d>
  4029. if (!sm2.setupOptions.useHTML5Audio || sm2.setupOptions.preferFlash) {
  4030. // notify that defaults are being changed.
  4031. messages.push(strings.mobileUA);
  4032. }
  4033. // </d>
  4034. sm2.setupOptions.useHTML5Audio = true;
  4035. sm2.setupOptions.preferFlash = false;
  4036. if (is_iDevice || (isAndroid && !ua.match(/android\s2\.3/i))) {
  4037. // iOS and Android devices tend to work better with a single audio instance, specifically for chained playback of sounds in sequence.
  4038. // common use case: exiting sound onfinish() -> createSound() -> play()
  4039. // <d>
  4040. messages.push(strings.globalHTML5);
  4041. // </d>
  4042. if (is_iDevice) {
  4043. sm2.ignoreFlash = true;
  4044. }
  4045. useGlobalHTML5Audio = true;
  4046. }
  4047. }
  4048. };
  4049. preInit();
  4050. // sniff up-front
  4051. detectFlash();
  4052. // focus and window load, init (primarily flash-driven)
  4053. event.add(window, 'focus', handleFocus);
  4054. event.add(window, 'load', delayWaitForEI);
  4055. event.add(window, 'load', winOnLoad);
  4056. if (doc.addEventListener) {
  4057. doc.addEventListener('DOMContentLoaded', domContentLoaded, false);
  4058. } else if (doc.attachEvent) {
  4059. doc.attachEvent('onreadystatechange', domContentLoadedIE);
  4060. } else {
  4061. // no add/attachevent support - safe to assume no JS -> Flash either
  4062. debugTS('onload', false);
  4063. catchError({type:'NO_DOM2_EVENTS', fatal:true});
  4064. }
  4065. } // SoundManager()
  4066. // SM2_DEFER details: http://www.schillmania.com/projects/soundmanager2/doc/getstarted/#lazy-loading
  4067. if (window.SM2_DEFER === undefined || !SM2_DEFER) {
  4068. soundManager = new SoundManager();
  4069. }
  4070. /**
  4071. * SoundManager public interfaces
  4072. * ------------------------------
  4073. */
  4074. window.SoundManager = SoundManager; // constructor
  4075. window.soundManager = soundManager; // public API, flash callbacks etc.
  4076. }(window));