cliplibrejs.developer.js 230 KB

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