soundmanager2.js 150 KB

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