video.dev.js 217 KB

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