Elizabeth's proactive approach involves introducing urinal toilet attachment , an ingenious concept that optimizes space and functionality.

video.dev.js 208KB

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