video.dev.js 224 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427242824292430243124322433243424352436243724382439244024412442244324442445244624472448244924502451245224532454245524562457245824592460246124622463246424652466246724682469247024712472247324742475247624772478247924802481248224832484248524862487248824892490249124922493249424952496249724982499250025012502250325042505250625072508250925102511251225132514251525162517251825192520252125222523252425252526252725282529253025312532253325342535253625372538253925402541254225432544254525462547254825492550255125522553255425552556255725582559256025612562256325642565256625672568256925702571257225732574257525762577257825792580258125822583258425852586258725882589259025912592259325942595259625972598259926002601260226032604260526062607260826092610261126122613261426152616261726182619262026212622262326242625262626272628262926302631263226332634263526362637263826392640264126422643264426452646264726482649265026512652265326542655265626572658265926602661266226632664266526662667266826692670267126722673267426752676267726782679268026812682268326842685268626872688268926902691269226932694269526962697269826992700270127022703270427052706270727082709271027112712271327142715271627172718271927202721272227232724272527262727272827292730273127322733273427352736273727382739274027412742274327442745274627472748274927502751275227532754275527562757275827592760276127622763276427652766276727682769277027712772277327742775277627772778277927802781278227832784278527862787278827892790279127922793279427952796279727982799280028012802280328042805280628072808280928102811281228132814281528162817281828192820282128222823282428252826282728282829283028312832283328342835283628372838283928402841284228432844284528462847284828492850285128522853285428552856285728582859286028612862286328642865286628672868286928702871287228732874287528762877287828792880288128822883288428852886288728882889289028912892289328942895289628972898289929002901290229032904290529062907290829092910291129122913291429152916291729182919292029212922292329242925292629272928292929302931293229332934293529362937293829392940294129422943294429452946294729482949295029512952295329542955295629572958295929602961296229632964296529662967296829692970297129722973297429752976297729782979298029812982298329842985298629872988298929902991299229932994299529962997299829993000300130023003300430053006300730083009301030113012301330143015301630173018301930203021302230233024302530263027302830293030303130323033303430353036303730383039304030413042304330443045304630473048304930503051305230533054305530563057305830593060306130623063306430653066306730683069307030713072307330743075307630773078307930803081308230833084308530863087308830893090309130923093309430953096309730983099310031013102310331043105310631073108310931103111311231133114311531163117311831193120312131223123312431253126312731283129313031313132313331343135313631373138313931403141314231433144314531463147314831493150315131523153315431553156315731583159316031613162316331643165316631673168316931703171317231733174317531763177317831793180318131823183318431853186318731883189319031913192319331943195319631973198319932003201320232033204320532063207320832093210321132123213321432153216321732183219322032213222322332243225322632273228322932303231323232333234323532363237323832393240324132423243324432453246324732483249325032513252325332543255325632573258325932603261326232633264326532663267326832693270327132723273327432753276327732783279328032813282328332843285328632873288328932903291329232933294329532963297329832993300330133023303330433053306330733083309331033113312331333143315331633173318331933203321332233233324332533263327332833293330333133323333333433353336333733383339334033413342334333443345334633473348334933503351335233533354335533563357335833593360336133623363336433653366336733683369337033713372337333743375337633773378337933803381338233833384338533863387338833893390339133923393339433953396339733983399340034013402340334043405340634073408340934103411341234133414341534163417341834193420342134223423342434253426342734283429343034313432343334343435343634373438343934403441344234433444344534463447344834493450345134523453345434553456345734583459346034613462346334643465346634673468346934703471347234733474347534763477347834793480348134823483348434853486348734883489349034913492349334943495349634973498349935003501350235033504350535063507350835093510351135123513351435153516351735183519352035213522352335243525352635273528352935303531353235333534353535363537353835393540354135423543354435453546354735483549355035513552355335543555355635573558355935603561356235633564356535663567356835693570357135723573357435753576357735783579358035813582358335843585358635873588358935903591359235933594359535963597359835993600360136023603360436053606360736083609361036113612361336143615361636173618361936203621362236233624362536263627362836293630363136323633363436353636363736383639364036413642364336443645364636473648364936503651365236533654365536563657365836593660366136623663366436653666366736683669367036713672367336743675367636773678367936803681368236833684368536863687368836893690369136923693369436953696369736983699370037013702370337043705370637073708370937103711371237133714371537163717371837193720372137223723372437253726372737283729373037313732373337343735373637373738373937403741374237433744374537463747374837493750375137523753375437553756375737583759376037613762376337643765376637673768376937703771377237733774377537763777377837793780378137823783378437853786378737883789379037913792379337943795379637973798379938003801380238033804380538063807380838093810381138123813381438153816381738183819382038213822382338243825382638273828382938303831383238333834383538363837383838393840384138423843384438453846384738483849385038513852385338543855385638573858385938603861386238633864386538663867386838693870387138723873387438753876387738783879388038813882388338843885388638873888388938903891389238933894389538963897389838993900390139023903390439053906390739083909391039113912391339143915391639173918391939203921392239233924392539263927392839293930393139323933393439353936393739383939394039413942394339443945394639473948394939503951395239533954395539563957395839593960396139623963396439653966396739683969397039713972397339743975397639773978397939803981398239833984398539863987398839893990399139923993399439953996399739983999400040014002400340044005400640074008400940104011401240134014401540164017401840194020402140224023402440254026402740284029403040314032403340344035403640374038403940404041404240434044404540464047404840494050405140524053405440554056405740584059406040614062406340644065406640674068406940704071407240734074407540764077407840794080408140824083408440854086408740884089409040914092409340944095409640974098409941004101410241034104410541064107410841094110411141124113411441154116411741184119412041214122412341244125412641274128412941304131413241334134413541364137413841394140414141424143414441454146414741484149415041514152415341544155415641574158415941604161416241634164416541664167416841694170417141724173417441754176417741784179418041814182418341844185418641874188418941904191419241934194419541964197419841994200420142024203420442054206420742084209421042114212421342144215421642174218421942204221422242234224422542264227422842294230423142324233423442354236423742384239424042414242424342444245424642474248424942504251425242534254425542564257425842594260426142624263426442654266426742684269427042714272427342744275427642774278427942804281428242834284428542864287428842894290429142924293429442954296429742984299430043014302430343044305430643074308430943104311431243134314431543164317431843194320432143224323432443254326432743284329433043314332433343344335433643374338433943404341434243434344434543464347434843494350435143524353435443554356435743584359436043614362436343644365436643674368436943704371437243734374437543764377437843794380438143824383438443854386438743884389439043914392439343944395439643974398439944004401440244034404440544064407440844094410441144124413441444154416441744184419442044214422442344244425442644274428442944304431443244334434443544364437443844394440444144424443444444454446444744484449445044514452445344544455445644574458445944604461446244634464446544664467446844694470447144724473447444754476447744784479448044814482448344844485448644874488448944904491449244934494449544964497449844994500450145024503450445054506450745084509451045114512451345144515451645174518451945204521452245234524452545264527452845294530453145324533453445354536453745384539454045414542454345444545454645474548454945504551455245534554455545564557455845594560456145624563456445654566456745684569457045714572457345744575457645774578457945804581458245834584458545864587458845894590459145924593459445954596459745984599460046014602460346044605460646074608460946104611461246134614461546164617461846194620462146224623462446254626462746284629463046314632463346344635463646374638463946404641464246434644464546464647464846494650465146524653465446554656465746584659466046614662466346644665466646674668466946704671467246734674467546764677467846794680468146824683468446854686468746884689469046914692469346944695469646974698469947004701470247034704470547064707470847094710471147124713471447154716471747184719472047214722472347244725472647274728472947304731473247334734473547364737473847394740474147424743474447454746474747484749475047514752475347544755475647574758475947604761476247634764476547664767476847694770477147724773477447754776477747784779478047814782478347844785478647874788478947904791479247934794479547964797479847994800480148024803480448054806480748084809481048114812481348144815481648174818481948204821482248234824482548264827482848294830483148324833483448354836483748384839484048414842484348444845484648474848484948504851485248534854485548564857485848594860486148624863486448654866486748684869487048714872487348744875487648774878487948804881488248834884488548864887488848894890489148924893489448954896489748984899490049014902490349044905490649074908490949104911491249134914491549164917491849194920492149224923492449254926492749284929493049314932493349344935493649374938493949404941494249434944494549464947494849494950495149524953495449554956495749584959496049614962496349644965496649674968496949704971497249734974497549764977497849794980498149824983498449854986498749884989499049914992499349944995499649974998499950005001500250035004500550065007500850095010501150125013501450155016501750185019502050215022502350245025502650275028502950305031503250335034503550365037503850395040504150425043504450455046504750485049505050515052505350545055505650575058505950605061506250635064506550665067506850695070507150725073507450755076507750785079508050815082508350845085508650875088508950905091509250935094509550965097509850995100510151025103510451055106510751085109511051115112511351145115511651175118511951205121512251235124512551265127512851295130513151325133513451355136513751385139514051415142514351445145514651475148514951505151515251535154515551565157515851595160516151625163516451655166516751685169517051715172517351745175517651775178517951805181518251835184518551865187518851895190519151925193519451955196519751985199520052015202520352045205520652075208520952105211521252135214521552165217521852195220522152225223522452255226522752285229523052315232523352345235523652375238523952405241524252435244524552465247524852495250525152525253525452555256525752585259526052615262526352645265526652675268526952705271527252735274527552765277527852795280528152825283528452855286528752885289529052915292529352945295529652975298529953005301530253035304530553065307530853095310531153125313531453155316531753185319532053215322532353245325532653275328532953305331533253335334533553365337533853395340534153425343534453455346534753485349535053515352535353545355535653575358535953605361536253635364536553665367536853695370537153725373537453755376537753785379538053815382538353845385538653875388538953905391539253935394539553965397539853995400540154025403540454055406540754085409541054115412541354145415541654175418541954205421542254235424542554265427542854295430543154325433543454355436543754385439544054415442544354445445544654475448544954505451545254535454545554565457545854595460546154625463546454655466546754685469547054715472547354745475547654775478547954805481548254835484548554865487548854895490549154925493549454955496549754985499550055015502550355045505550655075508550955105511551255135514551555165517551855195520552155225523552455255526552755285529553055315532553355345535553655375538553955405541554255435544554555465547554855495550555155525553555455555556555755585559556055615562556355645565556655675568556955705571557255735574557555765577557855795580558155825583558455855586558755885589559055915592559355945595559655975598559956005601560256035604560556065607560856095610561156125613561456155616561756185619562056215622562356245625562656275628562956305631563256335634563556365637563856395640564156425643564456455646564756485649565056515652565356545655565656575658565956605661566256635664566556665667566856695670567156725673567456755676567756785679568056815682568356845685568656875688568956905691569256935694569556965697569856995700570157025703570457055706570757085709571057115712571357145715571657175718571957205721572257235724572557265727572857295730573157325733573457355736573757385739574057415742574357445745574657475748574957505751575257535754575557565757575857595760576157625763576457655766576757685769577057715772577357745775577657775778577957805781578257835784578557865787578857895790579157925793579457955796579757985799580058015802580358045805580658075808580958105811581258135814581558165817581858195820582158225823582458255826582758285829583058315832583358345835583658375838583958405841584258435844584558465847584858495850585158525853585458555856585758585859586058615862586358645865586658675868586958705871587258735874587558765877587858795880588158825883588458855886588758885889589058915892589358945895589658975898589959005901590259035904590559065907590859095910591159125913591459155916591759185919592059215922592359245925592659275928592959305931593259335934593559365937593859395940594159425943594459455946594759485949595059515952595359545955595659575958595959605961596259635964596559665967596859695970597159725973597459755976597759785979598059815982598359845985598659875988598959905991599259935994599559965997599859996000600160026003600460056006600760086009601060116012601360146015601660176018601960206021602260236024602560266027602860296030603160326033603460356036603760386039604060416042604360446045604660476048604960506051605260536054605560566057605860596060606160626063606460656066606760686069607060716072607360746075607660776078607960806081608260836084608560866087608860896090609160926093609460956096609760986099610061016102610361046105610661076108610961106111611261136114611561166117611861196120612161226123612461256126612761286129613061316132613361346135613661376138613961406141614261436144614561466147614861496150615161526153615461556156615761586159616061616162616361646165616661676168616961706171617261736174617561766177617861796180618161826183618461856186618761886189619061916192619361946195619661976198619962006201620262036204620562066207620862096210621162126213621462156216621762186219622062216222622362246225622662276228622962306231623262336234623562366237623862396240624162426243624462456246624762486249625062516252625362546255625662576258625962606261626262636264626562666267626862696270627162726273627462756276627762786279628062816282628362846285628662876288628962906291629262936294629562966297629862996300630163026303630463056306630763086309631063116312631363146315631663176318631963206321632263236324632563266327632863296330633163326333633463356336633763386339634063416342634363446345634663476348634963506351635263536354635563566357635863596360636163626363636463656366636763686369637063716372637363746375637663776378637963806381638263836384638563866387638863896390639163926393639463956396639763986399640064016402640364046405640664076408640964106411641264136414641564166417641864196420642164226423642464256426642764286429643064316432643364346435643664376438643964406441644264436444644564466447644864496450645164526453645464556456645764586459646064616462646364646465646664676468646964706471647264736474647564766477647864796480648164826483648464856486648764886489649064916492649364946495649664976498649965006501650265036504650565066507650865096510651165126513651465156516651765186519652065216522652365246525652665276528652965306531653265336534653565366537653865396540654165426543654465456546654765486549655065516552655365546555655665576558655965606561656265636564656565666567656865696570657165726573657465756576657765786579658065816582658365846585658665876588658965906591659265936594659565966597659865996600660166026603660466056606660766086609661066116612661366146615661666176618661966206621662266236624662566266627662866296630663166326633663466356636663766386639664066416642664366446645664666476648664966506651665266536654665566566657665866596660666166626663666466656666666766686669667066716672667366746675667666776678667966806681668266836684668566866687668866896690669166926693669466956696669766986699670067016702670367046705670667076708670967106711671267136714671567166717671867196720672167226723672467256726672767286729673067316732673367346735673667376738673967406741674267436744674567466747674867496750675167526753675467556756675767586759676067616762676367646765676667676768676967706771677267736774677567766777677867796780678167826783678467856786678767886789679067916792679367946795679667976798679968006801680268036804680568066807680868096810681168126813681468156816681768186819682068216822682368246825682668276828682968306831683268336834683568366837683868396840684168426843684468456846684768486849685068516852685368546855685668576858685968606861686268636864686568666867686868696870687168726873687468756876687768786879688068816882688368846885688668876888688968906891689268936894689568966897689868996900690169026903690469056906690769086909691069116912691369146915691669176918691969206921692269236924692569266927692869296930693169326933693469356936693769386939694069416942694369446945694669476948694969506951695269536954695569566957695869596960696169626963696469656966696769686969697069716972697369746975697669776978697969806981698269836984698569866987698869896990699169926993699469956996699769986999700070017002700370047005700670077008700970107011701270137014701570167017701870197020702170227023702470257026702770287029703070317032703370347035703670377038703970407041704270437044704570467047704870497050705170527053705470557056705770587059706070617062706370647065706670677068706970707071707270737074707570767077707870797080708170827083708470857086708770887089709070917092709370947095709670977098709971007101710271037104710571067107710871097110711171127113711471157116711771187119712071217122712371247125712671277128712971307131713271337134713571367137713871397140714171427143714471457146714771487149715071517152715371547155715671577158715971607161716271637164716571667167716871697170717171727173717471757176717771787179718071817182718371847185718671877188718971907191719271937194719571967197719871997200720172027203720472057206720772087209721072117212721372147215721672177218721972207221722272237224722572267227722872297230723172327233723472357236723772387239724072417242724372447245724672477248724972507251725272537254725572567257725872597260726172627263726472657266726772687269727072717272727372747275727672777278
  1. /**
  2. * @fileoverview Main function src.
  3. */
  4. // HTML5 Shiv. Must be in <head> to support older browsers.
  5. document.createElement('video');
  6. document.createElement('audio');
  7. document.createElement('track');
  8. /**
  9. * Doubles as the main function for users to create a player instance and also
  10. * the main library object.
  11. *
  12. * **ALIASES** videojs, _V_ (deprecated)
  13. *
  14. * The `vjs` function can be used to initialize or retrieve a player.
  15. *
  16. * var myPlayer = vjs('my_video_id');
  17. *
  18. * @param {String|Element} id Video element or video element ID
  19. * @param {Object=} options Optional options object for config/settings
  20. * @param {Function=} ready Optional ready callback
  21. * @return {vjs.Player} A player instance
  22. * @namespace
  23. */
  24. var vjs = function (id, options, ready) {
  25. var tag; // Element of ID
  26. // Allow for element or ID to be passed in
  27. // String ID
  28. if (typeof id === 'string') {
  29. // Adjust for jQuery ID syntax
  30. if (id.indexOf('#') === 0) {
  31. id = id.slice(1);
  32. }
  33. // If a player instance has already been created for this ID return it.
  34. if (vjs.players[id]) {
  35. return vjs.players[id];
  36. // Otherwise get element for ID
  37. } else {
  38. tag = vjs.el(id);
  39. }
  40. // ID is a media element
  41. } else {
  42. tag = id;
  43. }
  44. // Check for a useable element
  45. if (!tag || !tag.nodeName) { // re: nodeName, could be a box div also
  46. throw new TypeError('The element or ID supplied is not valid. (videojs)'); // Returns
  47. }
  48. // Element may have a player attr referring to an already created player instance.
  49. // If not, set up a new player and return the instance.
  50. return tag['player'] || new vjs.Player(tag, options, ready);
  51. };
  52. // Extended name, also available externally, window.videojs
  53. var videojs = vjs;
  54. window.videojs = window.vjs = vjs;
  55. // CDN Version. Used to target right flash swf.
  56. vjs.CDN_VERSION = '4.3';
  57. vjs.ACCESS_PROTOCOL = ('https:' == document.location.protocol ? 'https://' : 'http://');
  58. /**
  59. * Global Player instance options, surfaced from vjs.Player.prototype.options_
  60. * vjs.options = vjs.Player.prototype.options_
  61. * All options should use string keys so they avoid
  62. * renaming by closure compiler
  63. * @type {Object}
  64. */
  65. vjs.options = {
  66. // Default order of fallback technology
  67. 'techOrder': ['html5', 'flash'],
  68. // techOrder: ['flash','html5'],
  69. 'html5': {},
  70. 'flash': {},
  71. // Default of web browser is 300x150. Should rely on source width/height.
  72. 'width': 300,
  73. 'height': 150,
  74. // defaultVolume: 0.85,
  75. 'defaultVolume': 0.00, // The freakin seaguls are driving me crazy!
  76. // Included control sets
  77. 'children': {
  78. 'mediaLoader': {},
  79. 'posterImage': {},
  80. 'textTrackDisplay': {},
  81. 'loadingSpinner': {},
  82. 'bigPlayButton': {},
  83. 'controlBar': {}
  84. },
  85. // Default message to show when a video cannot be played.
  86. 'notSupportedMessage': 'Sorry, no compatible source and playback ' +
  87. 'technology were found for this video. Try using another browser ' +
  88. 'like <a href="http://bit.ly/ccMUEC">Chrome</a> or download the ' +
  89. 'latest <a href="http://adobe.ly/mwfN1">Adobe Flash Player</a>.'
  90. };
  91. // Set CDN Version of swf
  92. // The added (+) blocks the replace from changing this 4.3 string
  93. if (vjs.CDN_VERSION !== 'GENERATED' + '_CDN_VSN') {
  94. videojs.options['flash']['swf'] = vjs.ACCESS_PROTOCOL + 'vjs.zencdn.net/' + vjs.CDN_VERSION + '/video-js.swf';
  95. }
  96. /**
  97. * Global player list
  98. * @type {Object}
  99. */
  100. vjs.players = {};
  101. /**
  102. * Core Object/Class for objects that use inheritance + contstructors
  103. *
  104. * To create a class that can be subclassed itself, extend the CoreObject class.
  105. *
  106. * var Animal = CoreObject.extend();
  107. * var Horse = Animal.extend();
  108. *
  109. * The constructor can be defined through the init property of an object argument.
  110. *
  111. * var Animal = CoreObject.extend({
  112. * init: function(name, sound){
  113. * this.name = name;
  114. * }
  115. * });
  116. *
  117. * Other methods and properties can be added the same way, or directly to the
  118. * prototype.
  119. *
  120. * var Animal = CoreObject.extend({
  121. * init: function(name){
  122. * this.name = name;
  123. * },
  124. * getName: function(){
  125. * return this.name;
  126. * },
  127. * sound: '...'
  128. * });
  129. *
  130. * Animal.prototype.makeSound = function(){
  131. * alert(this.sound);
  132. * };
  133. *
  134. * To create an instance of a class, use the create method.
  135. *
  136. * var fluffy = Animal.create('Fluffy');
  137. * fluffy.getName(); // -> Fluffy
  138. *
  139. * Methods and properties can be overridden in subclasses.
  140. *
  141. * var Horse = Animal.extend({
  142. * sound: 'Neighhhhh!'
  143. * });
  144. *
  145. * var horsey = Horse.create('Horsey');
  146. * horsey.getName(); // -> Horsey
  147. * horsey.makeSound(); // -> Alert: Neighhhhh!
  148. *
  149. * @class
  150. * @constructor
  151. */
  152. vjs.CoreObject = vjs['CoreObject'] = function () {
  153. };
  154. // Manually exporting vjs['CoreObject'] here for Closure Compiler
  155. // because of the use of the extend/create class methods
  156. // If we didn't do this, those functions would get flattend to something like
  157. // `a = ...` and `this.prototype` would refer to the global object instead of
  158. // CoreObject
  159. /**
  160. * Create a new object that inherits from this Object
  161. *
  162. * var Animal = CoreObject.extend();
  163. * var Horse = Animal.extend();
  164. *
  165. * @param {Object} props Functions and properties to be applied to the
  166. * new object's prototype
  167. * @return {vjs.CoreObject} An object that inherits from CoreObject
  168. * @this {*}
  169. */
  170. vjs.CoreObject.extend = function (props) {
  171. var init, subObj;
  172. props = props || {};
  173. // Set up the constructor using the supplied init method
  174. // or using the init of the parent object
  175. // Make sure to check the unobfuscated version for external libs
  176. init = props['init'] || props.init || this.prototype['init'] || this.prototype.init || function () {
  177. };
  178. // In Resig's simple class inheritance (previously used) the constructor
  179. // is a function that calls `this.init.apply(arguments)`
  180. // However that would prevent us from using `ParentObject.call(this);`
  181. // in a Child constuctor because the `this` in `this.init`
  182. // would still refer to the Child and cause an inifinite loop.
  183. // We would instead have to do
  184. // `ParentObject.prototype.init.apply(this, argumnents);`
  185. // Bleh. We're not creating a _super() function, so it's good to keep
  186. // the parent constructor reference simple.
  187. subObj = function () {
  188. init.apply(this, arguments);
  189. };
  190. // Inherit from this object's prototype
  191. subObj.prototype = vjs.obj.create(this.prototype);
  192. // Reset the constructor property for subObj otherwise
  193. // instances of subObj would have the constructor of the parent Object
  194. subObj.prototype.constructor = subObj;
  195. // Make the class extendable
  196. subObj.extend = vjs.CoreObject.extend;
  197. // Make a function for creating instances
  198. subObj.create = vjs.CoreObject.create;
  199. // Extend subObj's prototype with functions and other properties from props
  200. for (var name in props) {
  201. if (props.hasOwnProperty(name)) {
  202. subObj.prototype[name] = props[name];
  203. }
  204. }
  205. return subObj;
  206. };
  207. /**
  208. * Create a new instace of this Object class
  209. *
  210. * var myAnimal = Animal.create();
  211. *
  212. * @return {vjs.CoreObject} An instance of a CoreObject subclass
  213. * @this {*}
  214. */
  215. vjs.CoreObject.create = function () {
  216. // Create a new object that inherits from this object's prototype
  217. var inst = vjs.obj.create(this.prototype);
  218. // Apply this constructor function to the new object
  219. this.apply(inst, arguments);
  220. // Return the new object
  221. return inst;
  222. };
  223. /**
  224. * @fileoverview Event System (John Resig - Secrets of a JS Ninja http://jsninja.com/)
  225. * (Original book version wasn't completely usable, so fixed some things and made Closure Compiler compatible)
  226. * This should work very similarly to jQuery's events, however it's based off the book version which isn't as
  227. * robust as jquery's, so there's probably some differences.
  228. */
  229. /**
  230. * Add an event listener to element
  231. * It stores the handler function in a separate cache object
  232. * and adds a generic handler to the element's event,
  233. * along with a unique id (guid) to the element.
  234. * @param {Element|Object} elem Element or object to bind listeners to
  235. * @param {String} type Type of event to bind to.
  236. * @param {Function} fn Event listener.
  237. * @private
  238. */
  239. vjs.on = function (elem, type, fn) {
  240. var data = vjs.getData(elem);
  241. // We need a place to store all our handler data
  242. if (!data.handlers) data.handlers = {};
  243. if (!data.handlers[type]) data.handlers[type] = [];
  244. if (!fn.guid) fn.guid = vjs.guid++;
  245. data.handlers[type].push(fn);
  246. if (!data.dispatcher) {
  247. data.disabled = false;
  248. data.dispatcher = function (event) {
  249. if (data.disabled) return;
  250. event = vjs.fixEvent(event);
  251. var handlers = data.handlers[event.type];
  252. if (handlers) {
  253. // Copy handlers so if handlers are added/removed during the process it doesn't throw everything off.
  254. var handlersCopy = handlers.slice(0);
  255. for (var m = 0, n = handlersCopy.length; m < n; m++) {
  256. if (event.isImmediatePropagationStopped()) {
  257. break;
  258. } else {
  259. handlersCopy[m].call(elem, event);
  260. }
  261. }
  262. }
  263. };
  264. }
  265. if (data.handlers[type].length == 1) {
  266. if (document.addEventListener) {
  267. elem.addEventListener(type, data.dispatcher, false);
  268. } else if (document.attachEvent) {
  269. elem.attachEvent('on' + type, data.dispatcher);
  270. }
  271. }
  272. };
  273. /**
  274. * Removes event listeners from an element
  275. * @param {Element|Object} elem Object to remove listeners from
  276. * @param {String=} type Type of listener to remove. Don't include to remove all events from element.
  277. * @param {Function} fn Specific listener to remove. Don't incldue to remove listeners for an event type.
  278. * @private
  279. */
  280. vjs.off = function (elem, type, fn) {
  281. // Don't want to add a cache object through getData if not needed
  282. if (!vjs.hasData(elem)) return;
  283. var data = vjs.getData(elem);
  284. // If no events exist, nothing to unbind
  285. if (!data.handlers) {
  286. return;
  287. }
  288. // Utility function
  289. var removeType = function (t) {
  290. data.handlers[t] = [];
  291. vjs.cleanUpEvents(elem, t);
  292. };
  293. // Are we removing all bound events?
  294. if (!type) {
  295. for (var t in data.handlers) removeType(t);
  296. return;
  297. }
  298. var handlers = data.handlers[type];
  299. // If no handlers exist, nothing to unbind
  300. if (!handlers) return;
  301. // If no listener was provided, remove all listeners for type
  302. if (!fn) {
  303. removeType(type);
  304. return;
  305. }
  306. // We're only removing a single handler
  307. if (fn.guid) {
  308. for (var n = 0; n < handlers.length; n++) {
  309. if (handlers[n].guid === fn.guid) {
  310. handlers.splice(n--, 1);
  311. }
  312. }
  313. }
  314. vjs.cleanUpEvents(elem, type);
  315. };
  316. /**
  317. * Clean up the listener cache and dispatchers
  318. * @param {Element|Object} elem Element to clean up
  319. * @param {String} type Type of event to clean up
  320. * @private
  321. */
  322. vjs.cleanUpEvents = function (elem, type) {
  323. var data = vjs.getData(elem);
  324. // Remove the events of a particular type if there are none left
  325. if (data.handlers[type].length === 0) {
  326. delete data.handlers[type];
  327. // data.handlers[type] = null;
  328. // Setting to null was causing an error with data.handlers
  329. // Remove the meta-handler from the element
  330. if (document.removeEventListener) {
  331. elem.removeEventListener(type, data.dispatcher, false);
  332. } else if (document.detachEvent) {
  333. elem.detachEvent('on' + type, data.dispatcher);
  334. }
  335. }
  336. // Remove the events object if there are no types left
  337. if (vjs.isEmpty(data.handlers)) {
  338. delete data.handlers;
  339. delete data.dispatcher;
  340. delete data.disabled;
  341. // data.handlers = null;
  342. // data.dispatcher = null;
  343. // data.disabled = null;
  344. }
  345. // Finally remove the expando if there is no data left
  346. if (vjs.isEmpty(data)) {
  347. vjs.removeData(elem);
  348. }
  349. };
  350. /**
  351. * Fix a native event to have standard property values
  352. * @param {Object} event Event object to fix
  353. * @return {Object}
  354. * @private
  355. */
  356. vjs.fixEvent = function (event) {
  357. function returnTrue() {
  358. return true;
  359. }
  360. function returnFalse() {
  361. return false;
  362. }
  363. // Test if fixing up is needed
  364. // Used to check if !event.stopPropagation instead of isPropagationStopped
  365. // But native events return true for stopPropagation, but don't have
  366. // other expected methods like isPropagationStopped. Seems to be a problem
  367. // with the Javascript Ninja code. So we're just overriding all events now.
  368. if (!event || !event.isPropagationStopped) {
  369. var old = event || window.event;
  370. event = {};
  371. // Clone the old object so that we can modify the values event = {};
  372. // IE8 Doesn't like when you mess with native event properties
  373. // Firefox returns false for event.hasOwnProperty('type') and other props
  374. // which makes copying more difficult.
  375. // TODO: Probably best to create a whitelist of event props
  376. for (var key in old) {
  377. // Safari 6.0.3 warns you if you try to copy deprecated layerX/Y
  378. if (key !== 'layerX' && key !== 'layerY') {
  379. event[key] = old[key];
  380. }
  381. }
  382. // The event occurred on this element
  383. if (!event.target) {
  384. event.target = event.srcElement || document;
  385. }
  386. // Handle which other element the event is related to
  387. event.relatedTarget = event.fromElement === event.target ?
  388. event.toElement :
  389. event.fromElement;
  390. // Stop the default browser action
  391. event.preventDefault = function () {
  392. if (old.preventDefault) {
  393. old.preventDefault();
  394. }
  395. event.returnValue = false;
  396. event.isDefaultPrevented = returnTrue;
  397. };
  398. event.isDefaultPrevented = returnFalse;
  399. // Stop the event from bubbling
  400. event.stopPropagation = function () {
  401. if (old.stopPropagation) {
  402. old.stopPropagation();
  403. }
  404. event.cancelBubble = true;
  405. event.isPropagationStopped = returnTrue;
  406. };
  407. event.isPropagationStopped = returnFalse;
  408. // Stop the event from bubbling and executing other handlers
  409. event.stopImmediatePropagation = function () {
  410. if (old.stopImmediatePropagation) {
  411. old.stopImmediatePropagation();
  412. }
  413. event.isImmediatePropagationStopped = returnTrue;
  414. event.stopPropagation();
  415. };
  416. event.isImmediatePropagationStopped = returnFalse;
  417. // Handle mouse position
  418. if (event.clientX != null) {
  419. var doc = document.documentElement, body = document.body;
  420. event.pageX = event.clientX +
  421. (doc && doc.scrollLeft || body && body.scrollLeft || 0) -
  422. (doc && doc.clientLeft || body && body.clientLeft || 0);
  423. event.pageY = event.clientY +
  424. (doc && doc.scrollTop || body && body.scrollTop || 0) -
  425. (doc && doc.clientTop || body && body.clientTop || 0);
  426. }
  427. // Handle key presses
  428. event.which = event.charCode || event.keyCode;
  429. // Fix button for mouse clicks:
  430. // 0 == left; 1 == middle; 2 == right
  431. if (event.button != null) {
  432. event.button = (event.button & 1 ? 0 :
  433. (event.button & 4 ? 1 :
  434. (event.button & 2 ? 2 : 0)));
  435. }
  436. }
  437. // Returns fixed-up instance
  438. return event;
  439. };
  440. /**
  441. * Trigger an event for an element
  442. * @param {Element|Object} elem Element to trigger an event on
  443. * @param {String} event Type of event to trigger
  444. * @private
  445. */
  446. vjs.trigger = function (elem, event) {
  447. // Fetches element data and a reference to the parent (for bubbling).
  448. // Don't want to add a data object to cache for every parent,
  449. // so checking hasData first.
  450. var elemData = (vjs.hasData(elem)) ? vjs.getData(elem) : {};
  451. var parent = elem.parentNode || elem.ownerDocument;
  452. // type = event.type || event,
  453. // handler;
  454. // If an event name was passed as a string, creates an event out of it
  455. if (typeof event === 'string') {
  456. event = {type: event, target: elem};
  457. }
  458. // Normalizes the event properties.
  459. event = vjs.fixEvent(event);
  460. // If the passed element has a dispatcher, executes the established handlers.
  461. if (elemData.dispatcher) {
  462. elemData.dispatcher.call(elem, event);
  463. }
  464. // Unless explicitly stopped or the event does not bubble (e.g. media events)
  465. // recursively calls this function to bubble the event up the DOM.
  466. if (parent && !event.isPropagationStopped() && event.bubbles !== false) {
  467. vjs.trigger(parent, event);
  468. // If at the top of the DOM, triggers the default action unless disabled.
  469. } else if (!parent && !event.isDefaultPrevented()) {
  470. var targetData = vjs.getData(event.target);
  471. // Checks if the target has a default action for this event.
  472. if (event.target[event.type]) {
  473. // Temporarily disables event dispatching on the target as we have already executed the handler.
  474. targetData.disabled = true;
  475. // Executes the default action.
  476. if (typeof event.target[event.type] === 'function') {
  477. event.target[event.type]();
  478. }
  479. // Re-enables event dispatching.
  480. targetData.disabled = false;
  481. }
  482. }
  483. // Inform the triggerer if the default was prevented by returning false
  484. return !event.isDefaultPrevented();
  485. /* Original version of js ninja events wasn't complete.
  486. * We've since updated to the latest version, but keeping this around
  487. * for now just in case.
  488. */
  489. // // Added in attion to book. Book code was broke.
  490. // event = typeof event === 'object' ?
  491. // event[vjs.expando] ?
  492. // event :
  493. // new vjs.Event(type, event) :
  494. // new vjs.Event(type);
  495. // event.type = type;
  496. // if (handler) {
  497. // handler.call(elem, event);
  498. // }
  499. // // Clean up the event in case it is being reused
  500. // event.result = undefined;
  501. // event.target = elem;
  502. };
  503. /**
  504. * Trigger a listener only once for an event
  505. * @param {Element|Object} elem Element or object to
  506. * @param {String} type
  507. * @param {Function} fn
  508. * @private
  509. */
  510. vjs.one = function (elem, type, fn) {
  511. var func = function () {
  512. vjs.off(elem, type, func);
  513. fn.apply(this, arguments);
  514. };
  515. func.guid = fn.guid = fn.guid || vjs.guid++;
  516. vjs.on(elem, type, func);
  517. };
  518. var hasOwnProp = Object.prototype.hasOwnProperty;
  519. /**
  520. * Creates an element and applies properties.
  521. * @param {String=} tagName Name of tag to be created.
  522. * @param {Object=} properties Element properties to be applied.
  523. * @return {Element}
  524. * @private
  525. */
  526. vjs.createEl = function (tagName, properties) {
  527. var el, propName;
  528. el = document.createElement(tagName || 'div');
  529. for (propName in properties) {
  530. if (hasOwnProp.call(properties, propName)) {
  531. //el[propName] = properties[propName];
  532. // Not remembering why we were checking for dash
  533. // but using setAttribute means you have to use getAttribute
  534. // The check for dash checks for the aria-* attributes, like aria-label, aria-valuemin.
  535. // The additional check for "role" is because the default method for adding attributes does not
  536. // add the attribute "role". My guess is because it's not a valid attribute in some namespaces, although
  537. // browsers handle the attribute just fine. The W3C allows for aria-* attributes to be used in pre-HTML5 docs.
  538. // http://www.w3.org/TR/wai-aria-primer/#ariahtml. Using setAttribute gets around this problem.
  539. if (propName.indexOf('aria-') !== -1 || propName == 'role') {
  540. el.setAttribute(propName, properties[propName]);
  541. } else {
  542. el[propName] = properties[propName];
  543. }
  544. }
  545. }
  546. return el;
  547. };
  548. /**
  549. * Uppercase the first letter of a string
  550. * @param {String} string String to be uppercased
  551. * @return {String}
  552. * @private
  553. */
  554. vjs.capitalize = function (string) {
  555. return string.charAt(0).toUpperCase() + string.slice(1);
  556. };
  557. /**
  558. * Object functions container
  559. * @type {Object}
  560. * @private
  561. */
  562. vjs.obj = {};
  563. /**
  564. * Object.create shim for prototypal inheritance
  565. *
  566. * https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Object/create
  567. *
  568. * @function
  569. * @param {Object} obj Object to use as prototype
  570. * @private
  571. */
  572. vjs.obj.create = Object.create || function (obj) {
  573. //Create a new function called 'F' which is just an empty object.
  574. function F() {
  575. }
  576. //the prototype of the 'F' function should point to the
  577. //parameter of the anonymous function.
  578. F.prototype = obj;
  579. //create a new constructor function based off of the 'F' function.
  580. return new F();
  581. };
  582. /**
  583. * Loop through each property in an object and call a function
  584. * whose arguments are (key,value)
  585. * @param {Object} obj Object of properties
  586. * @param {Function} fn Function to be called on each property.
  587. * @this {*}
  588. * @private
  589. */
  590. vjs.obj.each = function (obj, fn, context) {
  591. for (var key in obj) {
  592. if (hasOwnProp.call(obj, key)) {
  593. fn.call(context || this, key, obj[key]);
  594. }
  595. }
  596. };
  597. /**
  598. * Merge two objects together and return the original.
  599. * @param {Object} obj1
  600. * @param {Object} obj2
  601. * @return {Object}
  602. * @private
  603. */
  604. vjs.obj.merge = function (obj1, obj2) {
  605. if (!obj2) {
  606. return obj1;
  607. }
  608. for (var key in obj2) {
  609. if (hasOwnProp.call(obj2, key)) {
  610. obj1[key] = obj2[key];
  611. }
  612. }
  613. return obj1;
  614. };
  615. /**
  616. * Merge two objects, and merge any properties that are objects
  617. * instead of just overwriting one. Uses to merge options hashes
  618. * where deeper default settings are important.
  619. * @param {Object} obj1 Object to override
  620. * @param {Object} obj2 Overriding object
  621. * @return {Object} New object. Obj1 and Obj2 will be untouched.
  622. * @private
  623. */
  624. vjs.obj.deepMerge = function (obj1, obj2) {
  625. var key, val1, val2;
  626. // make a copy of obj1 so we're not ovewriting original values.
  627. // like prototype.options_ and all sub options objects
  628. obj1 = vjs.obj.copy(obj1);
  629. for (key in obj2) {
  630. if (hasOwnProp.call(obj2, key)) {
  631. val1 = obj1[key];
  632. val2 = obj2[key];
  633. // Check if both properties are pure objects and do a deep merge if so
  634. if (vjs.obj.isPlain(val1) && vjs.obj.isPlain(val2)) {
  635. obj1[key] = vjs.obj.deepMerge(val1, val2);
  636. } else {
  637. obj1[key] = obj2[key];
  638. }
  639. }
  640. }
  641. return obj1;
  642. };
  643. /**
  644. * Make a copy of the supplied object
  645. * @param {Object} obj Object to copy
  646. * @return {Object} Copy of object
  647. * @private
  648. */
  649. vjs.obj.copy = function (obj) {
  650. return vjs.obj.merge({}, obj);
  651. };
  652. /**
  653. * Check if an object is plain, and not a dom node or any object sub-instance
  654. * @param {Object} obj Object to check
  655. * @return {Boolean} True if plain, false otherwise
  656. * @private
  657. */
  658. vjs.obj.isPlain = function (obj) {
  659. return !!obj
  660. && typeof obj === 'object'
  661. && obj.toString() === '[object Object]'
  662. && obj.constructor === Object;
  663. };
  664. /**
  665. * Bind (a.k.a proxy or Context). A simple method for changing the context of a function
  666. It also stores a unique id on the function so it can be easily removed from events
  667. * @param {*} context The object to bind as scope
  668. * @param {Function} fn The function to be bound to a scope
  669. * @param {Number=} uid An optional unique ID for the function to be set
  670. * @return {Function}
  671. * @private
  672. */
  673. vjs.bind = function (context, fn, uid) {
  674. // Make sure the function has a unique ID
  675. if (!fn.guid) {
  676. fn.guid = vjs.guid++;
  677. }
  678. // Create the new function that changes the context
  679. var ret = function () {
  680. return fn.apply(context, arguments);
  681. };
  682. // Allow for the ability to individualize this function
  683. // Needed in the case where multiple objects might share the same prototype
  684. // IF both items add an event listener with the same function, then you try to remove just one
  685. // it will remove both because they both have the same guid.
  686. // when using this, you need to use the bind method when you remove the listener as well.
  687. // currently used in text tracks
  688. ret.guid = (uid) ? uid + '_' + fn.guid : fn.guid;
  689. return ret;
  690. };
  691. /**
  692. * Element Data Store. Allows for binding data to an element without putting it directly on the element.
  693. * Ex. Event listneres are stored here.
  694. * (also from jsninja.com, slightly modified and updated for closure compiler)
  695. * @type {Object}
  696. * @private
  697. */
  698. vjs.cache = {};
  699. /**
  700. * Unique ID for an element or function
  701. * @type {Number}
  702. * @private
  703. */
  704. vjs.guid = 1;
  705. /**
  706. * Unique attribute name to store an element's guid in
  707. * @type {String}
  708. * @constant
  709. * @private
  710. */
  711. vjs.expando = 'vdata' + (new Date()).getTime();
  712. /**
  713. * Returns the cache object where data for an element is stored
  714. * @param {Element} el Element to store data for.
  715. * @return {Object}
  716. * @private
  717. */
  718. vjs.getData = function (el) {
  719. var id = el[vjs.expando];
  720. if (!id) {
  721. id = el[vjs.expando] = vjs.guid++;
  722. vjs.cache[id] = {};
  723. }
  724. return vjs.cache[id];
  725. };
  726. /**
  727. * Returns the cache object where data for an element is stored
  728. * @param {Element} el Element to store data for.
  729. * @return {Object}
  730. * @private
  731. */
  732. vjs.hasData = function (el) {
  733. var id = el[vjs.expando];
  734. return !(!id || vjs.isEmpty(vjs.cache[id]));
  735. };
  736. /**
  737. * Delete data for the element from the cache and the guid attr from getElementById
  738. * @param {Element} el Remove data for an element
  739. * @private
  740. */
  741. vjs.removeData = function (el) {
  742. var id = el[vjs.expando];
  743. if (!id) {
  744. return;
  745. }
  746. // Remove all stored data
  747. // Changed to = null
  748. // http://coding.smashingmagazine.com/2012/11/05/writing-fast-memory-efficient-javascript/
  749. // vjs.cache[id] = null;
  750. delete vjs.cache[id];
  751. // Remove the expando property from the DOM node
  752. try {
  753. delete el[vjs.expando];
  754. } catch (e) {
  755. if (el.removeAttribute) {
  756. el.removeAttribute(vjs.expando);
  757. } else {
  758. // IE doesn't appear to support removeAttribute on the document element
  759. el[vjs.expando] = null;
  760. }
  761. }
  762. };
  763. /**
  764. * Check if an object is empty
  765. * @param {Object} obj The object to check for emptiness
  766. * @return {Boolean}
  767. * @private
  768. */
  769. vjs.isEmpty = function (obj) {
  770. for (var prop in obj) {
  771. // Inlude null properties as empty.
  772. if (obj[prop] !== null) {
  773. return false;
  774. }
  775. }
  776. return true;
  777. };
  778. /**
  779. * Add a CSS class name to an element
  780. * @param {Element} element Element to add class name to
  781. * @param {String} classToAdd Classname to add
  782. * @private
  783. */
  784. vjs.addClass = function (element, classToAdd) {
  785. if ((' ' + element.className + ' ').indexOf(' ' + classToAdd + ' ') == -1) {
  786. element.className = element.className === '' ? classToAdd : element.className + ' ' + classToAdd;
  787. }
  788. };
  789. /**
  790. * Remove a CSS class name from an element
  791. * @param {Element} element Element to remove from class name
  792. * @param {String} classToAdd Classname to remove
  793. * @private
  794. */
  795. vjs.removeClass = function (element, classToRemove) {
  796. var classNames, i;
  797. if (element.className.indexOf(classToRemove) == -1) {
  798. return;
  799. }
  800. classNames = element.className.split(' ');
  801. // no arr.indexOf in ie8, and we don't want to add a big shim
  802. for (i = classNames.length - 1; i >= 0; i--) {
  803. if (classNames[i] === classToRemove) {
  804. classNames.splice(i, 1);
  805. }
  806. }
  807. element.className = classNames.join(' ');
  808. };
  809. /**
  810. * Element for testing browser HTML5 video capabilities
  811. * @type {Element}
  812. * @constant
  813. * @private
  814. */
  815. vjs.TEST_VID = vjs.createEl('video');
  816. /**
  817. * Useragent for browser testing.
  818. * @type {String}
  819. * @constant
  820. * @private
  821. */
  822. vjs.USER_AGENT = navigator.userAgent;
  823. /**
  824. * Device is an iPhone
  825. * @type {Boolean}
  826. * @constant
  827. * @private
  828. */
  829. vjs.IS_IPHONE = (/iPhone/i).test(vjs.USER_AGENT);
  830. vjs.IS_IPAD = (/iPad/i).test(vjs.USER_AGENT);
  831. vjs.IS_IPOD = (/iPod/i).test(vjs.USER_AGENT);
  832. vjs.IS_IOS = vjs.IS_IPHONE || vjs.IS_IPAD || vjs.IS_IPOD;
  833. vjs.IOS_VERSION = (function () {
  834. var match = vjs.USER_AGENT.match(/OS (\d+)_/i);
  835. if (match && match[1]) {
  836. return match[1];
  837. }
  838. })();
  839. vjs.IS_ANDROID = (/Android/i).test(vjs.USER_AGENT);
  840. vjs.ANDROID_VERSION = (function () {
  841. // This matches Android Major.Minor.Patch versions
  842. // ANDROID_VERSION is Major.Minor as a Number, if Minor isn't available, then only Major is returned
  843. var match = vjs.USER_AGENT.match(/Android (\d+)(?:\.(\d+))?(?:\.(\d+))*/i),
  844. major,
  845. minor;
  846. if (!match) {
  847. return null;
  848. }
  849. major = match[1] && parseFloat(match[1]);
  850. minor = match[2] && parseFloat(match[2]);
  851. if (major && minor) {
  852. return parseFloat(match[1] + '.' + match[2]);
  853. } else if (major) {
  854. return major;
  855. } else {
  856. return null;
  857. }
  858. })();
  859. // Old Android is defined as Version older than 2.3, and requiring a webkit version of the android browser
  860. vjs.IS_OLD_ANDROID = vjs.IS_ANDROID && (/webkit/i).test(vjs.USER_AGENT) && vjs.ANDROID_VERSION < 2.3;
  861. vjs.IS_FIREFOX = (/Firefox/i).test(vjs.USER_AGENT);
  862. vjs.IS_CHROME = (/Chrome/i).test(vjs.USER_AGENT);
  863. vjs.TOUCH_ENABLED = !!(('ontouchstart' in window) || window.DocumentTouch && document instanceof window.DocumentTouch);
  864. /**
  865. * Get an element's attribute values, as defined on the HTML tag
  866. * Attributs are not the same as properties. They're defined on the tag
  867. * or with setAttribute (which shouldn't be used with HTML)
  868. * This will return true or false for boolean attributes.
  869. * @param {Element} tag Element from which to get tag attributes
  870. * @return {Object}
  871. * @private
  872. */
  873. vjs.getAttributeValues = function (tag) {
  874. var obj, knownBooleans, attrs, attrName, attrVal;
  875. obj = {};
  876. // known boolean attributes
  877. // we can check for matching boolean properties, but older browsers
  878. // won't know about HTML5 boolean attributes that we still read from
  879. knownBooleans = ',' + 'autoplay,controls,loop,muted,default' + ',';
  880. if (tag && tag.attributes && tag.attributes.length > 0) {
  881. attrs = tag.attributes;
  882. for (var i = attrs.length - 1; i >= 0; i--) {
  883. attrName = attrs[i].name;
  884. attrVal = attrs[i].value;
  885. // check for known booleans
  886. // the matching element property will return a value for typeof
  887. if (typeof tag[attrName] === 'boolean' || knownBooleans.indexOf(',' + attrName + ',') !== -1) {
  888. // the value of an included boolean attribute is typically an empty
  889. // string ('') which would equal false if we just check for a false value.
  890. // we also don't want support bad code like autoplay='false'
  891. attrVal = (attrVal !== null) ? true : false;
  892. }
  893. obj[attrName] = attrVal;
  894. }
  895. }
  896. return obj;
  897. };
  898. /**
  899. * Get the computed style value for an element
  900. * From http://robertnyman.com/2006/04/24/get-the-rendered-style-of-an-element/
  901. * @param {Element} el Element to get style value for
  902. * @param {String} strCssRule Style name
  903. * @return {String} Style value
  904. * @private
  905. */
  906. vjs.getComputedDimension = function (el, strCssRule) {
  907. var strValue = '';
  908. if (document.defaultView && document.defaultView.getComputedStyle) {
  909. strValue = document.defaultView.getComputedStyle(el, '').getPropertyValue(strCssRule);
  910. } else if (el.currentStyle) {
  911. // IE8 Width/Height support
  912. strValue = el['client' + strCssRule.substr(0, 1).toUpperCase() + strCssRule.substr(1)] + 'px';
  913. }
  914. return strValue;
  915. };
  916. /**
  917. * Insert an element as the first child node of another
  918. * @param {Element} child Element to insert
  919. * @param {[type]} parent Element to insert child into
  920. * @private
  921. */
  922. vjs.insertFirst = function (child, parent) {
  923. if (parent.firstChild) {
  924. parent.insertBefore(child, parent.firstChild);
  925. } else {
  926. parent.appendChild(child);
  927. }
  928. };
  929. /**
  930. * Object to hold browser support information
  931. * @type {Object}
  932. * @private
  933. */
  934. vjs.support = {};
  935. /**
  936. * Shorthand for document.getElementById()
  937. * Also allows for CSS (jQuery) ID syntax. But nothing other than IDs.
  938. * @param {String} id Element ID
  939. * @return {Element} Element with supplied ID
  940. * @private
  941. */
  942. vjs.el = function (id) {
  943. if (id.indexOf('#') === 0) {
  944. id = id.slice(1);
  945. }
  946. return document.getElementById(id);
  947. };
  948. /**
  949. * Format seconds as a time string, H:MM:SS or M:SS
  950. * Supplying a guide (in seconds) will force a number of leading zeros
  951. * to cover the length of the guide
  952. * @param {Number} seconds Number of seconds to be turned into a string
  953. * @param {Number} guide Number (in seconds) to model the string after
  954. * @return {String} Time formatted as H:MM:SS or M:SS
  955. * @private
  956. */
  957. vjs.formatTime = function (seconds, guide) {
  958. // Default to using seconds as guide
  959. guide = guide || seconds;
  960. var s = Math.floor(seconds % 60),
  961. m = Math.floor(seconds / 60 % 60),
  962. h = Math.floor(seconds / 3600),
  963. gm = Math.floor(guide / 60 % 60),
  964. gh = Math.floor(guide / 3600);
  965. // handle invalid times
  966. if (isNaN(seconds) || seconds === Infinity) {
  967. // '-' is false for all relational operators (e.g. <, >=) so this setting
  968. // will add the minimum number of fields specified by the guide
  969. h = m = s = '-';
  970. }
  971. // Check if we need to show hours
  972. h = (h > 0 || gh > 0) ? h + ':' : '';
  973. // If hours are showing, we may need to add a leading zero.
  974. // Always show at least one digit of minutes.
  975. m = (((h || gm >= 10) && m < 10) ? '0' + m : m) + ':';
  976. // Check if leading zero is need for seconds
  977. s = (s < 10) ? '0' + s : s;
  978. return h + m + s;
  979. };
  980. // Attempt to block the ability to select text while dragging controls
  981. vjs.blockTextSelection = function () {
  982. document.body.focus();
  983. document.onselectstart = function () {
  984. return false;
  985. };
  986. };
  987. // Turn off text selection blocking
  988. vjs.unblockTextSelection = function () {
  989. document.onselectstart = function () {
  990. return true;
  991. };
  992. };
  993. /**
  994. * Trim whitespace from the ends of a string.
  995. * @param {String} string String to trim
  996. * @return {String} Trimmed string
  997. * @private
  998. */
  999. vjs.trim = function (str) {
  1000. return (str + '').replace(/^\s+|\s+$/g, '');
  1001. };
  1002. /**
  1003. * Should round off a number to a decimal place
  1004. * @param {Number} num Number to round
  1005. * @param {Number} dec Number of decimal places to round to
  1006. * @return {Number} Rounded number
  1007. * @private
  1008. */
  1009. vjs.round = function (num, dec) {
  1010. if (!dec) {
  1011. dec = 0;
  1012. }
  1013. return Math.round(num * Math.pow(10, dec)) / Math.pow(10, dec);
  1014. };
  1015. /**
  1016. * Should create a fake TimeRange object
  1017. * Mimics an HTML5 time range instance, which has functions that
  1018. * return the start and end times for a range
  1019. * TimeRanges are returned by the buffered() method
  1020. * @param {Number} start Start time in seconds
  1021. * @param {Number} end End time in seconds
  1022. * @return {Object} Fake TimeRange object
  1023. * @private
  1024. */
  1025. vjs.createTimeRange = function (start, end) {
  1026. return {
  1027. length: 1,
  1028. start: function () {
  1029. return start;
  1030. },
  1031. end: function () {
  1032. return end;
  1033. }
  1034. };
  1035. };
  1036. /**
  1037. * Simple http request for retrieving external files (e.g. text tracks)
  1038. * @param {String} url URL of resource
  1039. * @param {Function=} onSuccess Success callback
  1040. * @param {Function=} onError Error callback
  1041. * @private
  1042. */
  1043. vjs.get = function (url, onSuccess, onError) {
  1044. var local, request;
  1045. if (typeof XMLHttpRequest === 'undefined') {
  1046. window.XMLHttpRequest = function () {
  1047. try {
  1048. return new window.ActiveXObject('Msxml2.XMLHTTP.6.0');
  1049. } catch (e) {
  1050. }
  1051. try {
  1052. return new window.ActiveXObject('Msxml2.XMLHTTP.3.0');
  1053. } catch (f) {
  1054. }
  1055. try {
  1056. return new window.ActiveXObject('Msxml2.XMLHTTP');
  1057. } catch (g) {
  1058. }
  1059. throw new Error('This browser does not support XMLHttpRequest.');
  1060. };
  1061. }
  1062. request = new XMLHttpRequest();
  1063. try {
  1064. request.open('GET', url);
  1065. } catch (e) {
  1066. onError(e);
  1067. }
  1068. local = (url.indexOf('file:') === 0 || (window.location.href.indexOf('file:') === 0 && url.indexOf('http') === -1));
  1069. request.onreadystatechange = function () {
  1070. if (request.readyState === 4) {
  1071. if (request.status === 200 || local && request.status === 0) {
  1072. onSuccess(request.responseText);
  1073. } else {
  1074. if (onError) {
  1075. onError();
  1076. }
  1077. }
  1078. }
  1079. };
  1080. try {
  1081. request.send();
  1082. } catch (e) {
  1083. if (onError) {
  1084. onError(e);
  1085. }
  1086. }
  1087. };
  1088. /**
  1089. * Add to local storage (may removeable)
  1090. * @private
  1091. */
  1092. vjs.setLocalStorage = function (key, value) {
  1093. try {
  1094. // IE was throwing errors referencing the var anywhere without this
  1095. var localStorage = window.localStorage || false;
  1096. if (!localStorage) {
  1097. return;
  1098. }
  1099. localStorage[key] = value;
  1100. } catch (e) {
  1101. if (e.code == 22 || e.code == 1014) { // Webkit == 22 / Firefox == 1014
  1102. vjs.log('LocalStorage Full (VideoJS)', e);
  1103. } else {
  1104. if (e.code == 18) {
  1105. vjs.log('LocalStorage not allowed (VideoJS)', e);
  1106. } else {
  1107. vjs.log('LocalStorage Error (VideoJS)', e);
  1108. }
  1109. }
  1110. }
  1111. };
  1112. /**
  1113. * Get abosolute version of relative URL. Used to tell flash correct URL.
  1114. * http://stackoverflow.com/questions/470832/getting-an-absolute-url-from-a-relative-one-ie6-issue
  1115. * @param {String} url URL to make absolute
  1116. * @return {String} Absolute URL
  1117. * @private
  1118. */
  1119. vjs.getAbsoluteURL = function (url) {
  1120. // Check if absolute URL
  1121. if (!url.match(/^https?:\/\//)) {
  1122. // Convert to absolute URL. Flash hosted off-site needs an absolute URL.
  1123. url = vjs.createEl('div', {
  1124. innerHTML: '<a href="' + url + '">x</a>'
  1125. }).firstChild.href;
  1126. }
  1127. return url;
  1128. };
  1129. // usage: log('inside coolFunc',this,arguments);
  1130. // http://paulirish.com/2009/log-a-lightweight-wrapper-for-consolelog/
  1131. vjs.log = function () {
  1132. vjs.log.history = vjs.log.history || []; // store logs to an array for reference
  1133. vjs.log.history.push(arguments);
  1134. if (window.console) {
  1135. window.console.log(Array.prototype.slice.call(arguments));
  1136. }
  1137. };
  1138. // Offset Left
  1139. // getBoundingClientRect technique from John Resig http://ejohn.org/blog/getboundingclientrect-is-awesome/
  1140. vjs.findPosition = function (el) {
  1141. var box, docEl, body, clientLeft, scrollLeft, left, clientTop, scrollTop, top;
  1142. if (el.getBoundingClientRect && el.parentNode) {
  1143. box = el.getBoundingClientRect();
  1144. }
  1145. if (!box) {
  1146. return {
  1147. left: 0,
  1148. top: 0
  1149. };
  1150. }
  1151. docEl = document.documentElement;
  1152. body = document.body;
  1153. clientLeft = docEl.clientLeft || body.clientLeft || 0;
  1154. scrollLeft = window.pageXOffset || body.scrollLeft;
  1155. left = box.left + scrollLeft - clientLeft;
  1156. clientTop = docEl.clientTop || body.clientTop || 0;
  1157. scrollTop = window.pageYOffset || body.scrollTop;
  1158. top = box.top + scrollTop - clientTop;
  1159. return {
  1160. left: left,
  1161. top: top
  1162. };
  1163. };
  1164. /**
  1165. * @fileoverview Player Component - Base class for all UI objects
  1166. *
  1167. */
  1168. /**
  1169. * Base UI Component class
  1170. *
  1171. * Components are embeddable UI objects that are represented by both a
  1172. * javascript object and an element in the DOM. They can be children of other
  1173. * components, and can have many children themselves.
  1174. *
  1175. * // adding a button to the player
  1176. * var button = player.addChild('button');
  1177. * button.el(); // -> button element
  1178. *
  1179. * <div class="video-js">
  1180. * <div class="vjs-button">Button</div>
  1181. * </div>
  1182. *
  1183. * Components are also event emitters.
  1184. *
  1185. * button.on('click', function(){
  1186. * console.log('Button Clicked!');
  1187. * });
  1188. *
  1189. * button.trigger('customevent');
  1190. *
  1191. * @param {Object} player Main Player
  1192. * @param {Object=} options
  1193. * @class
  1194. * @constructor
  1195. * @extends vjs.CoreObject
  1196. */
  1197. vjs.Component = vjs.CoreObject.extend({
  1198. /**
  1199. * the constructor funciton for the class
  1200. *
  1201. * @constructor
  1202. */
  1203. init: function (player, options, ready) {
  1204. this.player_ = player;
  1205. // Make a copy of prototype.options_ to protect against overriding global defaults
  1206. this.options_ = vjs.obj.copy(this.options_);
  1207. // Updated options with supplied options
  1208. options = this.options(options);
  1209. // Get ID from options, element, or create using player ID and unique ID
  1210. this.id_ = options['id'] || ((options['el'] && options['el']['id']) ? options['el']['id'] : player.id() + '_component_' + vjs.guid++);
  1211. this.name_ = options['name'] || null;
  1212. // Create element if one wasn't provided in options
  1213. this.el_ = options['el'] || this.createEl();
  1214. this.children_ = [];
  1215. this.childIndex_ = {};
  1216. this.childNameIndex_ = {};
  1217. // Add any child components in options
  1218. this.initChildren();
  1219. this.ready(ready);
  1220. // Don't want to trigger ready here or it will before init is actually
  1221. // finished for all children that run this constructor
  1222. }
  1223. });
  1224. /**
  1225. * Dispose of the component and all child components
  1226. */
  1227. vjs.Component.prototype.dispose = function () {
  1228. this.trigger('dispose');
  1229. // Dispose all children.
  1230. if (this.children_) {
  1231. for (var i = this.children_.length - 1; i >= 0; i--) {
  1232. if (this.children_[i].dispose) {
  1233. this.children_[i].dispose();
  1234. }
  1235. }
  1236. }
  1237. // Delete child references
  1238. this.children_ = null;
  1239. this.childIndex_ = null;
  1240. this.childNameIndex_ = null;
  1241. // Remove all event listeners.
  1242. this.off();
  1243. // Remove element from DOM
  1244. if (this.el_.parentNode) {
  1245. this.el_.parentNode.removeChild(this.el_);
  1246. }
  1247. vjs.removeData(this.el_);
  1248. this.el_ = null;
  1249. };
  1250. /**
  1251. * Reference to main player instance
  1252. *
  1253. * @type {vjs.Player}
  1254. * @private
  1255. */
  1256. vjs.Component.prototype.player_ = true;
  1257. /**
  1258. * Return the component's player
  1259. *
  1260. * @return {vjs.Player}
  1261. */
  1262. vjs.Component.prototype.player = function () {
  1263. return this.player_;
  1264. };
  1265. /**
  1266. * The component's options object
  1267. *
  1268. * @type {Object}
  1269. * @private
  1270. */
  1271. vjs.Component.prototype.options_;
  1272. /**
  1273. * Deep merge of options objects
  1274. *
  1275. * Whenever a property is an object on both options objects
  1276. * the two properties will be merged using vjs.obj.deepMerge.
  1277. *
  1278. * This is used for merging options for child components. We
  1279. * want it to be easy to override individual options on a child
  1280. * component without having to rewrite all the other default options.
  1281. *
  1282. * Parent.prototype.options_ = {
  1283. * children: {
  1284. * 'childOne': { 'foo': 'bar', 'asdf': 'fdsa' },
  1285. * 'childTwo': {},
  1286. * 'childThree': {}
  1287. * }
  1288. * }
  1289. * newOptions = {
  1290. * children: {
  1291. * 'childOne': { 'foo': 'baz', 'abc': '123' }
  1292. * 'childTwo': null,
  1293. * 'childFour': {}
  1294. * }
  1295. * }
  1296. *
  1297. * this.options(newOptions);
  1298. *
  1299. * RESULT
  1300. *
  1301. * {
  1302. * children: {
  1303. * 'childOne': { 'foo': 'baz', 'asdf': 'fdsa', 'abc': '123' },
  1304. * 'childTwo': null, // Disabled. Won't be initialized.
  1305. * 'childThree': {},
  1306. * 'childFour': {}
  1307. * }
  1308. * }
  1309. *
  1310. * @param {Object} obj Object whose values will be overwritten
  1311. * @return {Object} NEW merged object. Does not return obj1.
  1312. */
  1313. vjs.Component.prototype.options = function (obj) {
  1314. if (obj === undefined) return this.options_;
  1315. return this.options_ = vjs.obj.deepMerge(this.options_, obj);
  1316. };
  1317. /**
  1318. * The DOM element for the component
  1319. *
  1320. * @type {Element}
  1321. * @private
  1322. */
  1323. vjs.Component.prototype.el_;
  1324. /**
  1325. * Create the component's DOM element
  1326. *
  1327. * @param {String=} tagName Element's node type. e.g. 'div'
  1328. * @param {Object=} attributes An object of element attributes that should be set on the element
  1329. * @return {Element}
  1330. */
  1331. vjs.Component.prototype.createEl = function (tagName, attributes) {
  1332. return vjs.createEl(tagName, attributes);
  1333. };
  1334. /**
  1335. * Get the component's DOM element
  1336. *
  1337. * var domEl = myComponent.el();
  1338. *
  1339. * @return {Element}
  1340. */
  1341. vjs.Component.prototype.el = function () {
  1342. return this.el_;
  1343. };
  1344. /**
  1345. * An optional element where, if defined, children will be inserted instead of
  1346. * directly in `el_`
  1347. *
  1348. * @type {Element}
  1349. * @private
  1350. */
  1351. vjs.Component.prototype.contentEl_;
  1352. /**
  1353. * Return the component's DOM element for embedding content.
  1354. * Will either be el_ or a new element defined in createEl.
  1355. *
  1356. * @return {Element}
  1357. */
  1358. vjs.Component.prototype.contentEl = function () {
  1359. return this.contentEl_ || this.el_;
  1360. };
  1361. /**
  1362. * The ID for the component
  1363. *
  1364. * @type {String}
  1365. * @private
  1366. */
  1367. vjs.Component.prototype.id_;
  1368. /**
  1369. * Get the component's ID
  1370. *
  1371. * var id = myComponent.id();
  1372. *
  1373. * @return {String}
  1374. */
  1375. vjs.Component.prototype.id = function () {
  1376. return this.id_;
  1377. };
  1378. /**
  1379. * The name for the component. Often used to reference the component.
  1380. *
  1381. * @type {String}
  1382. * @private
  1383. */
  1384. vjs.Component.prototype.name_;
  1385. /**
  1386. * Get the component's name. The name is often used to reference the component.
  1387. *
  1388. * var name = myComponent.name();
  1389. *
  1390. * @return {String}
  1391. */
  1392. vjs.Component.prototype.name = function () {
  1393. return this.name_;
  1394. };
  1395. /**
  1396. * Array of child components
  1397. *
  1398. * @type {Array}
  1399. * @private
  1400. */
  1401. vjs.Component.prototype.children_;
  1402. /**
  1403. * Get an array of all child components
  1404. *
  1405. * var kids = myComponent.children();
  1406. *
  1407. * @return {Array} The children
  1408. */
  1409. vjs.Component.prototype.children = function () {
  1410. return this.children_;
  1411. };
  1412. /**
  1413. * Object of child components by ID
  1414. *
  1415. * @type {Object}
  1416. * @private
  1417. */
  1418. vjs.Component.prototype.childIndex_;
  1419. /**
  1420. * Returns a child component with the provided ID
  1421. *
  1422. * @return {vjs.Component}
  1423. */
  1424. vjs.Component.prototype.getChildById = function (id) {
  1425. return this.childIndex_[id];
  1426. };
  1427. /**
  1428. * Object of child components by name
  1429. *
  1430. * @type {Object}
  1431. * @private
  1432. */
  1433. vjs.Component.prototype.childNameIndex_;
  1434. /**
  1435. * Returns a child component with the provided ID
  1436. *
  1437. * @return {vjs.Component}
  1438. */
  1439. vjs.Component.prototype.getChild = function (name) {
  1440. return this.childNameIndex_[name];
  1441. };
  1442. /**
  1443. * Adds a child component inside this component
  1444. *
  1445. * myComponent.el();
  1446. * // -> <div class='my-component'></div>
  1447. * myComonent.children();
  1448. * // [empty array]
  1449. *
  1450. * var myButton = myComponent.addChild('MyButton');
  1451. * // -> <div class='my-component'><div class="my-button">myButton<div></div>
  1452. * // -> myButton === myComonent.children()[0];
  1453. *
  1454. * Pass in options for child constructors and options for children of the child
  1455. *
  1456. * var myButton = myComponent.addChild('MyButton', {
  1457. * text: 'Press Me',
  1458. * children: {
  1459. * buttonChildExample: {
  1460. * buttonChildOption: true
  1461. * }
  1462. * }
  1463. * });
  1464. *
  1465. * @param {String|vjs.Component} child The class name or instance of a child to add
  1466. * @param {Object=} options Options, including options to be passed to children of the child.
  1467. * @return {vjs.Component} The child component (created by this process if a string was used)
  1468. * @suppress {accessControls|checkRegExp|checkTypes|checkVars|const|constantProperty|deprecated|duplicate|es5Strict|fileoverviewTags|globalThis|invalidCasts|missingProperties|nonStandardJsDocs|strictModuleDepCheck|undefinedNames|undefinedVars|unknownDefines|uselessCode|visibility}
  1469. */
  1470. vjs.Component.prototype.addChild = function (child, options) {
  1471. var component, componentClass, componentName, componentId;
  1472. // If string, create new component with options
  1473. if (typeof child === 'string') {
  1474. componentName = child;
  1475. // Make sure options is at least an empty object to protect against errors
  1476. options = options || {};
  1477. // Assume name of set is a lowercased name of the UI Class (PlayButton, etc.)
  1478. componentClass = options['componentClass'] || vjs.capitalize(componentName);
  1479. // Set name through options
  1480. options['name'] = componentName;
  1481. // Create a new object & element for this controls set
  1482. // If there's no .player_, this is a player
  1483. // Closure Compiler throws an 'incomplete alias' warning if we use the vjs variable directly.
  1484. // Every class should be exported, so this should never be a problem here.
  1485. component = new window['videojs'][componentClass](this.player_ || this, options);
  1486. // child is a component instance
  1487. } else {
  1488. component = child;
  1489. }
  1490. this.children_.push(component);
  1491. if (typeof component.id === 'function') {
  1492. this.childIndex_[component.id()] = component;
  1493. }
  1494. // If a name wasn't used to create the component, check if we can use the
  1495. // name function of the component
  1496. componentName = componentName || (component.name && component.name());
  1497. if (componentName) {
  1498. this.childNameIndex_[componentName] = component;
  1499. }
  1500. // Add the UI object's element to the container div (box)
  1501. // Having an element is not required
  1502. if (typeof component['el'] === 'function' && component['el']()) {
  1503. this.contentEl().appendChild(component['el']());
  1504. }
  1505. // Return so it can stored on parent object if desired.
  1506. return component;
  1507. };
  1508. /**
  1509. * Remove a child component from this component's list of children, and the
  1510. * child component's element from this component's element
  1511. *
  1512. * @param {vjs.Component} component Component to remove
  1513. */
  1514. vjs.Component.prototype.removeChild = function (component) {
  1515. if (typeof component === 'string') {
  1516. component = this.getChild(component);
  1517. }
  1518. if (!component || !this.children_) return;
  1519. var childFound = false;
  1520. for (var i = this.children_.length - 1; i >= 0; i--) {
  1521. if (this.children_[i] === component) {
  1522. childFound = true;
  1523. this.children_.splice(i, 1);
  1524. break;
  1525. }
  1526. }
  1527. if (!childFound) return;
  1528. this.childIndex_[component.id] = null;
  1529. this.childNameIndex_[component.name] = null;
  1530. var compEl = component.el();
  1531. if (compEl && compEl.parentNode === this.contentEl()) {
  1532. this.contentEl().removeChild(component.el());
  1533. }
  1534. };
  1535. /**
  1536. * Add and initialize default child components from options
  1537. *
  1538. * // when an instance of MyComponent is created, all children in options
  1539. * // will be added to the instance by their name strings and options
  1540. * MyComponent.prototype.options_.children = {
  1541. * myChildComponent: {
  1542. * myChildOption: true
  1543. * }
  1544. * }
  1545. */
  1546. vjs.Component.prototype.initChildren = function () {
  1547. var options = this.options_;
  1548. if (options && options['children']) {
  1549. var self = this;
  1550. // Loop through components and add them to the player
  1551. vjs.obj.each(options['children'], function (name, opts) {
  1552. // Allow for disabling default components
  1553. // e.g. vjs.options['children']['posterImage'] = false
  1554. if (opts === false) return;
  1555. // Allow waiting to add components until a specific event is called
  1556. var tempAdd = function () {
  1557. // Set property name on player. Could cause conflicts with other prop names, but it's worth making refs easy.
  1558. self[name] = self.addChild(name, opts);
  1559. };
  1560. if (opts['loadEvent']) {
  1561. // this.one(opts.loadEvent, tempAdd)
  1562. } else {
  1563. tempAdd();
  1564. }
  1565. });
  1566. }
  1567. };
  1568. /**
  1569. * Allows sub components to stack CSS class names
  1570. *
  1571. * @return {String} The constructed class name
  1572. */
  1573. vjs.Component.prototype.buildCSSClass = function () {
  1574. // Child classes can include a function that does:
  1575. // return 'CLASS NAME' + this._super();
  1576. return '';
  1577. };
  1578. /* Events
  1579. ============================================================================= */
  1580. /**
  1581. * Add an event listener to this component's element
  1582. *
  1583. * var myFunc = function(){
  1584. * var myPlayer = this;
  1585. * // Do something when the event is fired
  1586. * };
  1587. *
  1588. * myPlayer.on("eventName", myFunc);
  1589. *
  1590. * The context will be the component.
  1591. *
  1592. * @param {String} type The event type e.g. 'click'
  1593. * @param {Function} fn The event listener
  1594. * @return {vjs.Component} self
  1595. */
  1596. vjs.Component.prototype.on = function (type, fn) {
  1597. vjs.on(this.el_, type, vjs.bind(this, fn));
  1598. return this;
  1599. };
  1600. /**
  1601. * Remove an event listener from the component's element
  1602. *
  1603. * myComponent.off("eventName", myFunc);
  1604. *
  1605. * @param {String=} type Event type. Without type it will remove all listeners.
  1606. * @param {Function=} fn Event listener. Without fn it will remove all listeners for a type.
  1607. * @return {vjs.Component}
  1608. */
  1609. vjs.Component.prototype.off = function (type, fn) {
  1610. vjs.off(this.el_, type, fn);
  1611. return this;
  1612. };
  1613. /**
  1614. * Add an event listener to be triggered only once and then removed
  1615. *
  1616. * @param {String} type Event type
  1617. * @param {Function} fn Event listener
  1618. * @return {vjs.Component}
  1619. */
  1620. vjs.Component.prototype.one = function (type, fn) {
  1621. vjs.one(this.el_, type, vjs.bind(this, fn));
  1622. return this;
  1623. };
  1624. /**
  1625. * Trigger an event on an element
  1626. *
  1627. * myComponent.trigger('eventName');
  1628. *
  1629. * @param {String} type The event type to trigger, e.g. 'click'
  1630. * @param {Event|Object} event The event object to be passed to the listener
  1631. * @return {vjs.Component} self
  1632. */
  1633. vjs.Component.prototype.trigger = function (type, event) {
  1634. vjs.trigger(this.el_, type, event);
  1635. return this;
  1636. };
  1637. /* Ready
  1638. ================================================================================ */
  1639. /**
  1640. * Is the component loaded
  1641. * This can mean different things depending on the component.
  1642. *
  1643. * @private
  1644. * @type {Boolean}
  1645. */
  1646. vjs.Component.prototype.isReady_;
  1647. /**
  1648. * Trigger ready as soon as initialization is finished
  1649. *
  1650. * Allows for delaying ready. Override on a sub class prototype.
  1651. * If you set this.isReadyOnInitFinish_ it will affect all components.
  1652. * Specially used when waiting for the Flash player to asynchrnously load.
  1653. *
  1654. * @type {Boolean}
  1655. * @private
  1656. */
  1657. vjs.Component.prototype.isReadyOnInitFinish_ = true;
  1658. /**
  1659. * List of ready listeners
  1660. *
  1661. * @type {Array}
  1662. * @private
  1663. */
  1664. vjs.Component.prototype.readyQueue_;
  1665. /**
  1666. * Bind a listener to the component's ready state
  1667. *
  1668. * Different from event listeners in that if the ready event has already happend
  1669. * it will trigger the function immediately.
  1670. *
  1671. * @param {Function} fn Ready listener
  1672. * @return {vjs.Component}
  1673. */
  1674. vjs.Component.prototype.ready = function (fn) {
  1675. if (fn) {
  1676. if (this.isReady_) {
  1677. fn.call(this);
  1678. } else {
  1679. if (this.readyQueue_ === undefined) {
  1680. this.readyQueue_ = [];
  1681. }
  1682. this.readyQueue_.push(fn);
  1683. }
  1684. }
  1685. return this;
  1686. };
  1687. /**
  1688. * Trigger the ready listeners
  1689. *
  1690. * @return {vjs.Component}
  1691. */
  1692. vjs.Component.prototype.triggerReady = function () {
  1693. this.isReady_ = true;
  1694. var readyQueue = this.readyQueue_;
  1695. if (readyQueue && readyQueue.length > 0) {
  1696. for (var i = 0, j = readyQueue.length; i < j; i++) {
  1697. readyQueue[i].call(this);
  1698. }
  1699. // Reset Ready Queue
  1700. this.readyQueue_ = [];
  1701. // Allow for using event listeners also, in case you want to do something everytime a source is ready.
  1702. this.trigger('ready');
  1703. }
  1704. };
  1705. /* Display
  1706. ============================================================================= */
  1707. /**
  1708. * Add a CSS class name to the component's element
  1709. *
  1710. * @param {String} classToAdd Classname to add
  1711. * @return {vjs.Component}
  1712. */
  1713. vjs.Component.prototype.addClass = function (classToAdd) {
  1714. vjs.addClass(this.el_, classToAdd);
  1715. return this;
  1716. };
  1717. /**
  1718. * Remove a CSS class name from the component's element
  1719. *
  1720. * @param {String} classToRemove Classname to remove
  1721. * @return {vjs.Component}
  1722. */
  1723. vjs.Component.prototype.removeClass = function (classToRemove) {
  1724. vjs.removeClass(this.el_, classToRemove);
  1725. return this;
  1726. };
  1727. /**
  1728. * Show the component element if hidden
  1729. *
  1730. * @return {vjs.Component}
  1731. */
  1732. vjs.Component.prototype.show = function () {
  1733. this.el_.style.display = 'block';
  1734. return this;
  1735. };
  1736. /**
  1737. * Hide the component element if hidden
  1738. *
  1739. * @return {vjs.Component}
  1740. */
  1741. vjs.Component.prototype.hide = function () {
  1742. this.el_.style.display = 'none';
  1743. return this;
  1744. };
  1745. /**
  1746. * Lock an item in its visible state
  1747. * To be used with fadeIn/fadeOut.
  1748. *
  1749. * @return {vjs.Component}
  1750. * @private
  1751. */
  1752. vjs.Component.prototype.lockShowing = function () {
  1753. this.addClass('vjs-lock-showing');
  1754. return this;
  1755. };
  1756. /**
  1757. * Unlock an item to be hidden
  1758. * To be used with fadeIn/fadeOut.
  1759. *
  1760. * @return {vjs.Component}
  1761. * @private
  1762. */
  1763. vjs.Component.prototype.unlockShowing = function () {
  1764. this.removeClass('vjs-lock-showing');
  1765. return this;
  1766. };
  1767. /**
  1768. * Disable component by making it unshowable
  1769. */
  1770. vjs.Component.prototype.disable = function () {
  1771. this.hide();
  1772. this.show = function () {
  1773. };
  1774. };
  1775. /**
  1776. * Set or get the width of the component (CSS values)
  1777. *
  1778. * Video tag width/height only work in pixels. No percents.
  1779. * But allowing limited percents use. e.g. width() will return number+%, not computed width
  1780. *
  1781. * @param {Number|String=} num Optional width number
  1782. * @param {Boolean} skipListeners Skip the 'resize' event trigger
  1783. * @return {vjs.Component} Returns 'this' if width was set
  1784. * @return {Number|String} Returns the width if nothing was set
  1785. */
  1786. vjs.Component.prototype.width = function (num, skipListeners) {
  1787. return this.dimension('width', num, skipListeners);
  1788. };
  1789. /**
  1790. * Get or set the height of the component (CSS values)
  1791. *
  1792. * @param {Number|String=} num New component height
  1793. * @param {Boolean=} skipListeners Skip the resize event trigger
  1794. * @return {vjs.Component} The component if the height was set
  1795. * @return {Number|String} The height if it wasn't set
  1796. */
  1797. vjs.Component.prototype.height = function (num, skipListeners) {
  1798. return this.dimension('height', num, skipListeners);
  1799. };
  1800. /**
  1801. * Set both width and height at the same time
  1802. *
  1803. * @param {Number|String} width
  1804. * @param {Number|String} height
  1805. * @return {vjs.Component} The component
  1806. */
  1807. vjs.Component.prototype.dimensions = function (width, height) {
  1808. // Skip resize listeners on width for optimization
  1809. return this.width(width, true).height(height);
  1810. };
  1811. /**
  1812. * Get or set width or height
  1813. *
  1814. * This is the shared code for the width() and height() methods.
  1815. * All for an integer, integer + 'px' or integer + '%';
  1816. *
  1817. * Known issue: Hidden elements officially have a width of 0. We're defaulting
  1818. * to the style.width value and falling back to computedStyle which has the
  1819. * hidden element issue. Info, but probably not an efficient fix:
  1820. * http://www.foliotek.com/devblog/getting-the-width-of-a-hidden-element-with-jquery-using-width/
  1821. *
  1822. * @param {String} widthOrHeight 'width' or 'height'
  1823. * @param {Number|String=} num New dimension
  1824. * @param {Boolean=} skipListeners Skip resize event trigger
  1825. * @return {vjs.Component} The component if a dimension was set
  1826. * @return {Number|String} The dimension if nothing was set
  1827. * @private
  1828. */
  1829. vjs.Component.prototype.dimension = function (widthOrHeight, num, skipListeners) {
  1830. if (num !== undefined) {
  1831. // Check if using css width/height (% or px) and adjust
  1832. if (('' + num).indexOf('%') !== -1 || ('' + num).indexOf('px') !== -1) {
  1833. this.el_.style[widthOrHeight] = num;
  1834. } else if (num === 'auto') {
  1835. this.el_.style[widthOrHeight] = '';
  1836. } else {
  1837. this.el_.style[widthOrHeight] = num + 'px';
  1838. }
  1839. // skipListeners allows us to avoid triggering the resize event when setting both width and height
  1840. if (!skipListeners) {
  1841. this.trigger('resize');
  1842. }
  1843. // Return component
  1844. return this;
  1845. }
  1846. // Not setting a value, so getting it
  1847. // Make sure element exists
  1848. if (!this.el_) return 0;
  1849. // Get dimension value from style
  1850. var val = this.el_.style[widthOrHeight];
  1851. var pxIndex = val.indexOf('px');
  1852. if (pxIndex !== -1) {
  1853. // Return the pixel value with no 'px'
  1854. return parseInt(val.slice(0, pxIndex), 10);
  1855. // No px so using % or no style was set, so falling back to offsetWidth/height
  1856. // If component has display:none, offset will return 0
  1857. // TODO: handle display:none and no dimension style using px
  1858. } else {
  1859. return parseInt(this.el_['offset' + vjs.capitalize(widthOrHeight)], 10);
  1860. // ComputedStyle version.
  1861. // Only difference is if the element is hidden it will return
  1862. // the percent value (e.g. '100%'')
  1863. // instead of zero like offsetWidth returns.
  1864. // var val = vjs.getComputedStyleValue(this.el_, widthOrHeight);
  1865. // var pxIndex = val.indexOf('px');
  1866. // if (pxIndex !== -1) {
  1867. // return val.slice(0, pxIndex);
  1868. // } else {
  1869. // return val;
  1870. // }
  1871. }
  1872. };
  1873. /**
  1874. * Fired when the width and/or height of the component changes
  1875. * @event resize
  1876. */
  1877. vjs.Component.prototype.onResize;
  1878. /**
  1879. * Emit 'tap' events when touch events are supported
  1880. *
  1881. * This is used to support toggling the controls through a tap on the video.
  1882. *
  1883. * We're requireing them to be enabled because otherwise every component would
  1884. * have this extra overhead unnecessarily, on mobile devices where extra
  1885. * overhead is especially bad.
  1886. * @private
  1887. */
  1888. vjs.Component.prototype.emitTapEvents = function () {
  1889. var touchStart, touchTime, couldBeTap, noTap;
  1890. // Track the start time so we can determine how long the touch lasted
  1891. touchStart = 0;
  1892. this.on('touchstart', function (event) {
  1893. // Record start time so we can detect a tap vs. "touch and hold"
  1894. touchStart = new Date().getTime();
  1895. // Reset couldBeTap tracking
  1896. couldBeTap = true;
  1897. });
  1898. noTap = function () {
  1899. couldBeTap = false;
  1900. };
  1901. // TODO: Listen to the original target. http://youtu.be/DujfpXOKUp8?t=13m8s
  1902. this.on('touchmove', noTap);
  1903. this.on('touchleave', noTap);
  1904. this.on('touchcancel', noTap);
  1905. // When the touch ends, measure how long it took and trigger the appropriate
  1906. // event
  1907. this.on('touchend', function () {
  1908. // Proceed only if the touchmove/leave/cancel event didn't happen
  1909. if (couldBeTap === true) {
  1910. // Measure how long the touch lasted
  1911. touchTime = new Date().getTime() - touchStart;
  1912. // The touch needs to be quick in order to consider it a tap
  1913. if (touchTime < 250) {
  1914. this.trigger('tap');
  1915. // It may be good to copy the touchend event object and change the
  1916. // type to tap, if the other event properties aren't exact after
  1917. // vjs.fixEvent runs (e.g. event.target)
  1918. }
  1919. }
  1920. });
  1921. };
  1922. /* Button - Base class for all buttons
  1923. ================================================================================ */
  1924. /**
  1925. * Base class for all buttons
  1926. * @param {vjs.Player|Object} player
  1927. * @param {Object=} options
  1928. * @class
  1929. * @constructor
  1930. */
  1931. vjs.Button = vjs.Component.extend({
  1932. /**
  1933. * @constructor
  1934. * @inheritDoc
  1935. */
  1936. init: function (player, options) {
  1937. vjs.Component.call(this, player, options);
  1938. var touchstart = false;
  1939. this.on('touchstart', function (event) {
  1940. // Stop click and other mouse events from triggering also
  1941. event.preventDefault();
  1942. touchstart = true;
  1943. });
  1944. this.on('touchmove', function () {
  1945. touchstart = false;
  1946. });
  1947. var self = this;
  1948. this.on('touchend', function (event) {
  1949. if (touchstart) {
  1950. self.onClick(event);
  1951. }
  1952. event.preventDefault();
  1953. });
  1954. this.on('click', this.onClick);
  1955. this.on('focus', this.onFocus);
  1956. this.on('blur', this.onBlur);
  1957. }
  1958. });
  1959. vjs.Button.prototype.createEl = function (type, props) {
  1960. // Add standard Aria and Tabindex info
  1961. props = vjs.obj.merge({
  1962. className: this.buildCSSClass(),
  1963. innerHTML: '<div class="vjs-control-content"><span class="vjs-control-text">' + (this.buttonText || 'Need Text') + '</span></div>',
  1964. role: 'button',
  1965. 'aria-live': 'polite', // let the screen reader user know that the text of the button may change
  1966. tabIndex: 0
  1967. }, props);
  1968. return vjs.Component.prototype.createEl.call(this, type, props);
  1969. };
  1970. vjs.Button.prototype.buildCSSClass = function () {
  1971. // TODO: Change vjs-control to vjs-button?
  1972. return 'vjs-control ' + vjs.Component.prototype.buildCSSClass.call(this);
  1973. };
  1974. // Click - Override with specific functionality for button
  1975. vjs.Button.prototype.onClick = function () {
  1976. };
  1977. // Focus - Add keyboard functionality to element
  1978. vjs.Button.prototype.onFocus = function () {
  1979. vjs.on(document, 'keyup', vjs.bind(this, this.onKeyPress));
  1980. };
  1981. // KeyPress (document level) - Trigger click when keys are pressed
  1982. vjs.Button.prototype.onKeyPress = function (event) {
  1983. // Check for space bar (32) or enter (13) keys
  1984. if (event.which == 32 || event.which == 13) {
  1985. event.preventDefault();
  1986. this.onClick();
  1987. }
  1988. };
  1989. // Blur - Remove keyboard triggers
  1990. vjs.Button.prototype.onBlur = function () {
  1991. vjs.off(document, 'keyup', vjs.bind(this, this.onKeyPress));
  1992. };
  1993. /* Slider
  1994. ================================================================================ */
  1995. /**
  1996. * The base functionality for sliders like the volume bar and seek bar
  1997. *
  1998. * @param {vjs.Player|Object} player
  1999. * @param {Object=} options
  2000. * @constructor
  2001. */
  2002. vjs.Slider = vjs.Component.extend({
  2003. /** @constructor */
  2004. init: function (player, options) {
  2005. vjs.Component.call(this, player, options);
  2006. // Set property names to bar and handle to match with the child Slider class is looking for
  2007. this.bar = this.getChild(this.options_['barName']);
  2008. this.handle = this.getChild(this.options_['handleName']);
  2009. player.on(this.playerEvent, vjs.bind(this, this.update));
  2010. this.on('mousedown', this.onMouseDown);
  2011. this.on('touchstart', this.onMouseDown);
  2012. this.on('focus', this.onFocus);
  2013. this.on('blur', this.onBlur);
  2014. this.on('click', this.onClick);
  2015. this.player_.on('controlsvisible', vjs.bind(this, this.update));
  2016. // This is actually to fix the volume handle position. http://twitter.com/#!/gerritvanaaken/status/159046254519787520
  2017. // this.player_.one('timeupdate', vjs.bind(this, this.update));
  2018. player.ready(vjs.bind(this, this.update));
  2019. this.boundEvents = {};
  2020. }
  2021. });
  2022. vjs.Slider.prototype.createEl = function (type, props) {
  2023. props = props || {};
  2024. // Add the slider element class to all sub classes
  2025. props.className = props.className + ' vjs-slider';
  2026. props = vjs.obj.merge({
  2027. role: 'slider',
  2028. 'aria-valuenow': 0,
  2029. 'aria-valuemin': 0,
  2030. 'aria-valuemax': 100,
  2031. tabIndex: 0
  2032. }, props);
  2033. return vjs.Component.prototype.createEl.call(this, type, props);
  2034. };
  2035. vjs.Slider.prototype.onMouseDown = function (event) {
  2036. event.preventDefault();
  2037. vjs.blockTextSelection();
  2038. this.boundEvents.move = vjs.bind(this, this.onMouseMove);
  2039. this.boundEvents.end = vjs.bind(this, this.onMouseUp);
  2040. vjs.on(document, 'mousemove', this.boundEvents.move);
  2041. vjs.on(document, 'mouseup', this.boundEvents.end);
  2042. vjs.on(document, 'touchmove', this.boundEvents.move);
  2043. vjs.on(document, 'touchend', this.boundEvents.end);
  2044. this.onMouseMove(event);
  2045. };
  2046. vjs.Slider.prototype.onMouseUp = function () {
  2047. vjs.unblockTextSelection();
  2048. vjs.off(document, 'mousemove', this.boundEvents.move, false);
  2049. vjs.off(document, 'mouseup', this.boundEvents.end, false);
  2050. vjs.off(document, 'touchmove', this.boundEvents.move, false);
  2051. vjs.off(document, 'touchend', this.boundEvents.end, false);
  2052. this.update();
  2053. };
  2054. vjs.Slider.prototype.update = function () {
  2055. // In VolumeBar init we have a setTimeout for update that pops and update to the end of the
  2056. // execution stack. The player is destroyed before then update will cause an error
  2057. if (!this.el_) return;
  2058. // If scrubbing, we could use a cached value to make the handle keep up with the user's mouse.
  2059. // On HTML5 browsers scrubbing is really smooth, but some flash players are slow, so we might want to utilize this later.
  2060. // var progress = (this.player_.scrubbing) ? this.player_.getCache().currentTime / this.player_.duration() : this.player_.currentTime() / this.player_.duration();
  2061. var barProgress,
  2062. progress = this.getPercent(),
  2063. handle = this.handle,
  2064. bar = this.bar;
  2065. // Protect against no duration and other division issues
  2066. if (isNaN(progress)) {
  2067. progress = 0;
  2068. }
  2069. barProgress = progress;
  2070. // If there is a handle, we need to account for the handle in our calculation for progress bar
  2071. // so that it doesn't fall short of or extend past the handle.
  2072. if (handle) {
  2073. var box = this.el_,
  2074. boxWidth = box.offsetWidth,
  2075. handleWidth = handle.el().offsetWidth,
  2076. // The width of the handle in percent of the containing box
  2077. // In IE, widths may not be ready yet causing NaN
  2078. handlePercent = (handleWidth) ? handleWidth / boxWidth : 0,
  2079. // Get the adjusted size of the box, considering that the handle's center never touches the left or right side.
  2080. // There is a margin of half the handle's width on both sides.
  2081. boxAdjustedPercent = 1 - handlePercent,
  2082. // Adjust the progress that we'll use to set widths to the new adjusted box width
  2083. adjustedProgress = progress * boxAdjustedPercent;
  2084. // The bar does reach the left side, so we need to account for this in the bar's width
  2085. barProgress = adjustedProgress + (handlePercent / 2);
  2086. // Move the handle from the left based on the adjected progress
  2087. handle.el().style.left = vjs.round(adjustedProgress * 100, 2) + '%';
  2088. }
  2089. // Set the new bar width
  2090. bar.el().style.width = vjs.round(barProgress * 100, 2) + '%';
  2091. };
  2092. vjs.Slider.prototype.calculateDistance = function (event) {
  2093. var el, box, boxX, boxY, boxW, boxH, handle, pageX, pageY;
  2094. el = this.el_;
  2095. box = vjs.findPosition(el);
  2096. boxW = boxH = el.offsetWidth;
  2097. handle = this.handle;
  2098. if (this.options_.vertical) {
  2099. boxY = box.top;
  2100. if (event.changedTouches) {
  2101. pageY = event.changedTouches[0].pageY;
  2102. } else {
  2103. pageY = event.pageY;
  2104. }
  2105. if (handle) {
  2106. var handleH = handle.el().offsetHeight;
  2107. // Adjusted X and Width, so handle doesn't go outside the bar
  2108. boxY = boxY + (handleH / 2);
  2109. boxH = boxH - handleH;
  2110. }
  2111. // Percent that the click is through the adjusted area
  2112. return Math.max(0, Math.min(1, ((boxY - pageY) + boxH) / boxH));
  2113. } else {
  2114. boxX = box.left;
  2115. if (event.changedTouches) {
  2116. pageX = event.changedTouches[0].pageX;
  2117. } else {
  2118. pageX = event.pageX;
  2119. }
  2120. if (handle) {
  2121. var handleW = handle.el().offsetWidth;
  2122. // Adjusted X and Width, so handle doesn't go outside the bar
  2123. boxX = boxX + (handleW / 2);
  2124. boxW = boxW - handleW;
  2125. }
  2126. // Percent that the click is through the adjusted area
  2127. return Math.max(0, Math.min(1, (pageX - boxX) / boxW));
  2128. }
  2129. };
  2130. vjs.Slider.prototype.onFocus = function () {
  2131. vjs.on(document, 'keyup', vjs.bind(this, this.onKeyPress));
  2132. };
  2133. vjs.Slider.prototype.onKeyPress = function (event) {
  2134. if (event.which == 37) { // Left Arrow
  2135. event.preventDefault();
  2136. this.stepBack();
  2137. } else if (event.which == 39) { // Right Arrow
  2138. event.preventDefault();
  2139. this.stepForward();
  2140. }
  2141. };
  2142. vjs.Slider.prototype.onBlur = function () {
  2143. vjs.off(document, 'keyup', vjs.bind(this, this.onKeyPress));
  2144. };
  2145. /**
  2146. * Listener for click events on slider, used to prevent clicks
  2147. * from bubbling up to parent elements like button menus.
  2148. * @param {Object} event Event object
  2149. */
  2150. vjs.Slider.prototype.onClick = function (event) {
  2151. event.stopImmediatePropagation();
  2152. event.preventDefault();
  2153. };
  2154. /**
  2155. * SeekBar Behavior includes play progress bar, and seek handle
  2156. * Needed so it can determine seek position based on handle position/size
  2157. * @param {vjs.Player|Object} player
  2158. * @param {Object=} options
  2159. * @constructor
  2160. */
  2161. vjs.SliderHandle = vjs.Component.extend();
  2162. /**
  2163. * Default value of the slider
  2164. *
  2165. * @type {Number}
  2166. * @private
  2167. */
  2168. vjs.SliderHandle.prototype.defaultValue = 0;
  2169. /** @inheritDoc */
  2170. vjs.SliderHandle.prototype.createEl = function (type, props) {
  2171. props = props || {};
  2172. // Add the slider element class to all sub classes
  2173. props.className = props.className + ' vjs-slider-handle';
  2174. props = vjs.obj.merge({
  2175. innerHTML: '<span class="vjs-control-text">' + this.defaultValue + '</span>'
  2176. }, props);
  2177. return vjs.Component.prototype.createEl.call(this, 'div', props);
  2178. };
  2179. /* Menu
  2180. ================================================================================ */
  2181. /**
  2182. * The Menu component is used to build pop up menus, including subtitle and
  2183. * captions selection menus.
  2184. *
  2185. * @param {vjs.Player|Object} player
  2186. * @param {Object=} options
  2187. * @class
  2188. * @constructor
  2189. */
  2190. vjs.Menu = vjs.Component.extend();
  2191. /**
  2192. * Add a menu item to the menu
  2193. * @param {Object|String} component Component or component type to add
  2194. */
  2195. vjs.Menu.prototype.addItem = function (component) {
  2196. this.addChild(component);
  2197. component.on('click', vjs.bind(this, function () {
  2198. this.unlockShowing();
  2199. }));
  2200. };
  2201. /** @inheritDoc */
  2202. vjs.Menu.prototype.createEl = function () {
  2203. var contentElType = this.options().contentElType || 'ul';
  2204. this.contentEl_ = vjs.createEl(contentElType, {
  2205. className: 'vjs-menu-content'
  2206. });
  2207. var el = vjs.Component.prototype.createEl.call(this, 'div', {
  2208. append: this.contentEl_,
  2209. className: 'vjs-menu'
  2210. });
  2211. el.appendChild(this.contentEl_);
  2212. // Prevent clicks from bubbling up. Needed for Menu Buttons,
  2213. // where a click on the parent is significant
  2214. vjs.on(el, 'click', function (event) {
  2215. event.preventDefault();
  2216. event.stopImmediatePropagation();
  2217. });
  2218. return el;
  2219. };
  2220. /**
  2221. * The component for a menu item. `<li>`
  2222. *
  2223. * @param {vjs.Player|Object} player
  2224. * @param {Object=} options
  2225. * @class
  2226. * @constructor
  2227. */
  2228. vjs.MenuItem = vjs.Button.extend({
  2229. /** @constructor */
  2230. init: function (player, options) {
  2231. vjs.Button.call(this, player, options);
  2232. this.selected(options['selected']);
  2233. }
  2234. });
  2235. /** @inheritDoc */
  2236. vjs.MenuItem.prototype.createEl = function (type, props) {
  2237. return vjs.Button.prototype.createEl.call(this, 'li', vjs.obj.merge({
  2238. className: 'vjs-menu-item',
  2239. innerHTML: this.options_['label']
  2240. }, props));
  2241. };
  2242. /**
  2243. * Handle a click on the menu item, and set it to selected
  2244. */
  2245. vjs.MenuItem.prototype.onClick = function () {
  2246. this.selected(true);
  2247. };
  2248. /**
  2249. * Set this menu item as selected or not
  2250. * @param {Boolean} selected
  2251. */
  2252. vjs.MenuItem.prototype.selected = function (selected) {
  2253. if (selected) {
  2254. this.addClass('vjs-selected');
  2255. this.el_.setAttribute('aria-selected', true);
  2256. } else {
  2257. this.removeClass('vjs-selected');
  2258. this.el_.setAttribute('aria-selected', false);
  2259. }
  2260. };
  2261. /**
  2262. * A button class with a popup menu
  2263. * @param {vjs.Player|Object} player
  2264. * @param {Object=} options
  2265. * @constructor
  2266. */
  2267. vjs.MenuButton = vjs.Button.extend({
  2268. /** @constructor */
  2269. init: function (player, options) {
  2270. vjs.Button.call(this, player, options);
  2271. this.menu = this.createMenu();
  2272. // Add list to element
  2273. this.addChild(this.menu);
  2274. // Automatically hide empty menu buttons
  2275. if (this.items && this.items.length === 0) {
  2276. this.hide();
  2277. }
  2278. this.on('keyup', this.onKeyPress);
  2279. this.el_.setAttribute('aria-haspopup', true);
  2280. this.el_.setAttribute('role', 'button');
  2281. }
  2282. });
  2283. /**
  2284. * Track the state of the menu button
  2285. * @type {Boolean}
  2286. * @private
  2287. */
  2288. vjs.MenuButton.prototype.buttonPressed_ = false;
  2289. vjs.MenuButton.prototype.createMenu = function () {
  2290. var menu = new vjs.Menu(this.player_);
  2291. // Add a title list item to the top
  2292. if (this.options().title) {
  2293. menu.el().appendChild(vjs.createEl('li', {
  2294. className: 'vjs-menu-title',
  2295. innerHTML: vjs.capitalize(this.kind_),
  2296. tabindex: -1
  2297. }));
  2298. }
  2299. this.items = this['createItems']();
  2300. if (this.items) {
  2301. // Add menu items to the menu
  2302. for (var i = 0; i < this.items.length; i++) {
  2303. menu.addItem(this.items[i]);
  2304. }
  2305. }
  2306. return menu;
  2307. };
  2308. /**
  2309. * Create the list of menu items. Specific to each subclass.
  2310. */
  2311. vjs.MenuButton.prototype.createItems = function () {
  2312. };
  2313. /** @inheritDoc */
  2314. vjs.MenuButton.prototype.buildCSSClass = function () {
  2315. return this.className + ' vjs-menu-button ' + vjs.Button.prototype.buildCSSClass.call(this);
  2316. };
  2317. // Focus - Add keyboard functionality to element
  2318. // This function is not needed anymore. Instead, the keyboard functionality is handled by
  2319. // treating the button as triggering a submenu. When the button is pressed, the submenu
  2320. // appears. Pressing the button again makes the submenu disappear.
  2321. vjs.MenuButton.prototype.onFocus = function () {
  2322. };
  2323. // Can't turn off list display that we turned on with focus, because list would go away.
  2324. vjs.MenuButton.prototype.onBlur = function () {
  2325. };
  2326. vjs.MenuButton.prototype.onClick = function () {
  2327. // When you click the button it adds focus, which will show the menu indefinitely.
  2328. // So we'll remove focus when the mouse leaves the button.
  2329. // Focus is needed for tab navigation.
  2330. this.one('mouseout', vjs.bind(this, function () {
  2331. this.menu.unlockShowing();
  2332. this.el_.blur();
  2333. }));
  2334. if (this.buttonPressed_) {
  2335. this.unpressButton();
  2336. } else {
  2337. this.pressButton();
  2338. }
  2339. };
  2340. vjs.MenuButton.prototype.onKeyPress = function (event) {
  2341. event.preventDefault();
  2342. // Check for space bar (32) or enter (13) keys
  2343. if (event.which == 32 || event.which == 13) {
  2344. if (this.buttonPressed_) {
  2345. this.unpressButton();
  2346. } else {
  2347. this.pressButton();
  2348. }
  2349. // Check for escape (27) key
  2350. } else if (event.which == 27) {
  2351. if (this.buttonPressed_) {
  2352. this.unpressButton();
  2353. }
  2354. }
  2355. };
  2356. vjs.MenuButton.prototype.pressButton = function () {
  2357. this.buttonPressed_ = true;
  2358. this.menu.lockShowing();
  2359. this.el_.setAttribute('aria-pressed', true);
  2360. if (this.items && this.items.length > 0) {
  2361. this.items[0].el().focus(); // set the focus to the title of the submenu
  2362. }
  2363. };
  2364. vjs.MenuButton.prototype.unpressButton = function () {
  2365. this.buttonPressed_ = false;
  2366. this.menu.unlockShowing();
  2367. this.el_.setAttribute('aria-pressed', false);
  2368. };
  2369. /**
  2370. * An instance of the `vjs.Player` class is created when any of the Video.js setup methods are used to initialize a video.
  2371. *
  2372. * ```js
  2373. * var myPlayer = videojs('example_video_1');
  2374. * ```
  2375. *
  2376. * In the follwing example, the `data-setup` attribute tells the Video.js library to create a player instance when the library is ready.
  2377. *
  2378. * ```html
  2379. * <video id="example_video_1" data-setup='{}' controls>
  2380. * <source src="my-source.mp4" type="video/mp4">
  2381. * </video>
  2382. * ```
  2383. *
  2384. * After an instance has been created it can be accessed globally using `Video('example_video_1')`.
  2385. *
  2386. * @class
  2387. * @extends vjs.Component
  2388. */
  2389. vjs.Player = vjs.Component.extend({
  2390. /**
  2391. * player's constructor function
  2392. *
  2393. * @constructs
  2394. * @method init
  2395. * @param {Element} tag The original video tag used for configuring options
  2396. * @param {Object=} options Player options
  2397. * @param {Function=} ready Ready callback function
  2398. */
  2399. init: function (tag, options, ready) {
  2400. this.tag = tag; // Store the original tag used to set options
  2401. // Set Options
  2402. // The options argument overrides options set in the video tag
  2403. // which overrides globally set options.
  2404. // This latter part coincides with the load order
  2405. // (tag must exist before Player)
  2406. options = vjs.obj.merge(this.getTagSettings(tag), options);
  2407. // Cache for video property values.
  2408. this.cache_ = {};
  2409. // Set poster
  2410. this.poster_ = options['poster'];
  2411. // Set controls
  2412. this.controls_ = options['controls'];
  2413. // Original tag settings stored in options
  2414. // now remove immediately so native controls don't flash.
  2415. // May be turned back on by HTML5 tech if nativeControlsForTouch is true
  2416. tag.controls = false;
  2417. // Run base component initializing with new options.
  2418. // Builds the element through createEl()
  2419. // Inits and embeds any child components in opts
  2420. vjs.Component.call(this, this, options, ready);
  2421. // Update controls className. Can't do this when the controls are initially
  2422. // set because the element doesn't exist yet.
  2423. if (this.controls()) {
  2424. this.addClass('vjs-controls-enabled');
  2425. } else {
  2426. this.addClass('vjs-controls-disabled');
  2427. }
  2428. // TODO: Make this smarter. Toggle user state between touching/mousing
  2429. // using events, since devices can have both touch and mouse events.
  2430. // if (vjs.TOUCH_ENABLED) {
  2431. // this.addClass('vjs-touch-enabled');
  2432. // }
  2433. // Firstplay event implimentation. Not sold on the event yet.
  2434. // Could probably just check currentTime==0?
  2435. this.one('play', function (e) {
  2436. var fpEvent = {type: 'firstplay', target: this.el_};
  2437. // Using vjs.trigger so we can check if default was prevented
  2438. var keepGoing = vjs.trigger(this.el_, fpEvent);
  2439. if (!keepGoing) {
  2440. e.preventDefault();
  2441. e.stopPropagation();
  2442. e.stopImmediatePropagation();
  2443. }
  2444. });
  2445. this.on('ended', this.onEnded);
  2446. this.on('play', this.onPlay);
  2447. this.on('firstplay', this.onFirstPlay);
  2448. this.on('pause', this.onPause);
  2449. this.on('progress', this.onProgress);
  2450. this.on('durationchange', this.onDurationChange);
  2451. this.on('error', this.onError);
  2452. this.on('fullscreenchange', this.onFullscreenChange);
  2453. // Make player easily findable by ID
  2454. vjs.players[this.id_] = this;
  2455. if (options['plugins']) {
  2456. vjs.obj.each(options['plugins'], function (key, val) {
  2457. this[key](val);
  2458. }, this);
  2459. }
  2460. this.listenForUserActivity();
  2461. }
  2462. });
  2463. /**
  2464. * Player instance options, surfaced using vjs.options
  2465. * vjs.options = vjs.Player.prototype.options_
  2466. * Make changes in vjs.options, not here.
  2467. * All options should use string keys so they avoid
  2468. * renaming by closure compiler
  2469. * @type {Object}
  2470. * @private
  2471. */
  2472. vjs.Player.prototype.options_ = vjs.options;
  2473. /**
  2474. * Destroys the video player and does any necessary cleanup
  2475. *
  2476. * myPlayer.dispose();
  2477. *
  2478. * This is especially helpful if you are dynamically adding and removing videos
  2479. * to/from the DOM.
  2480. */
  2481. vjs.Player.prototype.dispose = function () {
  2482. this.trigger('dispose');
  2483. // prevent dispose from being called twice
  2484. this.off('dispose');
  2485. // Kill reference to this player
  2486. vjs.players[this.id_] = null;
  2487. if (this.tag && this.tag['player']) {
  2488. this.tag['player'] = null;
  2489. }
  2490. if (this.el_ && this.el_['player']) {
  2491. this.el_['player'] = null;
  2492. }
  2493. // Ensure that tracking progress and time progress will stop and plater deleted
  2494. this.stopTrackingProgress();
  2495. this.stopTrackingCurrentTime();
  2496. if (this.tech) {
  2497. this.tech.dispose();
  2498. }
  2499. // Component dispose
  2500. vjs.Component.prototype.dispose.call(this);
  2501. };
  2502. vjs.Player.prototype.getTagSettings = function (tag) {
  2503. var options = {
  2504. 'sources': [],
  2505. 'tracks': []
  2506. };
  2507. vjs.obj.merge(options, vjs.getAttributeValues(tag));
  2508. // Get tag children settings
  2509. if (tag.hasChildNodes()) {
  2510. var children, child, childName, i, j;
  2511. children = tag.childNodes;
  2512. for (i = 0, j = children.length; i < j; i++) {
  2513. child = children[i];
  2514. // Change case needed: http://ejohn.org/blog/nodename-case-sensitivity/
  2515. childName = child.nodeName.toLowerCase();
  2516. if (childName === 'source') {
  2517. options['sources'].push(vjs.getAttributeValues(child));
  2518. } else if (childName === 'track') {
  2519. options['tracks'].push(vjs.getAttributeValues(child));
  2520. }
  2521. }
  2522. }
  2523. return options;
  2524. };
  2525. vjs.Player.prototype.createEl = function () {
  2526. var el = this.el_ = vjs.Component.prototype.createEl.call(this, 'div');
  2527. var tag = this.tag;
  2528. // Remove width/height attrs from tag so CSS can make it 100% width/height
  2529. tag.removeAttribute('width');
  2530. tag.removeAttribute('height');
  2531. // Empty video tag tracks so the built-in player doesn't use them also.
  2532. // This may not be fast enough to stop HTML5 browsers from reading the tags
  2533. // so we'll need to turn off any default tracks if we're manually doing
  2534. // captions and subtitles. videoElement.textTracks
  2535. if (tag.hasChildNodes()) {
  2536. var nodes, nodesLength, i, node, nodeName, removeNodes;
  2537. nodes = tag.childNodes;
  2538. nodesLength = nodes.length;
  2539. removeNodes = [];
  2540. while (nodesLength--) {
  2541. node = nodes[nodesLength];
  2542. nodeName = node.nodeName.toLowerCase();
  2543. if (nodeName === 'track') {
  2544. removeNodes.push(node);
  2545. }
  2546. }
  2547. for (i = 0; i < removeNodes.length; i++) {
  2548. tag.removeChild(removeNodes[i]);
  2549. }
  2550. }
  2551. // Make sure tag ID exists
  2552. tag.id = tag.id || 'vjs_video_' + vjs.guid++;
  2553. // Give video tag ID and class to player div
  2554. // ID will now reference player box, not the video tag
  2555. el.id = tag.id;
  2556. el.className = tag.className;
  2557. // Update tag id/class for use as HTML5 playback tech
  2558. // Might think we should do this after embedding in container so .vjs-tech class
  2559. // doesn't flash 100% width/height, but class only applies with .video-js parent
  2560. tag.id += '_html5_api';
  2561. tag.className = 'vjs-tech';
  2562. // Make player findable on elements
  2563. tag['player'] = el['player'] = this;
  2564. // Default state of video is paused
  2565. this.addClass('vjs-paused');
  2566. // Make box use width/height of tag, or rely on default implementation
  2567. // Enforce with CSS since width/height attrs don't work on divs
  2568. this.width(this.options_['width'], true); // (true) Skip resize listener on load
  2569. this.height(this.options_['height'], true);
  2570. // Wrap video tag in div (el/box) container
  2571. if (tag.parentNode) {
  2572. tag.parentNode.insertBefore(el, tag);
  2573. }
  2574. vjs.insertFirst(tag, el); // Breaks iPhone, fixed in HTML5 setup.
  2575. return el;
  2576. };
  2577. // /* Media Technology (tech)
  2578. // ================================================================================ */
  2579. // Load/Create an instance of playback technlogy including element and API methods
  2580. // And append playback element in player div.
  2581. vjs.Player.prototype.loadTech = function (techName, source) {
  2582. // Pause and remove current playback technology
  2583. if (this.tech) {
  2584. this.unloadTech();
  2585. // if this is the first time loading, HTML5 tag will exist but won't be initialized
  2586. // so we need to remove it if we're not loading HTML5
  2587. } else if (techName !== 'Html5' && this.tag) {
  2588. vjs.Html5.disposeMediaElement(this.tag);
  2589. this.tag = null;
  2590. }
  2591. this.techName = techName;
  2592. // Turn off API access because we're loading a new tech that might load asynchronously
  2593. this.isReady_ = false;
  2594. var techReady = function () {
  2595. this.player_.triggerReady();
  2596. // Manually track progress in cases where the browser/flash player doesn't report it.
  2597. if (!this.features['progressEvents']) {
  2598. this.player_.manualProgressOn();
  2599. }
  2600. // Manually track timeudpates in cases where the browser/flash player doesn't report it.
  2601. if (!this.features['timeupdateEvents']) {
  2602. this.player_.manualTimeUpdatesOn();
  2603. }
  2604. };
  2605. // Grab tech-specific options from player options and add source and parent element to use.
  2606. var techOptions = vjs.obj.merge({'source': source, 'parentEl': this.el_}, this.options_[techName.toLowerCase()]);
  2607. if (source) {
  2608. if (source.src == this.cache_.src && this.cache_.currentTime > 0) {
  2609. techOptions['startTime'] = this.cache_.currentTime;
  2610. }
  2611. this.cache_.src = source.src;
  2612. }
  2613. // Initialize tech instance
  2614. this.tech = new window['videojs'][techName](this, techOptions);
  2615. this.tech.ready(techReady);
  2616. };
  2617. vjs.Player.prototype.unloadTech = function () {
  2618. this.isReady_ = false;
  2619. this.tech.dispose();
  2620. // Turn off any manual progress or timeupdate tracking
  2621. if (this.manualProgress) {
  2622. this.manualProgressOff();
  2623. }
  2624. if (this.manualTimeUpdates) {
  2625. this.manualTimeUpdatesOff();
  2626. }
  2627. this.tech = false;
  2628. };
  2629. // There's many issues around changing the size of a Flash (or other plugin) object.
  2630. // First is a plugin reload issue in Firefox that has been around for 11 years: https://bugzilla.mozilla.org/show_bug.cgi?id=90268
  2631. // Then with the new fullscreen API, Mozilla and webkit browsers will reload the flash object after going to fullscreen.
  2632. // To get around this, we're unloading the tech, caching source and currentTime values, and reloading the tech once the plugin is resized.
  2633. // reloadTech: function(betweenFn){
  2634. // vjs.log('unloadingTech')
  2635. // this.unloadTech();
  2636. // vjs.log('unloadedTech')
  2637. // if (betweenFn) { betweenFn.call(); }
  2638. // vjs.log('LoadingTech')
  2639. // this.loadTech(this.techName, { src: this.cache_.src })
  2640. // vjs.log('loadedTech')
  2641. // },
  2642. /* Fallbacks for unsupported event types
  2643. ================================================================================ */
  2644. // Manually trigger progress events based on changes to the buffered amount
  2645. // Many flash players and older HTML5 browsers don't send progress or progress-like events
  2646. vjs.Player.prototype.manualProgressOn = function () {
  2647. this.manualProgress = true;
  2648. // Trigger progress watching when a source begins loading
  2649. this.trackProgress();
  2650. // Watch for a native progress event call on the tech element
  2651. // In HTML5, some older versions don't support the progress event
  2652. // So we're assuming they don't, and turning off manual progress if they do.
  2653. // As opposed to doing user agent detection
  2654. this.tech.one('progress', function () {
  2655. // Update known progress support for this playback technology
  2656. this.features['progressEvents'] = true;
  2657. // Turn off manual progress tracking
  2658. this.player_.manualProgressOff();
  2659. });
  2660. };
  2661. vjs.Player.prototype.manualProgressOff = function () {
  2662. this.manualProgress = false;
  2663. this.stopTrackingProgress();
  2664. };
  2665. vjs.Player.prototype.trackProgress = function () {
  2666. this.progressInterval = setInterval(vjs.bind(this, function () {
  2667. // Don't trigger unless buffered amount is greater than last time
  2668. // log(this.cache_.bufferEnd, this.buffered().end(0), this.duration())
  2669. /* TODO: update for multiple buffered regions */
  2670. if (this.cache_.bufferEnd < this.buffered().end(0)) {
  2671. this.trigger('progress');
  2672. } else if (this.bufferedPercent() == 1) {
  2673. this.stopTrackingProgress();
  2674. this.trigger('progress'); // Last update
  2675. }
  2676. }), 500);
  2677. };
  2678. vjs.Player.prototype.stopTrackingProgress = function () {
  2679. clearInterval(this.progressInterval);
  2680. };
  2681. /*! Time Tracking -------------------------------------------------------------- */
  2682. vjs.Player.prototype.manualTimeUpdatesOn = function () {
  2683. this.manualTimeUpdates = true;
  2684. this.on('play', this.trackCurrentTime);
  2685. this.on('pause', this.stopTrackingCurrentTime);
  2686. // timeupdate is also called by .currentTime whenever current time is set
  2687. // Watch for native timeupdate event
  2688. this.tech.one('timeupdate', function () {
  2689. // Update known progress support for this playback technology
  2690. this.features['timeupdateEvents'] = true;
  2691. // Turn off manual progress tracking
  2692. this.player_.manualTimeUpdatesOff();
  2693. });
  2694. };
  2695. vjs.Player.prototype.manualTimeUpdatesOff = function () {
  2696. this.manualTimeUpdates = false;
  2697. this.stopTrackingCurrentTime();
  2698. this.off('play', this.trackCurrentTime);
  2699. this.off('pause', this.stopTrackingCurrentTime);
  2700. };
  2701. vjs.Player.prototype.trackCurrentTime = function () {
  2702. if (this.currentTimeInterval) {
  2703. this.stopTrackingCurrentTime();
  2704. }
  2705. this.currentTimeInterval = setInterval(vjs.bind(this, function () {
  2706. this.trigger('timeupdate');
  2707. }), 250); // 42 = 24 fps // 250 is what Webkit uses // FF uses 15
  2708. };
  2709. // Turn off play progress tracking (when paused or dragging)
  2710. vjs.Player.prototype.stopTrackingCurrentTime = function () {
  2711. clearInterval(this.currentTimeInterval);
  2712. };
  2713. // /* Player event handlers (how the player reacts to certain events)
  2714. // ================================================================================ */
  2715. /**
  2716. * Fired when the user agent begins looking for media data
  2717. * @event loadstart
  2718. */
  2719. vjs.Player.prototype.onLoadStart;
  2720. /**
  2721. * Fired when the player has initial duration and dimension information
  2722. * @event loadedmetadata
  2723. */
  2724. vjs.Player.prototype.onLoadedMetaData;
  2725. /**
  2726. * Fired when the player has downloaded data at the current playback position
  2727. * @event loadeddata
  2728. */
  2729. vjs.Player.prototype.onLoadedData;
  2730. /**
  2731. * Fired when the player has finished downloading the source data
  2732. * @event loadedalldata
  2733. */
  2734. vjs.Player.prototype.onLoadedAllData;
  2735. /**
  2736. * Fired whenever the media begins or resumes playback
  2737. * @event play
  2738. */
  2739. vjs.Player.prototype.onPlay = function () {
  2740. vjs.removeClass(this.el_, 'vjs-paused');
  2741. vjs.addClass(this.el_, 'vjs-playing');
  2742. };
  2743. /**
  2744. * Fired the first time a video is played
  2745. *
  2746. * Not part of the HLS spec, and we're not sure if this is the best
  2747. * implementation yet, so use sparingly. If you don't have a reason to
  2748. * prevent playback, use `myPlayer.one('play');` instead.
  2749. *
  2750. * @event firstplay
  2751. */
  2752. vjs.Player.prototype.onFirstPlay = function () {
  2753. //If the first starttime attribute is specified
  2754. //then we will start at the given offset in seconds
  2755. if (this.options_['starttime']) {
  2756. this.currentTime(this.options_['starttime']);
  2757. }
  2758. this.addClass('vjs-has-started');
  2759. };
  2760. /**
  2761. * Fired whenever the media has been paused
  2762. * @event pause
  2763. */
  2764. vjs.Player.prototype.onPause = function () {
  2765. vjs.removeClass(this.el_, 'vjs-playing');
  2766. vjs.addClass(this.el_, 'vjs-paused');
  2767. };
  2768. /**
  2769. * Fired when the current playback position has changed
  2770. *
  2771. * During playback this is fired every 15-250 milliseconds, depnding on the
  2772. * playback technology in use.
  2773. * @event timeupdate
  2774. */
  2775. vjs.Player.prototype.onTimeUpdate;
  2776. /**
  2777. * Fired while the user agent is downloading media data
  2778. * @event progress
  2779. */
  2780. vjs.Player.prototype.onProgress = function () {
  2781. // Add custom event for when source is finished downloading.
  2782. if (this.bufferedPercent() == 1) {
  2783. this.trigger('loadedalldata');
  2784. }
  2785. };
  2786. /**
  2787. * Fired when the end of the media resource is reached (currentTime == duration)
  2788. * @event ended
  2789. */
  2790. vjs.Player.prototype.onEnded = function () {
  2791. if (this.options_['loop']) {
  2792. this.currentTime(0);
  2793. this.play();
  2794. }
  2795. };
  2796. /**
  2797. * Fired when the duration of the media resource is first known or changed
  2798. * @event durationchange
  2799. */
  2800. vjs.Player.prototype.onDurationChange = function () {
  2801. // Allows for cacheing value instead of asking player each time.
  2802. this.duration(this.techGet('duration'));
  2803. };
  2804. /**
  2805. * Fired when the volume changes
  2806. * @event volumechange
  2807. */
  2808. vjs.Player.prototype.onVolumeChange;
  2809. /**
  2810. * Fired when the player switches in or out of fullscreen mode
  2811. * @event fullscreenchange
  2812. */
  2813. vjs.Player.prototype.onFullscreenChange = function () {
  2814. if (this.isFullScreen) {
  2815. this.addClass('vjs-fullscreen');
  2816. } else {
  2817. this.removeClass('vjs-fullscreen');
  2818. }
  2819. };
  2820. /**
  2821. * Fired when there is an error in playback
  2822. * @event error
  2823. */
  2824. vjs.Player.prototype.onError = function (e) {
  2825. vjs.log('Video Error', e);
  2826. };
  2827. // /* Player API
  2828. // ================================================================================ */
  2829. /**
  2830. * Object for cached values.
  2831. * @private
  2832. */
  2833. vjs.Player.prototype.cache_;
  2834. vjs.Player.prototype.getCache = function () {
  2835. return this.cache_;
  2836. };
  2837. // Pass values to the playback tech
  2838. vjs.Player.prototype.techCall = function (method, arg) {
  2839. // If it's not ready yet, call method when it is
  2840. if (this.tech && !this.tech.isReady_) {
  2841. this.tech.ready(function () {
  2842. this[method](arg);
  2843. });
  2844. // Otherwise call method now
  2845. } else {
  2846. try {
  2847. this.tech[method](arg);
  2848. } catch (e) {
  2849. vjs.log(e);
  2850. throw e;
  2851. }
  2852. }
  2853. };
  2854. // Get calls can't wait for the tech, and sometimes don't need to.
  2855. vjs.Player.prototype.techGet = function (method) {
  2856. if (this.tech && this.tech.isReady_) {
  2857. // Flash likes to die and reload when you hide or reposition it.
  2858. // In these cases the object methods go away and we get errors.
  2859. // When that happens we'll catch the errors and inform tech that it's not ready any more.
  2860. try {
  2861. return this.tech[method]();
  2862. } catch (e) {
  2863. // When building additional tech libs, an expected method may not be defined yet
  2864. if (this.tech[method] === undefined) {
  2865. vjs.log('Video.js: ' + method + ' method not defined for ' + this.techName + ' playback technology.', e);
  2866. } else {
  2867. // When a method isn't available on the object it throws a TypeError
  2868. if (e.name == 'TypeError') {
  2869. vjs.log('Video.js: ' + method + ' unavailable on ' + this.techName + ' playback technology element.', e);
  2870. this.tech.isReady_ = false;
  2871. } else {
  2872. vjs.log(e);
  2873. }
  2874. }
  2875. throw e;
  2876. }
  2877. }
  2878. return;
  2879. };
  2880. /**
  2881. * start media playback
  2882. *
  2883. * myPlayer.play();
  2884. *
  2885. * @return {vjs.Player} self
  2886. */
  2887. vjs.Player.prototype.play = function () {
  2888. this.techCall('play');
  2889. return this;
  2890. };
  2891. /**
  2892. * Pause the video playback
  2893. *
  2894. * myPlayer.pause();
  2895. *
  2896. * @return {vjs.Player} self
  2897. */
  2898. vjs.Player.prototype.pause = function () {
  2899. this.techCall('pause');
  2900. return this;
  2901. };
  2902. /**
  2903. * Check if the player is paused
  2904. *
  2905. * var isPaused = myPlayer.paused();
  2906. * var isPlaying = !myPlayer.paused();
  2907. *
  2908. * @return {Boolean} false if the media is currently playing, or true otherwise
  2909. */
  2910. vjs.Player.prototype.paused = function () {
  2911. // The initial state of paused should be true (in Safari it's actually false)
  2912. return (this.techGet('paused') === false) ? false : true;
  2913. };
  2914. /**
  2915. * Get or set the current time (in seconds)
  2916. *
  2917. * // get
  2918. * var whereYouAt = myPlayer.currentTime();
  2919. *
  2920. * // set
  2921. * myPlayer.currentTime(120); // 2 minutes into the video
  2922. *
  2923. * @param {Number|String=} seconds The time to seek to
  2924. * @return {Number} The time in seconds, when not setting
  2925. * @return {vjs.Player} self, when the current time is set
  2926. */
  2927. vjs.Player.prototype.currentTime = function (seconds) {
  2928. if (seconds !== undefined) {
  2929. // cache the last set value for smoother scrubbing
  2930. this.cache_.lastSetCurrentTime = seconds;
  2931. this.techCall('setCurrentTime', seconds);
  2932. // improve the accuracy of manual timeupdates
  2933. if (this.manualTimeUpdates) {
  2934. this.trigger('timeupdate');
  2935. }
  2936. return this;
  2937. }
  2938. // cache last currentTime and return
  2939. // default to 0 seconds
  2940. return this.cache_.currentTime = (this.techGet('currentTime') || 0);
  2941. };
  2942. /**
  2943. * Get the length in time of the video in seconds
  2944. *
  2945. * var lengthOfVideo = myPlayer.duration();
  2946. *
  2947. * **NOTE**: The video must have started loading before the duration can be
  2948. * known, and in the case of Flash, may not be known until the video starts
  2949. * playing.
  2950. *
  2951. * @return {Number} The duration of the video in seconds
  2952. */
  2953. vjs.Player.prototype.duration = function (seconds) {
  2954. if (seconds !== undefined) {
  2955. // cache the last set value for optimiized scrubbing (esp. Flash)
  2956. this.cache_.duration = parseFloat(seconds);
  2957. return this;
  2958. }
  2959. if (this.cache_.duration === undefined) {
  2960. this.onDurationChange();
  2961. }
  2962. return this.cache_.duration;
  2963. };
  2964. // Calculates how much time is left. Not in spec, but useful.
  2965. vjs.Player.prototype.remainingTime = function () {
  2966. return this.duration() - this.currentTime();
  2967. };
  2968. // http://dev.w3.org/html5/spec/video.html#dom-media-buffered
  2969. // Buffered returns a timerange object.
  2970. // Kind of like an array of portions of the video that have been downloaded.
  2971. // So far no browsers return more than one range (portion)
  2972. /**
  2973. * Get a TimeRange object with the times of the video that have been downloaded
  2974. *
  2975. * If you just want the percent of the video that's been downloaded,
  2976. * use bufferedPercent.
  2977. *
  2978. * // Number of different ranges of time have been buffered. Usually 1.
  2979. * numberOfRanges = bufferedTimeRange.length,
  2980. *
  2981. * // Time in seconds when the first range starts. Usually 0.
  2982. * firstRangeStart = bufferedTimeRange.start(0),
  2983. *
  2984. * // Time in seconds when the first range ends
  2985. * firstRangeEnd = bufferedTimeRange.end(0),
  2986. *
  2987. * // Length in seconds of the first time range
  2988. * firstRangeLength = firstRangeEnd - firstRangeStart;
  2989. *
  2990. * @return {Object} A mock TimeRange object (following HTML spec)
  2991. */
  2992. vjs.Player.prototype.buffered = function () {
  2993. var buffered = this.techGet('buffered'),
  2994. start = 0,
  2995. buflast = buffered.length - 1,
  2996. // Default end to 0 and store in values
  2997. end = this.cache_.bufferEnd = this.cache_.bufferEnd || 0;
  2998. if (buffered && buflast >= 0 && buffered.end(buflast) !== end) {
  2999. end = buffered.end(buflast);
  3000. // Storing values allows them be overridden by setBufferedFromProgress
  3001. this.cache_.bufferEnd = end;
  3002. }
  3003. return vjs.createTimeRange(start, end);
  3004. };
  3005. /**
  3006. * Get the percent (as a decimal) of the video that's been downloaded
  3007. *
  3008. * var howMuchIsDownloaded = myPlayer.bufferedPercent();
  3009. *
  3010. * 0 means none, 1 means all.
  3011. * (This method isn't in the HTML5 spec, but it's very convenient)
  3012. *
  3013. * @return {Number} A decimal between 0 and 1 representing the percent
  3014. */
  3015. vjs.Player.prototype.bufferedPercent = function () {
  3016. return (this.duration()) ? this.buffered().end(0) / this.duration() : 0;
  3017. };
  3018. /**
  3019. * Get or set the current volume of the media
  3020. *
  3021. * // get
  3022. * var howLoudIsIt = myPlayer.volume();
  3023. *
  3024. * // set
  3025. * myPlayer.volume(0.5); // Set volume to half
  3026. *
  3027. * 0 is off (muted), 1.0 is all the way up, 0.5 is half way.
  3028. *
  3029. * @param {Number} percentAsDecimal The new volume as a decimal percent
  3030. * @return {Number} The current volume, when getting
  3031. * @return {vjs.Player} self, when setting
  3032. */
  3033. vjs.Player.prototype.volume = function (percentAsDecimal) {
  3034. var vol;
  3035. if (percentAsDecimal !== undefined) {
  3036. vol = Math.max(0, Math.min(1, parseFloat(percentAsDecimal))); // Force value to between 0 and 1
  3037. this.cache_.volume = vol;
  3038. this.techCall('setVolume', vol);
  3039. vjs.setLocalStorage('volume', vol);
  3040. return this;
  3041. }
  3042. // Default to 1 when returning current volume.
  3043. vol = parseFloat(this.techGet('volume'));
  3044. return (isNaN(vol)) ? 1 : vol;
  3045. };
  3046. /**
  3047. * Get the current muted state, or turn mute on or off
  3048. *
  3049. * // get
  3050. * var isVolumeMuted = myPlayer.muted();
  3051. *
  3052. * // set
  3053. * myPlayer.muted(true); // mute the volume
  3054. *
  3055. * @param {Boolean=} muted True to mute, false to unmute
  3056. * @return {Boolean} True if mute is on, false if not, when getting
  3057. * @return {vjs.Player} self, when setting mute
  3058. */
  3059. vjs.Player.prototype.muted = function (muted) {
  3060. if (muted !== undefined) {
  3061. this.techCall('setMuted', muted);
  3062. return this;
  3063. }
  3064. return this.techGet('muted') || false; // Default to false
  3065. };
  3066. // Check if current tech can support native fullscreen (e.g. with built in controls lik iOS, so not our flash swf)
  3067. vjs.Player.prototype.supportsFullScreen = function () {
  3068. return this.techGet('supportsFullScreen') || false;
  3069. };
  3070. /**
  3071. * Increase the size of the video to full screen
  3072. *
  3073. * myPlayer.requestFullScreen();
  3074. *
  3075. * In some browsers, full screen is not supported natively, so it enters
  3076. * "full window mode", where the video fills the browser window.
  3077. * In browsers and devices that support native full screen, sometimes the
  3078. * browser's default controls will be shown, and not the Video.js custom skin.
  3079. * This includes most mobile devices (iOS, Android) and older versions of
  3080. * Safari.
  3081. *
  3082. * @return {vjs.Player} self
  3083. */
  3084. vjs.Player.prototype.requestFullScreen = function () {
  3085. var requestFullScreen = vjs.support.requestFullScreen;
  3086. this.isFullScreen = true;
  3087. if (requestFullScreen) {
  3088. // the browser supports going fullscreen at the element level so we can
  3089. // take the controls fullscreen as well as the video
  3090. // Trigger fullscreenchange event after change
  3091. // We have to specifically add this each time, and remove
  3092. // when cancelling fullscreen. Otherwise if there's multiple
  3093. // players on a page, they would all be reacting to the same fullscreen
  3094. // events
  3095. vjs.on(document, requestFullScreen.eventName, vjs.bind(this, function (e) {
  3096. this.isFullScreen = document[requestFullScreen.isFullScreen];
  3097. // If cancelling fullscreen, remove event listener.
  3098. if (this.isFullScreen === false) {
  3099. vjs.off(document, requestFullScreen.eventName, arguments.callee);
  3100. }
  3101. this.trigger('fullscreenchange');
  3102. }));
  3103. this.el_[requestFullScreen.requestFn]();
  3104. } else if (this.tech.supportsFullScreen()) {
  3105. // we can't take the video.js controls fullscreen but we can go fullscreen
  3106. // with native controls
  3107. this.techCall('enterFullScreen');
  3108. } else {
  3109. // fullscreen isn't supported so we'll just stretch the video element to
  3110. // fill the viewport
  3111. this.enterFullWindow();
  3112. this.trigger('fullscreenchange');
  3113. }
  3114. return this;
  3115. };
  3116. /**
  3117. * Return the video to its normal size after having been in full screen mode
  3118. *
  3119. * myPlayer.cancelFullScreen();
  3120. *
  3121. * @return {vjs.Player} self
  3122. */
  3123. vjs.Player.prototype.cancelFullScreen = function () {
  3124. var requestFullScreen = vjs.support.requestFullScreen;
  3125. this.isFullScreen = false;
  3126. // Check for browser element fullscreen support
  3127. if (requestFullScreen) {
  3128. document[requestFullScreen.cancelFn]();
  3129. } else if (this.tech.supportsFullScreen()) {
  3130. this.techCall('exitFullScreen');
  3131. } else {
  3132. this.exitFullWindow();
  3133. this.trigger('fullscreenchange');
  3134. }
  3135. return this;
  3136. };
  3137. // When fullscreen isn't supported we can stretch the video container to as wide as the browser will let us.
  3138. vjs.Player.prototype.enterFullWindow = function () {
  3139. this.isFullWindow = true;
  3140. // Storing original doc overflow value to return to when fullscreen is off
  3141. this.docOrigOverflow = document.documentElement.style.overflow;
  3142. // Add listener for esc key to exit fullscreen
  3143. vjs.on(document, 'keydown', vjs.bind(this, this.fullWindowOnEscKey));
  3144. // Hide any scroll bars
  3145. document.documentElement.style.overflow = 'hidden';
  3146. // Apply fullscreen styles
  3147. vjs.addClass(document.body, 'vjs-full-window');
  3148. this.trigger('enterFullWindow');
  3149. };
  3150. vjs.Player.prototype.fullWindowOnEscKey = function (event) {
  3151. if (event.keyCode === 27) {
  3152. if (this.isFullScreen === true) {
  3153. this.cancelFullScreen();
  3154. } else {
  3155. this.exitFullWindow();
  3156. }
  3157. }
  3158. };
  3159. vjs.Player.prototype.exitFullWindow = function () {
  3160. this.isFullWindow = false;
  3161. vjs.off(document, 'keydown', this.fullWindowOnEscKey);
  3162. // Unhide scroll bars.
  3163. document.documentElement.style.overflow = this.docOrigOverflow;
  3164. // Remove fullscreen styles
  3165. vjs.removeClass(document.body, 'vjs-full-window');
  3166. // Resize the box, controller, and poster to original sizes
  3167. // this.positionAll();
  3168. this.trigger('exitFullWindow');
  3169. };
  3170. vjs.Player.prototype.selectSource = function (sources) {
  3171. // Loop through each playback technology in the options order
  3172. for (var i = 0, j = this.options_['techOrder']; i < j.length; i++) {
  3173. var techName = vjs.capitalize(j[i]),
  3174. tech = window['videojs'][techName];
  3175. // Check if the browser supports this technology
  3176. if (tech.isSupported()) {
  3177. // Loop through each source object
  3178. for (var a = 0, b = sources; a < b.length; a++) {
  3179. var source = b[a];
  3180. // Check if source can be played with this technology
  3181. if (tech['canPlaySource'](source)) {
  3182. return {source: source, tech: techName};
  3183. }
  3184. }
  3185. }
  3186. }
  3187. return false;
  3188. };
  3189. /**
  3190. * The source function updates the video source
  3191. *
  3192. * There are three types of variables you can pass as the argument.
  3193. *
  3194. * **URL String**: A URL to the the video file. Use this method if you are sure
  3195. * the current playback technology (HTML5/Flash) can support the source you
  3196. * provide. Currently only MP4 files can be used in both HTML5 and Flash.
  3197. *
  3198. * myPlayer.src("http://www.example.com/path/to/video.mp4");
  3199. *
  3200. * **Source Object (or element):** A javascript object containing information
  3201. * about the source file. Use this method if you want the player to determine if
  3202. * it can support the file using the type information.
  3203. *
  3204. * myPlayer.src({ type: "video/mp4", src: "http://www.example.com/path/to/video.mp4" });
  3205. *
  3206. * **Array of Source Objects:** To provide multiple versions of the source so
  3207. * that it can be played using HTML5 across browsers you can use an array of
  3208. * source objects. Video.js will detect which version is supported and load that
  3209. * file.
  3210. *
  3211. * myPlayer.src([
  3212. * { type: "video/mp4", src: "http://www.example.com/path/to/video.mp4" },
  3213. * { type: "video/webm", src: "http://www.example.com/path/to/video.webm" },
  3214. * { type: "video/ogg", src: "http://www.example.com/path/to/video.ogv" }
  3215. * ]);
  3216. *
  3217. * @param {String|Object|Array=} source The source URL, object, or array of sources
  3218. * @return {vjs.Player} self
  3219. */
  3220. vjs.Player.prototype.src = function (source) {
  3221. // Case: Array of source objects to choose from and pick the best to play
  3222. if (source instanceof Array) {
  3223. var sourceTech = this.selectSource(source),
  3224. techName;
  3225. if (sourceTech) {
  3226. source = sourceTech.source;
  3227. techName = sourceTech.tech;
  3228. // If this technology is already loaded, set source
  3229. if (techName == this.techName) {
  3230. this.src(source); // Passing the source object
  3231. // Otherwise load this technology with chosen source
  3232. } else {
  3233. this.loadTech(techName, source);
  3234. }
  3235. } else {
  3236. this.el_.appendChild(vjs.createEl('p', {
  3237. innerHTML: this.options()['notSupportedMessage']
  3238. }));
  3239. }
  3240. // Case: Source object { src: '', type: '' ... }
  3241. } else if (source instanceof Object) {
  3242. if (window['videojs'][this.techName]['canPlaySource'](source)) {
  3243. this.src(source.src);
  3244. } else {
  3245. // Send through tech loop to check for a compatible technology.
  3246. this.src([source]);
  3247. }
  3248. // Case: URL String (http://myvideo...)
  3249. } else {
  3250. // Cache for getting last set source
  3251. this.cache_.src = source;
  3252. if (!this.isReady_) {
  3253. this.ready(function () {
  3254. this.src(source);
  3255. });
  3256. } else {
  3257. this.techCall('src', source);
  3258. if (this.options_['preload'] == 'auto') {
  3259. this.load();
  3260. }
  3261. if (this.options_['autoplay']) {
  3262. this.play();
  3263. }
  3264. }
  3265. }
  3266. return this;
  3267. };
  3268. // Begin loading the src data
  3269. // http://dev.w3.org/html5/spec/video.html#dom-media-load
  3270. vjs.Player.prototype.load = function () {
  3271. this.techCall('load');
  3272. return this;
  3273. };
  3274. // http://dev.w3.org/html5/spec/video.html#dom-media-currentsrc
  3275. vjs.Player.prototype.currentSrc = function () {
  3276. return this.techGet('currentSrc') || this.cache_.src || '';
  3277. };
  3278. // Attributes/Options
  3279. vjs.Player.prototype.preload = function (value) {
  3280. if (value !== undefined) {
  3281. this.techCall('setPreload', value);
  3282. this.options_['preload'] = value;
  3283. return this;
  3284. }
  3285. return this.techGet('preload');
  3286. };
  3287. vjs.Player.prototype.autoplay = function (value) {
  3288. if (value !== undefined) {
  3289. this.techCall('setAutoplay', value);
  3290. this.options_['autoplay'] = value;
  3291. return this;
  3292. }
  3293. return this.techGet('autoplay', value);
  3294. };
  3295. vjs.Player.prototype.loop = function (value) {
  3296. if (value !== undefined) {
  3297. this.techCall('setLoop', value);
  3298. this.options_['loop'] = value;
  3299. return this;
  3300. }
  3301. return this.techGet('loop');
  3302. };
  3303. /**
  3304. * the url of the poster image source
  3305. * @type {String}
  3306. * @private
  3307. */
  3308. vjs.Player.prototype.poster_;
  3309. /**
  3310. * get or set the poster image source url
  3311. *
  3312. * ##### EXAMPLE:
  3313. *
  3314. * // getting
  3315. * var currentPoster = myPlayer.poster();
  3316. *
  3317. * // setting
  3318. * myPlayer.poster('http://example.com/myImage.jpg');
  3319. *
  3320. * @param {String=} [src] Poster image source URL
  3321. * @return {String} poster URL when getting
  3322. * @return {vjs.Player} self when setting
  3323. */
  3324. vjs.Player.prototype.poster = function (src) {
  3325. if (src !== undefined) {
  3326. this.poster_ = src;
  3327. return this;
  3328. }
  3329. return this.poster_;
  3330. };
  3331. /**
  3332. * Whether or not the controls are showing
  3333. * @type {Boolean}
  3334. * @private
  3335. */
  3336. vjs.Player.prototype.controls_;
  3337. /**
  3338. * Get or set whether or not the controls are showing.
  3339. * @param {Boolean} controls Set controls to showing or not
  3340. * @return {Boolean} Controls are showing
  3341. */
  3342. vjs.Player.prototype.controls = function (bool) {
  3343. if (bool !== undefined) {
  3344. bool = !!bool; // force boolean
  3345. // Don't trigger a change event unless it actually changed
  3346. if (this.controls_ !== bool) {
  3347. this.controls_ = bool;
  3348. if (bool) {
  3349. this.removeClass('vjs-controls-disabled');
  3350. this.addClass('vjs-controls-enabled');
  3351. this.trigger('controlsenabled');
  3352. } else {
  3353. this.removeClass('vjs-controls-enabled');
  3354. this.addClass('vjs-controls-disabled');
  3355. this.trigger('controlsdisabled');
  3356. }
  3357. }
  3358. return this;
  3359. }
  3360. return this.controls_;
  3361. };
  3362. vjs.Player.prototype.usingNativeControls_;
  3363. /**
  3364. * Toggle native controls on/off. Native controls are the controls built into
  3365. * devices (e.g. default iPhone controls), Flash, or other techs
  3366. * (e.g. Vimeo Controls)
  3367. *
  3368. * **This should only be set by the current tech, because only the tech knows
  3369. * if it can support native controls**
  3370. *
  3371. * @param {Boolean} bool True signals that native controls are on
  3372. * @return {vjs.Player} Returns the player
  3373. * @private
  3374. */
  3375. vjs.Player.prototype.usingNativeControls = function (bool) {
  3376. if (bool !== undefined) {
  3377. bool = !!bool; // force boolean
  3378. // Don't trigger a change event unless it actually changed
  3379. if (this.usingNativeControls_ !== bool) {
  3380. this.usingNativeControls_ = bool;
  3381. if (bool) {
  3382. this.addClass('vjs-using-native-controls');
  3383. /**
  3384. * player is using the native device controls
  3385. *
  3386. * @event usingnativecontrols
  3387. * @memberof vjs.Player
  3388. * @instance
  3389. * @private
  3390. */
  3391. this.trigger('usingnativecontrols');
  3392. } else {
  3393. this.removeClass('vjs-using-native-controls');
  3394. /**
  3395. * player is using the custom HTML controls
  3396. *
  3397. * @event usingcustomcontrols
  3398. * @memberof vjs.Player
  3399. * @instance
  3400. * @private
  3401. */
  3402. this.trigger('usingcustomcontrols');
  3403. }
  3404. }
  3405. return this;
  3406. }
  3407. return this.usingNativeControls_;
  3408. };
  3409. vjs.Player.prototype.error = function () {
  3410. return this.techGet('error');
  3411. };
  3412. vjs.Player.prototype.ended = function () {
  3413. return this.techGet('ended');
  3414. };
  3415. vjs.Player.prototype.seeking = function () {
  3416. return this.techGet('seeking');
  3417. };
  3418. // When the player is first initialized, trigger activity so components
  3419. // like the control bar show themselves if needed
  3420. vjs.Player.prototype.userActivity_ = true;
  3421. vjs.Player.prototype.reportUserActivity = function (event) {
  3422. this.userActivity_ = true;
  3423. };
  3424. vjs.Player.prototype.userActive_ = true;
  3425. vjs.Player.prototype.userActive = function (bool) {
  3426. if (bool !== undefined) {
  3427. bool = !!bool;
  3428. if (bool !== this.userActive_) {
  3429. this.userActive_ = bool;
  3430. if (bool) {
  3431. // If the user was inactive and is now active we want to reset the
  3432. // inactivity timer
  3433. this.userActivity_ = true;
  3434. this.removeClass('vjs-user-inactive');
  3435. this.addClass('vjs-user-active');
  3436. this.trigger('useractive');
  3437. } else {
  3438. // We're switching the state to inactive manually, so erase any other
  3439. // activity
  3440. this.userActivity_ = false;
  3441. // Chrome/Safari/IE have bugs where when you change the cursor it can
  3442. // trigger a mousemove event. This causes an issue when you're hiding
  3443. // the cursor when the user is inactive, and a mousemove signals user
  3444. // activity. Making it impossible to go into inactive mode. Specifically
  3445. // this happens in fullscreen when we really need to hide the cursor.
  3446. //
  3447. // When this gets resolved in ALL browsers it can be removed
  3448. // https://code.google.com/p/chromium/issues/detail?id=103041
  3449. this.tech.one('mousemove', function (e) {
  3450. e.stopPropagation();
  3451. e.preventDefault();
  3452. });
  3453. this.removeClass('vjs-user-active');
  3454. this.addClass('vjs-user-inactive');
  3455. this.trigger('userinactive');
  3456. }
  3457. }
  3458. return this;
  3459. }
  3460. return this.userActive_;
  3461. };
  3462. vjs.Player.prototype.listenForUserActivity = function () {
  3463. var onMouseActivity, onMouseDown, mouseInProgress, onMouseUp,
  3464. activityCheck, inactivityTimeout;
  3465. onMouseActivity = this.reportUserActivity;
  3466. onMouseDown = function () {
  3467. onMouseActivity();
  3468. // For as long as the they are touching the device or have their mouse down,
  3469. // we consider them active even if they're not moving their finger or mouse.
  3470. // So we want to continue to update that they are active
  3471. clearInterval(mouseInProgress);
  3472. // Setting userActivity=true now and setting the interval to the same time
  3473. // as the activityCheck interval (250) should ensure we never miss the
  3474. // next activityCheck
  3475. mouseInProgress = setInterval(vjs.bind(this, onMouseActivity), 250);
  3476. };
  3477. onMouseUp = function (event) {
  3478. onMouseActivity();
  3479. // Stop the interval that maintains activity if the mouse/touch is down
  3480. clearInterval(mouseInProgress);
  3481. };
  3482. // Any mouse movement will be considered user activity
  3483. this.on('mousedown', onMouseDown);
  3484. this.on('mousemove', onMouseActivity);
  3485. this.on('mouseup', onMouseUp);
  3486. // Listen for keyboard navigation
  3487. // Shouldn't need to use inProgress interval because of key repeat
  3488. this.on('keydown', onMouseActivity);
  3489. this.on('keyup', onMouseActivity);
  3490. // Consider any touch events that bubble up to be activity
  3491. // Certain touches on the tech will be blocked from bubbling because they
  3492. // toggle controls
  3493. this.on('touchstart', onMouseDown);
  3494. this.on('touchmove', onMouseActivity);
  3495. this.on('touchend', onMouseUp);
  3496. this.on('touchcancel', onMouseUp);
  3497. // Run an interval every 250 milliseconds instead of stuffing everything into
  3498. // the mousemove/touchmove function itself, to prevent performance degradation.
  3499. // `this.reportUserActivity` simply sets this.userActivity_ to true, which
  3500. // then gets picked up by this loop
  3501. // http://ejohn.org/blog/learning-from-twitter/
  3502. activityCheck = setInterval(vjs.bind(this, function () {
  3503. // Check to see if mouse/touch activity has happened
  3504. if (this.userActivity_) {
  3505. // Reset the activity tracker
  3506. this.userActivity_ = false;
  3507. // If the user state was inactive, set the state to active
  3508. this.userActive(true);
  3509. // Clear any existing inactivity timeout to start the timer over
  3510. clearTimeout(inactivityTimeout);
  3511. // In X seconds, if no more activity has occurred the user will be
  3512. // considered inactive
  3513. inactivityTimeout = setTimeout(vjs.bind(this, function () {
  3514. // Protect against the case where the inactivityTimeout can trigger just
  3515. // before the next user activity is picked up by the activityCheck loop
  3516. // causing a flicker
  3517. if (!this.userActivity_) {
  3518. this.userActive(false);
  3519. }
  3520. }), 2000);
  3521. }
  3522. }), 250);
  3523. // Clean up the intervals when we kill the player
  3524. this.on('dispose', function () {
  3525. clearInterval(activityCheck);
  3526. clearTimeout(inactivityTimeout);
  3527. });
  3528. };
  3529. // Methods to add support for
  3530. // networkState: function(){ return this.techCall('networkState'); },
  3531. // readyState: function(){ return this.techCall('readyState'); },
  3532. // seeking: function(){ return this.techCall('seeking'); },
  3533. // initialTime: function(){ return this.techCall('initialTime'); },
  3534. // startOffsetTime: function(){ return this.techCall('startOffsetTime'); },
  3535. // played: function(){ return this.techCall('played'); },
  3536. // seekable: function(){ return this.techCall('seekable'); },
  3537. // videoTracks: function(){ return this.techCall('videoTracks'); },
  3538. // audioTracks: function(){ return this.techCall('audioTracks'); },
  3539. // videoWidth: function(){ return this.techCall('videoWidth'); },
  3540. // videoHeight: function(){ return this.techCall('videoHeight'); },
  3541. // defaultPlaybackRate: function(){ return this.techCall('defaultPlaybackRate'); },
  3542. // playbackRate: function(){ return this.techCall('playbackRate'); },
  3543. // mediaGroup: function(){ return this.techCall('mediaGroup'); },
  3544. // controller: function(){ return this.techCall('controller'); },
  3545. // defaultMuted: function(){ return this.techCall('defaultMuted'); }
  3546. // TODO
  3547. // currentSrcList: the array of sources including other formats and bitrates
  3548. // playList: array of source lists in order of playback
  3549. // RequestFullscreen API
  3550. (function () {
  3551. var prefix, requestFS, div;
  3552. div = document.createElement('div');
  3553. requestFS = {};
  3554. // Current W3C Spec
  3555. // http://dvcs.w3.org/hg/fullscreen/raw-file/tip/Overview.html#api
  3556. // Mozilla Draft: https://wiki.mozilla.org/Gecko:FullScreenAPI#fullscreenchange_event
  3557. // New: https://dvcs.w3.org/hg/fullscreen/raw-file/529a67b8d9f3/Overview.html
  3558. if (div.cancelFullscreen !== undefined) {
  3559. requestFS.requestFn = 'requestFullscreen';
  3560. requestFS.cancelFn = 'exitFullscreen';
  3561. requestFS.eventName = 'fullscreenchange';
  3562. requestFS.isFullScreen = 'fullScreen';
  3563. // Webkit (Chrome/Safari) and Mozilla (Firefox) have working implementations
  3564. // that use prefixes and vary slightly from the new W3C spec. Specifically,
  3565. // using 'exit' instead of 'cancel', and lowercasing the 'S' in Fullscreen.
  3566. // Other browsers don't have any hints of which version they might follow yet,
  3567. // so not going to try to predict by looping through all prefixes.
  3568. } else {
  3569. if (document.mozCancelFullScreen) {
  3570. prefix = 'moz';
  3571. requestFS.isFullScreen = prefix + 'FullScreen';
  3572. } else {
  3573. prefix = 'webkit';
  3574. requestFS.isFullScreen = prefix + 'IsFullScreen';
  3575. }
  3576. if (div[prefix + 'RequestFullScreen']) {
  3577. requestFS.requestFn = prefix + 'RequestFullScreen';
  3578. requestFS.cancelFn = prefix + 'CancelFullScreen';
  3579. }
  3580. requestFS.eventName = prefix + 'fullscreenchange';
  3581. }
  3582. if (document[requestFS.cancelFn]) {
  3583. vjs.support.requestFullScreen = requestFS;
  3584. }
  3585. })();
  3586. /**
  3587. * Container of main controls
  3588. * @param {vjs.Player|Object} player
  3589. * @param {Object=} options
  3590. * @class
  3591. * @constructor
  3592. * @extends vjs.Component
  3593. */
  3594. vjs.ControlBar = vjs.Component.extend();
  3595. vjs.ControlBar.prototype.options_ = {
  3596. loadEvent: 'play',
  3597. children: {
  3598. 'playToggle': {},
  3599. 'currentTimeDisplay': {},
  3600. 'timeDivider': {},
  3601. 'durationDisplay': {},
  3602. 'remainingTimeDisplay': {},
  3603. 'progressControl': {},
  3604. 'fullscreenToggle': {},
  3605. 'volumeControl': {},
  3606. 'muteToggle': {}
  3607. // 'volumeMenuButton': {}
  3608. }
  3609. };
  3610. vjs.ControlBar.prototype.createEl = function () {
  3611. return vjs.createEl('div', {
  3612. className: 'vjs-control-bar'
  3613. });
  3614. };
  3615. /**
  3616. * Button to toggle between play and pause
  3617. * @param {vjs.Player|Object} player
  3618. * @param {Object=} options
  3619. * @class
  3620. * @constructor
  3621. */
  3622. vjs.PlayToggle = vjs.Button.extend({
  3623. /** @constructor */
  3624. init: function (player, options) {
  3625. vjs.Button.call(this, player, options);
  3626. player.on('play', vjs.bind(this, this.onPlay));
  3627. player.on('pause', vjs.bind(this, this.onPause));
  3628. }
  3629. });
  3630. vjs.PlayToggle.prototype.buttonText = 'Play';
  3631. vjs.PlayToggle.prototype.buildCSSClass = function () {
  3632. return 'vjs-play-control ' + vjs.Button.prototype.buildCSSClass.call(this);
  3633. };
  3634. // OnClick - Toggle between play and pause
  3635. vjs.PlayToggle.prototype.onClick = function () {
  3636. if (this.player_.paused()) {
  3637. this.player_.play();
  3638. } else {
  3639. this.player_.pause();
  3640. }
  3641. };
  3642. // OnPlay - Add the vjs-playing class to the element so it can change appearance
  3643. vjs.PlayToggle.prototype.onPlay = function () {
  3644. vjs.removeClass(this.el_, 'vjs-paused');
  3645. vjs.addClass(this.el_, 'vjs-playing');
  3646. this.el_.children[0].children[0].innerHTML = 'Pause'; // change the button text to "Pause"
  3647. };
  3648. // OnPause - Add the vjs-paused class to the element so it can change appearance
  3649. vjs.PlayToggle.prototype.onPause = function () {
  3650. vjs.removeClass(this.el_, 'vjs-playing');
  3651. vjs.addClass(this.el_, 'vjs-paused');
  3652. this.el_.children[0].children[0].innerHTML = 'Play'; // change the button text to "Play"
  3653. };
  3654. /**
  3655. * Displays the current time
  3656. * @param {vjs.Player|Object} player
  3657. * @param {Object=} options
  3658. * @constructor
  3659. */
  3660. vjs.CurrentTimeDisplay = vjs.Component.extend({
  3661. /** @constructor */
  3662. init: function (player, options) {
  3663. vjs.Component.call(this, player, options);
  3664. player.on('timeupdate', vjs.bind(this, this.updateContent));
  3665. }
  3666. });
  3667. vjs.CurrentTimeDisplay.prototype.createEl = function () {
  3668. var el = vjs.Component.prototype.createEl.call(this, 'div', {
  3669. className: 'vjs-current-time vjs-time-controls vjs-control'
  3670. });
  3671. this.content = vjs.createEl('div', {
  3672. className: 'vjs-current-time-display',
  3673. innerHTML: '<span class="vjs-control-text">Current Time </span>' + '0:00', // label the current time for screen reader users
  3674. 'aria-live': 'off' // tell screen readers not to automatically read the time as it changes
  3675. });
  3676. el.appendChild(vjs.createEl('div').appendChild(this.content));
  3677. return el;
  3678. };
  3679. vjs.CurrentTimeDisplay.prototype.updateContent = function () {
  3680. // Allows for smooth scrubbing, when player can't keep up.
  3681. var time = (this.player_.scrubbing) ? this.player_.getCache().currentTime : this.player_.currentTime();
  3682. this.content.innerHTML = '<span class="vjs-control-text">Current Time </span>' + vjs.formatTime(time, this.player_.duration());
  3683. };
  3684. /**
  3685. * Displays the duration
  3686. * @param {vjs.Player|Object} player
  3687. * @param {Object=} options
  3688. * @constructor
  3689. */
  3690. vjs.DurationDisplay = vjs.Component.extend({
  3691. /** @constructor */
  3692. init: function (player, options) {
  3693. vjs.Component.call(this, player, options);
  3694. player.on('timeupdate', vjs.bind(this, this.updateContent)); // this might need to be changes to 'durationchange' instead of 'timeupdate' eventually, however the durationchange event fires before this.player_.duration() is set, so the value cannot be written out using this method. Once the order of durationchange and this.player_.duration() being set is figured out, this can be updated.
  3695. }
  3696. });
  3697. vjs.DurationDisplay.prototype.createEl = function () {
  3698. var el = vjs.Component.prototype.createEl.call(this, 'div', {
  3699. className: 'vjs-duration vjs-time-controls vjs-control'
  3700. });
  3701. this.content = vjs.createEl('div', {
  3702. className: 'vjs-duration-display',
  3703. innerHTML: '<span class="vjs-control-text">Duration Time </span>' + '0:00', // label the duration time for screen reader users
  3704. 'aria-live': 'off' // tell screen readers not to automatically read the time as it changes
  3705. });
  3706. el.appendChild(vjs.createEl('div').appendChild(this.content));
  3707. return el;
  3708. };
  3709. vjs.DurationDisplay.prototype.updateContent = function () {
  3710. var duration = this.player_.duration();
  3711. if (duration) {
  3712. this.content.innerHTML = '<span class="vjs-control-text">Duration Time </span>' + vjs.formatTime(duration); // label the duration time for screen reader users
  3713. }
  3714. };
  3715. /**
  3716. * The separator between the current time and duration
  3717. *
  3718. * Can be hidden if it's not needed in the design.
  3719. *
  3720. * @param {vjs.Player|Object} player
  3721. * @param {Object=} options
  3722. * @constructor
  3723. */
  3724. vjs.TimeDivider = vjs.Component.extend({
  3725. /** @constructor */
  3726. init: function (player, options) {
  3727. vjs.Component.call(this, player, options);
  3728. }
  3729. });
  3730. vjs.TimeDivider.prototype.createEl = function () {
  3731. return vjs.Component.prototype.createEl.call(this, 'div', {
  3732. className: 'vjs-time-divider',
  3733. innerHTML: '<div><span>/</span></div>'
  3734. });
  3735. };
  3736. /**
  3737. * Displays the time left in the video
  3738. * @param {vjs.Player|Object} player
  3739. * @param {Object=} options
  3740. * @constructor
  3741. */
  3742. vjs.RemainingTimeDisplay = vjs.Component.extend({
  3743. /** @constructor */
  3744. init: function (player, options) {
  3745. vjs.Component.call(this, player, options);
  3746. player.on('timeupdate', vjs.bind(this, this.updateContent));
  3747. }
  3748. });
  3749. vjs.RemainingTimeDisplay.prototype.createEl = function () {
  3750. var el = vjs.Component.prototype.createEl.call(this, 'div', {
  3751. className: 'vjs-remaining-time vjs-time-controls vjs-control'
  3752. });
  3753. this.content = vjs.createEl('div', {
  3754. className: 'vjs-remaining-time-display',
  3755. innerHTML: '<span class="vjs-control-text">Remaining Time </span>' + '-0:00', // label the remaining time for screen reader users
  3756. 'aria-live': 'off' // tell screen readers not to automatically read the time as it changes
  3757. });
  3758. el.appendChild(vjs.createEl('div').appendChild(this.content));
  3759. return el;
  3760. };
  3761. vjs.RemainingTimeDisplay.prototype.updateContent = function () {
  3762. if (this.player_.duration()) {
  3763. this.content.innerHTML = '<span class="vjs-control-text">Remaining Time </span>' + '-' + vjs.formatTime(this.player_.remainingTime());
  3764. }
  3765. // Allows for smooth scrubbing, when player can't keep up.
  3766. // var time = (this.player_.scrubbing) ? this.player_.getCache().currentTime : this.player_.currentTime();
  3767. // this.content.innerHTML = vjs.formatTime(time, this.player_.duration());
  3768. };
  3769. /**
  3770. * Toggle fullscreen video
  3771. * @param {vjs.Player|Object} player
  3772. * @param {Object=} options
  3773. * @class
  3774. * @extends vjs.Button
  3775. */
  3776. vjs.FullscreenToggle = vjs.Button.extend({
  3777. /**
  3778. * @constructor
  3779. * @memberof vjs.FullscreenToggle
  3780. * @instance
  3781. */
  3782. init: function (player, options) {
  3783. vjs.Button.call(this, player, options);
  3784. }
  3785. });
  3786. vjs.FullscreenToggle.prototype.buttonText = 'Fullscreen';
  3787. vjs.FullscreenToggle.prototype.buildCSSClass = function () {
  3788. return 'vjs-fullscreen-control ' + vjs.Button.prototype.buildCSSClass.call(this);
  3789. };
  3790. vjs.FullscreenToggle.prototype.onClick = function () {
  3791. if (!this.player_.isFullScreen) {
  3792. this.player_.requestFullScreen();
  3793. this.el_.children[0].children[0].innerHTML = 'Non-Fullscreen'; // change the button text to "Non-Fullscreen"
  3794. } else {
  3795. this.player_.cancelFullScreen();
  3796. this.el_.children[0].children[0].innerHTML = 'Fullscreen'; // change the button to "Fullscreen"
  3797. }
  3798. };
  3799. /**
  3800. * The Progress Control component contains the seek bar, load progress,
  3801. * and play progress
  3802. *
  3803. * @param {vjs.Player|Object} player
  3804. * @param {Object=} options
  3805. * @constructor
  3806. */
  3807. vjs.ProgressControl = vjs.Component.extend({
  3808. /** @constructor */
  3809. init: function (player, options) {
  3810. vjs.Component.call(this, player, options);
  3811. }
  3812. });
  3813. vjs.ProgressControl.prototype.options_ = {
  3814. children: {
  3815. 'seekBar': {}
  3816. }
  3817. };
  3818. vjs.ProgressControl.prototype.createEl = function () {
  3819. return vjs.Component.prototype.createEl.call(this, 'div', {
  3820. className: 'vjs-progress-control vjs-control'
  3821. });
  3822. };
  3823. /**
  3824. * Seek Bar and holder for the progress bars
  3825. *
  3826. * @param {vjs.Player|Object} player
  3827. * @param {Object=} options
  3828. * @constructor
  3829. */
  3830. vjs.SeekBar = vjs.Slider.extend({
  3831. /** @constructor */
  3832. init: function (player, options) {
  3833. vjs.Slider.call(this, player, options);
  3834. player.on('timeupdate', vjs.bind(this, this.updateARIAAttributes));
  3835. player.ready(vjs.bind(this, this.updateARIAAttributes));
  3836. }
  3837. });
  3838. vjs.SeekBar.prototype.options_ = {
  3839. children: {
  3840. 'loadProgressBar': {},
  3841. 'playProgressBar': {},
  3842. 'seekHandle': {}
  3843. },
  3844. 'barName': 'playProgressBar',
  3845. 'handleName': 'seekHandle'
  3846. };
  3847. vjs.SeekBar.prototype.playerEvent = 'timeupdate';
  3848. vjs.SeekBar.prototype.createEl = function () {
  3849. return vjs.Slider.prototype.createEl.call(this, 'div', {
  3850. className: 'vjs-progress-holder',
  3851. 'aria-label': 'video progress bar'
  3852. });
  3853. };
  3854. vjs.SeekBar.prototype.updateARIAAttributes = function () {
  3855. // Allows for smooth scrubbing, when player can't keep up.
  3856. var time = (this.player_.scrubbing) ? this.player_.getCache().currentTime : this.player_.currentTime();
  3857. this.el_.setAttribute('aria-valuenow', vjs.round(this.getPercent() * 100, 2)); // machine readable value of progress bar (percentage complete)
  3858. this.el_.setAttribute('aria-valuetext', vjs.formatTime(time, this.player_.duration())); // human readable value of progress bar (time complete)
  3859. };
  3860. vjs.SeekBar.prototype.getPercent = function () {
  3861. var currentTime;
  3862. // Flash RTMP provider will not report the correct time
  3863. // immediately after a seek. This isn't noticeable if you're
  3864. // seeking while the video is playing, but it is if you seek
  3865. // while the video is paused.
  3866. if (this.player_.techName === 'Flash' && this.player_.seeking()) {
  3867. var cache = this.player_.getCache();
  3868. if (cache.lastSetCurrentTime) {
  3869. currentTime = cache.lastSetCurrentTime;
  3870. } else {
  3871. currentTime = this.player_.currentTime();
  3872. }
  3873. } else {
  3874. currentTime = this.player_.currentTime();
  3875. }
  3876. return currentTime / this.player_.duration();
  3877. };
  3878. vjs.SeekBar.prototype.onMouseDown = function (event) {
  3879. vjs.Slider.prototype.onMouseDown.call(this, event);
  3880. this.player_.scrubbing = true;
  3881. this.videoWasPlaying = !this.player_.paused();
  3882. this.player_.pause();
  3883. };
  3884. vjs.SeekBar.prototype.onMouseMove = function (event) {
  3885. var newTime = this.calculateDistance(event) * this.player_.duration();
  3886. // Don't let video end while scrubbing.
  3887. if (newTime == this.player_.duration()) {
  3888. newTime = newTime - 0.1;
  3889. }
  3890. // Set new time (tell player to seek to new time)
  3891. this.player_.currentTime(newTime);
  3892. };
  3893. vjs.SeekBar.prototype.onMouseUp = function (event) {
  3894. debugger
  3895. vjs.Slider.prototype.onMouseUp.call(this, event);
  3896. this.player_.scrubbing = false;
  3897. if (this.videoWasPlaying) {
  3898. debugger
  3899. this.player_.play();
  3900. }
  3901. };
  3902. vjs.SeekBar.prototype.stepForward = function () {
  3903. this.player_.currentTime(this.player_.currentTime() + 5); // more quickly fast forward for keyboard-only users
  3904. };
  3905. vjs.SeekBar.prototype.stepBack = function () {
  3906. this.player_.currentTime(this.player_.currentTime() - 5); // more quickly rewind for keyboard-only users
  3907. };
  3908. /**
  3909. * Shows load progress
  3910. *
  3911. * @param {vjs.Player|Object} player
  3912. * @param {Object=} options
  3913. * @constructor
  3914. */
  3915. vjs.LoadProgressBar = vjs.Component.extend({
  3916. /** @constructor */
  3917. init: function (player, options) {
  3918. vjs.Component.call(this, player, options);
  3919. player.on('progress', vjs.bind(this, this.update));
  3920. }
  3921. });
  3922. vjs.LoadProgressBar.prototype.createEl = function () {
  3923. return vjs.Component.prototype.createEl.call(this, 'div', {
  3924. className: 'vjs-load-progress',
  3925. innerHTML: '<span class="vjs-control-text">Loaded: 0%</span>'
  3926. });
  3927. };
  3928. vjs.LoadProgressBar.prototype.update = function () {
  3929. if (this.el_.style) {
  3930. this.el_.style.width = vjs.round(this.player_.bufferedPercent() * 100, 2) + '%';
  3931. }
  3932. };
  3933. /**
  3934. * Shows play progress
  3935. *
  3936. * @param {vjs.Player|Object} player
  3937. * @param {Object=} options
  3938. * @constructor
  3939. */
  3940. vjs.PlayProgressBar = vjs.Component.extend({
  3941. /** @constructor */
  3942. init: function (player, options) {
  3943. vjs.Component.call(this, player, options);
  3944. }
  3945. });
  3946. vjs.PlayProgressBar.prototype.createEl = function () {
  3947. return vjs.Component.prototype.createEl.call(this, 'div', {
  3948. className: 'vjs-play-progress',
  3949. innerHTML: '<span class="vjs-control-text">Progress: 0%</span>'
  3950. });
  3951. };
  3952. /**
  3953. * The Seek Handle shows the current position of the playhead during playback,
  3954. * and can be dragged to adjust the playhead.
  3955. *
  3956. * @param {vjs.Player|Object} player
  3957. * @param {Object=} options
  3958. * @constructor
  3959. */
  3960. vjs.SeekHandle = vjs.SliderHandle.extend();
  3961. /**
  3962. * The default value for the handle content, which may be read by screen readers
  3963. *
  3964. * @type {String}
  3965. * @private
  3966. */
  3967. vjs.SeekHandle.prototype.defaultValue = '00:00';
  3968. /** @inheritDoc */
  3969. vjs.SeekHandle.prototype.createEl = function () {
  3970. return vjs.SliderHandle.prototype.createEl.call(this, 'div', {
  3971. className: 'vjs-seek-handle'
  3972. });
  3973. };
  3974. /**
  3975. * The component for controlling the volume level
  3976. *
  3977. * @param {vjs.Player|Object} player
  3978. * @param {Object=} options
  3979. * @constructor
  3980. */
  3981. vjs.VolumeControl = vjs.Component.extend({
  3982. /** @constructor */
  3983. init: function (player, options) {
  3984. vjs.Component.call(this, player, options);
  3985. // hide volume controls when they're not supported by the current tech
  3986. if (player.tech && player.tech.features && player.tech.features['volumeControl'] === false) {
  3987. this.addClass('vjs-hidden');
  3988. }
  3989. player.on('loadstart', vjs.bind(this, function () {
  3990. if (player.tech.features && player.tech.features['volumeControl'] === false) {
  3991. this.addClass('vjs-hidden');
  3992. } else {
  3993. this.removeClass('vjs-hidden');
  3994. }
  3995. }));
  3996. }
  3997. });
  3998. vjs.VolumeControl.prototype.options_ = {
  3999. children: {
  4000. 'volumeBar': {}
  4001. }
  4002. };
  4003. vjs.VolumeControl.prototype.createEl = function () {
  4004. return vjs.Component.prototype.createEl.call(this, 'div', {
  4005. className: 'vjs-volume-control vjs-control'
  4006. });
  4007. };
  4008. /**
  4009. * The bar that contains the volume level and can be clicked on to adjust the level
  4010. *
  4011. * @param {vjs.Player|Object} player
  4012. * @param {Object=} options
  4013. * @constructor
  4014. */
  4015. vjs.VolumeBar = vjs.Slider.extend({
  4016. /** @constructor */
  4017. init: function (player, options) {
  4018. vjs.Slider.call(this, player, options);
  4019. player.on('volumechange', vjs.bind(this, this.updateARIAAttributes));
  4020. player.ready(vjs.bind(this, this.updateARIAAttributes));
  4021. setTimeout(vjs.bind(this, this.update), 0); // update when elements is in DOM
  4022. }
  4023. });
  4024. vjs.VolumeBar.prototype.updateARIAAttributes = function () {
  4025. // Current value of volume bar as a percentage
  4026. this.el_.setAttribute('aria-valuenow', vjs.round(this.player_.volume() * 100, 2));
  4027. this.el_.setAttribute('aria-valuetext', vjs.round(this.player_.volume() * 100, 2) + '%');
  4028. };
  4029. vjs.VolumeBar.prototype.options_ = {
  4030. children: {
  4031. 'volumeLevel': {},
  4032. 'volumeHandle': {}
  4033. },
  4034. 'barName': 'volumeLevel',
  4035. 'handleName': 'volumeHandle'
  4036. };
  4037. vjs.VolumeBar.prototype.playerEvent = 'volumechange';
  4038. vjs.VolumeBar.prototype.createEl = function () {
  4039. return vjs.Slider.prototype.createEl.call(this, 'div', {
  4040. className: 'vjs-volume-bar',
  4041. 'aria-label': 'volume level'
  4042. });
  4043. };
  4044. vjs.VolumeBar.prototype.onMouseMove = function (event) {
  4045. if (this.player_.muted()) {
  4046. this.player_.muted(false);
  4047. }
  4048. this.player_.volume(this.calculateDistance(event));
  4049. };
  4050. vjs.VolumeBar.prototype.getPercent = function () {
  4051. if (this.player_.muted()) {
  4052. return 0;
  4053. } else {
  4054. return this.player_.volume();
  4055. }
  4056. };
  4057. vjs.VolumeBar.prototype.stepForward = function () {
  4058. this.player_.volume(this.player_.volume() + 0.1);
  4059. };
  4060. vjs.VolumeBar.prototype.stepBack = function () {
  4061. this.player_.volume(this.player_.volume() - 0.1);
  4062. };
  4063. /**
  4064. * Shows volume level
  4065. *
  4066. * @param {vjs.Player|Object} player
  4067. * @param {Object=} options
  4068. * @constructor
  4069. */
  4070. vjs.VolumeLevel = vjs.Component.extend({
  4071. /** @constructor */
  4072. init: function (player, options) {
  4073. vjs.Component.call(this, player, options);
  4074. }
  4075. });
  4076. vjs.VolumeLevel.prototype.createEl = function () {
  4077. return vjs.Component.prototype.createEl.call(this, 'div', {
  4078. className: 'vjs-volume-level',
  4079. innerHTML: '<span class="vjs-control-text"></span>'
  4080. });
  4081. };
  4082. /**
  4083. * The volume handle can be dragged to adjust the volume level
  4084. *
  4085. * @param {vjs.Player|Object} player
  4086. * @param {Object=} options
  4087. * @constructor
  4088. */
  4089. vjs.VolumeHandle = vjs.SliderHandle.extend();
  4090. vjs.VolumeHandle.prototype.defaultValue = '00:00';
  4091. /** @inheritDoc */
  4092. vjs.VolumeHandle.prototype.createEl = function () {
  4093. return vjs.SliderHandle.prototype.createEl.call(this, 'div', {
  4094. className: 'vjs-volume-handle'
  4095. });
  4096. };
  4097. /**
  4098. * A button component for muting the audio
  4099. *
  4100. * @param {vjs.Player|Object} player
  4101. * @param {Object=} options
  4102. * @constructor
  4103. */
  4104. vjs.MuteToggle = vjs.Button.extend({
  4105. /** @constructor */
  4106. init: function (player, options) {
  4107. vjs.Button.call(this, player, options);
  4108. player.on('volumechange', vjs.bind(this, this.update));
  4109. // hide mute toggle if the current tech doesn't support volume control
  4110. if (player.tech && player.tech.features && player.tech.features['volumeControl'] === false) {
  4111. this.addClass('vjs-hidden');
  4112. }
  4113. player.on('loadstart', vjs.bind(this, function () {
  4114. if (player.tech.features && player.tech.features['volumeControl'] === false) {
  4115. this.addClass('vjs-hidden');
  4116. } else {
  4117. this.removeClass('vjs-hidden');
  4118. }
  4119. }));
  4120. }
  4121. });
  4122. vjs.MuteToggle.prototype.createEl = function () {
  4123. return vjs.Button.prototype.createEl.call(this, 'div', {
  4124. className: 'vjs-mute-control vjs-control',
  4125. innerHTML: '<div><span class="vjs-control-text">Mute</span></div>'
  4126. });
  4127. };
  4128. vjs.MuteToggle.prototype.onClick = function () {
  4129. this.player_.muted(this.player_.muted() ? false : true);
  4130. };
  4131. vjs.MuteToggle.prototype.update = function () {
  4132. var vol = this.player_.volume(),
  4133. level = 3;
  4134. if (vol === 0 || this.player_.muted()) {
  4135. level = 0;
  4136. } else if (vol < 0.33) {
  4137. level = 1;
  4138. } else if (vol < 0.67) {
  4139. level = 2;
  4140. }
  4141. // Don't rewrite the button text if the actual text doesn't change.
  4142. // This causes unnecessary and confusing information for screen reader users.
  4143. // This check is needed because this function gets called every time the volume level is changed.
  4144. if (this.player_.muted()) {
  4145. if (this.el_.children[0].children[0].innerHTML != 'Unmute') {
  4146. this.el_.children[0].children[0].innerHTML = 'Unmute'; // change the button text to "Unmute"
  4147. }
  4148. } else {
  4149. if (this.el_.children[0].children[0].innerHTML != 'Mute') {
  4150. this.el_.children[0].children[0].innerHTML = 'Mute'; // change the button text to "Mute"
  4151. }
  4152. }
  4153. /* TODO improve muted icon classes */
  4154. for (var i = 0; i < 4; i++) {
  4155. vjs.removeClass(this.el_, 'vjs-vol-' + i);
  4156. }
  4157. vjs.addClass(this.el_, 'vjs-vol-' + level);
  4158. };
  4159. /**
  4160. * Menu button with a popup for showing the volume slider.
  4161. * @constructor
  4162. */
  4163. vjs.VolumeMenuButton = vjs.MenuButton.extend({
  4164. /** @constructor */
  4165. init: function (player, options) {
  4166. vjs.MenuButton.call(this, player, options);
  4167. // Same listeners as MuteToggle
  4168. player.on('volumechange', vjs.bind(this, this.update));
  4169. // hide mute toggle if the current tech doesn't support volume control
  4170. if (player.tech && player.tech.features && player.tech.features.volumeControl === false) {
  4171. this.addClass('vjs-hidden');
  4172. }
  4173. player.on('loadstart', vjs.bind(this, function () {
  4174. if (player.tech.features && player.tech.features.volumeControl === false) {
  4175. this.addClass('vjs-hidden');
  4176. } else {
  4177. this.removeClass('vjs-hidden');
  4178. }
  4179. }));
  4180. this.addClass('vjs-menu-button');
  4181. }
  4182. });
  4183. vjs.VolumeMenuButton.prototype.createMenu = function () {
  4184. var menu = new vjs.Menu(this.player_, {
  4185. contentElType: 'div'
  4186. });
  4187. var vc = new vjs.VolumeBar(this.player_, vjs.obj.merge({vertical: true}, this.options_.volumeBar));
  4188. menu.addChild(vc);
  4189. return menu;
  4190. };
  4191. vjs.VolumeMenuButton.prototype.onClick = function () {
  4192. vjs.MuteToggle.prototype.onClick.call(this);
  4193. vjs.MenuButton.prototype.onClick.call(this);
  4194. };
  4195. vjs.VolumeMenuButton.prototype.createEl = function () {
  4196. return vjs.Button.prototype.createEl.call(this, 'div', {
  4197. className: 'vjs-volume-menu-button vjs-menu-button vjs-control',
  4198. innerHTML: '<div><span class="vjs-control-text">Mute</span></div>'
  4199. });
  4200. };
  4201. vjs.VolumeMenuButton.prototype.update = vjs.MuteToggle.prototype.update;
  4202. /* Poster Image
  4203. ================================================================================ */
  4204. /**
  4205. * The component that handles showing the poster image.
  4206. *
  4207. * @param {vjs.Player|Object} player
  4208. * @param {Object=} options
  4209. * @constructor
  4210. */
  4211. vjs.PosterImage = vjs.Button.extend({
  4212. /** @constructor */
  4213. init: function (player, options) {
  4214. vjs.Button.call(this, player, options);
  4215. if (!player.poster() || !player.controls()) {
  4216. this.hide();
  4217. }
  4218. player.on('play', vjs.bind(this, this.hide));
  4219. }
  4220. });
  4221. vjs.PosterImage.prototype.createEl = function () {
  4222. var el = vjs.createEl('div', {
  4223. className: 'vjs-poster',
  4224. // Don't want poster to be tabbable.
  4225. tabIndex: -1
  4226. }),
  4227. poster = this.player_.poster();
  4228. if (poster) {
  4229. if ('backgroundSize' in el.style) {
  4230. el.style.backgroundImage = 'url("' + poster + '")';
  4231. } else {
  4232. el.appendChild(vjs.createEl('img', {src: poster}));
  4233. }
  4234. }
  4235. return el;
  4236. };
  4237. vjs.PosterImage.prototype.onClick = function () {
  4238. // Only accept clicks when controls are enabled
  4239. if (this.player().controls()) {
  4240. this.player_.play();
  4241. }
  4242. };
  4243. /* Loading Spinner
  4244. ================================================================================ */
  4245. /**
  4246. * Loading spinner for waiting events
  4247. * @param {vjs.Player|Object} player
  4248. * @param {Object=} options
  4249. * @class
  4250. * @constructor
  4251. */
  4252. vjs.LoadingSpinner = vjs.Component.extend({
  4253. /** @constructor */
  4254. init: function (player, options) {
  4255. vjs.Component.call(this, player, options);
  4256. player.on('canplay', vjs.bind(this, this.hide));
  4257. player.on('canplaythrough', vjs.bind(this, this.hide));
  4258. player.on('playing', vjs.bind(this, this.hide));
  4259. player.on('seeked', vjs.bind(this, this.hide));
  4260. player.on('seeking', vjs.bind(this, this.show));
  4261. // in some browsers seeking does not trigger the 'playing' event,
  4262. // so we also need to trap 'seeked' if we are going to set a
  4263. // 'seeking' event
  4264. player.on('seeked', vjs.bind(this, this.hide));
  4265. player.on('error', vjs.bind(this, this.show));
  4266. // Not showing spinner on stalled any more. Browsers may stall and then not trigger any events that would remove the spinner.
  4267. // Checked in Chrome 16 and Safari 5.1.2. http://help.videojs.com/discussions/problems/883-why-is-the-download-progress-showing
  4268. // player.on('stalled', vjs.bind(this, this.show));
  4269. player.on('waiting', vjs.bind(this, this.show));
  4270. }
  4271. });
  4272. vjs.LoadingSpinner.prototype.createEl = function () {
  4273. return vjs.Component.prototype.createEl.call(this, 'div', {
  4274. className: 'vjs-loading-spinner'
  4275. });
  4276. };
  4277. /* Big Play Button
  4278. ================================================================================ */
  4279. /**
  4280. * Initial play button. Shows before the video has played. The hiding of the
  4281. * big play button is done via CSS and player states.
  4282. * @param {vjs.Player|Object} player
  4283. * @param {Object=} options
  4284. * @class
  4285. * @constructor
  4286. */
  4287. vjs.BigPlayButton = vjs.Button.extend();
  4288. vjs.BigPlayButton.prototype.createEl = function () {
  4289. return vjs.Button.prototype.createEl.call(this, 'div', {
  4290. className: 'vjs-big-play-button',
  4291. innerHTML: '<span aria-hidden="true"></span>',
  4292. 'aria-label': 'play video'
  4293. });
  4294. };
  4295. vjs.BigPlayButton.prototype.onClick = function () {
  4296. this.player_.play();
  4297. };
  4298. /**
  4299. * @fileoverview Media Technology Controller - Base class for media playback
  4300. * technology controllers like Flash and HTML5
  4301. */
  4302. /**
  4303. * Base class for media (HTML5 Video, Flash) controllers
  4304. * @param {vjs.Player|Object} player Central player instance
  4305. * @param {Object=} options Options object
  4306. * @constructor
  4307. */
  4308. vjs.MediaTechController = vjs.Component.extend({
  4309. /** @constructor */
  4310. init: function (player, options, ready) {
  4311. vjs.Component.call(this, player, options, ready);
  4312. this.initControlsListeners();
  4313. }
  4314. });
  4315. /**
  4316. * Set up click and touch listeners for the playback element
  4317. * On desktops, a click on the video itself will toggle playback,
  4318. * on a mobile device a click on the video toggles controls.
  4319. * (toggling controls is done by toggling the user state between active and
  4320. * inactive)
  4321. *
  4322. * A tap can signal that a user has become active, or has become inactive
  4323. * e.g. a quick tap on an iPhone movie should reveal the controls. Another
  4324. * quick tap should hide them again (signaling the user is in an inactive
  4325. * viewing state)
  4326. *
  4327. * In addition to this, we still want the user to be considered inactive after
  4328. * a few seconds of inactivity.
  4329. *
  4330. * Note: the only part of iOS interaction we can't mimic with this setup
  4331. * is a touch and hold on the video element counting as activity in order to
  4332. * keep the controls showing, but that shouldn't be an issue. A touch and hold on
  4333. * any controls will still keep the user active
  4334. */
  4335. vjs.MediaTechController.prototype.initControlsListeners = function () {
  4336. var player, tech, activateControls, deactivateControls;
  4337. tech = this;
  4338. player = this.player();
  4339. var activateControls = function () {
  4340. if (player.controls() && !player.usingNativeControls()) {
  4341. tech.addControlsListeners();
  4342. }
  4343. };
  4344. deactivateControls = vjs.bind(tech, tech.removeControlsListeners);
  4345. // Set up event listeners once the tech is ready and has an element to apply
  4346. // listeners to
  4347. this.ready(activateControls);
  4348. player.on('controlsenabled', activateControls);
  4349. player.on('controlsdisabled', deactivateControls);
  4350. };
  4351. vjs.MediaTechController.prototype.addControlsListeners = function () {
  4352. var preventBubble, userWasActive;
  4353. // Some browsers (Chrome & IE) don't trigger a click on a flash swf, but do
  4354. // trigger mousedown/up.
  4355. // http://stackoverflow.com/questions/1444562/javascript-onclick-event-over-flash-object
  4356. // Any touch events are set to block the mousedown event from happening
  4357. this.on('mousedown', this.onClick);
  4358. // We need to block touch events on the video element from bubbling up,
  4359. // otherwise they'll signal activity prematurely. The specific use case is
  4360. // when the video is playing and the controls have faded out. In this case
  4361. // only a tap (fast touch) should toggle the user active state and turn the
  4362. // controls back on. A touch and move or touch and hold should not trigger
  4363. // the controls (per iOS as an example at least)
  4364. //
  4365. // We always want to stop propagation on touchstart because touchstart
  4366. // at the player level starts the touchInProgress interval. We can still
  4367. // report activity on the other events, but won't let them bubble for
  4368. // consistency. We don't want to bubble a touchend without a touchstart.
  4369. this.on('touchstart', function (event) {
  4370. // Stop the mouse events from also happening
  4371. event.preventDefault();
  4372. event.stopPropagation();
  4373. // Record if the user was active now so we don't have to keep polling it
  4374. userWasActive = this.player_.userActive();
  4375. });
  4376. preventBubble = function (event) {
  4377. event.stopPropagation();
  4378. if (userWasActive) {
  4379. this.player_.reportUserActivity();
  4380. }
  4381. };
  4382. // Treat all touch events the same for consistency
  4383. this.on('touchmove', preventBubble);
  4384. this.on('touchleave', preventBubble);
  4385. this.on('touchcancel', preventBubble);
  4386. this.on('touchend', preventBubble);
  4387. // Turn on component tap events
  4388. this.emitTapEvents();
  4389. // The tap listener needs to come after the touchend listener because the tap
  4390. // listener cancels out any reportedUserActivity when setting userActive(false)
  4391. this.on('tap', this.onTap);
  4392. };
  4393. /**
  4394. * Remove the listeners used for click and tap controls. This is needed for
  4395. * toggling to controls disabled, where a tap/touch should do nothing.
  4396. */
  4397. vjs.MediaTechController.prototype.removeControlsListeners = function () {
  4398. // We don't want to just use `this.off()` because there might be other needed
  4399. // listeners added by techs that extend this.
  4400. this.off('tap');
  4401. this.off('touchstart');
  4402. this.off('touchmove');
  4403. this.off('touchleave');
  4404. this.off('touchcancel');
  4405. this.off('touchend');
  4406. this.off('click');
  4407. this.off('mousedown');
  4408. };
  4409. /**
  4410. * Handle a click on the media element. By default will play/pause the media.
  4411. */
  4412. vjs.MediaTechController.prototype.onClick = function (event) {
  4413. // We're using mousedown to detect clicks thanks to Flash, but mousedown
  4414. // will also be triggered with right-clicks, so we need to prevent that
  4415. if (event.button !== 0) return;
  4416. // When controls are disabled a click should not toggle playback because
  4417. // the click is considered a control
  4418. if (this.player().controls()) {
  4419. if (this.player().paused()) {
  4420. this.player().play();
  4421. } else {
  4422. this.player().pause();
  4423. }
  4424. }
  4425. };
  4426. /**
  4427. * Handle a tap on the media element. By default it will toggle the user
  4428. * activity state, which hides and shows the controls.
  4429. */
  4430. vjs.MediaTechController.prototype.onTap = function () {
  4431. this.player().userActive(!this.player().userActive());
  4432. };
  4433. vjs.MediaTechController.prototype.features = {
  4434. 'volumeControl': true,
  4435. // Resizing plugins using request fullscreen reloads the plugin
  4436. 'fullscreenResize': false,
  4437. // Optional events that we can manually mimic with timers
  4438. // currently not triggered by video-js-swf
  4439. 'progressEvents': false,
  4440. 'timeupdateEvents': false
  4441. };
  4442. vjs.media = {};
  4443. /**
  4444. * List of default API methods for any MediaTechController
  4445. * @type {String}
  4446. */
  4447. vjs.media.ApiMethods = 'play,pause,paused,currentTime,setCurrentTime,duration,buffered,volume,setVolume,muted,setMuted,width,height,supportsFullScreen,enterFullScreen,src,load,currentSrc,preload,setPreload,autoplay,setAutoplay,loop,setLoop,error,networkState,readyState,seeking,initialTime,startOffsetTime,played,seekable,ended,videoTracks,audioTracks,videoWidth,videoHeight,textTracks,defaultPlaybackRate,playbackRate,mediaGroup,controller,controls,defaultMuted'.split(',');
  4448. // Create placeholder methods for each that warn when a method isn't supported by the current playback technology
  4449. function createMethod(methodName) {
  4450. return function () {
  4451. throw new Error('The "' + methodName + '" method is not available on the playback technology\'s API');
  4452. };
  4453. }
  4454. for (var i = vjs.media.ApiMethods.length - 1; i >= 0; i--) {
  4455. var methodName = vjs.media.ApiMethods[i];
  4456. vjs.MediaTechController.prototype[vjs.media.ApiMethods[i]] = createMethod(methodName);
  4457. }
  4458. /**
  4459. * @fileoverview HTML5 Media Controller - Wrapper for HTML5 Media API
  4460. */
  4461. /**
  4462. * HTML5 Media Controller - Wrapper for HTML5 Media API
  4463. * @param {vjs.Player|Object} player
  4464. * @param {Object=} options
  4465. * @param {Function=} ready
  4466. * @constructor
  4467. */
  4468. vjs.Html5 = vjs.MediaTechController.extend({
  4469. /** @constructor */
  4470. init: function (player, options, ready) {
  4471. // volume cannot be changed from 1 on iOS
  4472. this.features['volumeControl'] = vjs.Html5.canControlVolume();
  4473. // In iOS, if you move a video element in the DOM, it breaks video playback.
  4474. this.features['movingMediaElementInDOM'] = !vjs.IS_IOS;
  4475. // HTML video is able to automatically resize when going to fullscreen
  4476. this.features['fullscreenResize'] = true;
  4477. vjs.MediaTechController.call(this, player, options, ready);
  4478. var source = options['source'];
  4479. // If the element source is already set, we may have missed the loadstart event, and want to trigger it.
  4480. // We don't want to set the source again and interrupt playback.
  4481. if (source && this.el_.currentSrc === source.src && this.el_.networkState > 0) {
  4482. player.trigger('loadstart');
  4483. // Otherwise set the source if one was provided.
  4484. } else if (source) {
  4485. this.el_.src = source.src;
  4486. }
  4487. // Determine if native controls should be used
  4488. // Our goal should be to get the custom controls on mobile solid everywhere
  4489. // so we can remove this all together. Right now this will block custom
  4490. // controls on touch enabled laptops like the Chrome Pixel
  4491. if (vjs.TOUCH_ENABLED && player.options()['nativeControlsForTouch'] !== false) {
  4492. this.useNativeControls();
  4493. }
  4494. // Chrome and Safari both have issues with autoplay.
  4495. // In Safari (5.1.1), when we move the video element into the container div, autoplay doesn't work.
  4496. // In Chrome (15), if you have autoplay + a poster + no controls, the video gets hidden (but audio plays)
  4497. // This fixes both issues. Need to wait for API, so it updates displays correctly
  4498. player.ready(function () {
  4499. if (this.tag && this.options_['autoplay'] && this.paused()) {
  4500. delete this.tag['poster']; // Chrome Fix. Fixed in Chrome v16.
  4501. this.play();
  4502. }
  4503. });
  4504. this.setupTriggers();
  4505. this.triggerReady();
  4506. }
  4507. });
  4508. vjs.Html5.prototype.dispose = function () {
  4509. vjs.MediaTechController.prototype.dispose.call(this);
  4510. };
  4511. vjs.Html5.prototype.createEl = function () {
  4512. var player = this.player_,
  4513. // If possible, reuse original tag for HTML5 playback technology element
  4514. el = player.tag,
  4515. newEl,
  4516. clone;
  4517. // Check if this browser supports moving the element into the box.
  4518. // On the iPhone video will break if you move the element,
  4519. // So we have to create a brand new element.
  4520. if (!el || this.features['movingMediaElementInDOM'] === false) {
  4521. // If the original tag is still there, clone and remove it.
  4522. if (el) {
  4523. clone = el.cloneNode(false);
  4524. vjs.Html5.disposeMediaElement(el);
  4525. el = clone;
  4526. player.tag = null;
  4527. } else {
  4528. el = vjs.createEl('video', {
  4529. id: player.id() + '_html5_api',
  4530. className: 'vjs-tech'
  4531. });
  4532. }
  4533. // associate the player with the new tag
  4534. el['player'] = player;
  4535. vjs.insertFirst(el, player.el());
  4536. }
  4537. // Update specific tag settings, in case they were overridden
  4538. var attrs = ['autoplay', 'preload', 'loop', 'muted'];
  4539. for (var i = attrs.length - 1; i >= 0; i--) {
  4540. var attr = attrs[i];
  4541. if (player.options_[attr] !== null) {
  4542. el[attr] = player.options_[attr];
  4543. }
  4544. }
  4545. return el;
  4546. // jenniisawesome = true;
  4547. };
  4548. // Make video events trigger player events
  4549. // May seem verbose here, but makes other APIs possible.
  4550. vjs.Html5.prototype.setupTriggers = function () {
  4551. for (var i = vjs.Html5.Events.length - 1; i >= 0; i--) {
  4552. vjs.on(this.el_, vjs.Html5.Events[i], vjs.bind(this.player_, this.eventHandler));
  4553. }
  4554. };
  4555. // Triggers removed using this.off when disposed
  4556. vjs.Html5.prototype.eventHandler = function (e) {
  4557. this.trigger(e);
  4558. // No need for media events to bubble up.
  4559. e.stopPropagation();
  4560. };
  4561. vjs.Html5.prototype.useNativeControls = function () {
  4562. var tech, player, controlsOn, controlsOff, cleanUp;
  4563. tech = this;
  4564. player = this.player();
  4565. // If the player controls are enabled turn on the native controls
  4566. tech.setControls(player.controls());
  4567. // Update the native controls when player controls state is updated
  4568. controlsOn = function () {
  4569. tech.setControls(true);
  4570. };
  4571. controlsOff = function () {
  4572. tech.setControls(false);
  4573. };
  4574. player.on('controlsenabled', controlsOn);
  4575. player.on('controlsdisabled', controlsOff);
  4576. // Clean up when not using native controls anymore
  4577. cleanUp = function () {
  4578. player.off('controlsenabled', controlsOn);
  4579. player.off('controlsdisabled', controlsOff);
  4580. };
  4581. tech.on('dispose', cleanUp);
  4582. player.on('usingcustomcontrols', cleanUp);
  4583. // Update the state of the player to using native controls
  4584. player.usingNativeControls(true);
  4585. };
  4586. vjs.Html5.prototype.play = function () {
  4587. this.el_.play();
  4588. };
  4589. vjs.Html5.prototype.pause = function () {
  4590. this.el_.pause();
  4591. };
  4592. vjs.Html5.prototype.paused = function () {
  4593. return this.el_.paused;
  4594. };
  4595. vjs.Html5.prototype.currentTime = function () {
  4596. return this.el_.currentTime;
  4597. };
  4598. vjs.Html5.prototype.setCurrentTime = function (seconds) {
  4599. try {
  4600. this.el_.currentTime = seconds;
  4601. } catch (e) {
  4602. vjs.log(e, 'Video is not ready. (Video.js)');
  4603. // this.warning(VideoJS.warnings.videoNotReady);
  4604. }
  4605. };
  4606. vjs.Html5.prototype.duration = function () {
  4607. return this.el_.duration || 0;
  4608. };
  4609. vjs.Html5.prototype.buffered = function () {
  4610. return this.el_.buffered;
  4611. };
  4612. vjs.Html5.prototype.volume = function () {
  4613. return this.el_.volume;
  4614. };
  4615. vjs.Html5.prototype.setVolume = function (percentAsDecimal) {
  4616. this.el_.volume = percentAsDecimal;
  4617. };
  4618. vjs.Html5.prototype.muted = function () {
  4619. return this.el_.muted;
  4620. };
  4621. vjs.Html5.prototype.setMuted = function (muted) {
  4622. this.el_.muted = muted;
  4623. };
  4624. vjs.Html5.prototype.width = function () {
  4625. return this.el_.offsetWidth;
  4626. };
  4627. vjs.Html5.prototype.height = function () {
  4628. return this.el_.offsetHeight;
  4629. };
  4630. vjs.Html5.prototype.supportsFullScreen = function () {
  4631. if (typeof this.el_.webkitEnterFullScreen == 'function') {
  4632. // Seems to be broken in Chromium/Chrome && Safari in Leopard
  4633. if (/Android/.test(vjs.USER_AGENT) || !/Chrome|Mac OS X 10.5/.test(vjs.USER_AGENT)) {
  4634. return true;
  4635. }
  4636. }
  4637. return false;
  4638. };
  4639. vjs.Html5.prototype.enterFullScreen = function () {
  4640. var video = this.el_;
  4641. if (video.paused && video.networkState <= video.HAVE_METADATA) {
  4642. // attempt to prime the video element for programmatic access
  4643. // this isn't necessary on the desktop but shouldn't hurt
  4644. this.el_.play();
  4645. // playing and pausing synchronously during the transition to fullscreen
  4646. // can get iOS ~6.1 devices into a play/pause loop
  4647. setTimeout(function () {
  4648. video.pause();
  4649. video.webkitEnterFullScreen();
  4650. }, 0);
  4651. } else {
  4652. video.webkitEnterFullScreen();
  4653. }
  4654. };
  4655. vjs.Html5.prototype.exitFullScreen = function () {
  4656. this.el_.webkitExitFullScreen();
  4657. };
  4658. vjs.Html5.prototype.src = function (src) {
  4659. this.el_.src = src;
  4660. };
  4661. vjs.Html5.prototype.load = function () {
  4662. this.el_.load();
  4663. };
  4664. vjs.Html5.prototype.currentSrc = function () {
  4665. return this.el_.currentSrc;
  4666. };
  4667. vjs.Html5.prototype.preload = function () {
  4668. return this.el_.preload;
  4669. };
  4670. vjs.Html5.prototype.setPreload = function (val) {
  4671. this.el_.preload = val;
  4672. };
  4673. vjs.Html5.prototype.autoplay = function () {
  4674. return this.el_.autoplay;
  4675. };
  4676. vjs.Html5.prototype.setAutoplay = function (val) {
  4677. this.el_.autoplay = val;
  4678. };
  4679. vjs.Html5.prototype.controls = function () {
  4680. return this.el_.controls;
  4681. }
  4682. vjs.Html5.prototype.setControls = function (val) {
  4683. this.el_.controls = !!val;
  4684. }
  4685. vjs.Html5.prototype.loop = function () {
  4686. return this.el_.loop;
  4687. };
  4688. vjs.Html5.prototype.setLoop = function (val) {
  4689. this.el_.loop = val;
  4690. };
  4691. vjs.Html5.prototype.error = function () {
  4692. return this.el_.error;
  4693. };
  4694. vjs.Html5.prototype.seeking = function () {
  4695. return this.el_.seeking;
  4696. };
  4697. vjs.Html5.prototype.ended = function () {
  4698. return this.el_.ended;
  4699. };
  4700. vjs.Html5.prototype.defaultMuted = function () {
  4701. return this.el_.defaultMuted;
  4702. };
  4703. /* HTML5 Support Testing ---------------------------------------------------- */
  4704. vjs.Html5.isSupported = function () {
  4705. return !!vjs.TEST_VID.canPlayType;
  4706. };
  4707. vjs.Html5.canPlaySource = function (srcObj) {
  4708. // IE9 on Windows 7 without MediaPlayer throws an error here
  4709. // https://github.com/videojs/video.js/issues/519
  4710. try {
  4711. return !!vjs.TEST_VID.canPlayType(srcObj.type);
  4712. } catch (e) {
  4713. return '';
  4714. }
  4715. // TODO: Check Type
  4716. // If no Type, check ext
  4717. // Check Media Type
  4718. };
  4719. vjs.Html5.canControlVolume = function () {
  4720. var volume = vjs.TEST_VID.volume;
  4721. vjs.TEST_VID.volume = (volume / 2) + 0.1;
  4722. return volume !== vjs.TEST_VID.volume;
  4723. };
  4724. // List of all HTML5 events (various uses).
  4725. vjs.Html5.Events = 'loadstart,suspend,abort,error,emptied,stalled,loadedmetadata,loadeddata,canplay,canplaythrough,playing,waiting,seeking,seeked,ended,durationchange,timeupdate,progress,play,pause,ratechange,volumechange'.split(',');
  4726. vjs.Html5.disposeMediaElement = function (el) {
  4727. if (!el) {
  4728. return;
  4729. }
  4730. el['player'] = null;
  4731. if (el.parentNode) {
  4732. el.parentNode.removeChild(el);
  4733. }
  4734. // remove any child track or source nodes to prevent their loading
  4735. while (el.hasChildNodes()) {
  4736. el.removeChild(el.firstChild);
  4737. }
  4738. // remove any src reference. not setting `src=''` because that causes a warning
  4739. // in firefox
  4740. el.removeAttribute('src');
  4741. // force the media element to update its loading state by calling load()
  4742. if (typeof el.load === 'function') {
  4743. el.load();
  4744. }
  4745. };
  4746. // HTML5 Feature detection and Device Fixes --------------------------------- //
  4747. // Override Android 2.2 and less canPlayType method which is broken
  4748. if (vjs.IS_OLD_ANDROID) {
  4749. document.createElement('video').constructor.prototype.canPlayType = function (type) {
  4750. return (type && type.toLowerCase().indexOf('video/mp4') != -1) ? 'maybe' : '';
  4751. };
  4752. }
  4753. /**
  4754. * @fileoverview VideoJS-SWF - Custom Flash Player with HTML5-ish API
  4755. * https://github.com/zencoder/video-js-swf
  4756. * Not using setupTriggers. Using global onEvent func to distribute events
  4757. */
  4758. /**
  4759. * Flash Media Controller - Wrapper for fallback SWF API
  4760. *
  4761. * @param {vjs.Player} player
  4762. * @param {Object=} options
  4763. * @param {Function=} ready
  4764. * @constructor
  4765. */
  4766. vjs.Flash = vjs.MediaTechController.extend({
  4767. /** @constructor */
  4768. init: function (player, options, ready) {
  4769. vjs.MediaTechController.call(this, player, options, ready);
  4770. var source = options['source'],
  4771. // Which element to embed in
  4772. parentEl = options['parentEl'],
  4773. // Create a temporary element to be replaced by swf object
  4774. placeHolder = this.el_ = vjs.createEl('div', {id: player.id() + '_temp_flash'}),
  4775. // Generate ID for swf object
  4776. objId = player.id() + '_flash_api',
  4777. // Store player options in local var for optimization
  4778. // TODO: switch to using player methods instead of options
  4779. // e.g. player.autoplay();
  4780. playerOptions = player.options_,
  4781. // Merge default flashvars with ones passed in to init
  4782. flashVars = vjs.obj.merge({
  4783. // SWF Callback Functions
  4784. 'readyFunction': 'videojs.Flash.onReady',
  4785. 'eventProxyFunction': 'videojs.Flash.onEvent',
  4786. 'errorEventProxyFunction': 'videojs.Flash.onError',
  4787. // Player Settings
  4788. 'autoplay': playerOptions.autoplay,
  4789. 'preload': playerOptions.preload,
  4790. 'loop': playerOptions.loop,
  4791. 'muted': playerOptions.muted
  4792. }, options['flashVars']),
  4793. // Merge default parames with ones passed in
  4794. params = vjs.obj.merge({
  4795. 'wmode': 'opaque', // Opaque is needed to overlay controls, but can affect playback performance
  4796. 'bgcolor': '#000000' // Using bgcolor prevents a white flash when the object is loading
  4797. }, options['params']),
  4798. // Merge default attributes with ones passed in
  4799. attributes = vjs.obj.merge({
  4800. 'id': objId,
  4801. 'name': objId, // Both ID and Name needed or swf to identifty itself
  4802. 'class': 'vjs-tech'
  4803. }, options['attributes'])
  4804. ;
  4805. // If source was supplied pass as a flash var.
  4806. if (source) {
  4807. if (source.type && vjs.Flash.isStreamingType(source.type)) {
  4808. var parts = vjs.Flash.streamToParts(source.src);
  4809. flashVars['rtmpConnection'] = encodeURIComponent(parts.connection);
  4810. flashVars['rtmpStream'] = encodeURIComponent(parts.stream);
  4811. } else {
  4812. flashVars['src'] = encodeURIComponent(vjs.getAbsoluteURL(source.src));
  4813. }
  4814. }
  4815. // Add placeholder to player div
  4816. vjs.insertFirst(placeHolder, parentEl);
  4817. // Having issues with Flash reloading on certain page actions (hide/resize/fullscreen) in certain browsers
  4818. // This allows resetting the playhead when we catch the reload
  4819. if (options['startTime']) {
  4820. this.ready(function () {
  4821. this.load();
  4822. this.play();
  4823. this.currentTime(options['startTime']);
  4824. });
  4825. }
  4826. // Flash iFrame Mode
  4827. // In web browsers there are multiple instances where changing the parent element or visibility of a plugin causes the plugin to reload.
  4828. // - Firefox just about always. https://bugzilla.mozilla.org/show_bug.cgi?id=90268 (might be fixed by version 13)
  4829. // - Webkit when hiding the plugin
  4830. // - Webkit and Firefox when using requestFullScreen on a parent element
  4831. // Loading the flash plugin into a dynamically generated iFrame gets around most of these issues.
  4832. // Issues that remain include hiding the element and requestFullScreen in Firefox specifically
  4833. // There's on particularly annoying issue with this method which is that Firefox throws a security error on an offsite Flash object loaded into a dynamically created iFrame.
  4834. // Even though the iframe was inserted into a page on the web, Firefox + Flash considers it a local app trying to access an internet file.
  4835. // I tried mulitple ways of setting the iframe src attribute but couldn't find a src that worked well. Tried a real/fake source, in/out of domain.
  4836. // Also tried a method from stackoverflow that caused a security error in all browsers. http://stackoverflow.com/questions/2486901/how-to-set-document-domain-for-a-dynamically-generated-iframe
  4837. // In the end the solution I found to work was setting the iframe window.location.href right before doing a document.write of the Flash object.
  4838. // The only downside of this it seems to trigger another http request to the original page (no matter what's put in the href). Not sure why that is.
  4839. // NOTE (2012-01-29): Cannot get Firefox to load the remote hosted SWF into a dynamically created iFrame
  4840. // Firefox 9 throws a security error, unleess you call location.href right before doc.write.
  4841. // Not sure why that even works, but it causes the browser to look like it's continuously trying to load the page.
  4842. // Firefox 3.6 keeps calling the iframe onload function anytime I write to it, causing an endless loop.
  4843. if (options['iFrameMode'] === true && !vjs.IS_FIREFOX) {
  4844. // Create iFrame with vjs-tech class so it's 100% width/height
  4845. var iFrm = vjs.createEl('iframe', {
  4846. 'id': objId + '_iframe',
  4847. 'name': objId + '_iframe',
  4848. 'className': 'vjs-tech',
  4849. 'scrolling': 'no',
  4850. 'marginWidth': 0,
  4851. 'marginHeight': 0,
  4852. 'frameBorder': 0
  4853. });
  4854. // Update ready function names in flash vars for iframe window
  4855. flashVars['readyFunction'] = 'ready';
  4856. flashVars['eventProxyFunction'] = 'events';
  4857. flashVars['errorEventProxyFunction'] = 'errors';
  4858. // Tried multiple methods to get this to work in all browsers
  4859. // Tried embedding the flash object in the page first, and then adding a place holder to the iframe, then replacing the placeholder with the page object.
  4860. // The goal here was to try to load the swf URL in the parent page first and hope that got around the firefox security error
  4861. // var newObj = vjs.Flash.embed(options['swf'], placeHolder, flashVars, params, attributes);
  4862. // (in onload)
  4863. // var temp = vjs.createEl('a', { id:'asdf', innerHTML: 'asdf' } );
  4864. // iDoc.body.appendChild(temp);
  4865. // Tried embedding the flash object through javascript in the iframe source.
  4866. // This works in webkit but still triggers the firefox security error
  4867. // iFrm.src = 'javascript: document.write('"+vjs.Flash.getEmbedCode(options['swf'], flashVars, params, attributes)+"');";
  4868. // Tried an actual local iframe just to make sure that works, but it kills the easiness of the CDN version if you require the user to host an iframe
  4869. // We should add an option to host the iframe locally though, because it could help a lot of issues.
  4870. // iFrm.src = "iframe.html";
  4871. // Wait until iFrame has loaded to write into it.
  4872. vjs.on(iFrm, 'load', vjs.bind(this, function () {
  4873. var iDoc,
  4874. iWin = iFrm.contentWindow;
  4875. // The one working method I found was to use the iframe's document.write() to create the swf object
  4876. // This got around the security issue in all browsers except firefox.
  4877. // I did find a hack where if I call the iframe's window.location.href='', it would get around the security error
  4878. // However, the main page would look like it was loading indefinitely (URL bar loading spinner would never stop)
  4879. // Plus Firefox 3.6 didn't work no matter what I tried.
  4880. // if (vjs.USER_AGENT.match('Firefox')) {
  4881. // iWin.location.href = '';
  4882. // }
  4883. // Get the iFrame's document depending on what the browser supports
  4884. iDoc = iFrm.contentDocument ? iFrm.contentDocument : iFrm.contentWindow.document;
  4885. // Tried ensuring both document domains were the same, but they already were, so that wasn't the issue.
  4886. // Even tried adding /. that was mentioned in a browser security writeup
  4887. // document.domain = document.domain+'/.';
  4888. // iDoc.domain = document.domain+'/.';
  4889. // Tried adding the object to the iframe doc's innerHTML. Security error in all browsers.
  4890. // iDoc.body.innerHTML = swfObjectHTML;
  4891. // Tried appending the object to the iframe doc's body. Security error in all browsers.
  4892. // iDoc.body.appendChild(swfObject);
  4893. // Using document.write actually got around the security error that browsers were throwing.
  4894. // Again, it's a dynamically generated (same domain) iframe, loading an external Flash swf.
  4895. // Not sure why that's a security issue, but apparently it is.
  4896. iDoc.write(vjs.Flash.getEmbedCode(options['swf'], flashVars, params, attributes));
  4897. // Setting variables on the window needs to come after the doc write because otherwise they can get reset in some browsers
  4898. // So far no issues with swf ready event being called before it's set on the window.
  4899. iWin['player'] = this.player_;
  4900. // Create swf ready function for iFrame window
  4901. iWin['ready'] = vjs.bind(this.player_, function (currSwf) {
  4902. var el = iDoc.getElementById(currSwf),
  4903. player = this,
  4904. tech = player.tech;
  4905. // Update reference to playback technology element
  4906. tech.el_ = el;
  4907. // Make sure swf is actually ready. Sometimes the API isn't actually yet.
  4908. vjs.Flash.checkReady(tech);
  4909. });
  4910. // Create event listener for all swf events
  4911. iWin['events'] = vjs.bind(this.player_, function (swfID, eventName) {
  4912. var player = this;
  4913. if (player && player.techName === 'flash') {
  4914. player.trigger(eventName);
  4915. }
  4916. });
  4917. // Create error listener for all swf errors
  4918. iWin['errors'] = vjs.bind(this.player_, function (swfID, eventName) {
  4919. vjs.log('Flash Error', eventName);
  4920. });
  4921. }));
  4922. // Replace placeholder with iFrame (it will load now)
  4923. placeHolder.parentNode.replaceChild(iFrm, placeHolder);
  4924. // If not using iFrame mode, embed as normal object
  4925. } else {
  4926. vjs.Flash.embed(options['swf'], placeHolder, flashVars, params, attributes);
  4927. }
  4928. }
  4929. });
  4930. vjs.Flash.prototype.dispose = function () {
  4931. vjs.MediaTechController.prototype.dispose.call(this);
  4932. };
  4933. vjs.Flash.prototype.play = function () {
  4934. this.el_.vjs_play();
  4935. };
  4936. vjs.Flash.prototype.pause = function () {
  4937. this.el_.vjs_pause();
  4938. };
  4939. vjs.Flash.prototype.src = function (src) {
  4940. if (vjs.Flash.isStreamingSrc(src)) {
  4941. src = vjs.Flash.streamToParts(src);
  4942. this.setRtmpConnection(src.connection);
  4943. this.setRtmpStream(src.stream);
  4944. } else {
  4945. // Make sure source URL is abosolute.
  4946. src = vjs.getAbsoluteURL(src);
  4947. this.el_.vjs_src(src);
  4948. }
  4949. // Currently the SWF doesn't autoplay if you load a source later.
  4950. // e.g. Load player w/ no source, wait 2s, set src.
  4951. if (this.player_.autoplay()) {
  4952. var tech = this;
  4953. setTimeout(function () {
  4954. tech.play();
  4955. }, 0);
  4956. }
  4957. };
  4958. vjs.Flash.prototype.currentSrc = function () {
  4959. var src = this.el_.vjs_getProperty('currentSrc');
  4960. // no src, check and see if RTMP
  4961. if (src == null) {
  4962. var connection = this.rtmpConnection(),
  4963. stream = this.rtmpStream();
  4964. if (connection && stream) {
  4965. src = vjs.Flash.streamFromParts(connection, stream);
  4966. }
  4967. }
  4968. return src;
  4969. };
  4970. vjs.Flash.prototype.load = function () {
  4971. this.el_.vjs_load();
  4972. };
  4973. vjs.Flash.prototype.poster = function () {
  4974. this.el_.vjs_getProperty('poster');
  4975. };
  4976. vjs.Flash.prototype.buffered = function () {
  4977. return vjs.createTimeRange(0, this.el_.vjs_getProperty('buffered'));
  4978. };
  4979. vjs.Flash.prototype.supportsFullScreen = function () {
  4980. return false; // Flash does not allow fullscreen through javascript
  4981. };
  4982. vjs.Flash.prototype.enterFullScreen = function () {
  4983. return false;
  4984. };
  4985. // Create setters and getters for attributes
  4986. var api = vjs.Flash.prototype,
  4987. readWrite = 'rtmpConnection,rtmpStream,preload,currentTime,defaultPlaybackRate,playbackRate,autoplay,loop,mediaGroup,controller,controls,volume,muted,defaultMuted'.split(','),
  4988. readOnly = 'error,currentSrc,networkState,readyState,seeking,initialTime,duration,startOffsetTime,paused,played,seekable,ended,videoTracks,audioTracks,videoWidth,videoHeight,textTracks'.split(',');
  4989. // Overridden: buffered
  4990. /**
  4991. * @this {*}
  4992. * @private
  4993. */
  4994. var createSetter = function (attr) {
  4995. var attrUpper = attr.charAt(0).toUpperCase() + attr.slice(1);
  4996. api['set' + attrUpper] = function (val) {
  4997. return this.el_.vjs_setProperty(attr, val);
  4998. };
  4999. };
  5000. /**
  5001. * @this {*}
  5002. * @private
  5003. */
  5004. var createGetter = function (attr) {
  5005. api[attr] = function () {
  5006. return this.el_.vjs_getProperty(attr);
  5007. };
  5008. };
  5009. (function () {
  5010. var i;
  5011. // Create getter and setters for all read/write attributes
  5012. for (i = 0; i < readWrite.length; i++) {
  5013. createGetter(readWrite[i]);
  5014. createSetter(readWrite[i]);
  5015. }
  5016. // Create getters for read-only attributes
  5017. for (i = 0; i < readOnly.length; i++) {
  5018. createGetter(readOnly[i]);
  5019. }
  5020. })();
  5021. /* Flash Support Testing -------------------------------------------------------- */
  5022. vjs.Flash.isSupported = function () {
  5023. return vjs.Flash.version()[0] >= 10;
  5024. // return swfobject.hasFlashPlayerVersion('10');
  5025. };
  5026. vjs.Flash.canPlaySource = function (srcObj) {
  5027. var type;
  5028. if (!srcObj.type) {
  5029. return '';
  5030. }
  5031. type = srcObj.type.replace(/;.*/, '').toLowerCase();
  5032. if (type in vjs.Flash.formats || type in vjs.Flash.streamingFormats) {
  5033. return 'maybe';
  5034. }
  5035. };
  5036. vjs.Flash.formats = {
  5037. 'video/flv': 'FLV',
  5038. 'video/x-flv': 'FLV',
  5039. 'video/mp4': 'MP4',
  5040. 'video/m4v': 'MP4'
  5041. };
  5042. vjs.Flash.streamingFormats = {
  5043. 'rtmp/mp4': 'MP4',
  5044. 'rtmp/flv': 'FLV'
  5045. };
  5046. vjs.Flash['onReady'] = function (currSwf) {
  5047. var el = vjs.el(currSwf);
  5048. // Get player from box
  5049. // On firefox reloads, el might already have a player
  5050. var player = el['player'] || el.parentNode['player'],
  5051. tech = player.tech;
  5052. // Reference player on tech element
  5053. el['player'] = player;
  5054. // Update reference to playback technology element
  5055. tech.el_ = el;
  5056. vjs.Flash.checkReady(tech);
  5057. };
  5058. // The SWF isn't alwasy ready when it says it is. Sometimes the API functions still need to be added to the object.
  5059. // If it's not ready, we set a timeout to check again shortly.
  5060. vjs.Flash.checkReady = function (tech) {
  5061. // Check if API property exists
  5062. if (tech.el().vjs_getProperty) {
  5063. // If so, tell tech it's ready
  5064. tech.triggerReady();
  5065. // Otherwise wait longer.
  5066. } else {
  5067. setTimeout(function () {
  5068. vjs.Flash.checkReady(tech);
  5069. }, 50);
  5070. }
  5071. };
  5072. // Trigger events from the swf on the player
  5073. vjs.Flash['onEvent'] = function (swfID, eventName) {
  5074. var player = vjs.el(swfID)['player'];
  5075. player.trigger(eventName);
  5076. };
  5077. // Log errors from the swf
  5078. vjs.Flash['onError'] = function (swfID, err) {
  5079. var player = vjs.el(swfID)['player'];
  5080. player.trigger('error');
  5081. vjs.log('Flash Error', err, swfID);
  5082. };
  5083. // Flash Version Check
  5084. vjs.Flash.version = function () {
  5085. var version = '0,0,0';
  5086. // IE
  5087. try {
  5088. version = new window.ActiveXObject('ShockwaveFlash.ShockwaveFlash').GetVariable('$version').replace(/\D+/g, ',').match(/^,?(.+),?$/)[1];
  5089. // other browsers
  5090. } catch (e) {
  5091. try {
  5092. if (navigator.mimeTypes['application/x-shockwave-flash'].enabledPlugin) {
  5093. version = (navigator.plugins['Shockwave Flash 2.0'] || navigator.plugins['Shockwave Flash']).description.replace(/\D+/g, ',').match(/^,?(.+),?$/)[1];
  5094. }
  5095. } catch (err) {
  5096. }
  5097. }
  5098. return version.split(',');
  5099. };
  5100. // Flash embedding method. Only used in non-iframe mode
  5101. vjs.Flash.embed = function (swf, placeHolder, flashVars, params, attributes) {
  5102. var code = vjs.Flash.getEmbedCode(swf, flashVars, params, attributes),
  5103. // Get element by embedding code and retrieving created element
  5104. obj = vjs.createEl('div', {innerHTML: code}).childNodes[0],
  5105. par = placeHolder.parentNode
  5106. ;
  5107. placeHolder.parentNode.replaceChild(obj, placeHolder);
  5108. // IE6 seems to have an issue where it won't initialize the swf object after injecting it.
  5109. // This is a dumb fix
  5110. var newObj = par.childNodes[0];
  5111. setTimeout(function () {
  5112. newObj.style.display = 'block';
  5113. }, 1000);
  5114. return obj;
  5115. };
  5116. vjs.Flash.getEmbedCode = function (swf, flashVars, params, attributes) {
  5117. var objTag = '<object type="application/x-shockwave-flash"',
  5118. flashVarsString = '',
  5119. paramsString = '',
  5120. attrsString = '';
  5121. // Convert flash vars to string
  5122. if (flashVars) {
  5123. vjs.obj.each(flashVars, function (key, val) {
  5124. flashVarsString += (key + '=' + val + '&amp;');
  5125. });
  5126. }
  5127. // Add swf, flashVars, and other default params
  5128. params = vjs.obj.merge({
  5129. 'movie': swf,
  5130. 'flashvars': flashVarsString,
  5131. 'allowScriptAccess': 'always', // Required to talk to swf
  5132. 'allowNetworking': 'all' // All should be default, but having security issues.
  5133. }, params);
  5134. // Create param tags string
  5135. vjs.obj.each(params, function (key, val) {
  5136. paramsString += '<param name="' + key + '" value="' + val + '" />';
  5137. });
  5138. attributes = vjs.obj.merge({
  5139. // Add swf to attributes (need both for IE and Others to work)
  5140. 'data': swf,
  5141. // Default to 100% width/height
  5142. 'width': '100%',
  5143. 'height': '100%'
  5144. }, attributes);
  5145. // Create Attributes string
  5146. vjs.obj.each(attributes, function (key, val) {
  5147. attrsString += (key + '="' + val + '" ');
  5148. });
  5149. return objTag + attrsString + '>' + paramsString + '</object>';
  5150. };
  5151. vjs.Flash.streamFromParts = function (connection, stream) {
  5152. return connection + '&' + stream;
  5153. };
  5154. vjs.Flash.streamToParts = function (src) {
  5155. var parts = {
  5156. connection: '',
  5157. stream: ''
  5158. };
  5159. if (!src) {
  5160. return parts;
  5161. }
  5162. // Look for the normal URL separator we expect, '&'.
  5163. // If found, we split the URL into two pieces around the
  5164. // first '&'.
  5165. var connEnd = src.indexOf('&');
  5166. var streamBegin;
  5167. if (connEnd !== -1) {
  5168. streamBegin = connEnd + 1;
  5169. } else {
  5170. // If there's not a '&', we use the last '/' as the delimiter.
  5171. connEnd = streamBegin = src.lastIndexOf('/') + 1;
  5172. if (connEnd === 0) {
  5173. // really, there's not a '/'?
  5174. connEnd = streamBegin = src.length;
  5175. }
  5176. }
  5177. parts.connection = src.substring(0, connEnd);
  5178. parts.stream = src.substring(streamBegin, src.length);
  5179. return parts;
  5180. };
  5181. vjs.Flash.isStreamingType = function (srcType) {
  5182. return srcType in vjs.Flash.streamingFormats;
  5183. };
  5184. // RTMP has four variations, any string starting
  5185. // with one of these protocols should be valid
  5186. vjs.Flash.RTMP_RE = /^rtmp[set]?:\/\//i;
  5187. vjs.Flash.isStreamingSrc = function (src) {
  5188. return vjs.Flash.RTMP_RE.test(src);
  5189. };
  5190. /**
  5191. * The Media Loader is the component that decides which playback technology to load
  5192. * when the player is initialized.
  5193. *
  5194. * @constructor
  5195. */
  5196. vjs.MediaLoader = vjs.Component.extend({
  5197. /** @constructor */
  5198. init: function (player, options, ready) {
  5199. vjs.Component.call(this, player, options, ready);
  5200. // If there are no sources when the player is initialized,
  5201. // load the first supported playback technology.
  5202. if (!player.options_['sources'] || player.options_['sources'].length === 0) {
  5203. for (var i = 0, j = player.options_['techOrder']; i < j.length; i++) {
  5204. var techName = vjs.capitalize(j[i]),
  5205. tech = window['videojs'][techName];
  5206. // Check if the browser supports this technology
  5207. if (tech && tech.isSupported()) {
  5208. player.loadTech(techName);
  5209. break;
  5210. }
  5211. }
  5212. } else {
  5213. // // Loop through playback technologies (HTML5, Flash) and check for support.
  5214. // // Then load the best source.
  5215. // // A few assumptions here:
  5216. // // All playback technologies respect preload false.
  5217. player.src(player.options_['sources']);
  5218. }
  5219. }
  5220. });
  5221. /**
  5222. * @fileoverview Text Tracks
  5223. * Text tracks are tracks of timed text events.
  5224. * Captions - text displayed over the video for the hearing impared
  5225. * Subtitles - text displayed over the video for those who don't understand langauge in the video
  5226. * Chapters - text displayed in a menu allowing the user to jump to particular points (chapters) in the video
  5227. * Descriptions (not supported yet) - audio descriptions that are read back to the user by a screen reading device
  5228. */
  5229. // Player Additions - Functions add to the player object for easier access to tracks
  5230. /**
  5231. * List of associated text tracks
  5232. * @type {Array}
  5233. * @private
  5234. */
  5235. vjs.Player.prototype.textTracks_;
  5236. /**
  5237. * Get an array of associated text tracks. captions, subtitles, chapters, descriptions
  5238. * http://www.w3.org/html/wg/drafts/html/master/embedded-content-0.html#dom-media-texttracks
  5239. * @return {Array} Array of track objects
  5240. * @private
  5241. */
  5242. vjs.Player.prototype.textTracks = function () {
  5243. this.textTracks_ = this.textTracks_ || [];
  5244. return this.textTracks_;
  5245. };
  5246. /**
  5247. * Add a text track
  5248. * In addition to the W3C settings we allow adding additional info through options.
  5249. * http://www.w3.org/html/wg/drafts/html/master/embedded-content-0.html#dom-media-addtexttrack
  5250. * @param {String} kind Captions, subtitles, chapters, descriptions, or metadata
  5251. * @param {String=} label Optional label
  5252. * @param {String=} language Optional language
  5253. * @param {Object=} options Additional track options, like src
  5254. * @private
  5255. */
  5256. vjs.Player.prototype.addTextTrack = function (kind, label, language, options) {
  5257. var tracks = this.textTracks_ = this.textTracks_ || [];
  5258. options = options || {};
  5259. options['kind'] = kind;
  5260. options['label'] = label;
  5261. options['language'] = language;
  5262. // HTML5 Spec says default to subtitles.
  5263. // Uppercase first letter to match class names
  5264. var Kind = vjs.capitalize(kind || 'subtitles');
  5265. // Create correct texttrack class. CaptionsTrack, etc.
  5266. var track = new window['videojs'][Kind + 'Track'](this, options);
  5267. tracks.push(track);
  5268. // If track.dflt() is set, start showing immediately
  5269. // TODO: Add a process to deterime the best track to show for the specific kind
  5270. // Incase there are mulitple defaulted tracks of the same kind
  5271. // Or the user has a set preference of a specific language that should override the default
  5272. // if (track.dflt()) {
  5273. // this.ready(vjs.bind(track, track.show));
  5274. // }
  5275. return track;
  5276. };
  5277. /**
  5278. * Add an array of text tracks. captions, subtitles, chapters, descriptions
  5279. * Track objects will be stored in the player.textTracks() array
  5280. * @param {Array} trackList Array of track elements or objects (fake track elements)
  5281. * @private
  5282. */
  5283. vjs.Player.prototype.addTextTracks = function (trackList) {
  5284. var trackObj;
  5285. for (var i = 0; i < trackList.length; i++) {
  5286. trackObj = trackList[i];
  5287. this.addTextTrack(trackObj['kind'], trackObj['label'], trackObj['language'], trackObj);
  5288. }
  5289. return this;
  5290. };
  5291. // Show a text track
  5292. // disableSameKind: disable all other tracks of the same kind. Value should be a track kind (captions, etc.)
  5293. vjs.Player.prototype.showTextTrack = function (id, disableSameKind) {
  5294. var tracks = this.textTracks_,
  5295. i = 0,
  5296. j = tracks.length,
  5297. track, showTrack, kind;
  5298. // Find Track with same ID
  5299. for (; i < j; i++) {
  5300. track = tracks[i];
  5301. if (track.id() === id) {
  5302. track.show();
  5303. showTrack = track;
  5304. // Disable tracks of the same kind
  5305. } else if (disableSameKind && track.kind() == disableSameKind && track.mode() > 0) {
  5306. track.disable();
  5307. }
  5308. }
  5309. // Get track kind from shown track or disableSameKind
  5310. kind = (showTrack) ? showTrack.kind() : ((disableSameKind) ? disableSameKind : false);
  5311. // Trigger trackchange event, captionstrackchange, subtitlestrackchange, etc.
  5312. if (kind) {
  5313. this.trigger(kind + 'trackchange');
  5314. }
  5315. return this;
  5316. };
  5317. /**
  5318. * The base class for all text tracks
  5319. *
  5320. * Handles the parsing, hiding, and showing of text track cues
  5321. *
  5322. * @param {vjs.Player|Object} player
  5323. * @param {Object=} options
  5324. * @constructor
  5325. */
  5326. vjs.TextTrack = vjs.Component.extend({
  5327. /** @constructor */
  5328. init: function (player, options) {
  5329. vjs.Component.call(this, player, options);
  5330. // Apply track info to track object
  5331. // Options will often be a track element
  5332. // Build ID if one doesn't exist
  5333. this.id_ = options['id'] || ('vjs_' + options['kind'] + '_' + options['language'] + '_' + vjs.guid++);
  5334. this.src_ = options['src'];
  5335. // 'default' is a reserved keyword in js so we use an abbreviated version
  5336. this.dflt_ = options['default'] || options['dflt'];
  5337. this.title_ = options['title'];
  5338. this.language_ = options['srclang'];
  5339. this.label_ = options['label'];
  5340. this.cues_ = [];
  5341. this.activeCues_ = [];
  5342. this.readyState_ = 0;
  5343. this.mode_ = 0;
  5344. this.player_.on('fullscreenchange', vjs.bind(this, this.adjustFontSize));
  5345. }
  5346. });
  5347. /**
  5348. * Track kind value. Captions, subtitles, etc.
  5349. * @private
  5350. */
  5351. vjs.TextTrack.prototype.kind_;
  5352. /**
  5353. * Get the track kind value
  5354. * @return {String}
  5355. */
  5356. vjs.TextTrack.prototype.kind = function () {
  5357. return this.kind_;
  5358. };
  5359. /**
  5360. * Track src value
  5361. * @private
  5362. */
  5363. vjs.TextTrack.prototype.src_;
  5364. /**
  5365. * Get the track src value
  5366. * @return {String}
  5367. */
  5368. vjs.TextTrack.prototype.src = function () {
  5369. return this.src_;
  5370. };
  5371. /**
  5372. * Track default value
  5373. * If default is used, subtitles/captions to start showing
  5374. * @private
  5375. */
  5376. vjs.TextTrack.prototype.dflt_;
  5377. /**
  5378. * Get the track default value. ('default' is a reserved keyword)
  5379. * @return {Boolean}
  5380. */
  5381. vjs.TextTrack.prototype.dflt = function () {
  5382. return this.dflt_;
  5383. };
  5384. /**
  5385. * Track title value
  5386. * @private
  5387. */
  5388. vjs.TextTrack.prototype.title_;
  5389. /**
  5390. * Get the track title value
  5391. * @return {String}
  5392. */
  5393. vjs.TextTrack.prototype.title = function () {
  5394. return this.title_;
  5395. };
  5396. /**
  5397. * Language - two letter string to represent track language, e.g. 'en' for English
  5398. * Spec def: readonly attribute DOMString language;
  5399. * @private
  5400. */
  5401. vjs.TextTrack.prototype.language_;
  5402. /**
  5403. * Get the track language value
  5404. * @return {String}
  5405. */
  5406. vjs.TextTrack.prototype.language = function () {
  5407. return this.language_;
  5408. };
  5409. /**
  5410. * Track label e.g. 'English'
  5411. * Spec def: readonly attribute DOMString label;
  5412. * @private
  5413. */
  5414. vjs.TextTrack.prototype.label_;
  5415. /**
  5416. * Get the track label value
  5417. * @return {String}
  5418. */
  5419. vjs.TextTrack.prototype.label = function () {
  5420. return this.label_;
  5421. };
  5422. /**
  5423. * All cues of the track. Cues have a startTime, endTime, text, and other properties.
  5424. * Spec def: readonly attribute TextTrackCueList cues;
  5425. * @private
  5426. */
  5427. vjs.TextTrack.prototype.cues_;
  5428. /**
  5429. * Get the track cues
  5430. * @return {Array}
  5431. */
  5432. vjs.TextTrack.prototype.cues = function () {
  5433. return this.cues_;
  5434. };
  5435. /**
  5436. * ActiveCues is all cues that are currently showing
  5437. * Spec def: readonly attribute TextTrackCueList activeCues;
  5438. * @private
  5439. */
  5440. vjs.TextTrack.prototype.activeCues_;
  5441. /**
  5442. * Get the track active cues
  5443. * @return {Array}
  5444. */
  5445. vjs.TextTrack.prototype.activeCues = function () {
  5446. return this.activeCues_;
  5447. };
  5448. /**
  5449. * ReadyState describes if the text file has been loaded
  5450. * const unsigned short NONE = 0;
  5451. * const unsigned short LOADING = 1;
  5452. * const unsigned short LOADED = 2;
  5453. * const unsigned short ERROR = 3;
  5454. * readonly attribute unsigned short readyState;
  5455. * @private
  5456. */
  5457. vjs.TextTrack.prototype.readyState_;
  5458. /**
  5459. * Get the track readyState
  5460. * @return {Number}
  5461. */
  5462. vjs.TextTrack.prototype.readyState = function () {
  5463. return this.readyState_;
  5464. };
  5465. /**
  5466. * Mode describes if the track is showing, hidden, or disabled
  5467. * const unsigned short OFF = 0;
  5468. * const unsigned short HIDDEN = 1; (still triggering cuechange events, but not visible)
  5469. * const unsigned short SHOWING = 2;
  5470. * attribute unsigned short mode;
  5471. * @private
  5472. */
  5473. vjs.TextTrack.prototype.mode_;
  5474. /**
  5475. * Get the track mode
  5476. * @return {Number}
  5477. */
  5478. vjs.TextTrack.prototype.mode = function () {
  5479. return this.mode_;
  5480. };
  5481. /**
  5482. * Change the font size of the text track to make it larger when playing in fullscreen mode
  5483. * and restore it to its normal size when not in fullscreen mode.
  5484. */
  5485. vjs.TextTrack.prototype.adjustFontSize = function () {
  5486. if (this.player_.isFullScreen) {
  5487. // Scale the font by the same factor as increasing the video width to the full screen window width.
  5488. // Additionally, multiply that factor by 1.4, which is the default font size for
  5489. // the caption track (from the CSS)
  5490. this.el_.style.fontSize = screen.width / this.player_.width() * 1.4 * 100 + '%';
  5491. } else {
  5492. // Change the font size of the text track back to its original non-fullscreen size
  5493. this.el_.style.fontSize = '';
  5494. }
  5495. };
  5496. /**
  5497. * Create basic div to hold cue text
  5498. * @return {Element}
  5499. */
  5500. vjs.TextTrack.prototype.createEl = function () {
  5501. return vjs.Component.prototype.createEl.call(this, 'div', {
  5502. className: 'vjs-' + this.kind_ + ' vjs-text-track'
  5503. });
  5504. };
  5505. /**
  5506. * Show: Mode Showing (2)
  5507. * Indicates that the text track is active. If no attempt has yet been made to obtain the track's cues, the user agent will perform such an attempt momentarily.
  5508. * The user agent is maintaining a list of which cues are active, and events are being fired accordingly.
  5509. * In addition, for text tracks whose kind is subtitles or captions, the cues are being displayed over the video as appropriate;
  5510. * for text tracks whose kind is descriptions, the user agent is making the cues available to the user in a non-visual fashion;
  5511. * and for text tracks whose kind is chapters, the user agent is making available to the user a mechanism by which the user can navigate to any point in the media resource by selecting a cue.
  5512. * The showing by default state is used in conjunction with the default attribute on track elements to indicate that the text track was enabled due to that attribute.
  5513. * This allows the user agent to override the state if a later track is discovered that is more appropriate per the user's preferences.
  5514. */
  5515. vjs.TextTrack.prototype.show = function () {
  5516. this.activate();
  5517. this.mode_ = 2;
  5518. // Show element.
  5519. vjs.Component.prototype.show.call(this);
  5520. };
  5521. /**
  5522. * Hide: Mode Hidden (1)
  5523. * Indicates that the text track is active, but that the user agent is not actively displaying the cues.
  5524. * If no attempt has yet been made to obtain the track's cues, the user agent will perform such an attempt momentarily.
  5525. * The user agent is maintaining a list of which cues are active, and events are being fired accordingly.
  5526. */
  5527. vjs.TextTrack.prototype.hide = function () {
  5528. // When hidden, cues are still triggered. Disable to stop triggering.
  5529. this.activate();
  5530. this.mode_ = 1;
  5531. // Hide element.
  5532. vjs.Component.prototype.hide.call(this);
  5533. };
  5534. /**
  5535. * Disable: Mode Off/Disable (0)
  5536. * Indicates that the text track is not active. Other than for the purposes of exposing the track in the DOM, the user agent is ignoring the text track.
  5537. * No cues are active, no events are fired, and the user agent will not attempt to obtain the track's cues.
  5538. */
  5539. vjs.TextTrack.prototype.disable = function () {
  5540. // If showing, hide.
  5541. if (this.mode_ == 2) {
  5542. this.hide();
  5543. }
  5544. // Stop triggering cues
  5545. this.deactivate();
  5546. // Switch Mode to Off
  5547. this.mode_ = 0;
  5548. };
  5549. /**
  5550. * Turn on cue tracking. Tracks that are showing OR hidden are active.
  5551. */
  5552. vjs.TextTrack.prototype.activate = function () {
  5553. // Load text file if it hasn't been yet.
  5554. if (this.readyState_ === 0) {
  5555. this.load();
  5556. }
  5557. // Only activate if not already active.
  5558. if (this.mode_ === 0) {
  5559. // Update current cue on timeupdate
  5560. // Using unique ID for bind function so other tracks don't remove listener
  5561. this.player_.on('timeupdate', vjs.bind(this, this.update, this.id_));
  5562. // Reset cue time on media end
  5563. this.player_.on('ended', vjs.bind(this, this.reset, this.id_));
  5564. // Add to display
  5565. if (this.kind_ === 'captions' || this.kind_ === 'subtitles') {
  5566. this.player_.getChild('textTrackDisplay').addChild(this);
  5567. }
  5568. }
  5569. };
  5570. /**
  5571. * Turn off cue tracking.
  5572. */
  5573. vjs.TextTrack.prototype.deactivate = function () {
  5574. // Using unique ID for bind function so other tracks don't remove listener
  5575. this.player_.off('timeupdate', vjs.bind(this, this.update, this.id_));
  5576. this.player_.off('ended', vjs.bind(this, this.reset, this.id_));
  5577. this.reset(); // Reset
  5578. // Remove from display
  5579. this.player_.getChild('textTrackDisplay').removeChild(this);
  5580. };
  5581. // A readiness state
  5582. // One of the following:
  5583. //
  5584. // Not loaded
  5585. // Indicates that the text track is known to exist (e.g. it has been declared with a track element), but its cues have not been obtained.
  5586. //
  5587. // Loading
  5588. // Indicates that the text track is loading and there have been no fatal errors encountered so far. Further cues might still be added to the track.
  5589. //
  5590. // Loaded
  5591. // Indicates that the text track has been loaded with no fatal errors. No new cues will be added to the track except if the text track corresponds to a MutableTextTrack object.
  5592. //
  5593. // Failed to load
  5594. // Indicates that the text track was enabled, but when the user agent attempted to obtain it, this failed in some way (e.g. URL could not be resolved, network error, unknown text track format). Some or all of the cues are likely missing and will not be obtained.
  5595. vjs.TextTrack.prototype.load = function () {
  5596. // Only load if not loaded yet.
  5597. if (this.readyState_ === 0) {
  5598. this.readyState_ = 1;
  5599. vjs.get(this.src_, vjs.bind(this, this.parseCues), vjs.bind(this, this.onError));
  5600. }
  5601. };
  5602. vjs.TextTrack.prototype.onError = function (err) {
  5603. this.error = err;
  5604. this.readyState_ = 3;
  5605. this.trigger('error');
  5606. };
  5607. // Parse the WebVTT text format for cue times.
  5608. // TODO: Separate parser into own class so alternative timed text formats can be used. (TTML, DFXP)
  5609. vjs.TextTrack.prototype.parseCues = function (srcContent) {
  5610. var cue, time, text,
  5611. lines = srcContent.split('\n'),
  5612. line = '', id;
  5613. for (var i = 1, j = lines.length; i < j; i++) {
  5614. // Line 0 should be 'WEBVTT', so skipping i=0
  5615. line = vjs.trim(lines[i]); // Trim whitespace and linebreaks
  5616. if (line) { // Loop until a line with content
  5617. // First line could be an optional cue ID
  5618. // Check if line has the time separator
  5619. if (line.indexOf('-->') == -1) {
  5620. id = line;
  5621. // Advance to next line for timing.
  5622. line = vjs.trim(lines[++i]);
  5623. } else {
  5624. id = this.cues_.length;
  5625. }
  5626. // First line - Number
  5627. cue = {
  5628. id: id, // Cue Number
  5629. index: this.cues_.length // Position in Array
  5630. };
  5631. // Timing line
  5632. time = line.split(' --> ');
  5633. cue.startTime = this.parseCueTime(time[0]);
  5634. cue.endTime = this.parseCueTime(time[1]);
  5635. // Additional lines - Cue Text
  5636. text = [];
  5637. // Loop until a blank line or end of lines
  5638. // Assumeing trim('') returns false for blank lines
  5639. while (lines[++i] && (line = vjs.trim(lines[i]))) {
  5640. text.push(line);
  5641. }
  5642. cue.text = text.join('<br/>');
  5643. // Add this cue
  5644. this.cues_.push(cue);
  5645. }
  5646. }
  5647. this.readyState_ = 2;
  5648. this.trigger('loaded');
  5649. };
  5650. vjs.TextTrack.prototype.parseCueTime = function (timeText) {
  5651. var parts = timeText.split(':'),
  5652. time = 0,
  5653. hours, minutes, other, seconds, ms;
  5654. // Check if optional hours place is included
  5655. // 00:00:00.000 vs. 00:00.000
  5656. if (parts.length == 3) {
  5657. hours = parts[0];
  5658. minutes = parts[1];
  5659. other = parts[2];
  5660. } else {
  5661. hours = 0;
  5662. minutes = parts[0];
  5663. other = parts[1];
  5664. }
  5665. // Break other (seconds, milliseconds, and flags) by spaces
  5666. // TODO: Make additional cue layout settings work with flags
  5667. other = other.split(/\s+/);
  5668. // Remove seconds. Seconds is the first part before any spaces.
  5669. seconds = other.splice(0, 1)[0];
  5670. // Could use either . or , for decimal
  5671. seconds = seconds.split(/\.|,/);
  5672. // Get milliseconds
  5673. ms = parseFloat(seconds[1]);
  5674. seconds = seconds[0];
  5675. // hours => seconds
  5676. time += parseFloat(hours) * 3600;
  5677. // minutes => seconds
  5678. time += parseFloat(minutes) * 60;
  5679. // Add seconds
  5680. time += parseFloat(seconds);
  5681. // Add milliseconds
  5682. if (ms) {
  5683. time += ms / 1000;
  5684. }
  5685. return time;
  5686. };
  5687. // Update active cues whenever timeupdate events are triggered on the player.
  5688. vjs.TextTrack.prototype.update = function () {
  5689. if (this.cues_.length > 0) {
  5690. // Get curent player time
  5691. var time = this.player_.currentTime();
  5692. // Check if the new time is outside the time box created by the the last update.
  5693. if (this.prevChange === undefined || time < this.prevChange || this.nextChange <= time) {
  5694. var cues = this.cues_,
  5695. // Create a new time box for this state.
  5696. newNextChange = this.player_.duration(), // Start at beginning of the timeline
  5697. newPrevChange = 0, // Start at end
  5698. reverse = false, // Set the direction of the loop through the cues. Optimized the cue check.
  5699. newCues = [], // Store new active cues.
  5700. // Store where in the loop the current active cues are, to provide a smart starting point for the next loop.
  5701. firstActiveIndex, lastActiveIndex,
  5702. cue, i; // Loop vars
  5703. // Check if time is going forwards or backwards (scrubbing/rewinding)
  5704. // If we know the direction we can optimize the starting position and direction of the loop through the cues array.
  5705. if (time >= this.nextChange || this.nextChange === undefined) { // NextChange should happen
  5706. // Forwards, so start at the index of the first active cue and loop forward
  5707. i = (this.firstActiveIndex !== undefined) ? this.firstActiveIndex : 0;
  5708. } else {
  5709. // Backwards, so start at the index of the last active cue and loop backward
  5710. reverse = true;
  5711. i = (this.lastActiveIndex !== undefined) ? this.lastActiveIndex : cues.length - 1;
  5712. }
  5713. while (true) { // Loop until broken
  5714. cue = cues[i];
  5715. // Cue ended at this point
  5716. if (cue.endTime <= time) {
  5717. newPrevChange = Math.max(newPrevChange, cue.endTime);
  5718. if (cue.active) {
  5719. cue.active = false;
  5720. }
  5721. // No earlier cues should have an active start time.
  5722. // Nevermind. Assume first cue could have a duration the same as the video.
  5723. // In that case we need to loop all the way back to the beginning.
  5724. // if (reverse && cue.startTime) { break; }
  5725. // Cue hasn't started
  5726. } else if (time < cue.startTime) {
  5727. newNextChange = Math.min(newNextChange, cue.startTime);
  5728. if (cue.active) {
  5729. cue.active = false;
  5730. }
  5731. // No later cues should have an active start time.
  5732. if (!reverse) {
  5733. break;
  5734. }
  5735. // Cue is current
  5736. } else {
  5737. if (reverse) {
  5738. // Add cue to front of array to keep in time order
  5739. newCues.splice(0, 0, cue);
  5740. // If in reverse, the first current cue is our lastActiveCue
  5741. if (lastActiveIndex === undefined) {
  5742. lastActiveIndex = i;
  5743. }
  5744. firstActiveIndex = i;
  5745. } else {
  5746. // Add cue to end of array
  5747. newCues.push(cue);
  5748. // If forward, the first current cue is our firstActiveIndex
  5749. if (firstActiveIndex === undefined) {
  5750. firstActiveIndex = i;
  5751. }
  5752. lastActiveIndex = i;
  5753. }
  5754. newNextChange = Math.min(newNextChange, cue.endTime);
  5755. newPrevChange = Math.max(newPrevChange, cue.startTime);
  5756. cue.active = true;
  5757. }
  5758. if (reverse) {
  5759. // Reverse down the array of cues, break if at first
  5760. if (i === 0) {
  5761. break;
  5762. } else {
  5763. i--;
  5764. }
  5765. } else {
  5766. // Walk up the array fo cues, break if at last
  5767. if (i === cues.length - 1) {
  5768. break;
  5769. } else {
  5770. i++;
  5771. }
  5772. }
  5773. }
  5774. this.activeCues_ = newCues;
  5775. this.nextChange = newNextChange;
  5776. this.prevChange = newPrevChange;
  5777. this.firstActiveIndex = firstActiveIndex;
  5778. this.lastActiveIndex = lastActiveIndex;
  5779. this.updateDisplay();
  5780. this.trigger('cuechange');
  5781. }
  5782. }
  5783. };
  5784. // Add cue HTML to display
  5785. vjs.TextTrack.prototype.updateDisplay = function () {
  5786. var cues = this.activeCues_,
  5787. html = '',
  5788. i = 0, j = cues.length;
  5789. for (; i < j; i++) {
  5790. html += '<span class="vjs-tt-cue">' + cues[i].text + '</span>';
  5791. }
  5792. this.el_.innerHTML = html;
  5793. };
  5794. // Set all loop helper values back
  5795. vjs.TextTrack.prototype.reset = function () {
  5796. this.nextChange = 0;
  5797. this.prevChange = this.player_.duration();
  5798. this.firstActiveIndex = 0;
  5799. this.lastActiveIndex = 0;
  5800. };
  5801. // Create specific track types
  5802. /**
  5803. * The track component for managing the hiding and showing of captions
  5804. *
  5805. * @constructor
  5806. */
  5807. vjs.CaptionsTrack = vjs.TextTrack.extend();
  5808. vjs.CaptionsTrack.prototype.kind_ = 'captions';
  5809. // Exporting here because Track creation requires the track kind
  5810. // to be available on global object. e.g. new window['videojs'][Kind + 'Track']
  5811. /**
  5812. * The track component for managing the hiding and showing of subtitles
  5813. *
  5814. * @constructor
  5815. */
  5816. vjs.SubtitlesTrack = vjs.TextTrack.extend();
  5817. vjs.SubtitlesTrack.prototype.kind_ = 'subtitles';
  5818. /**
  5819. * The track component for managing the hiding and showing of chapters
  5820. *
  5821. * @constructor
  5822. */
  5823. vjs.ChaptersTrack = vjs.TextTrack.extend();
  5824. vjs.ChaptersTrack.prototype.kind_ = 'chapters';
  5825. /* Text Track Display
  5826. ============================================================================= */
  5827. // Global container for both subtitle and captions text. Simple div container.
  5828. /**
  5829. * The component for displaying text track cues
  5830. *
  5831. * @constructor
  5832. */
  5833. vjs.TextTrackDisplay = vjs.Component.extend({
  5834. /** @constructor */
  5835. init: function (player, options, ready) {
  5836. vjs.Component.call(this, player, options, ready);
  5837. // This used to be called during player init, but was causing an error
  5838. // if a track should show by default and the display hadn't loaded yet.
  5839. // Should probably be moved to an external track loader when we support
  5840. // tracks that don't need a display.
  5841. if (player.options_['tracks'] && player.options_['tracks'].length > 0) {
  5842. this.player_.addTextTracks(player.options_['tracks']);
  5843. }
  5844. }
  5845. });
  5846. vjs.TextTrackDisplay.prototype.createEl = function () {
  5847. return vjs.Component.prototype.createEl.call(this, 'div', {
  5848. className: 'vjs-text-track-display'
  5849. });
  5850. };
  5851. /**
  5852. * The specific menu item type for selecting a language within a text track kind
  5853. *
  5854. * @constructor
  5855. */
  5856. vjs.TextTrackMenuItem = vjs.MenuItem.extend({
  5857. /** @constructor */
  5858. init: function (player, options) {
  5859. var track = this.track = options['track'];
  5860. // Modify options for parent MenuItem class's init.
  5861. options['label'] = track.label();
  5862. options['selected'] = track.dflt();
  5863. vjs.MenuItem.call(this, player, options);
  5864. this.player_.on(track.kind() + 'trackchange', vjs.bind(this, this.update));
  5865. }
  5866. });
  5867. vjs.TextTrackMenuItem.prototype.onClick = function () {
  5868. vjs.MenuItem.prototype.onClick.call(this);
  5869. this.player_.showTextTrack(this.track.id_, this.track.kind());
  5870. };
  5871. vjs.TextTrackMenuItem.prototype.update = function () {
  5872. this.selected(this.track.mode() == 2);
  5873. };
  5874. /**
  5875. * A special menu item for turning of a specific type of text track
  5876. *
  5877. * @constructor
  5878. */
  5879. vjs.OffTextTrackMenuItem = vjs.TextTrackMenuItem.extend({
  5880. /** @constructor */
  5881. init: function (player, options) {
  5882. // Create pseudo track info
  5883. // Requires options['kind']
  5884. options['track'] = {
  5885. kind: function () {
  5886. return options['kind'];
  5887. },
  5888. player: player,
  5889. label: function () {
  5890. return options['kind'] + ' off';
  5891. },
  5892. dflt: function () {
  5893. return false;
  5894. },
  5895. mode: function () {
  5896. return false;
  5897. }
  5898. };
  5899. vjs.TextTrackMenuItem.call(this, player, options);
  5900. this.selected(true);
  5901. }
  5902. });
  5903. vjs.OffTextTrackMenuItem.prototype.onClick = function () {
  5904. vjs.TextTrackMenuItem.prototype.onClick.call(this);
  5905. this.player_.showTextTrack(this.track.id_, this.track.kind());
  5906. };
  5907. vjs.OffTextTrackMenuItem.prototype.update = function () {
  5908. var tracks = this.player_.textTracks(),
  5909. i = 0, j = tracks.length, track,
  5910. off = true;
  5911. for (; i < j; i++) {
  5912. track = tracks[i];
  5913. if (track.kind() == this.track.kind() && track.mode() == 2) {
  5914. off = false;
  5915. }
  5916. }
  5917. this.selected(off);
  5918. };
  5919. /**
  5920. * The base class for buttons that toggle specific text track types (e.g. subtitles)
  5921. *
  5922. * @constructor
  5923. */
  5924. vjs.TextTrackButton = vjs.MenuButton.extend({
  5925. /** @constructor */
  5926. init: function (player, options) {
  5927. vjs.MenuButton.call(this, player, options);
  5928. if (this.items.length <= 1) {
  5929. this.hide();
  5930. }
  5931. }
  5932. });
  5933. // vjs.TextTrackButton.prototype.buttonPressed = false;
  5934. // vjs.TextTrackButton.prototype.createMenu = function(){
  5935. // var menu = new vjs.Menu(this.player_);
  5936. // // Add a title list item to the top
  5937. // // menu.el().appendChild(vjs.createEl('li', {
  5938. // // className: 'vjs-menu-title',
  5939. // // innerHTML: vjs.capitalize(this.kind_),
  5940. // // tabindex: -1
  5941. // // }));
  5942. // this.items = this.createItems();
  5943. // // Add menu items to the menu
  5944. // for (var i = 0; i < this.items.length; i++) {
  5945. // menu.addItem(this.items[i]);
  5946. // }
  5947. // // Add list to element
  5948. // this.addChild(menu);
  5949. // return menu;
  5950. // };
  5951. // Create a menu item for each text track
  5952. vjs.TextTrackButton.prototype.createItems = function () {
  5953. var items = [], track;
  5954. // Add an OFF menu item to turn all tracks off
  5955. items.push(new vjs.OffTextTrackMenuItem(this.player_, {'kind': this.kind_}));
  5956. for (var i = 0; i < this.player_.textTracks().length; i++) {
  5957. track = this.player_.textTracks()[i];
  5958. if (track.kind() === this.kind_) {
  5959. items.push(new vjs.TextTrackMenuItem(this.player_, {
  5960. 'track': track
  5961. }));
  5962. }
  5963. }
  5964. return items;
  5965. };
  5966. /**
  5967. * The button component for toggling and selecting captions
  5968. *
  5969. * @constructor
  5970. */
  5971. vjs.CaptionsButton = vjs.TextTrackButton.extend({
  5972. /** @constructor */
  5973. init: function (player, options, ready) {
  5974. vjs.TextTrackButton.call(this, player, options, ready);
  5975. this.el_.setAttribute('aria-label', 'Captions Menu');
  5976. }
  5977. });
  5978. vjs.CaptionsButton.prototype.kind_ = 'captions';
  5979. vjs.CaptionsButton.prototype.buttonText = 'Captions';
  5980. vjs.CaptionsButton.prototype.className = 'vjs-captions-button';
  5981. /**
  5982. * The button component for toggling and selecting subtitles
  5983. *
  5984. * @constructor
  5985. */
  5986. vjs.SubtitlesButton = vjs.TextTrackButton.extend({
  5987. /** @constructor */
  5988. init: function (player, options, ready) {
  5989. vjs.TextTrackButton.call(this, player, options, ready);
  5990. this.el_.setAttribute('aria-label', 'Subtitles Menu');
  5991. }
  5992. });
  5993. vjs.SubtitlesButton.prototype.kind_ = 'subtitles';
  5994. vjs.SubtitlesButton.prototype.buttonText = 'Subtitles';
  5995. vjs.SubtitlesButton.prototype.className = 'vjs-subtitles-button';
  5996. // Chapters act much differently than other text tracks
  5997. // Cues are navigation vs. other tracks of alternative languages
  5998. /**
  5999. * The button component for toggling and selecting chapters
  6000. *
  6001. * @constructor
  6002. */
  6003. vjs.ChaptersButton = vjs.TextTrackButton.extend({
  6004. /** @constructor */
  6005. init: function (player, options, ready) {
  6006. vjs.TextTrackButton.call(this, player, options, ready);
  6007. this.el_.setAttribute('aria-label', 'Chapters Menu');
  6008. }
  6009. });
  6010. vjs.ChaptersButton.prototype.kind_ = 'chapters';
  6011. vjs.ChaptersButton.prototype.buttonText = 'Chapters';
  6012. vjs.ChaptersButton.prototype.className = 'vjs-chapters-button';
  6013. // Create a menu item for each text track
  6014. vjs.ChaptersButton.prototype.createItems = function () {
  6015. var items = [], track;
  6016. for (var i = 0; i < this.player_.textTracks().length; i++) {
  6017. track = this.player_.textTracks()[i];
  6018. if (track.kind() === this.kind_) {
  6019. items.push(new vjs.TextTrackMenuItem(this.player_, {
  6020. 'track': track
  6021. }));
  6022. }
  6023. }
  6024. return items;
  6025. };
  6026. vjs.ChaptersButton.prototype.createMenu = function () {
  6027. var tracks = this.player_.textTracks(),
  6028. i = 0,
  6029. j = tracks.length,
  6030. track, chaptersTrack,
  6031. items = this.items = [];
  6032. for (; i < j; i++) {
  6033. track = tracks[i];
  6034. if (track.kind() == this.kind_ && track.dflt()) {
  6035. if (track.readyState() < 2) {
  6036. this.chaptersTrack = track;
  6037. track.on('loaded', vjs.bind(this, this.createMenu));
  6038. return;
  6039. } else {
  6040. chaptersTrack = track;
  6041. break;
  6042. }
  6043. }
  6044. }
  6045. var menu = this.menu = new vjs.Menu(this.player_);
  6046. menu.el_.appendChild(vjs.createEl('li', {
  6047. className: 'vjs-menu-title',
  6048. innerHTML: vjs.capitalize(this.kind_),
  6049. tabindex: -1
  6050. }));
  6051. if (chaptersTrack) {
  6052. var cues = chaptersTrack.cues_, cue, mi;
  6053. i = 0;
  6054. j = cues.length;
  6055. for (; i < j; i++) {
  6056. cue = cues[i];
  6057. mi = new vjs.ChaptersTrackMenuItem(this.player_, {
  6058. 'track': chaptersTrack,
  6059. 'cue': cue
  6060. });
  6061. items.push(mi);
  6062. menu.addChild(mi);
  6063. }
  6064. }
  6065. if (this.items.length > 0) {
  6066. this.show();
  6067. }
  6068. return menu;
  6069. };
  6070. /**
  6071. * @constructor
  6072. */
  6073. vjs.ChaptersTrackMenuItem = vjs.MenuItem.extend({
  6074. /** @constructor */
  6075. init: function (player, options) {
  6076. var track = this.track = options['track'],
  6077. cue = this.cue = options['cue'],
  6078. currentTime = player.currentTime();
  6079. // Modify options for parent MenuItem class's init.
  6080. options['label'] = cue.text;
  6081. options['selected'] = (cue.startTime <= currentTime && currentTime < cue.endTime);
  6082. vjs.MenuItem.call(this, player, options);
  6083. track.on('cuechange', vjs.bind(this, this.update));
  6084. }
  6085. });
  6086. vjs.ChaptersTrackMenuItem.prototype.onClick = function () {
  6087. vjs.MenuItem.prototype.onClick.call(this);
  6088. this.player_.currentTime(this.cue.startTime);
  6089. this.update(this.cue.startTime);
  6090. };
  6091. vjs.ChaptersTrackMenuItem.prototype.update = function () {
  6092. var cue = this.cue,
  6093. currentTime = this.player_.currentTime();
  6094. // vjs.log(currentTime, cue.startTime);
  6095. this.selected(cue.startTime <= currentTime && currentTime < cue.endTime);
  6096. };
  6097. // Add Buttons to controlBar
  6098. vjs.obj.merge(vjs.ControlBar.prototype.options_['children'], {
  6099. 'subtitlesButton': {},
  6100. 'captionsButton': {},
  6101. 'chaptersButton': {}
  6102. });
  6103. // vjs.Cue = vjs.Component.extend({
  6104. // /** @constructor */
  6105. // init: function(player, options){
  6106. // vjs.Component.call(this, player, options);
  6107. // }
  6108. // });
  6109. /**
  6110. * @fileoverview Add JSON support
  6111. * @suppress {undefinedVars}
  6112. * (Compiler doesn't like JSON not being declared)
  6113. */
  6114. /**
  6115. * Javascript JSON implementation
  6116. * (Parse Method Only)
  6117. * https://github.com/douglascrockford/JSON-js/blob/master/json2.js
  6118. * Only using for parse method when parsing data-setup attribute JSON.
  6119. * @suppress {undefinedVars}
  6120. * @namespace
  6121. * @private
  6122. */
  6123. vjs.JSON;
  6124. if (typeof window.JSON !== 'undefined' && window.JSON.parse === 'function') {
  6125. vjs.JSON = window.JSON;
  6126. } else {
  6127. vjs.JSON = {};
  6128. var cx = /[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g;
  6129. /**
  6130. * parse the json
  6131. *
  6132. * @memberof vjs.JSON
  6133. * @return {Object|Array} The parsed JSON
  6134. */
  6135. vjs.JSON.parse = function (text, reviver) {
  6136. var j;
  6137. function walk(holder, key) {
  6138. var k, v, value = holder[key];
  6139. if (value && typeof value === 'object') {
  6140. for (k in value) {
  6141. if (Object.prototype.hasOwnProperty.call(value, k)) {
  6142. v = walk(value, k);
  6143. if (v !== undefined) {
  6144. value[k] = v;
  6145. } else {
  6146. delete value[k];
  6147. }
  6148. }
  6149. }
  6150. }
  6151. return reviver.call(holder, key, value);
  6152. }
  6153. text = String(text);
  6154. cx.lastIndex = 0;
  6155. if (cx.test(text)) {
  6156. text = text.replace(cx, function (a) {
  6157. return '\\u' +
  6158. ('0000' + a.charCodeAt(0).toString(16)).slice(-4);
  6159. });
  6160. }
  6161. if (/^[\],:{}\s]*$/
  6162. .test(text.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, '@')
  6163. .replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']')
  6164. .replace(/(?:^|:|,)(?:\s*\[)+/g, ''))) {
  6165. j = eval('(' + text + ')');
  6166. return typeof reviver === 'function' ?
  6167. walk({'': j}, '') : j;
  6168. }
  6169. throw new SyntaxError('JSON.parse(): invalid or malformed JSON data');
  6170. };
  6171. }
  6172. /**
  6173. * @fileoverview Functions for automatically setting up a player
  6174. * based on the data-setup attribute of the video tag
  6175. */
  6176. // Automatically set up any tags that have a data-setup attribute
  6177. vjs.autoSetup = function () {
  6178. var options, vid, player,
  6179. vids = document.getElementsByTagName('video');
  6180. // Check if any media elements exist
  6181. if (vids && vids.length > 0) {
  6182. for (var i = 0, j = vids.length; i < j; i++) {
  6183. vid = vids[i];
  6184. // Check if element exists, has getAttribute func.
  6185. // IE seems to consider typeof el.getAttribute == 'object' instead of 'function' like expected, at least when loading the player immediately.
  6186. if (vid && vid.getAttribute) {
  6187. // Make sure this player hasn't already been set up.
  6188. if (vid['player'] === undefined) {
  6189. options = vid.getAttribute('data-setup');
  6190. // Check if data-setup attr exists.
  6191. // We only auto-setup if they've added the data-setup attr.
  6192. if (options !== null) {
  6193. // Parse options JSON
  6194. // If empty string, make it a parsable json object.
  6195. options = vjs.JSON.parse(options || '{}');
  6196. // Create new video.js instance.
  6197. player = videojs(vid, options);
  6198. }
  6199. }
  6200. // If getAttribute isn't defined, we need to wait for the DOM.
  6201. } else {
  6202. vjs.autoSetupTimeout(1);
  6203. break;
  6204. }
  6205. }
  6206. // No videos were found, so keep looping unless page is finisehd loading.
  6207. } else if (!vjs.windowLoaded) {
  6208. vjs.autoSetupTimeout(1);
  6209. }
  6210. };
  6211. // Pause to let the DOM keep processing
  6212. vjs.autoSetupTimeout = function (wait) {
  6213. setTimeout(vjs.autoSetup, wait);
  6214. };
  6215. if (document.readyState === 'complete') {
  6216. vjs.windowLoaded = true;
  6217. } else {
  6218. vjs.one(window, 'load', function () {
  6219. vjs.windowLoaded = true;
  6220. });
  6221. }
  6222. // Run Auto-load players
  6223. // You have to wait at least once in case this script is loaded after your video in the DOM (weird behavior only with minified version)
  6224. vjs.autoSetupTimeout(1);
  6225. /**
  6226. * the method for registering a video.js plugin
  6227. *
  6228. * @param {String} name The name of the plugin
  6229. * @param {Function} init The function that is run when the player inits
  6230. */
  6231. vjs.plugin = function (name, init) {
  6232. vjs.Player.prototype[name] = init;
  6233. };